diff --git a/tasmota/xdrv_23_zigbee_5_converters.ino b/tasmota/xdrv_23_zigbee_5_converters.ino index 5f5b5f868..074c44ec6 100644 --- a/tasmota/xdrv_23_zigbee_5_converters.ino +++ b/tasmota/xdrv_23_zigbee_5_converters.ino @@ -625,15 +625,15 @@ const Z_AttributeConverter Z_PostProcess[] PROGMEM = { { Ztuya1, CxEF00, 0x0112, Z_(TuyaWindowDetection), Cm1, 0 }, { Ztuya1, CxEF00, 0x0114, Z_(TuyaValveDetection), Cm1, 0 }, { Ztuya1, CxEF00, 0x0174, Z_(TuyaAutoLock), Cm1, 0 }, - { Ztuya2, CxEF00, 0x0202, Z_(TuyaTempTarget), Cm_10, 0 }, - { Ztuya2, CxEF00, 0x0203, Z_(LocalTemperature), Cm_10, 0 }, // will be overwritten by actual LocalTemperature + { Zint16, CxEF00, 0x0202, Z_(TuyaTempTarget), Cm_10, Z_MAPPING(Z_Data_Thermo, temperature_target) }, + { Zint16, CxEF00, 0x0203, Z_(LocalTemperature), Cm_10, Z_MAPPING(Z_Data_Thermo, temperature) }, // will be overwritten by actual LocalTemperature { Ztuya2, CxEF00, 0x0215, Z_(TuyaBattery), Cm1, 0 }, // TODO check equivalent? { Ztuya2, CxEF00, 0x0266, Z_(TuyaMinTemp), Cm1, 0 }, { Ztuya2, CxEF00, 0x0267, Z_(TuyaMaxTemp), Cm1, 0 }, { Ztuya2, CxEF00, 0x0269, Z_(TuyaBoostTime), Cm1, 0 }, { Ztuya2, CxEF00, 0x026B, Z_(TuyaComfortTemp), Cm1, 0 }, { Ztuya2, CxEF00, 0x026C, Z_(TuyaEcoTemp), Cm1, 0 }, - { Ztuya2, CxEF00, 0x026D, Z_(TuyaValvePosition), Cm1, 0 }, + { Zuint8, CxEF00, 0x026D, Z_(TuyaValvePosition), Cm1, Z_MAPPING(Z_Data_Thermo, th_setpoint) }, { Ztuya2, CxEF00, 0x0272, Z_(TuyaAwayTemp), Cm1, 0 }, { Ztuya2, CxEF00, 0x0275, Z_(TuyaAwayDays), Cm1, 0 }, { Ztuya4, CxEF00, 0x0404, Z_(TuyaPreset), Cm1, 0 }, @@ -1932,6 +1932,13 @@ void Z_postProcessAttributes(uint16_t shortaddr, uint16_t src_ep, class Z_attrib uint32_t uval32 = attr.getUInt(); // call converter to uint only once int32_t ival32 = attr.getInt(); // call converter to int only once // AddLog_P(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_ZIGBEE "Mapping type=%d offset=%d zigbee_type=%02X value=%d\n"), (uint8_t) map_type, map_offset, zigbee_type, ival32); + switch (ccccaaaa) { + case 0xEF000202: + case 0xEF000203: // need to convert Tuya temperatures from 1/10 to 1/00 °C + ival32 = ival32 * 10; + break; + } + switch (zigbee_type) { case Zenum8: case Zmap8: diff --git a/tasmota/xsns_75_prometheus.ino b/tasmota/xsns_75_prometheus.ino index 503291cd2..e4880a3fd 100644 --- a/tasmota/xsns_75_prometheus.ino +++ b/tasmota/xsns_75_prometheus.ino @@ -24,6 +24,61 @@ #define XSNS_75 75 +const char *UnitfromType(const char *type) // find unit for measurment type +{ + if (strcmp(type, "time") == 0) + { + return "_seconds"; + } + if (strcmp(type, "temperature") == 0 || strcmp(type, "dewpoint") == 0) + { + return "_celsius"; + } + if (strcmp(type, "pressure") == 0) + { + return "_hpa"; + } + if (strcmp(type, "voltage") == 0) + { + return "_volts"; + } + if (strcmp(type, "current") == 0) + { + return "_amperes"; + } + if (strcmp(type, "mass") == 0) + { + return "_grams"; + } + if (strcmp(type, "carbondioxide") == 0) + { + return "_ppm"; + } + if (strcmp(type, "humidity") == 0) + { + return "_percentage"; + } + return ""; +} + +const char *FormatMetricName(const char *metric) // cleanup spaces and uppercases for Prmetheus metrics conventions +{ + char *formated = (char *)malloc(strlen(metric)+1); + uint32_t cnt = 0; + for (cnt; cnt < strlen(metric)+1; cnt++) + { + if (metric[cnt] == ' ') + { + formated[cnt] = '_'; + } + else + { + formated[cnt] = tolower(metric[cnt]); + } + } + return formated; +} + void HandleMetrics(void) { if (!HttpCheckPriviledgedAccess()) { return; } @@ -80,19 +135,63 @@ void HandleMetrics(void) WSContentSend_P(PSTR("# TYPE relay%d_state gauge\nrelay%d_state %d\n"), device+1, device+1, (TasmotaGlobal.power & mask)); } -/* - // Alternative method using the complete sensor JSON data - // For prometheus it may need to be decoded to # TYPE messages ResponseClear(); - MqttShowSensor(); - char json[strlen(TasmotaGlobal.mqtt_data) +1]; + MqttShowSensor(); //Pull sensor data + char json[strlen(TasmotaGlobal.mqtt_data)+1]; snprintf_P(json, sizeof(json), TasmotaGlobal.mqtt_data); - - // Do your Prometheus specific processing here. - // Look at function DisplayAnalyzeJson() in file xdrv_13_display.ino as an example how to decode the JSON message - - WSContentSend_P(json); -*/ + String jsonStr = json; + JsonParser parser((char *)jsonStr.c_str()); + JsonParserObject root = parser.getRootObject(); + if (root) + { // did JSON parsing went ok? + for (auto key1 : root) + { + JsonParserToken value1 = key1.getValue(); + if (value1.isObject()) + { + JsonParserObject Object2 = value1.getObject(); + for (auto key2 : Object2) + { + JsonParserToken value2 = key2.getValue(); + if (value2.isObject()) + { + JsonParserObject Object3 = value2.getObject(); + for (auto key3 : Object3) + { + const char *value = key3.getValue().getStr(nullptr); + if (value != nullptr && isdigit(value[0])) + { + const char *sensor = FormatMetricName(key2.getStr()); //cleanup sensor name + const char *type = FormatMetricName(key3.getStr()); //cleanup sensor type + const char *unit = UnitfromType(type); //grab base unit corresponding to type + WSContentSend_P(PSTR("# TYPE tasmota_sensors_%s%s gauge\ntasmota_sensors_%s%s{sensor=\"%s\"} %s\n"), type, unit, type, unit, sensor, value); //build metric as "# TYPE tasmota_sensors_%type%_%unit% gauge\ntasmotasensors_%type%_%unit%{sensor=%sensor%"} %value%"" + } + } + } + else + { + const char *value = value2.getStr(nullptr); + if (value != nullptr && isdigit(value[0])) + { + const char *sensor = FormatMetricName(key1.getStr()); + const char *type = FormatMetricName(key2.getStr()); + const char *unit = UnitfromType(type); + WSContentSend_P(PSTR("# TYPE tasmota_sensors_%s%s gauge\ntasmota_sensors_%s%s{sensor=\"%s\"} %s\n"), type, unit, type, unit, sensor, value); + } + } + } + } + else + { + const char *value = value1.getStr(nullptr); + if (value != nullptr && isdigit(value[0] && strcmp(key1.getStr(), "Time") != 0)) //remove false 'time' metric + { + const char *sensor = FormatMetricName(key1.getStr()); + WSContentSend_P(PSTR("# TYPE tasmota_sensors_%s gauge\ntasmota_sensors{sensor=\"%s\"} %s\n"), sensor, sensor, value); + } + } + } + } WSContentEnd(); }