diff --git a/src/httpserver/hass.c b/src/httpserver/hass.c index ff8ed46e5..4587efb33 100644 --- a/src/httpserver/hass.c +++ b/src/httpserver/hass.c @@ -17,8 +17,6 @@ Sensor - https://www.home-assistant.io/integrations/sensor.mqtt/ //CFG_GetShortDeviceName and clientId so it needs to be bigger than them. +64 for light/switch/etc. static char g_hassBuffer[CGF_MQTT_CLIENT_ID_SIZE + 64]; -static char* STATE_TOPIC_KEY = "stat_t"; -static char* COMMAND_TOPIC_KEY = "cmd_t"; /// @brief Populates HomeAssistant unique id for the entity. /// @param type Entity type @@ -245,9 +243,9 @@ HassDeviceInfo* hass_init_relay_device_info(int index, ENTITY_TYPE type) { HassDeviceInfo* info = hass_init_device_info(type, index, "1", "0"); sprintf(g_hassBuffer, "~/%i/get", index); - cJSON_AddStringToObject(info->root, STATE_TOPIC_KEY, g_hassBuffer); //state_topic + cJSON_AddStringToObject(info->root, "stat_t", g_hassBuffer); //state_topic sprintf(g_hassBuffer, "~/%i/set", index); - cJSON_AddStringToObject(info->root, COMMAND_TOPIC_KEY, g_hassBuffer); //command_topic + cJSON_AddStringToObject(info->root, "cmd_t", g_hassBuffer); //command_topic return info; } @@ -306,9 +304,9 @@ HassDeviceInfo* hass_init_light_device_info(ENTITY_TYPE type) { cJSON_AddStringToObject(info->root, "max_mirs", g_hassBuffer); //max_mireds } - cJSON_AddStringToObject(info->root, STATE_TOPIC_KEY, "~/led_enableAll/get"); //state_topic + cJSON_AddStringToObject(info->root, "stat_t", "~/led_enableAll/get"); //state_topic sprintf(g_hassBuffer, "cmnd/%s/led_enableAll", clientId); - cJSON_AddStringToObject(info->root, COMMAND_TOPIC_KEY, g_hassBuffer); //command_topic + cJSON_AddStringToObject(info->root, "cmd_t", g_hassBuffer); //command_topic cJSON_AddStringToObject(info->root, "bri_stat_t", "~/led_dimmer/get"); //brightness_state_topic sprintf(g_hassBuffer, "cmnd/%s/led_dimmer", clientId); @@ -326,7 +324,7 @@ HassDeviceInfo* hass_init_binary_sensor_device_info(int index) { HassDeviceInfo* info = hass_init_device_info(BINARY_SENSOR, index, "1", "0"); sprintf(g_hassBuffer, "~/%i/get", index); - cJSON_AddStringToObject(info->root, STATE_TOPIC_KEY, g_hassBuffer); //state_topic + cJSON_AddStringToObject(info->root, "stat_t", g_hassBuffer); //state_topic return info; } @@ -347,7 +345,7 @@ HassDeviceInfo* hass_init_power_sensor_device_info(int index) { cJSON_AddStringToObject(info->root, "unit_of_meas", sensor_mqtt_device_units[index]); //unit_of_measurement sprintf(g_hassBuffer, "~/%s/get", sensor_mqttNames[index]); - cJSON_AddStringToObject(info->root, STATE_TOPIC_KEY, g_hassBuffer); + cJSON_AddStringToObject(info->root, "stat_t", g_hassBuffer); cJSON_AddStringToObject(info->root, "stat_cla", "measurement"); } @@ -363,7 +361,7 @@ HassDeviceInfo* hass_init_power_sensor_device_info(int index) { } sprintf(g_hassBuffer, "~/%s/get", counter_mqttNames[index - OBK_CONSUMPTION_TOTAL]); - cJSON_AddStringToObject(info->root, STATE_TOPIC_KEY, g_hassBuffer); + cJSON_AddStringToObject(info->root, "stat_t", g_hassBuffer); } return info; @@ -371,11 +369,33 @@ HassDeviceInfo* hass_init_power_sensor_device_info(int index) { #endif +// generate string like "{{ float(value)*0.1|round(2) }}" +// {{ float(value)*0.1 }} for value=12 give 1.2000000000000002, using round() to limit the decimal places +char *hass_generate_multiplyAndRound_template(int decimalPlacesForRounding, int decimalPointOffset) { + char tmp[8]; + int i; + + strcpy(g_hassBuffer, "{{ float(value)*"); + if (decimalPointOffset != 0) { + strcat(g_hassBuffer, "0."); + for (i = 1; i < decimalPointOffset; i++) { + strcat(g_hassBuffer, "0"); + } + } + strcat(g_hassBuffer, "1|round("); + sprintf(tmp, "%i", decimalPlacesForRounding); + strcat(g_hassBuffer, tmp); + strcat(g_hassBuffer, ") }}"); + + return g_hassBuffer; +} /// @brief Initializes HomeAssistant sensor device discovery storage. /// @param type /// @param channel /// @return HassDeviceInfo* hass_init_sensor_device_info(ENTITY_TYPE type, int channel) { + int i; + //Assuming that there is only one DHT setup per device which keeps uniqueid/names simpler HassDeviceInfo* info = hass_init_device_info(type, channel, NULL, NULL); //using channel as index to generate uniqueId @@ -386,43 +406,43 @@ HassDeviceInfo* hass_init_sensor_device_info(ENTITY_TYPE type, int channel) { cJSON_AddStringToObject(info->root, "unit_of_meas", "°C"); //https://www.home-assistant.io/integrations/sensor.mqtt/ refers to value_template (val_tpl) - //{{ float(value)*0.1 }} for value=12 give 1.2000000000000002, using round() to limit the decimal places - cJSON_AddStringToObject(info->root, "val_tpl", "{{ float(value)*0.1|round(2) }}"); + cJSON_AddStringToObject(info->root, "val_tpl", hass_generate_multiplyAndRound_template(2,1)); + sprintf(g_hassBuffer, "~/%d/get", channel); - cJSON_AddStringToObject(info->root, STATE_TOPIC_KEY, g_hassBuffer); + cJSON_AddStringToObject(info->root, "stat_t", g_hassBuffer); break; case HUMIDITY_SENSOR: cJSON_AddStringToObject(info->root, "dev_cla", "humidity"); cJSON_AddStringToObject(info->root, "unit_of_meas", "%"); sprintf(g_hassBuffer, "~/%d/get", channel); - cJSON_AddStringToObject(info->root, STATE_TOPIC_KEY, g_hassBuffer); + cJSON_AddStringToObject(info->root, "stat_t", g_hassBuffer); break; case CO2_SENSOR: cJSON_AddStringToObject(info->root, "dev_cla", "carbon_dioxide"); cJSON_AddStringToObject(info->root, "unit_of_meas", "ppm"); sprintf(g_hassBuffer, "~/%d/get", channel); - cJSON_AddStringToObject(info->root, STATE_TOPIC_KEY, g_hassBuffer); + cJSON_AddStringToObject(info->root, "stat_t", g_hassBuffer); break; case TVOC_SENSOR: cJSON_AddStringToObject(info->root, "dev_cla", "volatile_organic_compounds"); cJSON_AddStringToObject(info->root, "unit_of_meas", "ppb"); sprintf(g_hassBuffer, "~/%d/get", channel); - cJSON_AddStringToObject(info->root, STATE_TOPIC_KEY, g_hassBuffer); + cJSON_AddStringToObject(info->root, "stat_t", g_hassBuffer); break; case BATTERY_SENSOR: cJSON_AddStringToObject(info->root, "dev_cla", "battery"); cJSON_AddStringToObject(info->root, "unit_of_meas", "%"); - cJSON_AddStringToObject(info->root, STATE_TOPIC_KEY, "~/battery/get"); + cJSON_AddStringToObject(info->root, "stat_t", "~/battery/get"); break; case BATTERY_VOLTAGE_SENSOR: cJSON_AddStringToObject(info->root, "dev_cla", "voltage"); cJSON_AddStringToObject(info->root, "unit_of_meas", "mV"); - cJSON_AddStringToObject(info->root, STATE_TOPIC_KEY, "~/voltage/get"); + cJSON_AddStringToObject(info->root, "stat_t", "~/voltage/get"); break; default: sprintf(g_hassBuffer, "~/%d/get", channel); - cJSON_AddStringToObject(info->root, STATE_TOPIC_KEY, g_hassBuffer); + cJSON_AddStringToObject(info->root, "stat_t", g_hassBuffer); return NULL; } diff --git a/src/httpserver/hass.h b/src/httpserver/hass.h index d1d8eb197..5c9dc893f 100644 --- a/src/httpserver/hass.h +++ b/src/httpserver/hass.h @@ -76,4 +76,6 @@ HassDeviceInfo* hass_init_power_sensor_device_info(int index); HassDeviceInfo* hass_init_binary_sensor_device_info(int index); HassDeviceInfo* hass_init_sensor_device_info(ENTITY_TYPE type, int channel); const char* hass_build_discovery_json(HassDeviceInfo* info); -void hass_free_device_info(HassDeviceInfo* info); +void hass_free_device_info(HassDeviceInfo* info); +char *hass_generate_multiplyAndRound_template(int decimalPlacesForRounding, int decimalPointOffset); + diff --git a/src/httpserver/http_fns.c b/src/httpserver/http_fns.c index 4fb92be22..c89d9c68a 100644 --- a/src/httpserver/http_fns.c +++ b/src/httpserver/http_fns.c @@ -1576,6 +1576,7 @@ void doHomeAssistantDiscovery(const char* topic, http_request_t* request) { bool measuringBattery = false; struct cJSON_Hooks hooks; bool discoveryQueued = false; + int type; if (topic == 0 || *topic == 0) { topic = "homeassistant"; @@ -1705,7 +1706,41 @@ void doHomeAssistantDiscovery(const char* topic, http_request_t* request) { discoveryQueued = true; } } +#if WINDOWS + for (i = 0; i < CHANNEL_MAX; i++) { + type = g_cfg.pins.channelTypes[i]; + switch (type) + { + case ChType_OpenClosed: + { + dev_info = hass_init_binary_sensor_device_info(i); + MQTT_QueuePublish(topic, dev_info->channel, hass_build_discovery_json(dev_info), OBK_PUBLISH_FLAG_RETAIN); + hass_free_device_info(dev_info); + discoveryQueued = true; + } + break; + case ChType_Temperature: + { + dev_info = hass_init_sensor_device_info(TEMPERATURE_SENSOR, i); + MQTT_QueuePublish(topic, dev_info->channel, hass_build_discovery_json(dev_info), OBK_PUBLISH_FLAG_RETAIN); + hass_free_device_info(dev_info); + + discoveryQueued = true; + } + break; + case ChType_Humidity: + { + dev_info = hass_init_sensor_device_info(HUMIDITY_SENSOR, i); + MQTT_QueuePublish(topic, dev_info->channel, hass_build_discovery_json(dev_info), OBK_PUBLISH_FLAG_RETAIN); + hass_free_device_info(dev_info); + + discoveryQueued = true; + } + break; + } + } +#endif if (discoveryQueued) { MQTT_InvokeCommandAtEnd(PublishChannels); } diff --git a/src/selftest/selftest_hass_discovery.c b/src/selftest/selftest_hass_discovery.c index 801c37ed5..204d82580 100644 --- a/src/selftest/selftest_hass_discovery.c +++ b/src/selftest/selftest_hass_discovery.c @@ -191,6 +191,7 @@ void Test_HassDiscovery_DHT11() { // first dev - as temperature SELFTEST_ASSERT_HAS_MQTT_JSON_SENT_ANY("homeassistant", true, 0, 0, "dev_cla", "temperature"); //SELFTEST_ASSERT_HAS_MQTT_JSON_SENT_ANY("homeassistant", true, 0, 0, "unit_of_meas", "°C"); + SELFTEST_ASSERT_HAS_MQTT_JSON_SENT_ANY("homeassistant", true, 0, 0, "val_tpl", "{{ float(value)*0.1|round(2) }}"); // second dev - humidity SELFTEST_ASSERT_HAS_MQTT_JSON_SENT_ANY("homeassistant", true, 0, 0, "dev_cla", "humidity"); SELFTEST_ASSERT_HAS_MQTT_JSON_SENT_ANY("homeassistant", true, 0, 0, "unit_of_meas", "%"); diff --git a/src/selftest/selftest_hass_discovery_base.c b/src/selftest/selftest_hass_discovery_base.c new file mode 100644 index 000000000..655aa1f20 --- /dev/null +++ b/src/selftest/selftest_hass_discovery_base.c @@ -0,0 +1,28 @@ +#ifdef WINDOWS + +#include "selftest_local.h" +#include "../httpserver/hass.h" + +void Test_HassDiscovery_Base() { + + + SELFTEST_ASSERT_STRING(hass_generate_multiplyAndRound_template(2, 1), "{{ float(value)*0.1|round(2) }}"); + SELFTEST_ASSERT_STRING(hass_generate_multiplyAndRound_template(2, 2), "{{ float(value)*0.01|round(2) }}"); + SELFTEST_ASSERT_STRING(hass_generate_multiplyAndRound_template(2, 3), "{{ float(value)*0.001|round(2) }}"); + SELFTEST_ASSERT_STRING(hass_generate_multiplyAndRound_template(2, 4), "{{ float(value)*0.0001|round(2) }}"); + SELFTEST_ASSERT_STRING(hass_generate_multiplyAndRound_template(2, 5), "{{ float(value)*0.00001|round(2) }}"); + SELFTEST_ASSERT_STRING(hass_generate_multiplyAndRound_template(1, 0), "{{ float(value)*1|round(1) }}"); + SELFTEST_ASSERT_STRING(hass_generate_multiplyAndRound_template(2, 0), "{{ float(value)*1|round(2) }}"); + SELFTEST_ASSERT_STRING(hass_generate_multiplyAndRound_template(3, 0), "{{ float(value)*1|round(3) }}"); + SELFTEST_ASSERT_STRING(hass_generate_multiplyAndRound_template(4, 0), "{{ float(value)*1|round(4) }}"); + SELFTEST_ASSERT_STRING(hass_generate_multiplyAndRound_template(1, 5), "{{ float(value)*0.00001|round(1) }}"); + SELFTEST_ASSERT_STRING(hass_generate_multiplyAndRound_template(2, 5), "{{ float(value)*0.00001|round(2) }}"); + SELFTEST_ASSERT_STRING(hass_generate_multiplyAndRound_template(3, 5), "{{ float(value)*0.00001|round(3) }}"); + SELFTEST_ASSERT_STRING(hass_generate_multiplyAndRound_template(4, 5), "{{ float(value)*0.00001|round(4) }}"); + + // causes an error + //SELFTEST_ASSERT_STRING(hass_generate_multiplyAndRound_template(3, 5), "{{ float(value)*0.00001|round(4) }}"); +} + + +#endif diff --git a/src/selftest/selftest_hass_discovery_ext.c b/src/selftest/selftest_hass_discovery_ext.c new file mode 100644 index 000000000..63b0c4a4d --- /dev/null +++ b/src/selftest/selftest_hass_discovery_ext.c @@ -0,0 +1,37 @@ +#ifdef WINDOWS + +#include "selftest_local.h". + +void Test_HassDiscovery_TuyaMCU_VoltageCurrentPower() { + const char *shortName = "WinTuyatest"; + const char *fullName = "Windows Fake Tuya"; + const char *mqttName = "testTuya"; + SIM_ClearOBK(shortName); + SIM_ClearAndPrepareForMQTTTesting(mqttName, "bekens"); + + CFG_SetShortDeviceName(shortName); + CFG_SetDeviceName(fullName); + + CHANNEL_SetType(0, ChType_Voltage_div10); + CHANNEL_SetType(1, ChType_Power); + CHANNEL_SetType(2, ChType_Current_div100); + + SIM_ClearMQTTHistory(); + CMD_ExecuteCommand("scheduleHADiscovery 1", 0); + Sim_RunSeconds(5, false); + + // OBK device should publish JSON on MQTT topic "homeassistant" + /*SELFTEST_ASSERT_HAS_MQTT_JSON_SENT("homeassistant", true); + SELFTEST_ASSERT_JSON_VALUE_STRING("dev", "name", shortName); + SELFTEST_ASSERT_JSON_VALUE_STRING("dev", "sw", USER_SW_VER); + SELFTEST_ASSERT_JSON_VALUE_STRING("dev", "mf", MANUFACTURER); + SELFTEST_ASSERT_JSON_VALUE_STRING("dev", "mdl", PLATFORM_MCU_NAME);*/ + +} + +void Test_HassDiscovery_Ext() { + Test_HassDiscovery_TuyaMCU_VoltageCurrentPower(); +} + + +#endif diff --git a/src/selftest/selftest_local.h b/src/selftest/selftest_local.h index b1cfbb9d8..77b4a49ac 100644 --- a/src/selftest/selftest_local.h +++ b/src/selftest/selftest_local.h @@ -88,6 +88,8 @@ void Test_DHT(); void Test_Flags(); void Test_MultiplePinsOnChannel(); void Test_HassDiscovery(); +void Test_HassDiscovery_Base(); +void Test_HassDiscovery_Ext(); void Test_Demo_ExclusiveRelays(); void Test_MapRanges(); void Test_Demo_MapFanSpeedToRelays(); diff --git a/src/win_main.c b/src/win_main.c index c4985e775..c9d30a28e 100644 --- a/src/win_main.c +++ b/src/win_main.c @@ -135,7 +135,9 @@ void Win_DoUnitTests() { Test_WaitFor(); Test_TwoPWMsOneChannel(); Test_ClockEvents(); + Test_HassDiscovery_Base(); Test_HassDiscovery(); + Test_HassDiscovery_Ext(); Test_Role_ToggleAll_2(); Test_Demo_ButtonToggleGroup(); Test_Demo_ButtonScrollingChannelValues();