add is_system to analog and temperature sensors, highlight in Sensors page, remove from Dashboard, change button icon to be consistent when updating

This commit is contained in:
proddy
2025-11-09 14:56:50 +01:00
parent a3e01b8a3b
commit f75b7b1a59
14 changed files with 270 additions and 128 deletions

View File

@@ -155,7 +155,7 @@ void AnalogSensor::reload(bool get_nvs) {
}
}
if (!found) {
sensors_.emplace_back(sensor.gpio, sensor.name, sensor.offset, sensor.factor, sensor.uom, sensor.type);
sensors_.emplace_back(sensor.gpio, sensor.name, sensor.offset, sensor.factor, sensor.uom, sensor.type, sensor.is_system);
sensors_.back().ha_registered = false; // this will trigger recreate of the HA config
if (sensor.type == AnalogType::COUNTER || sensor.type >= AnalogType::DIGITAL_OUT) {
sensors_.back().set_value(sensor.offset);
@@ -530,7 +530,7 @@ bool AnalogSensor::update(uint8_t gpio, std::string & name, double offset, doubl
newSensor.factor = factor;
newSensor.uom = uom;
newSensor.type = type;
newSensor.is_system = false;
newSensor.is_system = is_system;
settings.analogCustomizations.push_back(newSensor);
LOG_DEBUG("Adding new customization for analog sensor GPIO %02d", gpio);
return StateUpdateResult::CHANGED; // persist the change
@@ -871,13 +871,20 @@ void AnalogSensor::get_value_json(JsonObject output, const Sensor & sensor) {
}
// this creates the sensor, initializing everything
AnalogSensor::Sensor::Sensor(const uint8_t gpio, const std::string & name, const double offset, const double factor, const uint8_t uom, const int8_t type)
AnalogSensor::Sensor::Sensor(const uint8_t gpio,
const std::string & name,
const double offset,
const double factor,
const uint8_t uom,
const int8_t type,
const bool is_system)
: gpio_(gpio)
, name_(name)
, offset_(offset)
, factor_(factor)
, uom_(uom)
, type_(type) {
, type_(type)
, is_system_(is_system) {
value_ = 0; // init value to 0 always
}

View File

@@ -34,7 +34,7 @@ class AnalogSensor {
public:
class Sensor {
public:
Sensor(const uint8_t gpio, const std::string & name, const double offset, const double factor, const uint8_t uom, const int8_t type);
Sensor(const uint8_t gpio, const std::string & name, const double offset, const double factor, const uint8_t uom, const int8_t type, const bool is_system);
~Sensor() = default;
void set_offset(const double offset) {
@@ -44,7 +44,6 @@ class AnalogSensor {
std::string name() const {
return name_;
}
void set_name(const std::string & name) {
name_ = name;
}
@@ -64,11 +63,13 @@ class AnalogSensor {
bool is_system() const {
return is_system_;
}
void set_is_system(const bool is_system) {
is_system_ = is_system;
}
double factor() const {
return factor_;
}
void set_factor(const double factor) {
factor_ = factor;
}
@@ -80,7 +81,6 @@ class AnalogSensor {
void set_uom(const uint8_t uom) {
uom_ = uom;
}
uint8_t uom() const {
return uom_;
}
@@ -166,13 +166,15 @@ class AnalogSensor {
size_t count_entities(bool include_disabled = true) const {
if (!include_disabled) {
// count number of items in sensors_ where type is not set to disabled
return std::count_if(sensors_.begin(), sensors_.end(), [](const Sensor & sensor) { return sensor.type() != AnalogSensor::AnalogType::NOTUSED; });
// count number of items in sensors_ where type is not set to disabled and not a system sensor
return std::count_if(sensors_.begin(), sensors_.end(), [](const Sensor & sensor) {
return sensor.type() != AnalogSensor::AnalogType::NOTUSED && !sensor.is_system();
});
}
return sensors_.size();
}
bool update(uint8_t gpio, std::string & name, double offset, double factor, uint8_t uom, int8_t type, bool deleted = false, bool is_system = false);
bool update(uint8_t gpio, std::string & name, double offset, double factor, uint8_t uom, int8_t type, bool deleted, bool is_system);
bool get_value_info(JsonObject output, const char * cmd, const int8_t id = -1);
void store_counters();

View File

@@ -545,16 +545,21 @@ void EMSESP::show_sensor_values(uuid::console::Shell & shell) {
for (const auto & sensor : temperaturesensor_.sensors()) {
if (Helpers::hasValue(sensor.temperature_c)) {
shell.printfln(" %s: %s%s °%c%s (offset %s, ID: %s)",
shell.printfln(" %s: %s%s °%c%s (Offset: %s, ID: %s, System: %s)",
sensor.name().c_str(),
COLOR_BRIGHT_GREEN,
Helpers::render_value(s, sensor.temperature_c, 10, fahrenheit),
(fahrenheit == 0) ? 'C' : 'F',
COLOR_RESET,
Helpers::render_value(s2, sensor.offset(), 10, fahrenheit),
sensor.id().c_str());
sensor.id().c_str(),
sensor.is_system() ? "Yes" : "No");
} else {
shell.printfln(" %s (offset %s, ID: %s)", sensor.name().c_str(), Helpers::render_value(s, sensor.offset(), 10, fahrenheit), sensor.id().c_str());
shell.printfln(" %s (Offset: %s, ID: %s, System: %s)",
sensor.name().c_str(),
Helpers::render_value(s, sensor.offset(), 10, fahrenheit),
sensor.id().c_str(),
sensor.is_system() ? "Yes" : "No");
}
}
shell.println();
@@ -563,18 +568,20 @@ void EMSESP::show_sensor_values(uuid::console::Shell & shell) {
if (analogsensor_.have_sensors()) {
char s[10];
char s2[10];
char s3[10];
shell.printfln("Analog sensors:");
for (const auto & sensor : analogsensor_.sensors()) {
switch (sensor.type()) {
case AnalogSensor::AnalogType::ADC:
shell.printfln(" %s: %s%s %s%s (Type: ADC, Factor: %s, Offset: %d)",
shell.printfln(" %s: %s%s %s%s (Type: ADC, Factor: %s, Offset: %s, System: %s)",
sensor.name().c_str(),
COLOR_BRIGHT_GREEN,
Helpers::render_value(s, sensor.value(), 2),
EMSdevice::uom_to_string(sensor.uom()),
COLOR_RESET,
Helpers::render_value(s2, sensor.factor(), 4),
sensor.offset());
Helpers::render_value(s3, sensor.offset(), 2),
sensor.is_system() ? "Yes" : "No");
break;
default:
// case AnalogSensor::AnalogType::DIGITAL_IN:

View File

@@ -217,11 +217,13 @@ void TemperatureSensor::loop() {
if (!EMSESP::nvs_.isKey("intTemp")) {
EMSESP::nvs_.putString("intTemp", s->id().c_str());
}
s->set_is_system(true); // mark as internal system temperature sensor
EMSESP::webCustomizationService.update([&](WebCustomization & settings) {
auto newSensor = SensorCustomization();
newSensor.id = s->id();
newSensor.name = s->name();
newSensor.offset = 0;
auto newSensor = SensorCustomization();
newSensor.id = s->id();
newSensor.name = s->name();
newSensor.offset = 0;
newSensor.is_system = s->is_system(); // always true
settings.sensorCustomizations.push_back(newSensor);
return StateUpdateResult::CHANGED;
});
@@ -313,7 +315,7 @@ int16_t TemperatureSensor::get_temperature_c(const uint8_t addr[]) {
}
// update temperature sensor information name and offset
bool TemperatureSensor::update(const std::string & id, const std::string & name, int16_t offset) {
bool TemperatureSensor::update(const std::string & id, const std::string & name, int16_t offset, bool is_system) {
// find the sensor
for (auto & sensor : sensors_) {
if (sensor.id() == id) {
@@ -329,23 +331,25 @@ bool TemperatureSensor::update(const std::string & id, const std::string & name,
sensor.set_offset(offset);
// store the new name and offset in our configuration
EMSESP::webCustomizationService.update([&id, &name, &offset, &sensor](WebCustomization & settings) {
EMSESP::webCustomizationService.update([&id, &name, &offset, &is_system, &sensor](WebCustomization & settings) {
// look it up to see if it exists
bool found = false;
for (auto & SensorCustomization : settings.sensorCustomizations) {
if (SensorCustomization.id == id) {
SensorCustomization.name = name;
SensorCustomization.offset = offset;
found = true;
SensorCustomization.name = name;
SensorCustomization.offset = offset;
SensorCustomization.is_system = is_system;
found = true;
LOG_DEBUG("Customizing existing sensor ID %s", id.c_str());
break;
}
}
if (!found) {
auto newSensor = SensorCustomization();
newSensor.id = id;
newSensor.name = name;
newSensor.offset = offset;
auto newSensor = SensorCustomization();
newSensor.id = id;
newSensor.name = name;
newSensor.offset = offset;
newSensor.is_system = false; // is user defined, not system
settings.sensorCustomizations.push_back(newSensor);
LOG_DEBUG("Adding new customization for sensor ID %s", id.c_str());
}
@@ -628,6 +632,7 @@ void TemperatureSensor::load_test_data() {
sensors_.back().apply_customization();
sensors_.back().temperature_c = 123; // 12.3
sensors_.back().read = true;
sensors_.back().set_is_system(false);
publish_sensor(sensors_.back()); // call publish single
// Sensor ID: 0B_0C0D_0E0F_1011
@@ -636,6 +641,16 @@ void TemperatureSensor::load_test_data() {
sensors_.back().apply_customization();
sensors_.back().temperature_c = 456; // 45.6
sensors_.back().read = true;
sensors_.back().set_is_system(false);
publish_sensor(sensors_.back()); // call publish single
// Sensor ID: 28_1767_7B13_2502
uint8_t addr3[ADDR_LEN] = {0x28, 0x17, 0x67, 0x7B, 0x13, 0x25, 0x02};
sensors_.emplace_back(addr3);
sensors_.back().apply_customization();
sensors_.back().temperature_c = 281; // 28.1
sensors_.back().read = true;
sensors_.back().set_is_system(true);
publish_sensor(sensors_.back()); // call publish single
}
#endif

View File

@@ -44,6 +44,13 @@ class TemperatureSensor {
return internal_id_;
}
bool is_system() const {
return is_system_;
}
void set_is_system(const bool is_system) {
is_system_ = is_system;
}
std::string id() const {
return id_;
}
@@ -72,6 +79,7 @@ class TemperatureSensor {
std::string id_;
std::string name_;
int16_t offset_;
bool is_system_;
};
TemperatureSensor() = default;
@@ -106,11 +114,13 @@ class TemperatureSensor {
return (!sensors_.empty());
}
size_t count_entities() const {
return sensors_.size();
size_t count_entities(bool include_system = true) const {
return std::count_if(sensors_.begin(), sensors_.end(), [include_system](const Sensor & sensor) {
return include_system ? sensor.is_system() : !sensor.is_system();
});
}
bool update(const std::string & id, const std::string & name, int16_t offset);
bool update(const std::string & id, const std::string & name, int16_t offset, bool is_system);
#if defined(EMSESP_TEST)
void load_test_data();

View File

@@ -880,7 +880,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const
shell.invoke_command("call system publish");
// rename
EMSESP::temperaturesensor_.update("01_0203_0405_0607", "testtemperature", 2);
EMSESP::temperaturesensor_.update("01_0203_0405_0607", "testtemperature", 2, false);
shell.invoke_command("show values");
shell.invoke_command("call system publish");
ok = true;

View File

@@ -52,12 +52,12 @@ WebCustomizationService::WebCustomizationService(AsyncWebServer * server, FS * f
void WebCustomization::read(WebCustomization & customizations, JsonObject root) {
// Temperature Sensor customization
JsonArray sensorsJson = root["ts"].to<JsonArray>();
for (const SensorCustomization & sensor : customizations.sensorCustomizations) {
JsonObject sensorJson = sensorsJson.add<JsonObject>();
sensorJson["id"] = sensor.id; // ID of chip
sensorJson["name"] = sensor.name; // n
sensorJson["offset"] = sensor.offset; // o
JsonObject sensorJson = sensorsJson.add<JsonObject>();
sensorJson["id"] = sensor.id; // ID of chip
sensorJson["name"] = sensor.name; // n
sensorJson["offset"] = sensor.offset; // o
sensorJson["is_system"] = sensor.is_system; // s for core_voltage, supply_voltage
}
// Analog Sensor customization
@@ -70,7 +70,7 @@ void WebCustomization::read(WebCustomization & customizations, JsonObject root)
sensorJson["factor"] = sensor.factor; // f
sensorJson["uom"] = sensor.uom; // u
sensorJson["type"] = sensor.type; // t
sensorJson["is_system"] = sensor.is_system; // s
sensorJson["is_system"] = sensor.is_system; // s for core_voltage, supply_voltage
}
// Masked entities customization and custom device name (optional)
@@ -105,6 +105,7 @@ StateUpdateResult WebCustomization::update(JsonObject root, WebCustomization & c
if (sensor.id == sensor.name) {
sensor.name = ""; // no need to store id as name
}
sensor.is_system = sensorJson["is_system"];
std::replace(sensor.id.begin(), sensor.id.end(), '-', '_'); // change old ids to v3.7 style
customizations.sensorCustomizations.push_back(sensor); // add to list
}
@@ -373,55 +374,78 @@ void WebCustomizationService::load_test_data() {
// Temperature sensors
webCustomization.sensorCustomizations.clear(); // delete all existing sensors
auto sensor = SensorCustomization();
sensor.id = "01_0203_0405_0607";
sensor.name = "test_tempsensor1";
sensor.offset = 0;
webCustomization.sensorCustomizations.push_back(sensor);
auto sensor1 = SensorCustomization();
sensor1.id = "01_0203_0405_0607";
sensor1.name = "test_tempsensor1";
sensor1.offset = 0;
sensor1.is_system = false;
webCustomization.sensorCustomizations.push_back(sensor1);
auto sensor2 = SensorCustomization();
sensor2.id = "0B_0C0D_0E0F_1011";
sensor2.name = "test_tempsensor2";
sensor2.offset = 4;
auto sensor2 = SensorCustomization();
sensor2.id = "0B_0C0D_0E0F_1011";
sensor2.name = "test_tempsensor2";
sensor2.offset = 4;
sensor2.is_system = false;
webCustomization.sensorCustomizations.push_back(sensor2);
auto sensor3 = SensorCustomization();
sensor3.id = "28_1767_7B13_2502";
sensor3.name = "gateway_temperature";
sensor3.offset = 0;
sensor3.is_system = true;
webCustomization.sensorCustomizations.push_back(sensor3);
// Analog sensors
// This actually adds the sensors as we use customizations to store them
webCustomization.analogCustomizations.clear();
auto analog = AnalogCustomization();
analog.gpio = 36;
analog.name = "test_analogsensor1";
analog.offset = 0;
analog.factor = 0.1;
analog.uom = 17;
analog.type = 3; // ADC
auto analog = AnalogCustomization();
analog.gpio = 36;
analog.name = "test_analogsensor1";
analog.offset = 0;
analog.factor = 0.2;
analog.uom = 17;
analog.type = 3; // ADC
analog.is_system = false;
webCustomization.analogCustomizations.push_back(analog);
analog = AnalogCustomization();
analog.gpio = 37;
analog.name = "test_analogsensor2";
analog.offset = 0;
analog.factor = 1;
analog.uom = 0;
analog.type = 1; // DIGITAL_IN
analog = AnalogCustomization();
analog.gpio = 37;
analog.name = "test_analogsensor2";
analog.offset = 0;
analog.factor = 1;
analog.uom = 0;
analog.type = 1; // DIGITAL_IN
analog.is_system = false;
webCustomization.analogCustomizations.push_back(analog);
analog = AnalogCustomization();
analog.gpio = 38;
analog.name = "test_analogsensor3";
analog.offset = 0;
analog.factor = 1;
analog.uom = 0;
analog.type = 0; // disabled, not-used
analog = AnalogCustomization();
analog.gpio = 38;
analog.name = "test_analogsensor3";
analog.offset = 0;
analog.factor = 1;
analog.uom = 0;
analog.type = 0; // disabled, not-used
analog.is_system = false;
webCustomization.analogCustomizations.push_back(analog);
analog = AnalogCustomization();
analog.gpio = 33;
analog.name = "test_analogsensor4";
analog.offset = 0;
analog.factor = 1;
analog.uom = 0;
analog.type = 2; // COUNTER
analog = AnalogCustomization();
analog.gpio = 33;
analog.name = "test_analogsensor4";
analog.offset = 0;
analog.factor = 1;
analog.uom = 0;
analog.type = 2; // COUNTER
analog.is_system = false;
webCustomization.analogCustomizations.push_back(analog);
analog = AnalogCustomization();
analog.gpio = 39;
analog.name = "test_analogsensor5"; // core_voltage
analog.offset = 0;
analog.factor = 0.003771;
analog.uom = 23;
analog.type = 3; // ADC
analog.is_system = true;
webCustomization.analogCustomizations.push_back(analog);
// EMS entities, mark some as favorites

View File

@@ -37,6 +37,7 @@ class SensorCustomization {
std::string id;
std::string name;
uint16_t offset;
bool is_system; // if true, the customization is a system customization
};
class AnalogCustomization {
@@ -45,9 +46,9 @@ class AnalogCustomization {
std::string name;
double offset;
double factor;
uint8_t uom; // 0 is none
int8_t type; // -1 is for deletion
bool is_system = false; // if true, the customization is a system customization
uint8_t uom; // 0 is none
int8_t type; // -1 is for deletion
bool is_system; // if true, the customization is a system customization
// used for removing from a list
bool operator==(const AnalogCustomization & a) const {

View File

@@ -127,6 +127,7 @@ void WebDataService::sensor_data(AsyncWebServerRequest * request) {
obj["u"] = DeviceValueUOM::DEGREES;
obj["o"] = (float)(sensor.offset()) / 10;
}
obj["s"] = sensor.is_system();
}
}
@@ -323,7 +324,9 @@ void WebDataService::write_temperature_sensor(AsyncWebServerRequest * request, J
offset10 = offset / 0.18;
}
ok = EMSESP::temperaturesensor_.update(id, name, offset10);
bool is_system = sensor["is_system"];
ok = EMSESP::temperaturesensor_.update(id, name, offset10, is_system);
}
AsyncWebServerResponse * response = request->beginResponse(ok ? 200 : 400); // bad request
@@ -336,14 +339,15 @@ void WebDataService::write_analog_sensor(AsyncWebServerRequest * request, JsonVa
if (json.is<JsonObject>()) {
JsonObject analog = json;
uint8_t gpio = analog["gpio"];
std::string name = analog["name"];
double factor = analog["factor"];
double offset = analog["offset"];
uint8_t uom = analog["uom"];
int8_t type = analog["type"];
bool deleted = analog["deleted"];
ok = EMSESP::analogsensor_.update(gpio, name, offset, factor, uom, type, deleted);
uint8_t gpio = analog["gpio"];
std::string name = analog["name"];
double factor = analog["factor"];
double offset = analog["offset"];
uint8_t uom = analog["uom"];
int8_t type = analog["type"];
bool deleted = analog["deleted"];
bool is_system = analog["is_system"];
ok = EMSESP::analogsensor_.update(gpio, name, offset, factor, uom, type, deleted, is_system);
}
AsyncWebServerResponse * response = request->beginResponse(ok ? 200 : 400); // ok or bad request
@@ -388,13 +392,17 @@ void WebDataService::dashboard_data(AsyncWebServerRequest * request) {
}
// add temperature sensors, if we have any
if (EMSESP::temperaturesensor_.have_sensors()) {
if (EMSESP::temperaturesensor_.count_entities(true)) { // no system sensors
JsonObject obj = nodes.add<JsonObject>();
obj["id"] = EMSdevice::DeviceTypeUniqueID::TEMPERATURESENSOR_UID; // it's unique id
obj["t"] = EMSdevice::DeviceType::TEMPERATURESENSOR; // device type number
JsonArray nodes = obj["nodes"].to<JsonArray>();
uint8_t count = 0;
for (const auto & sensor : EMSESP::temperaturesensor_.sensors()) {
// ignore system sensors
if (sensor.is_system()) {
continue;
}
JsonObject node = nodes.add<JsonObject>();
node["id"] = (EMSdevice::DeviceTypeUniqueID::TEMPERATURESENSOR_UID * 100) + count++;
@@ -422,6 +430,10 @@ void WebDataService::dashboard_data(AsyncWebServerRequest * request) {
JsonArray nodes = obj["nodes"].to<JsonArray>();
uint8_t count = 0;
for (const auto & sensor : EMSESP::analogsensor_.sensors()) {
// ignore system sensors
if (sensor.is_system()) {
continue;
}
if (sensor.type() != AnalogSensor::AnalogType::NOTUSED) { // ignore disabled
JsonObject node = nodes.add<JsonObject>();
node["id"] = (EMSdevice::DeviceTypeUniqueID::ANALOGSENSOR_UID * 100) + count++;