2020-01-01 16:17:10 +00:00
/*
2020-06-27 10:52:44 +01:00
xsns_75_prometheus . ino - Web based information for Tasmota
2020-01-01 16:17:10 +00:00
2021-01-01 12:44:04 +00:00
Copyright ( C ) 2021 Theo Arends
2020-01-01 16:17:10 +00:00
This program is free software : you can redistribute it and / or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation , either version 3 of the License , or
( at your option ) any later version .
This program is distributed in the hope that it will be useful ,
but WITHOUT ANY WARRANTY ; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
GNU General Public License for more details .
You should have received a copy of the GNU General Public License
along with this program . If not , see < http : //www.gnu.org/licenses/>.
*/
# ifdef USE_PROMETHEUS
/*********************************************************************************************\
* Prometheus support
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2020-12-16 22:51:02 +00:00
# define XSNS_75 75
2020-12-16 22:39:33 +00:00
2020-12-16 22:58:11 +00:00
const char * UnitfromType ( const char * type ) // find unit for measurment type
2020-12-16 22:39:33 +00:00
{
2020-12-24 11:37:59 +00:00
if ( strcmp ( type , " time " ) = = 0 ) {
return " seconds " ;
2020-12-16 22:39:33 +00:00
}
2020-12-24 11:37:59 +00:00
if ( strcmp ( type , " temperature " ) = = 0 | | strcmp ( type , " dewpoint " ) = = 0 ) {
return " celsius " ;
2020-12-16 22:39:33 +00:00
}
2020-12-24 11:37:59 +00:00
if ( strcmp ( type , " pressure " ) = = 0 ) {
return " hpa " ;
2020-12-16 22:39:33 +00:00
}
2020-12-24 11:37:59 +00:00
if ( strcmp ( type , " voltage " ) = = 0 ) {
return " volts " ;
2020-12-16 22:39:33 +00:00
}
2020-12-24 11:37:59 +00:00
if ( strcmp ( type , " current " ) = = 0 ) {
return " amperes " ;
2020-12-16 22:39:33 +00:00
}
2020-12-24 11:37:59 +00:00
if ( strcmp ( type , " mass " ) = = 0 ) {
return " grams " ;
2020-12-16 22:39:33 +00:00
}
2020-12-24 11:37:59 +00:00
if ( strcmp ( type , " carbondioxide " ) = = 0 ) {
return " ppm " ;
2020-12-16 22:39:33 +00:00
}
2020-12-24 11:37:59 +00:00
if ( strcmp ( type , " humidity " ) = = 0 ) {
return " percentage " ;
2020-12-16 22:39:33 +00:00
}
2021-04-28 17:58:19 +01:00
if ( strcmp ( type , " id " ) = = 0 ) {
return " untyped " ;
}
2020-12-16 22:39:33 +00:00
return " " ;
}
2020-12-24 11:37:59 +00:00
String FormatMetricName ( const char * metric ) { // cleanup spaces and uppercases for Prmetheus metrics conventions
String formatted = metric ;
formatted . toLowerCase ( ) ;
formatted . replace ( " " , " _ " ) ;
2021-03-18 02:26:24 +00:00
formatted . replace ( " . " , " _ " ) ;
2020-12-24 11:37:59 +00:00
return formatted ;
2020-12-16 22:39:33 +00:00
}
2020-01-01 16:17:10 +00:00
2020-12-24 11:37:59 +00:00
void HandleMetrics ( void ) {
2020-12-16 22:51:02 +00:00
if ( ! HttpCheckPriviledgedAccess ( ) ) { return ; }
2020-01-01 16:17:10 +00:00
2021-01-23 16:10:06 +00:00
AddLog ( LOG_LEVEL_DEBUG , PSTR ( D_LOG_HTTP " Prometheus " ) ) ;
2020-01-01 16:17:10 +00:00
WSContentBegin ( 200 , CT_PLAIN ) ;
char parameter [ FLOATSZ ] ;
2020-06-27 12:21:11 +01:00
// Pseudo-metric providing metadata about the running firmware version.
WSContentSend_P ( PSTR ( " # TYPE tasmota_info gauge \n tasmota_info{version= \" %s \" ,image= \" %s \" ,build_timestamp= \" %s \" } 1 \n " ) ,
2020-10-30 11:29:48 +00:00
TasmotaGlobal . version , TasmotaGlobal . image_name , GetBuildDateAndTime ( ) . c_str ( ) ) ;
2020-10-28 16:32:07 +00:00
WSContentSend_P ( PSTR ( " # TYPE tasmota_uptime_seconds gauge \n tasmota_uptime_seconds %d \n " ) , TasmotaGlobal . uptime ) ;
2020-06-27 03:10:00 +01:00
WSContentSend_P ( PSTR ( " # TYPE tasmota_boot_count counter \n tasmota_boot_count %d \n " ) , Settings . bootcount ) ;
2020-06-29 01:35:27 +01:00
WSContentSend_P ( PSTR ( " # TYPE tasmota_flash_writes_total counter \n tasmota_flash_writes_total %d \n " ) , Settings . save_flag ) ;
2020-06-25 11:06:24 +01:00
2020-12-16 22:51:02 +00:00
2020-06-28 02:43:29 +01:00
// Pseudo-metric providing metadata about the WiFi station.
WSContentSend_P ( PSTR ( " # TYPE tasmota_wifi_station_info gauge \n tasmota_wifi_station_info{bssid= \" %s \" ,ssid= \" %s \" } 1 \n " ) , WiFi . BSSIDstr ( ) . c_str ( ) , WiFi . SSID ( ) . c_str ( ) ) ;
// Wi-Fi Signal strength
WSContentSend_P ( PSTR ( " # TYPE tasmota_wifi_station_signal_dbm gauge \n tasmota_wifi_station_signal_dbm{mac_address= \" %s \" } %d \n " ) , WiFi . BSSIDstr ( ) . c_str ( ) , WiFi . RSSI ( ) ) ;
2020-12-16 22:51:02 +00:00
if ( ! isnan ( TasmotaGlobal . temperature_celsius ) ) {
2020-10-28 18:03:39 +00:00
dtostrfd ( TasmotaGlobal . temperature_celsius , Settings . flag2 . temperature_resolution , parameter ) ;
2021-04-28 21:39:00 +01:00
WSContentSend_P ( PSTR ( " # TYPE tasmota_global_temperature_celsius gauge \n tasmota_global_temperature_celsius %s \n " ) , parameter ) ;
2020-01-01 16:17:10 +00:00
}
2020-12-16 22:51:02 +00:00
if ( TasmotaGlobal . humidity ! = 0 ) {
2020-10-28 18:03:39 +00:00
dtostrfd ( TasmotaGlobal . humidity , Settings . flag2 . humidity_resolution , parameter ) ;
2021-04-28 21:39:00 +01:00
WSContentSend_P ( PSTR ( " # TYPE tasmota_global_humidity gauge \n tasmota_global_humidity_percentage %s \n " ) , parameter ) ;
2020-01-01 16:17:10 +00:00
}
2020-12-16 22:51:02 +00:00
if ( TasmotaGlobal . pressure_hpa ! = 0 ) {
2020-10-28 18:03:39 +00:00
dtostrfd ( TasmotaGlobal . pressure_hpa , Settings . flag2 . pressure_resolution , parameter ) ;
2021-04-28 21:39:00 +01:00
WSContentSend_P ( PSTR ( " # TYPE tasmota_global_pressure_hpa gauge \n tasmota_global_pressure_hpa %s \n " ) , parameter ) ;
2020-01-01 16:17:10 +00:00
}
2021-04-29 09:20:05 +01:00
// Pseudo-metric providing metadata about the free memory.
# ifdef ESP32
int32_t freeMaxMem = 100 - ( int32_t ) ( ESP_getMaxAllocHeap ( ) * 100 / ESP_getFreeHeap ( ) ) ;
WSContentSend_PD ( PSTR ( " # TYPE tasmota_memory_bytes gauge \n tasmota_memory_bytes{memory= \" Ram \" } %d \n " ) , ESP_getFreeHeap ( ) ) ;
WSContentSend_PD ( PSTR ( " # TYPE tasmota_memory_ratio gauge \n tasmota_memory_ratio{memory= \" Fragmentation \" } %d) " ) , freeMaxMem / 100 ) ;
if ( psramFound ( ) ) {
WSContentSend_P ( PSTR ( " # TYPE tasmota_memory_bytes gauge \n tasmota_memory_bytes{memory= \" Psram \" } %d \n " ) , ESP . getFreePsram ( ) ) ;
}
# else // ESP32
WSContentSend_PD ( PSTR ( " # TYPE tasmota_memory_bytes gauge \n tasmota_memory_bytes{memory= \" ram \" } %d \n " ) , ESP_getFreeHeap ( ) ) ;
# endif // ESP32
2020-01-01 16:17:10 +00:00
# ifdef USE_ENERGY_SENSOR
dtostrfd ( Energy . voltage [ 0 ] , Settings . flag2 . voltage_resolution , parameter ) ;
2020-08-19 19:57:28 +01:00
WSContentSend_P ( PSTR ( " # TYPE energy_voltage_volts gauge \n energy_voltage_volts %s \n " ) , parameter ) ;
2020-01-01 16:17:10 +00:00
dtostrfd ( Energy . current [ 0 ] , Settings . flag2 . current_resolution , parameter ) ;
2020-08-19 19:57:28 +01:00
WSContentSend_P ( PSTR ( " # TYPE energy_current_amperes gauge \n energy_current_amperes %s \n " ) , parameter ) ;
2020-01-01 16:17:10 +00:00
dtostrfd ( Energy . active_power [ 0 ] , Settings . flag2 . wattage_resolution , parameter ) ;
2020-08-19 19:57:28 +01:00
WSContentSend_P ( PSTR ( " # TYPE energy_power_active_watts gauge \n energy_power_active_watts %s \n " ) , parameter ) ;
2020-01-01 16:17:10 +00:00
dtostrfd ( Energy . daily , Settings . flag2 . energy_resolution , parameter ) ;
2020-08-19 19:57:28 +01:00
WSContentSend_P ( PSTR ( " # TYPE energy_power_kilowatts_daily counter \n energy_power_kilowatts_daily %s \n " ) , parameter ) ;
2020-01-01 16:17:10 +00:00
dtostrfd ( Energy . total , Settings . flag2 . energy_resolution , parameter ) ;
2020-08-19 19:57:28 +01:00
WSContentSend_P ( PSTR ( " # TYPE energy_power_kilowatts_total counter \n energy_power_kilowatts_total %s \n " ) , parameter ) ;
2020-01-01 16:17:10 +00:00
# endif
2020-12-16 22:51:02 +00:00
for ( uint32_t device = 0 ; device < TasmotaGlobal . devices_present ; device + + ) {
2020-12-12 09:35:57 +00:00
power_t mask = 1 < < device ;
2020-12-16 22:51:02 +00:00
WSContentSend_P ( PSTR ( " # TYPE relay%d_state gauge \n relay%d_state %d \n " ) , device + 1 , device + 1 , ( TasmotaGlobal . power & mask ) ) ;
2020-12-12 09:35:57 +00:00
}
2020-10-30 11:29:48 +00:00
ResponseClear ( ) ;
2020-12-16 22:58:11 +00:00
MqttShowSensor ( ) ; //Pull sensor data
2021-05-24 15:52:59 +01:00
char json [ ResponseLength ( ) + 1 ] ;
2020-10-30 11:29:48 +00:00
snprintf_P ( json , sizeof ( json ) , TasmotaGlobal . mqtt_data ) ;
2020-12-16 22:39:33 +00:00
String jsonStr = json ;
JsonParser parser ( ( char * ) jsonStr . c_str ( ) ) ;
JsonParserObject root = parser . getRootObject ( ) ;
2020-12-24 11:37:59 +00:00
if ( root ) { // did JSON parsing went ok?
for ( auto key1 : root ) {
2020-12-16 22:39:33 +00:00
JsonParserToken value1 = key1 . getValue ( ) ;
2020-12-24 11:37:59 +00:00
if ( value1 . isObject ( ) ) {
2020-12-16 22:39:33 +00:00
JsonParserObject Object2 = value1 . getObject ( ) ;
2020-12-24 11:37:59 +00:00
for ( auto key2 : Object2 ) {
2020-12-16 22:39:33 +00:00
JsonParserToken value2 = key2 . getValue ( ) ;
2020-12-24 11:37:59 +00:00
if ( value2 . isObject ( ) ) {
2020-12-16 22:39:33 +00:00
JsonParserObject Object3 = value2 . getObject ( ) ;
2020-12-24 11:37:59 +00:00
for ( auto key3 : Object3 ) {
2020-12-16 22:39:33 +00:00
const char * value = key3 . getValue ( ) . getStr ( nullptr ) ;
2020-12-24 11:37:59 +00:00
if ( value ! = nullptr & & isdigit ( value [ 0 ] ) ) {
String sensor = FormatMetricName ( key2 . getStr ( ) ) ;
String type = FormatMetricName ( key3 . getStr ( ) ) ;
const char * unit = UnitfromType ( type . c_str ( ) ) ; //grab base unit corresponding to type
WSContentSend_P ( PSTR ( " # TYPE tasmota_sensors_%s_%s gauge \n tasmota_sensors_%s_%s{sensor= \" %s \" } %s \n " ) ,
type . c_str ( ) , unit , type . c_str ( ) , unit , sensor . c_str ( ) , value ) ; //build metric as "# TYPE tasmota_sensors_%type%_%unit% gauge\ntasmotasensors_%type%_%unit%{sensor=%sensor%"} %value%""
2020-12-16 22:39:33 +00:00
}
}
2020-12-24 11:37:59 +00:00
} else {
2020-12-16 22:39:33 +00:00
const char * value = value2 . getStr ( nullptr ) ;
2020-12-24 11:37:59 +00:00
if ( value ! = nullptr & & isdigit ( value [ 0 ] ) ) {
String sensor = FormatMetricName ( key1 . getStr ( ) ) ;
String type = FormatMetricName ( key2 . getStr ( ) ) ;
const char * unit = UnitfromType ( type . c_str ( ) ) ;
if ( strcmp ( type . c_str ( ) , " totalstarttime " ) ! = 0 ) { // this metric causes prometheus of fail
2021-04-28 17:58:19 +01:00
if ( strcmp ( type . c_str ( ) , " id " ) = = 0 ) { // this metric is NaN, so convert it to a label, see Wi-Fi metrics above
WSContentSend_P ( PSTR ( " # TYPE tasmota_sensors_%s_%s gauge \n tasmota_sensors_%s_%s{sensor= \" %s \" ,id= \" %s \" } 1 \n " ) ,
type . c_str ( ) , unit , type . c_str ( ) , unit , sensor . c_str ( ) , value ) ;
} else {
WSContentSend_P ( PSTR ( " # TYPE tasmota_sensors_%s_%s gauge \n tasmota_sensors_%s_%s{sensor= \" %s \" } %s \n " ) ,
type . c_str ( ) , unit , type . c_str ( ) , unit , sensor . c_str ( ) , value ) ;
}
2020-12-24 11:37:59 +00:00
}
2020-12-16 22:39:33 +00:00
}
}
}
2020-12-24 11:37:59 +00:00
} else {
2020-12-16 22:39:33 +00:00
const char * value = value1 . getStr ( nullptr ) ;
2020-12-24 11:37:59 +00:00
String sensor = FormatMetricName ( key1 . getStr ( ) ) ;
if ( value ! = nullptr & & isdigit ( value [ 0 ] & & strcmp ( sensor . c_str ( ) , " time " ) ! = 0 ) ) { //remove false 'time' metric
WSContentSend_P ( PSTR ( " # TYPE tasmota_sensors_%s gauge \n tasmota_sensors{sensor= \" %s \" } %s \n " ) , sensor . c_str ( ) , sensor . c_str ( ) , value ) ;
2020-12-16 22:39:33 +00:00
}
}
}
}
2020-01-02 13:17:49 +00:00
2020-01-01 16:17:10 +00:00
WSContentEnd ( ) ;
}
/*********************************************************************************************\
* Interface
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2020-12-24 11:37:59 +00:00
bool Xsns75 ( uint8_t function ) {
2020-01-01 16:17:10 +00:00
bool result = false ;
2020-12-16 22:51:02 +00:00
switch ( function ) {
2020-12-16 22:58:11 +00:00
case FUNC_WEB_ADD_HANDLER :
WebServer_on ( PSTR ( " /metrics " ) , HandleMetrics ) ;
break ;
2020-01-01 16:17:10 +00:00
}
return result ;
}
2020-12-16 22:51:02 +00:00
# endif // USE_PROMETHEUS