mirror of
https://github.com/openshwprojects/OpenBK7231T_App.git
synced 2026-02-12 08:05:37 +00:00
Add energy sensors to MQTT + HA discovery for active power, reactive power, power factor (#1102)
* HASS discovery energy stats fixed except energycounter_clear_date * HASS: add friendly entity names, remove timestamp class from energycounter_clear_date as workaround for hass-incompatible date format * HA energycounter_clear_date fixed for correct interpreting as home assistant timestamp sensor * refactor HA power sensors discovery info * refactor HA power sensors discovery more * add apparent power, reactive power, power factor to mqtt + hass discovery, refactor some vars into new energy_sensors[] struct * amend hass sensor unique_ids due to mqtt topic/channel too long; 'Error:MQTT:Unable to queue! Topic (13), channel (66) or value (437) exceeds size limit' * hass sensors: add 'energy 2 days ago', 'energy 3 days ago', 'uptime' web UI: energy sensors apply their rounding setting drv_bl_shared.c: add enum for daily_stats[], put rearrange energy_sensor[] struct to expose only names via DRV_GetEnergySensorNames() * -HA energy sensor uniq_id values made consistent with prior builds via .hass_uniq_id_suffix -Refactor drv_bl_shared sensor/counter vars into energy_sensors[] to simplify mqtt transmissions etc -Add energy '2 days ago'/'3 days ago' to main web ui, data from vars already being saved to/from flash -NTP fix html formatting in web ui * -HA energy sensor uniq_id values made consistent with prior builds via .hass_uniq_id_suffix -Refactor drv_bl_shared sensor/counter vars into energy_sensors[] to simplify mqtt transmissions etc -Add energy '2 days ago'/'3 days ago' to main web ui, data from vars already being saved to/from flash -NTP fix html formatting in web ui * Update settings.json ignore vscode settings... * Update settings.json * Update settings.json * minor fix * fix OBK_CONSUMPTION_LAST_HOUR missing from mqtt --------- Co-authored-by: Stefan Smith <stefan064>
This commit is contained in:
@ -50,7 +50,11 @@ void hass_populate_unique_id(ENTITY_TYPE type, int index, char* uniq_id) {
|
||||
sprintf(uniq_id, "%s_%s_%d", longDeviceName, "relay", index);
|
||||
break;
|
||||
|
||||
case VCP_SENSOR:
|
||||
case ENERGY_METER_SENSOR:
|
||||
#ifndef OBK_DISABLE_ALL_DRIVERS
|
||||
sprintf(uniq_id, "%s_sensor_%s", longDeviceName, DRV_GetEnergySensorNames(index)->hass_uniq_id_suffix);
|
||||
#endif
|
||||
break;
|
||||
case POWER_SENSOR:
|
||||
case ENERGY_SENSOR:
|
||||
case TIMESTAMP_SENSOR:
|
||||
@ -94,6 +98,9 @@ void hass_populate_unique_id(ENTITY_TYPE type, int index, char* uniq_id) {
|
||||
break;
|
||||
case HASS_RSSI:
|
||||
sprintf(uniq_id, "%s_rssi", longDeviceName);
|
||||
break;
|
||||
case HASS_UPTIME:
|
||||
sprintf(uniq_id, "%s_uptime", longDeviceName);
|
||||
break;
|
||||
default:
|
||||
// TODO: USE type here as well?
|
||||
@ -141,7 +148,7 @@ void hass_populate_device_config_channel(ENTITY_TYPE type, char* uniq_id, HassDe
|
||||
case CO2_SENSOR:
|
||||
case TVOC_SENSOR:
|
||||
case POWER_SENSOR:
|
||||
case VCP_SENSOR:
|
||||
case ENERGY_METER_SENSOR:
|
||||
case ENERGY_SENSOR:
|
||||
case TIMESTAMP_SENSOR:
|
||||
case BATTERY_SENSOR:
|
||||
@ -182,7 +189,7 @@ cJSON* hass_build_device_node(cJSON* ids) {
|
||||
/// @brief Initializes HomeAssistant device discovery storage with common values.
|
||||
/// @param type
|
||||
/// @param index This is used to generate generate unique_id and name.
|
||||
/// It is ignored for RGB. For power sensors, index corresponds to sensor_mqttNames. For regular sensor, index can be be the channel.
|
||||
/// It is ignored for RGB. For energy sensors, index corresponds to energySensor_t. For regular sensor, index can be be the channel.
|
||||
/// @param payload_on The payload that represents enabled state. This is not added for POWER_SENSOR.
|
||||
/// @param payload_off The payload that represents disabled state. This is not added for POWER_SENSOR.
|
||||
/// @return
|
||||
@ -218,20 +225,19 @@ HassDeviceInfo* hass_init_device_info(ENTITY_TYPE type, int index, const char* p
|
||||
//for 2 PWM case.
|
||||
sprintf(g_hassBuffer, "Light");
|
||||
break;
|
||||
case VCP_SENSOR:
|
||||
case ENERGY_METER_SENSOR:
|
||||
isSensor = true;
|
||||
#ifndef OBK_DISABLE_ALL_DRIVERS
|
||||
if ((index >= OBK_VOLTAGE) && (index <= OBK_POWER))
|
||||
sprintf(g_hassBuffer, "%s", sensor_hassNames[index]);
|
||||
if (index <= OBK__LAST)
|
||||
sprintf(g_hassBuffer, "%s", DRV_GetEnergySensorNames(index)->name_friendly);
|
||||
else
|
||||
sprintf(g_hassBuffer, "Voltage or Current or Power");
|
||||
sprintf(g_hassBuffer, "Unknown Energy Meter Sensor");
|
||||
#endif
|
||||
break;
|
||||
case POWER_SENSOR:
|
||||
isSensor = true;
|
||||
sprintf(g_hassBuffer, "Power");
|
||||
break;
|
||||
|
||||
case TEMPERATURE_SENSOR:
|
||||
isSensor = true;
|
||||
sprintf(g_hassBuffer, "Temperature");
|
||||
@ -271,22 +277,15 @@ HassDeviceInfo* hass_init_device_info(ENTITY_TYPE type, int index, const char* p
|
||||
case HASS_RSSI:
|
||||
sprintf(g_hassBuffer, "RSSI");
|
||||
break;
|
||||
case HASS_UPTIME:
|
||||
sprintf(g_hassBuffer, "Uptime");
|
||||
break;
|
||||
case ENERGY_SENSOR:
|
||||
isSensor = true;
|
||||
#ifndef OBK_DISABLE_ALL_DRIVERS
|
||||
if ((index >= OBK_CONSUMPTION_TOTAL) && (index <= OBK_CONSUMPTION_TODAY))
|
||||
sprintf(g_hassBuffer, "%s", sensor_hassNames[index]);
|
||||
else
|
||||
sprintf(g_hassBuffer, "Energy");
|
||||
#endif
|
||||
sprintf(g_hassBuffer, "Energy");
|
||||
break;
|
||||
case TIMESTAMP_SENSOR:
|
||||
#ifndef OBK_DISABLE_ALL_DRIVERS
|
||||
if (index == OBK_CONSUMPTION_CLEAR_DATE)
|
||||
sprintf(g_hassBuffer, "%s", sensor_hassNames[index]);
|
||||
else
|
||||
sprintf(g_hassBuffer, "Timestamp");
|
||||
#endif
|
||||
sprintf(g_hassBuffer, "Timestamp");
|
||||
break;
|
||||
default:
|
||||
sprintf(g_hassBuffer, "%s", CHANNEL_GetLabel(index));
|
||||
@ -430,48 +429,35 @@ HassDeviceInfo* hass_init_binary_sensor_device_info(int index, bool bInverse) {
|
||||
#ifndef OBK_DISABLE_ALL_DRIVERS
|
||||
|
||||
/// @brief Initializes HomeAssistant power sensor device discovery storage.
|
||||
/// @param index Index corresponding to sensor_mqttNames.
|
||||
/// @param index Index corresponding to energySensor_t.
|
||||
/// @return
|
||||
HassDeviceInfo* hass_init_power_sensor_device_info(int index) {
|
||||
HassDeviceInfo* hass_init_energy_sensor_device_info(int index) {
|
||||
HassDeviceInfo* info = 0;
|
||||
|
||||
//https://developers.home-assistant.io/docs/core/entity/sensor/#available-device-classes
|
||||
//device_class automatically assigns unit,icon
|
||||
if ((index >= OBK_VOLTAGE) && (index <= OBK_POWER))
|
||||
{
|
||||
info = hass_init_device_info(VCP_SENSOR, index, NULL, NULL);
|
||||
cJSON_AddStringToObject(info->root, "dev_cla", sensor_mqtt_device_classes[index]); //device_class=voltage,current,power
|
||||
cJSON_AddStringToObject(info->root, "unit_of_meas", sensor_mqtt_device_units[index]); //unit_of_measurement
|
||||
if (index > OBK__LAST) return info;
|
||||
if (index >= OBK_CONSUMPTION__DAILY_FIRST && !DRV_IsRunning("NTP")) return info; //include daily stats only when time is valid
|
||||
|
||||
sprintf(g_hassBuffer, "~/%s/get", sensor_mqttNames[index]);
|
||||
cJSON_AddStringToObject(info->root, "stat_t", g_hassBuffer);
|
||||
info = hass_init_device_info(ENERGY_METER_SENSOR, index, NULL, NULL);
|
||||
|
||||
cJSON_AddStringToObject(info->root, "dev_cla", DRV_GetEnergySensorNames(index)->hass_dev_class); //device_class=voltage,current,power, energy, timestamp
|
||||
cJSON_AddStringToObject(info->root, "unit_of_meas", DRV_GetEnergySensorNames(index)->units); //unit_of_measurement. Sets as empty string if not present. HA doesn't seem to mind
|
||||
|
||||
sprintf(g_hassBuffer, "~/%s/get", DRV_GetEnergySensorNames(index)->name_mqtt);
|
||||
cJSON_AddStringToObject(info->root, "stat_t", g_hassBuffer);
|
||||
|
||||
if (!strcmp(DRV_GetEnergySensorNames(index)->hass_dev_class, "energy")) {
|
||||
//state_class can be measurement, total or total_increasing. Energy values should be total_increasing.
|
||||
cJSON_AddStringToObject(info->root, "stat_cla", "total_increasing");
|
||||
cJSON_AddStringToObject(info->root, "unit_of_meas", CFG_HasFlag(OBK_FLAG_MQTT_ENERGY_IN_KWH) ? "kWh" : "Wh");
|
||||
} else {
|
||||
cJSON_AddStringToObject(info->root, "stat_cla", "measurement");
|
||||
cJSON_AddStringToObject(info->root, "unit_of_meas", DRV_GetEnergySensorNames(index)->units);
|
||||
}
|
||||
else if ((index >= OBK_CONSUMPTION_TOTAL) && (index <= OBK_NUM_EMUNS_MAX))
|
||||
{
|
||||
if (index >= OBK_CONSUMPTION_YESTERDAY && !DRV_IsRunning("NTP")) return info; //include daily stats only when time is valid
|
||||
|
||||
info = hass_init_device_info(index == OBK_CONSUMPTION_CLEAR_DATE ? TIMESTAMP_SENSOR : ENERGY_SENSOR, index, NULL, NULL);
|
||||
const char* device_class_value = counter_devClasses[index - OBK_CONSUMPTION_TOTAL];
|
||||
if (strlen(device_class_value) > 0) {
|
||||
cJSON_AddStringToObject(info->root, "dev_cla", device_class_value); //device_class=energy,timestamp
|
||||
if (!strcmp(device_class_value, "energy")) {
|
||||
if (CFG_HasFlag(OBK_FLAG_MQTT_ENERGY_IN_KWH)) {
|
||||
cJSON_AddStringToObject(info->root, "unit_of_meas", "kWh"); //unit_of_measurement
|
||||
}
|
||||
else {
|
||||
cJSON_AddStringToObject(info->root, "unit_of_meas", "Wh"); //unit_of_measurement
|
||||
}
|
||||
//state_class can be measurement, total or total_increasing. Energy values should be total_increasing.
|
||||
cJSON_AddStringToObject(info->root, "stat_cla", "total_increasing");
|
||||
}
|
||||
}
|
||||
|
||||
sprintf(g_hassBuffer, "~/%s/get", counter_mqttNames[index - OBK_CONSUMPTION_TOTAL]);
|
||||
cJSON_AddStringToObject(info->root, "stat_t", g_hassBuffer);
|
||||
}
|
||||
|
||||
// if (index == OBK_CONSUMPTION_STATS) { //hide this as its not working anyway at present
|
||||
// cJSON_AddStringToObject(info->root, "enabled_by_default ", "false");
|
||||
// }
|
||||
return info;
|
||||
}
|
||||
|
||||
@ -657,8 +643,15 @@ HassDeviceInfo* hass_init_sensor_device_info(ENTITY_TYPE type, int channel, int
|
||||
cJSON_AddStringToObject(info->root, "dev_cla", "signal_strength");
|
||||
cJSON_AddStringToObject(info->root, "stat_t", "~/rssi");
|
||||
cJSON_AddStringToObject(info->root, "unit_of_meas", "dBm");
|
||||
cJSON_AddStringToObject(info->root, "entity_category", "diagnostic");
|
||||
//cJSON_AddStringToObject(info->root, "icon_template", "mdi:access-point");
|
||||
|
||||
break;
|
||||
case HASS_UPTIME:
|
||||
cJSON_AddStringToObject(info->root, "dev_cla", "duration");
|
||||
cJSON_AddStringToObject(info->root, "stat_t", "~/uptime");
|
||||
cJSON_AddStringToObject(info->root, "unit_of_meas", "s");
|
||||
cJSON_AddStringToObject(info->root, "entity_category", "diagnostic");
|
||||
cJSON_AddStringToObject(info->root, "stat_cla", "total_increasing");
|
||||
break;
|
||||
default:
|
||||
sprintf(g_hassBuffer, "~/%d/get", channel);
|
||||
@ -666,7 +659,7 @@ HassDeviceInfo* hass_init_sensor_device_info(ENTITY_TYPE type, int channel, int
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (type != READONLYLOWMIDHIGH_SENSOR && type != ENERGY_SENSOR && type != HASS_RSSI) {
|
||||
if (type != READONLYLOWMIDHIGH_SENSOR && !cJSON_HasObjectItem(info->root, "stat_cla")) {
|
||||
cJSON_AddStringToObject(info->root, "stat_cla", "measurement");
|
||||
}
|
||||
|
||||
|
||||
@ -25,7 +25,7 @@ typedef enum {
|
||||
LIGHT_RGBCW,
|
||||
|
||||
/// @brief Power sensors (voltage, current, power)
|
||||
VCP_SENSOR,
|
||||
ENERGY_METER_SENSOR,
|
||||
|
||||
POWER_SENSOR,
|
||||
|
||||
@ -65,8 +65,10 @@ typedef enum {
|
||||
READONLYLOWMIDHIGH_SENSOR,
|
||||
// lx unit
|
||||
ILLUMINANCE_SENSOR,
|
||||
// dBm unit
|
||||
/// @brief dBm unit
|
||||
HASS_RSSI,
|
||||
/// @brief Time firmware is alive in secs
|
||||
HASS_UPTIME,
|
||||
/// @brief Wh, kWh
|
||||
ENERGY_SENSOR,
|
||||
// hPa
|
||||
@ -102,7 +104,7 @@ void hass_print_unique_id(http_request_t* request, const char* fmt, ENTITY_TYPE
|
||||
HassDeviceInfo* hass_init_relay_device_info(int index, ENTITY_TYPE type, bool bInverse);
|
||||
HassDeviceInfo* hass_init_device_info(ENTITY_TYPE type, int index, const char* payload_on, const char* payload_off);
|
||||
HassDeviceInfo* hass_init_light_device_info(ENTITY_TYPE type);
|
||||
HassDeviceInfo* hass_init_power_sensor_device_info(int index);
|
||||
HassDeviceInfo* hass_init_energy_sensor_device_info(int index);
|
||||
HassDeviceInfo* hass_init_light_singleColor_onChannels(int toggle, int dimmer, int brightness_scale);
|
||||
HassDeviceInfo* hass_init_binary_sensor_device_info(int index, bool bInverse);
|
||||
HassDeviceInfo* hass_init_sensor_device_info(ENTITY_TYPE type, int channel, int decPlaces, int decOffset, int divider);
|
||||
|
||||
@ -1819,9 +1819,9 @@ void doHomeAssistantDiscovery(const char* topic, http_request_t* request) {
|
||||
|
||||
#ifndef OBK_DISABLE_ALL_DRIVERS
|
||||
if (measuringPower == true) {
|
||||
for (i = 0; i < OBK_NUM_SENSOR_COUNT; i++)
|
||||
for (i = OBK__FIRST; i < OBK__LAST; i++)
|
||||
{
|
||||
dev_info = hass_init_power_sensor_device_info(i);
|
||||
dev_info = hass_init_energy_sensor_device_info(i);
|
||||
if (dev_info) {
|
||||
MQTT_QueuePublish(topic, dev_info->channel, hass_build_discovery_json(dev_info), OBK_PUBLISH_FLAG_RETAIN);
|
||||
hass_free_device_info(dev_info);
|
||||
@ -2130,7 +2130,9 @@ void doHomeAssistantDiscovery(const char* topic, http_request_t* request) {
|
||||
dev_info = hass_init_sensor_device_info(HASS_RSSI, 0, -1, -1, 1);
|
||||
MQTT_QueuePublish(topic, dev_info->channel, hass_build_discovery_json(dev_info), OBK_PUBLISH_FLAG_RETAIN);
|
||||
hass_free_device_info(dev_info);
|
||||
|
||||
dev_info = hass_init_sensor_device_info(HASS_UPTIME, 0, -1, -1, 1);
|
||||
MQTT_QueuePublish(topic, dev_info->channel, hass_build_discovery_json(dev_info), OBK_PUBLISH_FLAG_RETAIN);
|
||||
hass_free_device_info(dev_info);
|
||||
discoveryQueued = true;
|
||||
|
||||
}
|
||||
|
||||
@ -193,17 +193,14 @@ static int http_tasmota_json_power(void* request, jsonCb_t printer) {
|
||||
{"StatusSNS":{"Time":"2022-07-30T10:11:26","ENERGY":{"TotalStartTime":"2022-05-12T10:56:31","Total":0.003,"Yesterday":0.003,"Today":0.000,"Power": 0,"ApparentPower": 0,"ReactivePower": 0,"Factor":0.00,"Voltage":236,"Current":0.000}}}
|
||||
*/
|
||||
|
||||
// returns NaN values as 0
|
||||
static float _getReading_NanToZero(energySensor_t type) {
|
||||
float retval = DRV_GetReading(type);
|
||||
return OBK_IS_NAN(retval) ? 0 : retval;
|
||||
}
|
||||
|
||||
static int http_tasmota_json_ENERGY(void* request, jsonCb_t printer) {
|
||||
float power, voltage, current, batterypercentage = 0;
|
||||
float energy, energy_hour, energy_yesterday;
|
||||
|
||||
voltage = DRV_GetReading(OBK_VOLTAGE);
|
||||
current = DRV_GetReading(OBK_CURRENT);
|
||||
power = DRV_GetReading(OBK_POWER);
|
||||
energy = DRV_GetReading(OBK_CONSUMPTION_TOTAL);
|
||||
energy_hour = DRV_GetReading(OBK_CONSUMPTION_LAST_HOUR);
|
||||
energy_yesterday = DRV_GetReading(OBK_CONSUMPTION_YESTERDAY);
|
||||
float voltage, batterypercentage = 0;
|
||||
|
||||
if (DRV_IsMeasuringBattery()) {
|
||||
#ifdef ENABLE_DRIVER_BATTERY
|
||||
@ -211,33 +208,22 @@ static int http_tasmota_json_ENERGY(void* request, jsonCb_t printer) {
|
||||
batterypercentage = Battery_lastreading(OBK_BATT_LEVEL);
|
||||
#endif
|
||||
printer(request, "{");
|
||||
printer(request, "\"Voltage\":%.4f,", voltage);
|
||||
printer(request, "\"Voltage\":%.4f,", _getReading_NanToZero(OBK_VOLTAGE));
|
||||
printer(request, "\"Batterypercentage\":%.0f", batterypercentage);
|
||||
// close ENERGY block
|
||||
printer(request, "}");
|
||||
}
|
||||
else {
|
||||
// following check will clear NaN values
|
||||
if (OBK_IS_NAN(energy)) {
|
||||
energy = 0;
|
||||
}
|
||||
if (OBK_IS_NAN(energy_hour)) {
|
||||
energy_hour = 0;
|
||||
}
|
||||
if (OBK_IS_NAN(energy_yesterday)) {
|
||||
energy_yesterday = 0;
|
||||
}
|
||||
|
||||
printer(request, "{");
|
||||
printer(request, "\"Power\": %f,", power);
|
||||
printer(request, "\"ApparentPower\": %f,", g_apparentPower);
|
||||
printer(request, "\"ReactivePower\": %f,", g_reactivePower);
|
||||
printer(request, "\"Factor\":%f,", g_powerFactor);
|
||||
printer(request, "\"Voltage\":%f,", voltage);
|
||||
printer(request, "\"Current\":%f,", current);
|
||||
printer(request, "\"ConsumptionTotal\":%f,", energy);
|
||||
printer(request, "\"Yesterday\": %f,", energy_yesterday);
|
||||
printer(request, "\"ConsumptionLastHour\":%f", energy_hour);
|
||||
printer(request, "\"Power\": %f,", _getReading_NanToZero(OBK_POWER));
|
||||
printer(request, "\"ApparentPower\": %f,", _getReading_NanToZero(OBK_POWER_APPARENT));
|
||||
printer(request, "\"ReactivePower\": %f,", _getReading_NanToZero(OBK_POWER_REACTIVE));
|
||||
printer(request, "\"Factor\":%f,", _getReading_NanToZero(OBK_POWER_FACTOR));
|
||||
printer(request, "\"Voltage\":%f,", _getReading_NanToZero(OBK_VOLTAGE));
|
||||
printer(request, "\"Current\":%f,", _getReading_NanToZero(OBK_CURRENT));
|
||||
printer(request, "\"ConsumptionTotal\":%f,", _getReading_NanToZero(OBK_CONSUMPTION_TOTAL));
|
||||
printer(request, "\"Yesterday\": %f,", _getReading_NanToZero(OBK_CONSUMPTION_YESTERDAY));
|
||||
printer(request, "\"ConsumptionLastHour\":%f", _getReading_NanToZero(OBK_CONSUMPTION_LAST_HOUR));
|
||||
// close ENERGY block
|
||||
printer(request, "}");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user