add analog sensor NTC and RGB-Led, dev29

This commit is contained in:
MichaelDvP
2025-03-22 11:41:37 +01:00
parent 1da08633ec
commit 3796fb8027
8 changed files with 137 additions and 20 deletions

View File

@@ -20,6 +20,7 @@ For more details go to [docs.emsesp.org](https://docs.emsesp.org/).
- support nested conditions in scheduler [#2451](https://github.com/emsesp/EMS-ESP32/issues/2451) - support nested conditions in scheduler [#2451](https://github.com/emsesp/EMS-ESP32/issues/2451)
- allow mixed case in scheduler expressions [#2457](https://github.com/emsesp/EMS-ESP32/issues/2457) - allow mixed case in scheduler expressions [#2457](https://github.com/emsesp/EMS-ESP32/issues/2457)
- Suprapur-o [#2470](https://github.com/emsesp/EMS-ESP32/issues/2470) - Suprapur-o [#2470](https://github.com/emsesp/EMS-ESP32/issues/2470)
- analogsensor types: NTC and RGB-Led
## Fixed ## Fixed

View File

@@ -171,6 +171,27 @@ const SensorsAnalogDialog = ({
/> />
</Grid> </Grid>
)} )}
{editItem.t === AnalogType.NTC && (
<Grid>
<TextField
name="o"
label={LL.OFFSET()}
value={numberValue(editItem.o)}
sx={{ width: '11ch' }}
type="number"
variant="outlined"
onChange={updateFormValue}
slotProps={{
input: {
startAdornment: (
<InputAdornment position="start">°C</InputAdornment>
)
},
htmlInput: { min: '-20', max: '20', step: '0.1' }
}}
/>
</Grid>
)}
{editItem.t === AnalogType.COUNTER && ( {editItem.t === AnalogType.COUNTER && (
<Grid> <Grid>
<TextField <TextField
@@ -187,6 +208,19 @@ const SensorsAnalogDialog = ({
/> />
</Grid> </Grid>
)} )}
{editItem.t === AnalogType.RGB && (
<Grid>
<TextField
name="o"
label={'RGB ' + LL.VALUE(0)}
value={numberValue(editItem.o)}
type="number"
sx={{ width: '11ch' }}
variant="outlined"
onChange={updateFormValue}
/>
</Grid>
)}
{editItem.t >= AnalogType.COUNTER && editItem.t <= AnalogType.RATE && ( {editItem.t >= AnalogType.COUNTER && editItem.t <= AnalogType.RATE && (
<Grid> <Grid>
<TextField <TextField

View File

@@ -232,20 +232,24 @@ export enum AnalogType {
DIGITAL_OUT = 6, DIGITAL_OUT = 6,
PWM_0 = 7, PWM_0 = 7,
PWM_1 = 8, PWM_1 = 8,
PWM_2 = 9 PWM_2 = 9,
NTC = 10,
RGB = 11
} }
export const AnalogTypeNames = [ export const AnalogTypeNames = [
'(disabled)', '(disabled)',
'Digital In', 'Digital In',
'Counter', 'Counter',
'ADC', 'ADC In',
'Timer', 'Timer',
'Rate', 'Rate',
'Digital Out', 'Digital Out',
'PWM 0', 'PWM 0',
'PWM 1', 'PWM 1',
'PWM 2' 'PWM 2',
'NTC Temp.',
'RGB Led'
]; ];
type BoardProfiles = Record<string, string>; type BoardProfiles = Record<string, string>;

View File

@@ -30,7 +30,7 @@ void AnalogSensor::start() {
return; return;
} }
analogSetAttenuation(ADC_2_5db); // for all channels 1.5V analogSetAttenuation(ADC_11db); // for all channels 3.3V
LOG_INFO("Starting Analog Sensor service"); LOG_INFO("Starting Analog Sensor service");
@@ -110,13 +110,15 @@ void AnalogSensor::reload(bool get_nvs) {
sensors_.back().set_value(0); // reset value only for new sensors sensors_.back().set_value(0); // reset value only for new sensors
} }
} }
if (sensor.type == AnalogType::COUNTER || sensor.type >= AnalogType::DIGITAL_OUT) { if (sensor.type == AnalogType::COUNTER || (sensor.type >= AnalogType::DIGITAL_OUT && sensor.type <= AnalogType::PWM_2)
|| sensor.type == AnalogType::RGB) {
Command::add( Command::add(
EMSdevice::DeviceType::ANALOGSENSOR, EMSdevice::DeviceType::ANALOGSENSOR,
sensor.name.c_str(), sensor.name.c_str(),
[&](const char * value, const int8_t id) { return command_setvalue(value, sensor.gpio); }, [&](const char * value, const int8_t id) { return command_setvalue(value, sensor.gpio); },
sensor.type == AnalogType::COUNTER ? FL_(counter) sensor.type == AnalogType::COUNTER ? FL_(counter)
: sensor.type == AnalogType::DIGITAL_OUT ? FL_(digital_out) : sensor.type == AnalogType::DIGITAL_OUT ? FL_(digital_out)
: sensor.type == AnalogType::RGB ? FL_(RGB)
: FL_(pwm), : FL_(pwm),
CommandFlag::ADMIN_ONLY); CommandFlag::ADMIN_ONLY);
} }
@@ -157,6 +159,10 @@ void AnalogSensor::reload(bool get_nvs) {
// analogSetPinAttenuation does not work with analogReadMilliVolts // analogSetPinAttenuation does not work with analogReadMilliVolts
sensor.analog_ = 0; // initialize sensor.analog_ = 0; // initialize
sensor.last_reading_ = 0; sensor.last_reading_ = 0;
} else if (sensor.type() == AnalogType::NTC) {
LOG_DEBUG("NTC Sensor on GPIO %02d", sensor.gpio());
// analogSetPinAttenuation(sensor.gpio(), ADC_11db); //does not work with analogReadMilliVolts
sensor.set_uom(DeviceValueUOM::DEGREES);
} else if (sensor.type() == AnalogType::COUNTER) { } else if (sensor.type() == AnalogType::COUNTER) {
LOG_DEBUG("I/O Counter on GPIO %02d", sensor.gpio()); LOG_DEBUG("I/O Counter on GPIO %02d", sensor.gpio());
pinMode(sensor.gpio(), INPUT_PULLUP); pinMode(sensor.gpio(), INPUT_PULLUP);
@@ -183,6 +189,18 @@ void AnalogSensor::reload(bool get_nvs) {
sensor.polltime_ = 0; sensor.polltime_ = 0;
sensor.poll_ = digitalRead(sensor.gpio()); sensor.poll_ = digitalRead(sensor.gpio());
publish_sensor(sensor); publish_sensor(sensor);
} else if (sensor.type() == AnalogType::RGB) {
LOG_DEBUG("RGB on GPIO %02d", sensor.gpio());
uint32_t v = sensor.value();
uint8_t r = v / 10000;
uint8_t g = (v - r * 10000) / 100;
uint8_t b = v % 100;
#if ESP_ARDUINO_VERSION_MAJOR < 3
neopixelWrite(sensor.gpio(), 2 * r, 2 * g, 2 * b);
#else
rgbLedWrite(sensor.gpio(), 2 * r, 2 * g, 2 * b);
#endif
LOG_DEBUG("RGB set to %d, %d, %d", r, g, b);
} else if (sensor.type() == AnalogType::DIGITAL_OUT) { } else if (sensor.type() == AnalogType::DIGITAL_OUT) {
LOG_DEBUG("Digital Write on GPIO %02d", sensor.gpio()); LOG_DEBUG("Digital Write on GPIO %02d", sensor.gpio());
pinMode(sensor.gpio(), OUTPUT); pinMode(sensor.gpio(), OUTPUT);
@@ -272,6 +290,23 @@ void AnalogSensor::measure() {
changed_ = true; changed_ = true;
publish_sensor(sensor); publish_sensor(sensor);
} }
} else if (sensor.type() == AnalogType::NTC) {
auto a = analogReadMilliVolts(sensor.gpio());
if (!sensor.analog_) { // init first time
sensor.analog_ = a;
sensor.sum_ = a * 16;
} else { // simple moving average filter
sensor.sum_ = (sensor.sum_ * 15 + a * 16) / 16;
sensor.analog_ = sensor.sum_ / 16;
}
if (sensor.analog_ > 0 && sensor.analog_ < 3300 && (sensor.last_reading_ + 1 < sensor.analog_ || sensor.last_reading_ > sensor.analog_ + 1)) {
sensor.set_value(sensor.offset() + 1 / (1 / T25 + log((double)sensor.analog_ / (3300 - sensor.analog_) * (Rt / R0)) / Beta)
- T0); // Temperature in Celsius
sensor.last_reading_ = sensor.analog_;
sensorreads_++;
changed_ = true;
publish_sensor(sensor);
}
} }
} }
} }
@@ -350,12 +385,14 @@ bool AnalogSensor::update(uint8_t gpio, std::string & name, double offset, doubl
bool found_sensor = false; bool found_sensor = false;
EMSESP::webCustomizationService.update([&](WebCustomization & settings) { EMSESP::webCustomizationService.update([&](WebCustomization & settings) {
for (auto & AnalogCustomization : settings.analogCustomizations) { for (auto & AnalogCustomization : settings.analogCustomizations) {
if (AnalogCustomization.type == AnalogType::COUNTER || AnalogCustomization.type >= AnalogType::DIGITAL_OUT) { if (AnalogCustomization.type == AnalogType::COUNTER
|| (AnalogCustomization.type >= AnalogType::DIGITAL_OUT && AnalogCustomization.type <= AnalogType::PWM_2)
|| AnalogCustomization.type >= AnalogType::RGB) {
Command::erase_command(EMSdevice::DeviceType::ANALOGSENSOR, AnalogCustomization.name.c_str()); Command::erase_command(EMSdevice::DeviceType::ANALOGSENSOR, AnalogCustomization.name.c_str());
} }
if (name.empty()) { if (name.empty()) {
char n[20]; char n[20];
snprintf(n, sizeof(n), "%s_%02d", FL_(AnalogTypeName)[type], gpio); snprintf(n, sizeof(n), "%s_%02d", FL_(list_sensortype)[type], gpio);
name = n; name = n;
} }
if (AnalogCustomization.gpio == gpio) { if (AnalogCustomization.gpio == gpio) {
@@ -430,8 +467,13 @@ void AnalogSensor::publish_sensor(const Sensor & sensor) const {
} else { } else {
snprintf(topic, sizeof(topic), "%s%s/%s", F_(analogsensor), "_data", sensor.name().c_str()); snprintf(topic, sizeof(topic), "%s%s/%s", F_(analogsensor), "_data", sensor.name().c_str());
} }
char payload[10]; char result[12];
Mqtt::queue_publish(topic, Helpers::render_value(payload, sensor.value(), 2)); // always publish as doubles if (sensor.type() == AnalogType::DIGITAL_IN || sensor.type() == AnalogType::DIGITAL_OUT) {
Helpers::render_boolean(result, sensor.value() != 0);
} else {
Helpers::render_value(result, sensor.value(), 2); // double
}
Mqtt::queue_publish(topic, result); // always publish as doubles
} }
char cmd[COMMAND_MAX_LENGTH]; char cmd[COMMAND_MAX_LENGTH];
snprintf(cmd, sizeof(cmd), "%s/%s", F_(analogsensor), sensor.name().c_str()); snprintf(cmd, sizeof(cmd), "%s/%s", F_(analogsensor), sensor.name().c_str());
@@ -455,7 +497,9 @@ void AnalogSensor::remove_ha_topic(const int8_t type, const uint8_t gpio) const
snprintf(topic, sizeof(topic), "switch/%s/%s_%02d/config", Mqtt::basename().c_str(), F_(analogsensor), gpio); snprintf(topic, sizeof(topic), "switch/%s/%s_%02d/config", Mqtt::basename().c_str(), F_(analogsensor), gpio);
} else if (type == AnalogType::DIGITAL_OUT) { // DAC } else if (type == AnalogType::DIGITAL_OUT) { // DAC
snprintf(topic, sizeof(topic), "number/%s/%s_%02d/config", Mqtt::basename().c_str(), F_(analogsensor), gpio); snprintf(topic, sizeof(topic), "number/%s/%s_%02d/config", Mqtt::basename().c_str(), F_(analogsensor), gpio);
} else if (type >= AnalogType::PWM_0) { } else if (type >= AnalogType::PWM_0 && type <= AnalogType::PWM_2) {
snprintf(topic, sizeof(topic), "number/%s/%s_%02d/config", Mqtt::basename().c_str(), F_(analogsensor), gpio);
} else if (type >= AnalogType::RGB) {
snprintf(topic, sizeof(topic), "number/%s/%s_%02d/config", Mqtt::basename().c_str(), F_(analogsensor), gpio); snprintf(topic, sizeof(topic), "number/%s/%s_%02d/config", Mqtt::basename().c_str(), F_(analogsensor), gpio);
} else if (type == AnalogType::DIGITAL_IN) { } else if (type == AnalogType::DIGITAL_IN) {
snprintf(topic, sizeof(topic), "binary_sensor/%s/%s_%02d/config", Mqtt::basename().c_str(), F_(analogsensor), gpio); snprintf(topic, sizeof(topic), "binary_sensor/%s/%s_%02d/config", Mqtt::basename().c_str(), F_(analogsensor), gpio);
@@ -495,6 +539,8 @@ void AnalogSensor::publish_values(const bool force) {
case AnalogType::PWM_0: case AnalogType::PWM_0:
case AnalogType::PWM_1: case AnalogType::PWM_1:
case AnalogType::PWM_2: case AnalogType::PWM_2:
case AnalogType::RGB:
case AnalogType::NTC:
dataSensor["value"] = serialized(Helpers::render_value(s, sensor.value(), 2)); // double dataSensor["value"] = serialized(Helpers::render_value(s, sensor.value(), 2)); // double
break; break;
case AnalogType::DIGITAL_IN: case AnalogType::DIGITAL_IN:
@@ -597,6 +643,14 @@ void AnalogSensor::publish_values(const bool force) {
config["max"] = 100; config["max"] = 100;
config["mode"] = "box"; // auto, slider or box config["mode"] = "box"; // auto, slider or box
config["step"] = 0.1; config["step"] = 0.1;
} else if (sensor.type() == AnalogType::RGB) {
snprintf(topic, sizeof(topic), "number/%s/%s_%02d/config", Mqtt::basename().c_str(), F_(analogsensor), sensor.gpio());
snprintf(command_topic, sizeof(command_topic), "%s/%s/%s", Mqtt::base().c_str(), F_(analogsensor), sensor.name().c_str());
config["cmd_t"] = command_topic;
config["min"] = 0;
config["max"] = 999999;
config["mode"] = "box"; // auto, slider or box
config["step"] = 1;
} else if (sensor.type() == AnalogType::COUNTER) { } else if (sensor.type() == AnalogType::COUNTER) {
snprintf(topic, sizeof(topic), "sensor/%s/%s_%02d/config", Mqtt::basename().c_str(), F_(analogsensor), sensor.gpio()); snprintf(topic, sizeof(topic), "sensor/%s/%s_%02d/config", Mqtt::basename().c_str(), F_(analogsensor), sensor.gpio());
snprintf(command_topic, sizeof(command_topic), "%s/%s/%s", Mqtt::base().c_str(), F_(analogsensor), sensor.name().c_str()); snprintf(command_topic, sizeof(command_topic), "%s/%s/%s", Mqtt::base().c_str(), F_(analogsensor), sensor.name().c_str());
@@ -679,8 +733,9 @@ void AnalogSensor::get_value_json(JsonObject output, const Sensor & sensor) {
output["analog"] = FL_(list_sensortype)[sensor.type()]; output["analog"] = FL_(list_sensortype)[sensor.type()];
output["value"] = sensor.value(); output["value"] = sensor.value();
output["readable"] = true; output["readable"] = true;
output["writeable"] = sensor.type() == AnalogType::COUNTER || (sensor.type() >= AnalogType::DIGITAL_OUT && sensor.type() <= AnalogType::PWM_2); output["writeable"] = sensor.type() == AnalogType::COUNTER || sensor.type() >= AnalogType::RGB
output["visible"] = true; || (sensor.type() >= AnalogType::DIGITAL_OUT && sensor.type() <= AnalogType::PWM_2);
output["visible"] = true;
if (sensor.type() == AnalogType::COUNTER) { if (sensor.type() == AnalogType::COUNTER) {
output["min"] = 0; output["min"] = 0;
output["max"] = 4000000; output["max"] = 4000000;
@@ -733,10 +788,10 @@ bool AnalogSensor::command_setvalue(const char * value, const int8_t gpio) {
double oldoffset = sensor.offset(); double oldoffset = sensor.offset();
if (sensor.type() == AnalogType::COUNTER) { if (sensor.type() == AnalogType::COUNTER) {
if (val < 0 || value[0] == '+') { // sign corrects values if (val < 0 || value[0] == '+') { // sign corrects values
sensor.set_offset(sensor.value() + val); // sensor.set_offset(sensor.value() + val);
sensor.set_value(sensor.value() + val); sensor.set_value(sensor.value() + val);
} else { // positive values are set } else { // positive values are set
sensor.set_offset(val); // sensor.set_offset(val);
sensor.set_value(val); sensor.set_value(val);
} }
if (oldoffset != sensor.offset() && sensor.offset() != EMSESP::nvs_.getDouble(sensor.name().c_str())) { if (oldoffset != sensor.offset() && sensor.offset() != EMSESP::nvs_.getDouble(sensor.name().c_str())) {
@@ -744,6 +799,19 @@ bool AnalogSensor::command_setvalue(const char * value, const int8_t gpio) {
} }
} else if (sensor.type() == AnalogType::ADC) { } else if (sensor.type() == AnalogType::ADC) {
sensor.set_offset(val); sensor.set_offset(val);
} else if (sensor.type() == AnalogType::RGB) {
uint32_t v = val;
sensor.set_offset(v);
sensor.set_value(v);
uint8_t r = v / 10000;
uint8_t g = (v - r * 10000) / 100;
uint8_t b = v % 100;
#if ESP_ARDUINO_VERSION_MAJOR < 3
neopixelWrite(sensor.gpio(), 2 * r, 2 * g, 2 * b);
#else
rgbLedWrite(sensor.gpio(), 2 * r, 2 * g, 2 * b);
#endif
LOG_DEBUG("RGB set to %d, %d, %d", r, g, b);
} else if (sensor.type() == AnalogType::DIGITAL_OUT) { } else if (sensor.type() == AnalogType::DIGITAL_OUT) {
uint8_t v = val; uint8_t v = val;
#if CONFIG_IDF_TARGET_ESP32 #if CONFIG_IDF_TARGET_ESP32

View File

@@ -27,8 +27,8 @@
namespace emsesp { namespace emsesp {
// names, same order as AnalogType // names, same order as AnalogType, see list_sensortype in local_common.h
MAKE_ENUM_FIXED(AnalogTypeName, "disabled", "dig_in", "counter", "adc", "timer", "rate", "dig_out", "pwm0", "pwm1", "pwm2") // MAKE_ENUM_FIXED(AnalogTypeName, "disabled", "dig_in", "counter", "adc", "timer", "rate", "dig_out", "pwm0", "pwm1", "pwm2")
class AnalogSensor { class AnalogSensor {
public: public:
@@ -122,7 +122,9 @@ class AnalogSensor {
DIGITAL_OUT = 6, DIGITAL_OUT = 6,
PWM_0 = 7, PWM_0 = 7,
PWM_1 = 8, PWM_1 = 8,
PWM_2 = 9 PWM_2 = 9,
NTC = 10,
RGB = 11
}; };
void start(); void start();
@@ -166,6 +168,11 @@ class AnalogSensor {
void store_counters(); void store_counters();
private: private:
static constexpr double Beta = 3380;
static constexpr double T0 = 273.15;
static constexpr double T25 = 298.15;
static constexpr double R0 = 10000;
static constexpr double Rt = 10000;
static constexpr uint8_t MAX_SENSORS = 20; static constexpr uint8_t MAX_SENSORS = 20;
static constexpr uint32_t MEASURE_ANALOG_INTERVAL = 500; static constexpr uint32_t MEASURE_ANALOG_INTERVAL = 500;

View File

@@ -276,7 +276,8 @@ MAKE_ENUM_FIXED(list_syslog_level, "off", "emerg", "alert", "crit", "error", "wa
// sensors // sensors
MAKE_ENUM_FIXED(counter, "counter") MAKE_ENUM_FIXED(counter, "counter")
MAKE_ENUM_FIXED(digital_out, "digital_out") MAKE_ENUM_FIXED(digital_out, "digital_out")
MAKE_ENUM_FIXED(list_sensortype, "disabled", "digital in", "counter", "adc", "timer", "rate", "digital out", "pwm 0", "pwm 1", "pwm 2") MAKE_ENUM_FIXED(RGB, "RGB")
MAKE_ENUM_FIXED(list_sensortype, "disabled", "digital in", "counter", "adc", "timer", "rate", "digital out", "pwm 0", "pwm 1", "pwm 2", "NTC Temp", "RGB Led")
// watch // watch
MAKE_ENUM_FIXED(list_watch, "off", "on", "raw", "unknown") MAKE_ENUM_FIXED(list_watch, "off", "on", "raw", "unknown")

View File

@@ -1 +1 @@
#define EMSESP_APP_VERSION "3.7.2-dev.28" #define EMSESP_APP_VERSION "3.7.2-dev.29"

View File

@@ -446,7 +446,9 @@ void WebDataService::dashboard_data(AsyncWebServerRequest * request) {
dv["v"] = Helpers::transformNumFloat(sensor.value()); dv["v"] = Helpers::transformNumFloat(sensor.value());
dv["u"] = sensor.uom(); dv["u"] = sensor.uom();
} }
if (sensor.type() == AnalogSensor::AnalogType::COUNTER || sensor.type() >= AnalogSensor::AnalogType::DIGITAL_OUT) { if (sensor.type() == AnalogSensor::AnalogType::COUNTER
|| (sensor.type() >= AnalogSensor::AnalogType::DIGITAL_OUT && sensor.type() <= AnalogSensor::AnalogType::PWM_2)
|| sensor.type() == AnalogSensor::AnalogType::RGB) {
dv["c"] = sensor.name(); dv["c"] = sensor.name();
} }
} }