diff --git a/src/httpserver/hass.c b/src/httpserver/hass.c index 4587efb33..f6f622a9d 100644 --- a/src/httpserver/hass.c +++ b/src/httpserver/hass.c @@ -393,7 +393,7 @@ char *hass_generate_multiplyAndRound_template(int decimalPlacesForRounding, int /// @param type /// @param channel /// @return -HassDeviceInfo* hass_init_sensor_device_info(ENTITY_TYPE type, int channel) { +HassDeviceInfo* hass_init_sensor_device_info(ENTITY_TYPE type, int channel, int decPlaces, int decOffset) { int i; //Assuming that there is only one DHT setup per device which keeps uniqueid/names simpler @@ -405,9 +405,6 @@ HassDeviceInfo* hass_init_sensor_device_info(ENTITY_TYPE type, int channel) { cJSON_AddStringToObject(info->root, "dev_cla", "temperature"); cJSON_AddStringToObject(info->root, "unit_of_meas", "°C"); - //https://www.home-assistant.io/integrations/sensor.mqtt/ refers to value_template (val_tpl) - cJSON_AddStringToObject(info->root, "val_tpl", hass_generate_multiplyAndRound_template(2,1)); - sprintf(g_hassBuffer, "~/%d/get", channel); cJSON_AddStringToObject(info->root, "stat_t", g_hassBuffer); break; @@ -439,7 +436,36 @@ HassDeviceInfo* hass_init_sensor_device_info(ENTITY_TYPE type, int channel) { cJSON_AddStringToObject(info->root, "unit_of_meas", "mV"); cJSON_AddStringToObject(info->root, "stat_t", "~/voltage/get"); break; - + case VOLTAGE_SENSOR: + cJSON_AddStringToObject(info->root, "dev_cla", "voltage"); + cJSON_AddStringToObject(info->root, "unit_of_meas", "V"); + sprintf(g_hassBuffer, "~/%d/get", channel); + cJSON_AddStringToObject(info->root, "stat_t", g_hassBuffer); + break; + case CURRENT_SENSOR: + cJSON_AddStringToObject(info->root, "dev_cla", "current"); + cJSON_AddStringToObject(info->root, "unit_of_meas", "A"); + sprintf(g_hassBuffer, "~/%d/get", channel); + cJSON_AddStringToObject(info->root, "stat_t", g_hassBuffer); + break; + case POWER_SENSOR: + cJSON_AddStringToObject(info->root, "dev_cla", "power"); + cJSON_AddStringToObject(info->root, "unit_of_meas", "W"); + sprintf(g_hassBuffer, "~/%d/get", channel); + cJSON_AddStringToObject(info->root, "stat_t", g_hassBuffer); + break; + case POWERFACTOR_SENSOR: + cJSON_AddStringToObject(info->root, "dev_cla", "power_factor"); + //cJSON_AddStringToObject(info->root, "unit_of_meas", "W"); + sprintf(g_hassBuffer, "~/%d/get", channel); + cJSON_AddStringToObject(info->root, "stat_t", g_hassBuffer); + break; + case FREQUENCY_SENSOR: + cJSON_AddStringToObject(info->root, "dev_cla", "frequency"); + cJSON_AddStringToObject(info->root, "unit_of_meas", "Hz"); + sprintf(g_hassBuffer, "~/%d/get", channel); + cJSON_AddStringToObject(info->root, "stat_t", g_hassBuffer); + break; default: sprintf(g_hassBuffer, "~/%d/get", channel); cJSON_AddStringToObject(info->root, "stat_t", g_hassBuffer); @@ -447,6 +473,13 @@ HassDeviceInfo* hass_init_sensor_device_info(ENTITY_TYPE type, int channel) { } cJSON_AddStringToObject(info->root, "stat_cla", "measurement"); + + + if (decPlaces != -1 && decOffset != -1) { + //https://www.home-assistant.io/integrations/sensor.mqtt/ refers to value_template (val_tpl) + cJSON_AddStringToObject(info->root, "val_tpl", hass_generate_multiplyAndRound_template(decPlaces, decOffset)); + } + return info; } diff --git a/src/httpserver/hass.h b/src/httpserver/hass.h index 5c9dc893f..8891ad9f4 100644 --- a/src/httpserver/hass.h +++ b/src/httpserver/hass.h @@ -43,7 +43,18 @@ typedef enum { /// @brief CO2 sensor in ppm CO2_SENSOR, /// @brief TVOC sensor in ppb - TVOC_SENSOR + TVOC_SENSOR, + + /// @brief + VOLTAGE_SENSOR, + /// @brief + CURRENT_SENSOR, + /// @brief + //POWER_SINGLE_SENSOR, + /// @brief + POWERFACTOR_SENSOR, + /// @brief + FREQUENCY_SENSOR, } ENTITY_TYPE; @@ -74,7 +85,7 @@ HassDeviceInfo* hass_init_relay_device_info(int index, ENTITY_TYPE type); HassDeviceInfo* hass_init_light_device_info(ENTITY_TYPE type); 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); +HassDeviceInfo* hass_init_sensor_device_info(ENTITY_TYPE type, int channel, int decPlaces, int decOffset); const char* hass_build_discovery_json(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 c89d9c68a..08f725266 100644 --- a/src/httpserver/http_fns.c +++ b/src/httpserver/http_fns.c @@ -1577,6 +1577,12 @@ void doHomeAssistantDiscovery(const char* topic, http_request_t* request) { struct cJSON_Hooks hooks; bool discoveryQueued = false; int type; + // warning - this is 32 bit + int flagsChannelPublished; + int ch; + + // no channels published yet + flagsChannelPublished = 0; if (topic == 0 || *topic == 0) { topic = "homeassistant"; @@ -1598,6 +1604,8 @@ void doHomeAssistantDiscovery(const char* topic, http_request_t* request) { if (relayCount > 0) { for (i = 0; i < CHANNEL_MAX; i++) { if (h_isChannelRelay(i)) { + // TODO: flags are 32 bit and there are 64 max channels + BIT_SET(flagsChannelPublished, i); if (CFG_HasFlag(OBK_FLAG_MQTT_HASS_ADD_RELAYS_AS_LIGHTS)) { dev_info = hass_init_relay_device_info(i, LIGHT_ON_OFF); } @@ -1615,6 +1623,8 @@ void doHomeAssistantDiscovery(const char* topic, http_request_t* request) { if (dInputCount > 0) { for (i = 0; i < CHANNEL_MAX; i++) { if (h_isChannelDigitalInput(i)) { + // TODO: flags are 32 bit and there are 64 max channels + BIT_SET(flagsChannelPublished, i); 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); @@ -1671,11 +1681,11 @@ void doHomeAssistantDiscovery(const char* topic, http_request_t* request) { #endif if (measuringBattery == true) { - dev_info = hass_init_sensor_device_info(BATTERY_SENSOR, 0); + dev_info = hass_init_sensor_device_info(BATTERY_SENSOR, 0, -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(BATTERY_VOLTAGE_SENSOR, 0); + dev_info = hass_init_sensor_device_info(BATTERY_VOLTAGE_SENSOR, 0, -1, -1); MQTT_QueuePublish(topic, dev_info->channel, hass_build_discovery_json(dev_info), OBK_PUBLISH_FLAG_RETAIN); hass_free_device_info(dev_info); @@ -1684,22 +1694,34 @@ void doHomeAssistantDiscovery(const char* topic, http_request_t* request) { for (i = 0; i < PLATFORM_GPIO_MAX; i++) { if (IS_PIN_DHT_ROLE(g_cfg.pins.roles[i]) || IS_PIN_TEMP_HUM_SENSOR_ROLE(g_cfg.pins.roles[i])) { - dev_info = hass_init_sensor_device_info(TEMPERATURE_SENSOR, PIN_GetPinChannelForPinIndex(i)); + ch = PIN_GetPinChannelForPinIndex(i); + // TODO: flags are 32 bit and there are 64 max channels + BIT_SET(flagsChannelPublished, ch); + dev_info = hass_init_sensor_device_info(TEMPERATURE_SENSOR, ch, 2, 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(HUMIDITY_SENSOR, PIN_GetPinChannel2ForPinIndex(i)); + ch = PIN_GetPinChannel2ForPinIndex(i); + // TODO: flags are 32 bit and there are 64 max channels + BIT_SET(flagsChannelPublished, ch); + dev_info = hass_init_sensor_device_info(HUMIDITY_SENSOR, ch, -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; } else if (IS_PIN_AIR_SENSOR_ROLE(g_cfg.pins.roles[i])) { - dev_info = hass_init_sensor_device_info(CO2_SENSOR, PIN_GetPinChannelForPinIndex(i)); + ch = PIN_GetPinChannelForPinIndex(i); + // TODO: flags are 32 bit and there are 64 max channels + BIT_SET(flagsChannelPublished, ch); + dev_info = hass_init_sensor_device_info(CO2_SENSOR, ch, -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(TVOC_SENSOR, PIN_GetPinChannel2ForPinIndex(i)); + ch = PIN_GetPinChannel2ForPinIndex(i); + // TODO: flags are 32 bit and there are 64 max channels + BIT_SET(flagsChannelPublished, ch); + dev_info = hass_init_sensor_device_info(TVOC_SENSOR, ch, -1, -1); MQTT_QueuePublish(topic, dev_info->channel, hass_build_discovery_json(dev_info), OBK_PUBLISH_FLAG_RETAIN); hass_free_device_info(dev_info); @@ -1709,6 +1731,10 @@ void doHomeAssistantDiscovery(const char* topic, http_request_t* request) { #if WINDOWS for (i = 0; i < CHANNEL_MAX; i++) { type = g_cfg.pins.channelTypes[i]; + // TODO: flags are 32 bit and there are 64 max channels + if (BIT_CHECK(flagsChannelPublished, i)) { + continue; + } switch (type) { case ChType_OpenClosed: @@ -1720,9 +1746,27 @@ void doHomeAssistantDiscovery(const char* topic, http_request_t* request) { discoveryQueued = true; } break; + case ChType_Voltage_div10: + { + dev_info = hass_init_sensor_device_info(VOLTAGE_SENSOR, i, 2, 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; + } + break; case ChType_Temperature: { - dev_info = hass_init_sensor_device_info(TEMPERATURE_SENSOR, i); + dev_info = hass_init_sensor_device_info(TEMPERATURE_SENSOR, i, -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; + } + break; + case ChType_Temperature_div10: + { + dev_info = hass_init_sensor_device_info(TEMPERATURE_SENSOR, i, 2, 1); MQTT_QueuePublish(topic, dev_info->channel, hass_build_discovery_json(dev_info), OBK_PUBLISH_FLAG_RETAIN); hass_free_device_info(dev_info); @@ -1731,7 +1775,61 @@ void doHomeAssistantDiscovery(const char* topic, http_request_t* request) { break; case ChType_Humidity: { - dev_info = hass_init_sensor_device_info(HUMIDITY_SENSOR, i); + dev_info = hass_init_sensor_device_info(HUMIDITY_SENSOR, i, -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; + } + break; + case ChType_Humidity_div10: + { + dev_info = hass_init_sensor_device_info(HUMIDITY_SENSOR, i, 2, 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; + } + break; + case ChType_Current_div100: + { + dev_info = hass_init_sensor_device_info(CURRENT_SENSOR, i, 3, 2); + 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_Power: + { + dev_info = hass_init_sensor_device_info(POWER_SENSOR, i, -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; + } + break; + case ChType_Power_div10: + { + dev_info = hass_init_sensor_device_info(POWER_SENSOR, i, 2, 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; + } + break; + case ChType_PowerFactor_div1000: + { + dev_info = hass_init_sensor_device_info(POWERFACTOR_SENSOR, i, 4, 3); + 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_Frequency_div100: + { + dev_info = hass_init_sensor_device_info(FREQUENCY_SENSOR, i, 3, 2); MQTT_QueuePublish(topic, dev_info->channel, hass_build_discovery_json(dev_info), OBK_PUBLISH_FLAG_RETAIN); hass_free_device_info(dev_info); diff --git a/src/selftest/selftest_hass_discovery_ext.c b/src/selftest/selftest_hass_discovery_ext.c index 63b0c4a4d..5f6944748 100644 --- a/src/selftest/selftest_hass_discovery_ext.c +++ b/src/selftest/selftest_hass_discovery_ext.c @@ -29,8 +29,71 @@ void Test_HassDiscovery_TuyaMCU_VoltageCurrentPower() { } + +void Test_HassDiscovery_Channel_Humidity() { + const char *shortName = "WinHumTest"; + const char *fullName = "Windows Fake Hum"; + const char *mqttName = "testHum"; + SIM_ClearOBK(shortName); + SIM_ClearAndPrepareForMQTTTesting(mqttName, "bekens"); + + CFG_SetShortDeviceName(shortName); + CFG_SetDeviceName(fullName); + + // in this example, i am using channel 15 + CHANNEL_SetType(15, ChType_Humidity); + + 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); + SELFTEST_ASSERT_JSON_VALUE_STRING(0, "name", "WinHumTest Humidity"); + SELFTEST_ASSERT_JSON_VALUE_STRING(0, "~", mqttName); + SELFTEST_ASSERT_JSON_VALUE_STRING(0, "dev_cla", "humidity"); + SELFTEST_ASSERT_JSON_VALUE_STRING(0, "unit_of_meas", "%"); + // in this example, i am using channel 15 + SELFTEST_ASSERT_JSON_VALUE_STRING(0, "stat_t", "~/15/get"); + SELFTEST_ASSERT_JSON_VALUE_STRING(0, "stat_cla", "measurement"); +} +void Test_HassDiscovery_Channel_Temperature() { + const char *shortName = "WinTempTest"; + const char *fullName = "Windows Fake Temp"; + const char *mqttName = "testTemp"; + SIM_ClearOBK(shortName); + SIM_ClearAndPrepareForMQTTTesting(mqttName, "bekens"); + + CFG_SetShortDeviceName(shortName); + CFG_SetDeviceName(fullName); + + CHANNEL_SetType(0, ChType_Temperature); + + 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); + SELFTEST_ASSERT_JSON_VALUE_STRING(0, "name", "WinTempTest Temperature"); + SELFTEST_ASSERT_JSON_VALUE_STRING(0, "~", mqttName); + SELFTEST_ASSERT_JSON_VALUE_STRING(0, "dev_cla", "temperature"); + //SELFTEST_ASSERT_JSON_VALUE_STRING(0, "unit_of_meas", "C"); + SELFTEST_ASSERT_JSON_VALUE_STRING(0, "stat_t", "~/0/get"); + SELFTEST_ASSERT_JSON_VALUE_STRING(0, "stat_cla", "measurement"); +} void Test_HassDiscovery_Ext() { Test_HassDiscovery_TuyaMCU_VoltageCurrentPower(); + Test_HassDiscovery_Channel_Humidity(); + Test_HassDiscovery_Channel_Temperature(); }