12 Commits

Author SHA1 Message Date
Proddy
c05e1cb77b Merge pull request #2966 from MichaelDvP/dev
fixes for #2960 and #2962
2026-02-19 21:58:20 +01:00
MichaelDvP
5879ce4090 fix SRC mode setting from HA #2960 2026-02-18 08:14:47 +01:00
MichaelDvP
ac3e5c793c fix typo for SRC ha-climate creation 2026-02-17 10:09:22 +01:00
MichaelDvP
4326fb931b add prometheus metrics for analog/scheduler/custom #2962 2026-02-16 15:56:23 +01:00
MichaelDvP
ced7051ce7 add prometheus metrics for temperaturesensors 2026-02-16 12:05:45 +01:00
MichaelDvP
421da246ed fix SRC seltemp offset for auto mode #2960 2026-02-16 07:51:10 +01:00
MichaelDvP
148a721e17 read connect seltemp after mode/icon to create HA-climate 2026-02-15 16:49:21 +01:00
MichaelDvP
a811670c5a 3.8.2-dev.6, changelog 2026-02-15 12:03:33 +01:00
MichaelDvP
72f08a86cf fix SRC climate, #2960 2026-02-15 12:03:07 +01:00
MichaelDvP
27c471f45f set model for ems-esp devices, #2958 2026-02-15 12:02:36 +01:00
MichaelDvP
e303972d26 update AsyncWebserver and pkg 2026-02-15 12:01:50 +01:00
MichaelDvP
97bb03d703 add missing check for number mode change 2026-02-15 12:01:10 +01:00
16 changed files with 247 additions and 69 deletions

View File

@@ -8,10 +8,12 @@ For more details go to [emsesp.org](https://emsesp.org/).
- comfortpoint for BC400 [#2935](https://github.com/emsesp/EMS-ESP32/issues/2935)
- customize device brand [#2784](https://github.com/emsesp/EMS-ESP32/issues/2784)
- set model for ems-esp devices temperature, analog, etc. [#2958](https://github.com/emsesp/EMS-ESP32/discussions/2958)
- prometheus metrics for temperature/analog/scheduler/custom [#2962](https://github.com/emsesp/EMS-ESP32/issues/2962)
## Fixed
- SRC climate creation [#2936](https://github.com/emsesp/EMS-ESP32/issues/2936)
- SRC climate creation [#2936](https://github.com/emsesp/EMS-ESP32/issues/2936) and [#2960](https://github.com/emsesp/EMS-ESP32/issues/2960)
## Changed

View File

@@ -508,13 +508,9 @@ packages:
resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==}
engines: {node: '>=18.18'}
'@isaacs/balanced-match@4.0.1':
resolution: {integrity: sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==}
engines: {node: 20 || >=22}
'@isaacs/brace-expansion@5.0.1':
resolution: {integrity: sha512-WMz71T1JS624nWj2n2fnYAuPovhv7EUhk69R6i9dsVyzxt5eM3bjwvgk9L+APE1TRscGysAVMANkB0jh0LQZrQ==}
engines: {node: 20 || >=22}
'@isaacs/cliui@9.0.0':
resolution: {integrity: sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg==}
engines: {node: '>=18'}
'@jridgewell/gen-mapping@0.3.13':
resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==}
@@ -1062,6 +1058,10 @@ packages:
balanced-match@1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
balanced-match@4.0.2:
resolution: {integrity: sha512-x0K50QvKQ97fdEz2kPehIerj+YTeptKF9hyYkKf6egnwmMWAkADiO0QCzSp0R5xN8FTZgYaBfSaue46Ej62nMg==}
engines: {node: 20 || >=22}
base64-js@1.5.1:
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
@@ -1101,6 +1101,10 @@ packages:
brace-expansion@2.0.2:
resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==}
brace-expansion@5.0.2:
resolution: {integrity: sha512-Pdk8c9poy+YhOgVWw1JNN22/HcivgKWwpxKq04M/jTmHyCZn12WPJebZxdjSa5TmBqISrUSgNYU3eRORljfCCw==}
engines: {node: 20 || >=22}
braces@3.0.3:
resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
engines: {node: '>=8'}
@@ -1155,8 +1159,8 @@ packages:
resolution: {integrity: sha512-DLIsRzJVBQu72meAKPkWQOLcujdXT32hwdfnkI1frSiSRMK1MofjKHf+MEx0SB6fjEFXL8fBDv1dKymBlOp4Qw==}
engines: {node: '>=0.10.0'}
caniuse-lite@1.0.30001769:
resolution: {integrity: sha512-BCfFL1sHijQlBGWBMuJyhZUhzo7wer5sVj9hqekB/7xn0Ypy+pER/edCYQm4exbXj4WiySGp40P8UuTh6w1srg==}
caniuse-lite@1.0.30001770:
resolution: {integrity: sha512-x/2CLQ1jHENRbHg5PSId2sXq1CIO1CISvwWAj027ltMVG2UNgW+w9oH2+HzgEIRFembL8bUlXtfbBHR1fCg2xw==}
caw@2.0.1:
resolution: {integrity: sha512-Cg8/ZSBEa8ZVY9HspcGUYaK63d/bN7rqS3CYCzEGUxuYv6UlmcjzDUz2fCFFHyTvUW5Pk0I+3hkA3iXlIj6guA==}
@@ -2077,6 +2081,10 @@ packages:
resolution: {integrity: sha512-1P/yWsxPlDtn7QeRD+ULKQPaIaN6yF368GZ2vDfv0AL0NwpStafjWCDDdn0k8wgFMWpVAqG7oJhxHnlud42i9w==}
engines: {node: '>= 4'}
jackspeak@4.2.3:
resolution: {integrity: sha512-ykkVRwrYvFm1nb2AJfKKYPr0emF6IiXDYUaFx4Zn9ZuIH7MrzEZ3sD5RlqGXNRpHtvUHJyOnCEFxOlNDtGo7wg==}
engines: {node: 20 || >=22}
javascript-natural-sort@0.7.1:
resolution: {integrity: sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw==}
@@ -2247,8 +2255,8 @@ packages:
resolution: {integrity: sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==}
engines: {node: '>=4'}
minimatch@10.1.2:
resolution: {integrity: sha512-fu656aJ0n2kcXwsnwnv9g24tkU5uSmOlTjd6WyyaKm2Z+h1qmY6bAjrcaIxF/BslFqbZ8UBtbJi7KgQOZD2PTw==}
minimatch@10.2.0:
resolution: {integrity: sha512-ugkC31VaVg9cF0DFVoADH12k6061zNZkZON+aX8AWsR9GhPcErkcMBceb6znR8wLERM2AkkOxy2nWRLpT9Jq5w==}
engines: {node: 20 || >=22}
minimatch@3.1.2:
@@ -3432,7 +3440,7 @@ snapshots:
dependencies:
'@eslint/object-schema': 3.0.1
debug: 4.4.3
minimatch: 10.1.2
minimatch: 10.2.0
transitivePeerDependencies:
- supports-color
@@ -3466,11 +3474,7 @@ snapshots:
'@humanwhocodes/retry@0.4.3': {}
'@isaacs/balanced-match@4.0.1': {}
'@isaacs/brace-expansion@5.0.1':
dependencies:
'@isaacs/balanced-match': 4.0.1
'@isaacs/cliui@9.0.0': {}
'@jridgewell/gen-mapping@0.3.13':
dependencies:
@@ -3805,7 +3809,7 @@ snapshots:
'@types/minimatch@6.0.0':
dependencies:
minimatch: 10.1.2
minimatch: 10.2.0
'@types/node@25.2.3':
dependencies:
@@ -3986,6 +3990,10 @@ snapshots:
balanced-match@1.0.2: {}
balanced-match@4.0.2:
dependencies:
jackspeak: 4.2.3
base64-js@1.5.1: {}
baseline-browser-mapping@2.9.19: {}
@@ -4039,6 +4047,10 @@ snapshots:
dependencies:
balanced-match: 1.0.2
brace-expansion@5.0.2:
dependencies:
balanced-match: 4.0.2
braces@3.0.3:
dependencies:
fill-range: 7.1.1
@@ -4046,7 +4058,7 @@ snapshots:
browserslist@4.28.1:
dependencies:
baseline-browser-mapping: 2.9.19
caniuse-lite: 1.0.30001769
caniuse-lite: 1.0.30001770
electron-to-chromium: 1.5.286
node-releases: 2.0.27
update-browserslist-db: 1.2.3(browserslist@4.28.1)
@@ -4105,7 +4117,7 @@ snapshots:
camelcase@2.1.1: {}
caniuse-lite@1.0.30001769: {}
caniuse-lite@1.0.30001770: {}
caw@2.0.1:
dependencies:
@@ -4581,7 +4593,7 @@ snapshots:
imurmurhash: 0.1.4
is-glob: 4.0.3
json-stable-stringify-without-jsonify: 1.0.1
minimatch: 10.1.2
minimatch: 10.2.0
natural-compare: 1.4.0
optionator: 0.9.4
transitivePeerDependencies:
@@ -5116,6 +5128,10 @@ snapshots:
has-to-string-tag-x: 1.4.1
is-object: 1.0.2
jackspeak@4.2.3:
dependencies:
'@isaacs/cliui': 9.0.0
javascript-natural-sort@0.7.1: {}
jpegtran-bin@5.0.2:
@@ -5274,9 +5290,9 @@ snapshots:
mimic-response@1.0.1: {}
minimatch@10.1.2:
minimatch@10.2.0:
dependencies:
'@isaacs/brace-expansion': 5.0.1
brace-expansion: 5.0.2
minimatch@3.1.2:
dependencies:

View File

@@ -106,7 +106,7 @@ board_build.filesystem = littlefs
lib_deps =
bblanchon/ArduinoJson @ 7.4.2
ESP32Async/AsyncTCP @ 3.4.10
ESP32Async/ESPAsyncWebServer @ 3.9.6
ESP32Async/ESPAsyncWebServer @ 3.10.0
https://github.com/emsesp/EMS-ESP-Modules.git @ 1.0.8

View File

@@ -316,6 +316,10 @@ StateUpdateResult MqttSettings::update(JsonObject root, MqttSettings & settings)
changed = true;
}
if (newSettings.ha_number_mode != settings.ha_number_mode) {
changed = true;
}
if (newSettings.entity_format != settings.entity_format) {
changed = true;
}

View File

@@ -852,6 +852,15 @@ bool AnalogSensor::get_value_info(JsonObject output, const char * cmd, const int
return true;
}
if (!strcmp(cmd, F_(metrics))) {
std::string metrics = get_metrics_prometheus();
if (!metrics.empty()) {
output["api_data"] = metrics;
return true;
}
return false;
}
// this is for a specific sensor, return the value
const char * attribute_s = Command::get_attribute(cmd);
@@ -866,6 +875,35 @@ bool AnalogSensor::get_value_info(JsonObject output, const char * cmd, const int
return false; // not found
}
// generate Prometheus metrics format from analog values
std::string AnalogSensor::get_metrics_prometheus() {
std::string result;
result.reserve(sensors_.size() * 140);
char val[10];
for (auto & sensor : sensors_) {
result += (std::string) "# HELP emsesp_" + sensor.name() + " " + sensor.name();
if (sensor.type() != AnalogType::DIGITAL_OUT && sensor.type() != AnalogType::DIGITAL_IN) {
result += (std::string) ", " + EMSdevice::uom_to_string(sensor.uom());
} else {
result += (std::string) ", boolean";
}
result += (std::string) ", readable, visible";
if (sensor.type() == AnalogType::COUNTER || sensor.type() == AnalogType::RGB || sensor.type() == AnalogType::PULSE
|| (sensor.type() >= AnalogType::DIGITAL_OUT && sensor.type() <= AnalogType::PWM_2)
|| (sensor.type() >= AnalogType::CNT_0 && sensor.type() <= AnalogType::CNT_2)) {
result += (std::string) ", writable";
}
result += (std::string) "\n# TYPE emsesp_" + sensor.name() + " gauge\n";
result += (std::string) "emsesp_" + sensor.name() + " ";
if (sensor.type() != AnalogType::DIGITAL_OUT && sensor.type() != AnalogType::DIGITAL_IN) {
result += (std::string) Helpers::render_value(val, sensor.value(), 2) + "\n";
} else {
result += (std::string) (sensor.value() == 0 ? "0\n" : "1\n");
}
}
return result;
}
// note we don't add the device and state classes here, as we do in the custom entity service
void AnalogSensor::get_value_json(JsonObject output, const Sensor & sensor) {
output["name"] = (const char *)sensor.name();

View File

@@ -177,6 +177,7 @@ class AnalogSensor {
bool update(uint8_t gpio, const char * 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();
std::string get_metrics_prometheus();
static std::vector<uint8_t> exclude_types() {
return exclude_types_;
}

View File

@@ -912,7 +912,7 @@ void EMSdevice::publish_value(void * value_p) const {
// looks up the UOM for a given key from the device value table
std::string EMSdevice::get_value_uom(const std::string & shortname) const {
for (const auto & dv : devicevalues_) {
if ((!dv.has_state(DeviceValueState::DV_WEB_EXCLUDE)) && (dv.short_name == shortname)) {
if ((!dv.has_state(DeviceValueState::DV_WEB_EXCLUDE)) && !strcmp(dv.short_name, shortname.c_str())) {
// ignore TIME since "minutes" is already added to the string value
if ((dv.uom == DeviceValueUOM::NONE) || (dv.uom == DeviceValueUOM::MINUTES)) {
break;
@@ -1282,7 +1282,7 @@ void EMSdevice::setCustomizationEntity(const std::string & entity_id) {
// set the min / max
dv.set_custom_minmax();
if (Mqtt::ha_enabled() && dv.short_name == FL_(seltemp)[0] && (min != dv.min || max != dv.max)) {
if (Mqtt::ha_enabled() && dv.tag <= DeviceValueTAG::TAG_HC8 && !strcmp(dv.short_name, FL_(selRoomTemp)[0]) && (min != dv.min || max != dv.max)) {
set_climate_minmax(dv.tag, dv.min, dv.max);
}
@@ -2135,7 +2135,7 @@ void EMSdevice::mqtt_ha_entity_config_create() {
if (needs_update) {
const char * const ** mode_options = nullptr;
for (auto & d : devicevalues_) {
for (const auto & d : devicevalues_) {
// make sure mode in same circuit is DeviceValueType::ENUM
if ((d.tag == dv.tag) && (d.type == DeviceValueType::ENUM) && !strcmp(d.short_name, FL_(mode)[0]) && (d.options_size > 0)) {
// get options
@@ -2166,20 +2166,26 @@ void EMSdevice::mqtt_ha_entity_config_create() {
count++;
}
// SRC thermostats mapped to connect/src1/... always contains mode, seltemp, currtemp
if (dv.tag >= DeviceValueTAG::TAG_SRC1 && dv.tag <= DeviceValueTAG::TAG_SRC16 && !strcmp(dv.short_name, FL_(mode)[0])) {
// add icon if we have one
// SRC thermostats mapped to connect/src1/... always contains mode, selRoomTemp, currtemp
if (dv.tag >= DeviceValueTAG::TAG_SRC1 && dv.tag <= DeviceValueTAG::TAG_SRC16 && !strcmp(dv.short_name, FL_(selRoomTemp)[0])) {
// add modes and icon if we have one
const char * icon = nullptr;
for (auto & d : devicevalues_) {
if (d.tag == dv.tag && !strcmp(d.short_name, FL_(icon)[0]) && (dv.type == DeviceValueType::ENUM)) {
const char * const ** mode_options = nullptr;
for (const auto & d : devicevalues_) {
if ((d.tag != dv.tag) || (d.type != DeviceValueType::ENUM)) {
continue;
}
if (!strcmp(d.short_name, FL_(mode)[0]) && (d.options_size > 0)) {
mode_options = d.options;
}
if (!strcmp(d.short_name, FL_(icon)[0])) {
uint8_t val = *(uint8_t *)(d.value_p);
if (val != 0 && val < d.options_size) {
icon = d.options[val][0];
}
break;
}
}
Mqtt::publish_ha_climate_config(dv, true, dv.options, false, icon);
Mqtt::publish_ha_climate_config(dv, true, mode_options, false, icon);
count++;
}

View File

@@ -1545,9 +1545,7 @@ void Mqtt::add_ha_dev_section(JsonObject doc, const char * name, const bool crea
// add mf (manufacturer/brand), mdl (model), sw (software version) and via_device
dev_json["mf"] = brand != nullptr ? brand : "EMS-ESP";
if (model != nullptr) {
dev_json["mdl"] = model;
}
dev_json["mdl"] = model != nullptr ? model : "EMS-ESP";
dev_json["sw"] = version != nullptr ? version : "v" + std::string(EMSESP_APP_VERSION);
dev_json["via_device"] = Mqtt::basename();
}

View File

@@ -400,6 +400,15 @@ bool TemperatureSensor::get_value_info(JsonObject output, const char * cmd, cons
return true;
}
if (!strcmp(cmd, F_(metrics))) {
std::string metrics = get_metrics_prometheus();
if (!metrics.empty()) {
output["api_data"] = metrics;
return true;
}
return false;
}
// this is for a specific sensor
const char * attribute_s = Command::get_attribute(cmd);
@@ -414,6 +423,21 @@ bool TemperatureSensor::get_value_info(JsonObject output, const char * cmd, cons
return false; // not found
}
// generate Prometheus metrics format from temperature values
std::string TemperatureSensor::get_metrics_prometheus() {
std::string result;
result.reserve(sensors_.size() * 120);
char val[10];
for (auto & sensor : sensors_) {
result += (std::string) "# HELP emsesp_" + sensor.name() + " " + sensor.name() + ", "
+ EMSdevice::uom_to_string(EMSESP::system_.fahrenheit() ? DeviceValueUOM::FAHRENHEIT : DeviceValueUOM::DEGREES) + ", readable, visible\n";
result += (std::string) "# TYPE emsesp_" + sensor.name() + " gauge\n";
result +=
(std::string) "emsesp_" + sensor.name() + " " + Helpers::render_value(val, sensor.temperature_c, 10, EMSESP::system_.fahrenheit() ? 2 : 0) + "\n";
}
return result;
}
// note we don't add the device and state classes here, as we do in the custom entity service
void TemperatureSensor::get_value_json(JsonObject output, const Sensor & sensor) {
output["id"] = sensor.id();

View File

@@ -96,6 +96,8 @@ class TemperatureSensor {
bool updated_values();
bool get_value_info(JsonObject output, const char * cmd, const int8_t id = -1);
std::string get_metrics_prometheus();
// return back reference to the sensor list, used by other classes
std::vector<Sensor, AllocatorPSRAM<Sensor>> sensors() const {
return sensors_;

View File

@@ -129,15 +129,20 @@ void Connect::process_roomThermostat(std::shared_ptr<const Telegram> telegram) {
}
has_update(telegram, rc->temp_, 0);
has_update(telegram, rc->humidity_, 2); // could show -3 if not set
// make sure we have read mode and icon, needed for ha climate
if (!Mqtt::ha_enabled() || (Helpers::hasValue(rc->mode_) && Helpers::hasValue(rc->icon_))) {
has_update(telegram, rc->seltemp_, 3);
}
// calculate dew temperature
if (rc->humidity_ >= 0 && rc->humidity_ <= 100) {
const float k2 = 17.62;
const float k3 = 243.12;
const float t = (float)rc->temp_ / 10;
const float h = (float)rc->humidity_ / 100;
int16_t dt = (10 * k3 * (((k2 * t) / (k3 + t)) + log(h)) / (((k2 * k3) / (k3 + t)) - log(h)));
has_update(rc->dewtemp_, dt);
}
}
// gateway(0x48) W gateway(0x50), ?(0x0B42), data: 01 // icon in offset 0
@@ -206,12 +211,13 @@ bool Connect::set_mode(const char * value, const int8_t id) {
return false;
}
uint8_t v;
if (Helpers::value2enum(value, v, FL_(enum_mode2), {3, 1, 0})) {
// if (Helpers::value2enum(value, v, FL_(enum_mode8))) {
if (!Helpers::value2enum(value, v, FL_(enum_mode2), {3, 1, 0})) {
if (!Helpers::value2enum(value, v, FL_(enum_mode_ha), {3, 1, 0})) {
return false;
}
}
write_command(0xBB5 + rc->room(), 0, v); // no validate, mode change is broadcasted
return true;
}
return false;
}
bool Connect::set_seltemp(const char * value, const int8_t id) {
@@ -221,8 +227,9 @@ bool Connect::set_seltemp(const char * value, const int8_t id) {
}
float v;
if (Helpers::value2float(value, v)) {
// write_command(0xBB5 + rc->room(), rc->mode_ == 2 ? 1 : 3, v == -1 ? 0xFF : uint8_t(v * 2));
write_command(0xBB5 + rc->room(), rc->mode_ == 0 ? 1 : 3, v == -1 ? 0xFF : uint8_t(v * 2));
// depends on mode, auto (2 for enum_mode2, 0 for enum_mode8) set in offset 1
write_command(0xBB5 + rc->room(), rc->mode_ == 2 ? 1 : 3, v == -1 ? 0xFF : uint8_t(v * 2));
// write_command(0xBB5 + rc->room(), rc->mode_ == 0 ? 1 : 3, v == -1 ? 0xFF : uint8_t(v * 2));
return true;
}
return false;

View File

@@ -1 +1 @@
#define EMSESP_APP_VERSION "3.8.2-dev.5"
#define EMSESP_APP_VERSION "3.8.2-dev.7"

View File

@@ -343,6 +343,15 @@ bool WebCustomEntityService::get_value_info(JsonObject output, const char * cmd)
return true;
}
if (!strcmp(cmd, F_(metrics))) {
std::string metrics = get_metrics_prometheus();
if (!metrics.empty()) {
output["api_data"] = metrics;
return true;
}
return false;
}
// specific value info
const char * attribute_s = Command::get_attribute(cmd);
for (auto const & entity : *customEntityItems_) {
@@ -354,6 +363,54 @@ bool WebCustomEntityService::get_value_info(JsonObject output, const char * cmd)
return false; // not found
}
// generate Prometheus metrics format from custom entities
std::string WebCustomEntityService::get_metrics_prometheus() {
std::string result;
result.reserve(customEntityItems_->size() * 140);
char val[10];
for (CustomEntityItem & entity : *customEntityItems_) {
if (entity.hide || entity.name[0] == '\0') {
continue;
}
result += (std::string) "# HELP emsesp_" + entity.name + " " + entity.name;
if (entity.uom != 0) {
result += (std::string) ", " + EMSdevice::uom_to_string(entity.uom);
}
result += (std::string) ", readable, visible" + (entity.writeable ? ", writable\n" : "\n");
result += (std::string) "# TYPE emsesp_" + entity.name + " gauge\n";
result += (std::string) "emsesp_" + entity.name + " ";
switch (entity.value_type) {
case DeviceValueType::BOOL:
result += (std::string)(entity.value == 0 ? "0" : "1");
break;
case DeviceValueType::INT8:
result += (std::string)Helpers::render_value(val, entity.factor * (int8_t)entity.value, 2);
break;
case DeviceValueType::UINT8:
result += (std::string)Helpers::render_value(val, entity.factor * (uint8_t)entity.value, 2);
break;
case DeviceValueType::INT16:
result += (std::string)Helpers::render_value(val, entity.factor * (int16_t)entity.value, 2);
break;
case DeviceValueType::UINT16:
result += (std::string)Helpers::render_value(val, entity.factor * (uint16_t)entity.value, 2);
break;
case DeviceValueType::UINT24:
case DeviceValueType::TIME:
case DeviceValueType::UINT32:
result += (std::string)Helpers::render_value(val, entity.factor * entity.value, 2);
break;
default:
if (entity.data.length() > 0) {
result += entity.data;
}
break;
}
result += (std::string) "\n";
}
return result;
}
// build the json for specific entity
void WebCustomEntityService::get_value_json(JsonObject output, CustomEntityItem const & entity) {
output["name"] = (const char *)entity.name;

View File

@@ -68,6 +68,8 @@ class WebCustomEntityService : public StatefulService<WebCustomEntity> {
void show_values(JsonObject output);
void generate_value_web(JsonObject output, const bool is_dashboard = false);
std::string get_metrics_prometheus();
uint8_t count_entities();
void ha_reset() {
ha_configdone_ = false;

View File

@@ -102,7 +102,7 @@ StateUpdateResult WebScheduler::update(JsonObject root, WebScheduler & webSchedu
if (webScheduler.scheduleItems.back().name[0] != '\0') {
char key[sizeof(webScheduler.scheduleItems.back().name) + 2];
snprintf(key, sizeof(key), "s:%s", webScheduler.scheduleItems.back().name);
if (EMSESP::nvs_.isKey(key)) {
if (EMSESP::nvs_.isKey(key) && webScheduler.scheduleItems.back().flags != SCHEDULEFLAG_SCHEDULE_IMMEDIATE) {
webScheduler.scheduleItems.back().active = EMSESP::nvs_.getBool(key);
}
Command::add(
@@ -138,20 +138,11 @@ bool WebSchedulerService::command_setvalue(const char * value, const int8_t id,
publish();
}
// save new state to nvs #2946
if (scheduleItem.flags != SCHEDULEFLAG_SCHEDULE_IMMEDIATE) {
char key[sizeof(scheduleItem.name) + 2];
snprintf(key, sizeof(key), "s:%s", scheduleItem.name);
EMSESP::nvs_.putBool(key, scheduleItem.active);
/* save to filesystem
EMSESP::webSchedulerService.update([&](WebScheduler & webSchedule) {
for (auto si : webSchedule.scheduleItems) {
if (!strcmp(si.name, scheduleItem.name)) {
si.active = scheduleItem.active;
break;
}
}
return StateUpdateResult::CHANGED;
});
*/
return true;
}
}
@@ -184,6 +175,15 @@ bool WebSchedulerService::get_value_info(JsonObject output, const char * cmd) {
return true;
}
if (!strcmp(cmd, F_(metrics))) {
std::string metrics = get_metrics_prometheus();
if (!metrics.empty()) {
output["api_data"] = metrics;
return true;
}
return false;
}
const char * attribute_s = Command::get_attribute(cmd);
for (const ScheduleItem & scheduleItem : *scheduleItems_) {
if (Helpers::toLower(scheduleItem.name) == cmd) {
@@ -195,6 +195,21 @@ bool WebSchedulerService::get_value_info(JsonObject output, const char * cmd) {
return false; // not found
}
// generate Prometheus metrics format from scheduler values
std::string WebSchedulerService::get_metrics_prometheus() {
std::string result;
result.reserve(scheduleItems_->size() * 140);
for (const ScheduleItem & scheduleItem : *scheduleItems_) {
if (scheduleItem.name[0] == '\0') {
continue;
}
result += (std::string) "# HELP emsesp_" + scheduleItem.name + " " + scheduleItem.name + ", boolean, readable, visible, writable\n";
result += (std::string) "# TYPE emsesp_" + scheduleItem.name + " gauge\n";
result += (std::string) "emsesp_" + scheduleItem.name + " " + (scheduleItem.active ? "1\n" : "0\n");
}
return result;
}
// build the json for specific entity
void WebSchedulerService::get_value_json(JsonObject output, const ScheduleItem & scheduleItem) {
output["name"] = (const char *)scheduleItem.name;
@@ -483,6 +498,10 @@ void WebSchedulerService::loop() {
if (scheduleItem.active && scheduleItem.flags == SCHEDULEFLAG_SCHEDULE_IMMEDIATE) {
command(scheduleItem.name, scheduleItem.cmd.c_str(), compute(scheduleItem.value.c_str()));
scheduleItem.active = false;
publish_single(scheduleItem.name, false);
if (EMSESP::mqtt_.get_publish_onchange(0)) {
publish();
}
}
}

View File

@@ -88,6 +88,8 @@ class WebSchedulerService : public StatefulService<WebScheduler> {
uint8_t count_entities(bool cmd_only = false);
bool onChange(const char * cmd);
std::string get_metrics_prometheus();
std::string raw_value;
std::string computed_value;