/* xdrv_07_domoticz.ino - domoticz support for Tasmota Copyright (C) 2021 Theo Arends 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 . */ #ifdef USE_DOMOTICZ /*********************************************************************************************\ * Domoticz support * * Adds commands: * DzIdx - Set power number to Domoticz Idx allowing Domoticz to control Tasmota power * DzKeyIdx - Set button number to Domoticz Idx allowing Tasmota button as input to Domoticz * DzSwitchId - Set switch number to Domoticz Idx allowing Tasmota switch as input to Domoticz * DzSensorIdx - Set sensor type to Domoticz Idx * DzUpdateTimer 0 - Send power state at teleperiod to Domoticz (default) * DzUpdateTimer - Send power state at interval to Domoticz * DzSend1 , - {\"idx\":,\"nvalue\":0,\"svalue\":\"\",\"Battery\":xx,\"RSSI\":yy} * Example: rule1 on power1#state do dzsend1 9001,%value% endon * DzSend1 418,%var1%;%var2% or DzSend1 418,%var1%:%var2% - Notice colon as substitute to semi-colon * DzSend2 , - USE_SHUTTER only - {\"idx\":,\"nvalue\":,\"svalue\":\"\",\"Battery\":xx,\"RSSI\":yy} * DzSend3 , - {\"idx\":,\"nvalue\":,\"Battery\":xx,\"RSSI\":yy} * DzSend4 , - {\"command\":\"switchlight\",\"idx\":,\"switchcmd\":\"\"} * DzSend5 , - {\"command\":\"switchscene\",\"idx\":,\"switchcmd\":\"\"} \*********************************************************************************************/ #define XDRV_07 7 //#define USE_DOMOTICZ_DEBUG // Enable additional debug logging #define D_PRFX_DOMOTICZ "Dz" #define D_CMND_IDX "Idx" #define D_CMND_KEYIDX "KeyIdx" #define D_CMND_SWITCHIDX "SwitchIdx" #define D_CMND_SENSORIDX "SensorIdx" #define D_CMND_UPDATETIMER "UpdateTimer" #define D_CMND_DZSEND "Send" const char kDomoticzCommands[] PROGMEM = D_PRFX_DOMOTICZ "|" // Prefix D_CMND_IDX "|" D_CMND_KEYIDX "|" D_CMND_SWITCHIDX "|" D_CMND_SENSORIDX "|" D_CMND_UPDATETIMER "|" D_CMND_DZSEND ; void (* const DomoticzCommand[])(void) PROGMEM = { &CmndDomoticzIdx, &CmndDomoticzKeyIdx, &CmndDomoticzSwitchIdx, &CmndDomoticzSensorIdx, &CmndDomoticzUpdateTimer, &CmndDomoticzSend }; const char DOMOTICZ_MESSAGE[] PROGMEM = "{\"idx\":%d,\"nvalue\":%d,\"svalue\":\"%s\",\"Battery\":%d,\"RSSI\":%d}"; #if MAX_DOMOTICZ_SNS_IDX < DZ_MAX_SENSORS #error "Domoticz: Too many sensors or change settings.h layout" #endif const char kDomoticzSensors[] PROGMEM = D_DOMOTICZ_TEMP "|" D_DOMOTICZ_TEMP_HUM "|" D_DOMOTICZ_TEMP_HUM_BARO "|" D_DOMOTICZ_POWER_ENERGY "|" D_DOMOTICZ_ILLUMINANCE "|" D_DOMOTICZ_COUNT "|" D_DOMOTICZ_VOLTAGE "|" D_DOMOTICZ_CURRENT "|" D_DOMOTICZ_AIRQUALITY "|" D_DOMOTICZ_P1_SMART_METER "|" D_DOMOTICZ_SHUTTER ; const char kDomoticzCommand[] PROGMEM = "switchlight|switchscene"; char domoticz_in_topic[] = DOMOTICZ_IN_TOPIC; typedef struct Domoticz_t { uint32_t relay_idx[MAX_RELAYS_SET - MAX_DOMOTICZ_IDX]; uint32_t fan_debounce; // iFan02 state debounce timer int update_timer; bool subscribe; bool update_flag; #ifdef USE_SHUTTER bool is_shutter; #endif // USE_SHUTTER } Domoticz_t; Domoticz_t* Domoticz; int DomoticzBatteryQuality(void) { // Battery 0%: ESP 2.6V (minimum operating voltage is 2.5) // Battery 100%: ESP 3.6V (maximum operating voltage is 3.6) // Battery 101% to 200%: ESP over 3.6V (means over maximum operating voltage) int quality = 100; // Voltage range from 2,6V > 0% to 3,6V > 100% #ifdef ESP8266 #ifdef USE_ADC_VCC uint16_t voltage = ESP.getVcc(); if (voltage <= 2600) { quality = 0; } else if (voltage >= 4600) { quality = 200; } else { quality = (voltage - 2600) / 10; } #endif // USE_ADC_VCC #endif // ESP8266 return quality; } int DomoticzRssiQuality(void) { // RSSI range: 0% to 10% (12 means disable RSSI in Domoticz) return WifiGetRssiAsQuality(WiFi.RSSI()) / 10; } uint32_t DomoticzRelayIdx(uint32_t relay) { return (relay < MAX_DOMOTICZ_IDX) ? Settings->domoticz_relay_idx[relay] : Domoticz->relay_idx[relay -MAX_DOMOTICZ_IDX]; } void DomoticzSetRelayIdx(uint32_t relay, uint32_t idx) { if (relay < MAX_DOMOTICZ_IDX) { Settings->domoticz_relay_idx[relay] = idx; } else { Domoticz->relay_idx[relay -MAX_DOMOTICZ_IDX] = idx; } } #ifdef USE_SONOFF_IFAN void MqttPublishDomoticzFanState(void) { if (Settings->flag.mqtt_enabled && DomoticzRelayIdx(1)) { // SetOption3 - Enable MQTT char svalue[8]; // Fanspeed value int fan_speed = GetFanspeed(); snprintf_P(svalue, sizeof(svalue), PSTR("%d"), fan_speed * 10); Response_P(DOMOTICZ_MESSAGE, (int)DomoticzRelayIdx(1), (0 == fan_speed) ? 0 : 2, svalue, DomoticzBatteryQuality(), DomoticzRssiQuality()); MqttPublish(domoticz_in_topic); Domoticz->fan_debounce = millis(); } } void DomoticzUpdateFanState(void) { if (Domoticz->update_flag) { MqttPublishDomoticzFanState(); } Domoticz->update_flag = true; } #endif // USE_SONOFF_IFAN void MqttPublishDomoticzPowerState(uint8_t device) { if (Settings->flag.mqtt_enabled) { // SetOption3 - Enable MQTT if (device < 1) { device = 1; } if ((device > TasmotaGlobal.devices_present) || (device > MAX_RELAYS_SET)) { return; } if (DomoticzRelayIdx(device -1)) { #ifdef USE_SHUTTER if (Domoticz->is_shutter) { // Shutter is updated by sensor update - power state should not be sent } else { #endif // USE_SHUTTER #ifdef USE_SONOFF_IFAN if (IsModuleIfan() && (device > 1)) { // Fan handled by MqttPublishDomoticzFanState } else { #endif // USE_SONOFF_IFAN char svalue[8]; // Dimmer value snprintf_P(svalue, sizeof(svalue), PSTR("%d"), Settings->light_dimmer); Response_P(DOMOTICZ_MESSAGE, (int)DomoticzRelayIdx(device -1), (TasmotaGlobal.power & (1 << (device -1))) ? 1 : 0, (TasmotaGlobal.light_type) ? svalue : "", DomoticzBatteryQuality(), DomoticzRssiQuality()); MqttPublish(domoticz_in_topic); #ifdef USE_SONOFF_IFAN } #endif // USE_SONOFF_IFAN #ifdef USE_SHUTTER } #endif //USE_SHUTTER } } } void DomoticzUpdatePowerState(uint8_t device) { if (Domoticz->update_flag) { MqttPublishDomoticzPowerState(device); } Domoticz->update_flag = true; } void DomoticzMqttUpdate(void) { if (Domoticz->subscribe && (Settings->domoticz_update_timer || Domoticz->update_timer)) { Domoticz->update_timer--; if (Domoticz->update_timer <= 0) { Domoticz->update_timer = Settings->domoticz_update_timer; for (uint32_t i = 1; i <= TasmotaGlobal.devices_present; i++) { #ifdef USE_SHUTTER if (Domoticz->is_shutter) { // no power state updates for shutters break; } #endif // USE_SHUTTER #ifdef USE_SONOFF_IFAN if (IsModuleIfan() && (i > 1)) { MqttPublishDomoticzFanState(); break; } else { #endif // USE_SONOFF_IFAN MqttPublishDomoticzPowerState(i); #ifdef USE_SONOFF_IFAN } #endif // USE_SONOFF_IFAN } } } } void DomoticzMqttSubscribe(void) { uint8_t maxdev = (TasmotaGlobal.devices_present > MAX_RELAYS_SET) ? MAX_RELAYS_SET : TasmotaGlobal.devices_present; bool any_relay = false; for (uint32_t i = 0; i < maxdev; i++) { if (DomoticzRelayIdx(i)) { any_relay = true; break; } } char stopic[TOPSZ]; snprintf_P(stopic, sizeof(stopic), PSTR(DOMOTICZ_OUT_TOPIC "/#")); // domoticz topic if (Domoticz->subscribe && !any_relay) { Domoticz->subscribe = false; MqttUnsubscribe(stopic); } if (!Domoticz->subscribe && any_relay) { Domoticz->subscribe = true; MqttSubscribe(stopic); } } int DomoticzIdx2Relay(uint32_t idx) { if (idx > 0) { uint32_t maxdev = (TasmotaGlobal.devices_present > MAX_RELAYS_SET) ? MAX_RELAYS_SET : TasmotaGlobal.devices_present; for (uint32_t i = 0; i < maxdev; i++) { if (idx == DomoticzRelayIdx(i)) { return i; } } } return -1; // Idx not mine } bool DomoticzMqttData(void) { /* XdrvMailbox.topic = topic; XdrvMailbox.index = strlen(topic); XdrvMailbox.data = (char*)data; XdrvMailbox.data_len = data_len; */ Domoticz->update_flag = true; if (!Domoticz->subscribe) { return false; // No Domoticz driver subscription so try user subscribes } // Default subscibed to domoticz/out/# if (strncasecmp_P(XdrvMailbox.topic, PSTR(DOMOTICZ_OUT_TOPIC), strlen(DOMOTICZ_OUT_TOPIC)) != 0) { return false; // Process unchanged data } // topic is domoticz/out so check if valid data could be available if (XdrvMailbox.data_len < 20) { return true; // No valid data } #ifdef USE_DOMOTICZ_DEBUG char dom_data[XdrvMailbox.data_len +1]; strcpy(dom_data, XdrvMailbox.data); AddLog(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_DOMOTICZ "%s = %s"), XdrvMailbox.topic, RemoveControlCharacter(dom_data)); #endif // USE_DOMOTICZ_DEBUG // Quick check if this is mine using topic domoticz/out/{$idx} if (strlen(XdrvMailbox.topic) > strlen(DOMOTICZ_OUT_TOPIC)) { char* topic_index = &XdrvMailbox.topic[strlen(DOMOTICZ_OUT_TOPIC) +1]; if (strchr(topic_index, '/') == nullptr) { // Skip if topic ...floor/room if (DomoticzIdx2Relay(atoi(topic_index)) < 0) { return true; // Idx not mine } } } String domoticz_data = XdrvMailbox.data; // Copy the string into a new buffer that will be modified JsonParser parser((char*)domoticz_data.c_str()); JsonParserObject domoticz = parser.getRootObject(); if (!domoticz) { return true; // To much or invalid data } int32_t relay_index = DomoticzIdx2Relay(domoticz.getUInt(PSTR("idx"), 0)); if (relay_index < 0) { return true; // Idx not mine } int32_t nvalue = domoticz.getInt(PSTR("nvalue"), -1); if ((nvalue < 0) || (nvalue > 16)) { return true; // Nvalue out of boundaries } AddLog(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_DOMOTICZ "%s, idx %d, nvalue %d"), XdrvMailbox.topic, DomoticzRelayIdx(relay_index), nvalue); bool iscolordimmer = (strcmp_P(domoticz.getStr(PSTR("dtype")), PSTR("Color Switch")) == 0); bool isShutter = (strcmp_P(domoticz.getStr(PSTR("dtype")), PSTR("Light/Switch")) == 0) && (strncmp_P(domoticz.getStr(PSTR("switchType")),PSTR("Blinds"), 6) == 0); #ifdef USE_SONOFF_IFAN if (IsModuleIfan() && (1 == relay_index)) { // Idx 2 is fanspeed JsonParserToken svalue_tok = domoticz[PSTR("svalue1")]; if (!svalue_tok) { return true; } uint32_t svalue = svalue_tok.getUInt(); svalue = (2 == nvalue) ? svalue / 10 : 0; if (GetFanspeed() == svalue) { return true; // Stop as already set } if (TimePassedSince(Domoticz->fan_debounce) < 1000) { return true; // Stop if device in limbo } snprintf_P(XdrvMailbox.topic, XdrvMailbox.index, PSTR("/" D_CMND_FANSPEED)); snprintf_P(XdrvMailbox.data, XdrvMailbox.data_len, PSTR("%d"), svalue); } else #endif // USE_SONOFF_IFAN #ifdef USE_SHUTTER if (isShutter) { uint32_t position = domoticz.getUInt(PSTR("svalue1"), 0); if (nvalue != 2) { position = (0 == nvalue) ? 0 : 100; } snprintf_P(XdrvMailbox.topic, TOPSZ, PSTR("/" D_PRFX_SHUTTER D_CMND_SHUTTER_POSITION)); snprintf_P(XdrvMailbox.data, XdrvMailbox.data_len, PSTR("%d"), position); XdrvMailbox.data_len = position > 99 ? 3 : (position > 9 ? 2 : 1); } else #endif // USE_SHUTTER #ifdef USE_LIGHT if (iscolordimmer && 10 == nvalue) { // Color_SetColor // https://www.domoticz.com/wiki/Domoticz_API/JSON_URL%27s#Set_a_light_to_a_certain_color_or_color_temperature JsonParserObject color = domoticz[PSTR("Color")].getObject(); // JsonObject& color = domoticz["Color"]; uint32_t level = nvalue = domoticz.getUInt(PSTR("svalue1"), 0); uint32_t r = color.getUInt(PSTR("r"), 0) * level / 100; uint32_t g = color.getUInt(PSTR("g"), 0) * level / 100; uint32_t b = color.getUInt(PSTR("b"), 0) * level / 100; uint32_t cw = color.getUInt(PSTR("cw"), 0) * level / 100; uint32_t ww = color.getUInt(PSTR("ww"), 0) * level / 100; uint32_t m = color.getUInt(PSTR("m"), 0); uint32_t t = color.getUInt(PSTR("t"), 0); if (2 == m) { // White with color temperature. Valid fields: t snprintf_P(XdrvMailbox.topic, XdrvMailbox.index, PSTR("/" D_CMND_BACKLOG)); snprintf_P(XdrvMailbox.data, XdrvMailbox.data_len, PSTR(D_CMND_COLORTEMPERATURE " %d;" D_CMND_DIMMER " %d"), changeUIntScale(t, 0, 255, CT_MIN, CT_MAX), level); } else { snprintf_P(XdrvMailbox.topic, XdrvMailbox.index, PSTR("/" D_CMND_COLOR)); snprintf_P(XdrvMailbox.data, XdrvMailbox.data_len, PSTR("%02x%02x%02x%02x%02x"), r, g, b, cw, ww); } } else if ((!iscolordimmer && 2 == nvalue) || // gswitch_sSetLevel (iscolordimmer && 15 == nvalue)) { // Color_SetBrightnessLevel if (domoticz[PSTR("svalue1")]) { nvalue = domoticz.getUInt(PSTR("svalue1"), 0); } else { return true; // Invalid data } if (TasmotaGlobal.light_type && (Settings->light_dimmer == nvalue) && ((TasmotaGlobal.power >> relay_index) &1)) { return true; // State already set } snprintf_P(XdrvMailbox.topic, XdrvMailbox.index, PSTR("/" D_CMND_DIMMER)); snprintf_P(XdrvMailbox.data, XdrvMailbox.data_len, PSTR("%d"), nvalue); } else #endif // USE_LIGHT if (1 == nvalue || 0 == nvalue) { if (((TasmotaGlobal.power >> relay_index) &1) == (power_t)nvalue) { return true; // Stop loop } char stemp1[10]; snprintf_P(XdrvMailbox.topic, XdrvMailbox.index, PSTR("/" D_CMND_POWER "%s"), (TasmotaGlobal.devices_present > 1) ? itoa(relay_index +1, stemp1, 10) : ""); snprintf_P(XdrvMailbox.data, XdrvMailbox.data_len, PSTR("%d"), nvalue); } else { return true; // No command received } AddLog(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_DOMOTICZ D_RECEIVED_TOPIC " %s, " D_DATA " %s"), XdrvMailbox.topic, XdrvMailbox.data); Domoticz->update_flag = false; return false; // Process new data } /*********************************************************************************************/ void DomoticzSendSwitch(uint32_t type, uint32_t index, uint32_t state) { char stemp[16]; // "switchlight" or "switchscene" Response_P(PSTR("{\"command\":\"%s\",\"idx\":%d,\"switchcmd\":\"%s\"}"), GetTextIndexed(stemp, sizeof(stemp), type, kDomoticzCommand), index, (state) ? (POWER_TOGGLE == state) ? "Toggle" : "On" : "Off"); // Domoticz case sensitive MqttPublish(domoticz_in_topic); } bool DomoticzSendKey(uint8_t key, uint8_t device, uint8_t state, uint8_t svalflg) { bool result = false; if (device <= MAX_DOMOTICZ_IDX) { if ((Settings->domoticz_key_idx[device -1] || Settings->domoticz_switch_idx[device -1]) && (svalflg)) { DomoticzSendSwitch(0, (key) ? Settings->domoticz_switch_idx[device -1] : Settings->domoticz_key_idx[device -1], state); result = true; } } return result; } /*********************************************************************************************\ * Sensors * * Source : https://www.domoticz.com/wiki/Domoticz_API/JSON_URL%27s * https://www.domoticz.com/wiki/MQTT * * Percentage, Barometric, Air Quality: * {\"idx\":%d,\"nvalue\":%s}, Idx, Value * * Humidity: * {\"idx\":%d,\"nvalue\":%s,\"svalue\":\"%s\"}, Idx, Humidity, HumidityStatus * * All other: * {\"idx\":%d,\"nvalue\":0,\"svalue\":\"%s\"}, Idx, Value(s) * \*********************************************************************************************/ void DomoticzSendData(uint32_t sensor_idx, uint32_t idx, char *data) { char payload[128]; // {"idx":26700,"nvalue":0,"svalue":"22330.1;10234.4;22000.5;10243.4;1006;3000","Battery":100,"RSSI":10} if (DZ_AIRQUALITY == sensor_idx) { snprintf_P(payload, sizeof(payload), PSTR("{\"idx\":%d,\"nvalue\":%s,\"Battery\":%d,\"RSSI\":%d}"), idx, data, DomoticzBatteryQuality(), DomoticzRssiQuality()); } else { uint8_t nvalue = 0; #ifdef USE_SHUTTER if (DZ_SHUTTER == sensor_idx) { uint8_t position = atoi(data); nvalue = position < 2 ? 0 : (position == 100 ? 1 : 2); } #endif // USE_SHUTTER snprintf_P(payload, sizeof(payload), DOMOTICZ_MESSAGE, // "{\"idx\":%d,\"nvalue\":%d,\"svalue\":\"%s\",\"Battery\":%d,\"RSSI\":%d}" idx, nvalue, data, DomoticzBatteryQuality(), DomoticzRssiQuality()); } MqttPublishPayload(domoticz_in_topic, payload); } void DomoticzSensor(uint8_t idx, char *data) { if (Settings->domoticz_sensor_idx[idx]) { DomoticzSendData(idx, Settings->domoticz_sensor_idx[idx], data); } } uint8_t DomoticzHumidityState(float h) { return (!h) ? 0 : (h < 40) ? 2 : (h > 70) ? 3 : 1; } void DomoticzSensor(uint8_t idx, int value) { char data[16]; snprintf_P(data, sizeof(data), PSTR("%d"), value); DomoticzSensor(idx, data); } void DomoticzFloatSensor(uint8_t idx, float value) { uint32_t resolution = 1; /* switch (idx) { case DZ_TEMP: resolution = Settings->flag2.temperature_resolution; break; case DZ_POWER_ENERGY: resolution = Settings->flag2.wattage_resolution; break; case DZ_VOLTAGE: resolution = Settings->flag2.voltage_resolution; break; case DZ_CURRENT: resolution = Settings->flag2.current_resolution; break; } */ if (DZ_TEMP == idx) { resolution = Settings->flag2.temperature_resolution; } else if (DZ_POWER_ENERGY == idx) { resolution = Settings->flag2.wattage_resolution; } else if (DZ_VOLTAGE == idx) { resolution = Settings->flag2.voltage_resolution; } else if (DZ_CURRENT == idx) { resolution = Settings->flag2.current_resolution; } char data[FLOATSZ]; dtostrfd(value, resolution, data); DomoticzSensor(idx, data); } //void DomoticzTempHumPressureSensor(float temp, float hum, float baro = -1); void DomoticzTempHumPressureSensor(float temp, float hum, float baro) { char temperature[FLOATSZ]; dtostrfd(temp, Settings->flag2.temperature_resolution, temperature); char humidity[FLOATSZ]; dtostrfd(hum, Settings->flag2.humidity_resolution, humidity); char data[32]; if (baro > -1) { char pressure[FLOATSZ]; dtostrfd(baro, Settings->flag2.pressure_resolution, pressure); snprintf_P(data, sizeof(data), PSTR("%s;%s;%d;%s;5"), temperature, humidity, DomoticzHumidityState(hum), pressure); DomoticzSensor(DZ_TEMP_HUM_BARO, data); } else { snprintf_P(data, sizeof(data), PSTR("%s;%s;%d"), temperature, humidity, DomoticzHumidityState(hum)); DomoticzSensor(DZ_TEMP_HUM, data); } } void DomoticzSensorPowerEnergy(int power, char *energy) { char data[16]; snprintf_P(data, sizeof(data), PSTR("%d;%s"), power, energy); DomoticzSensor(DZ_POWER_ENERGY, data); } void DomoticzSensorP1SmartMeter(char *usage1, char *usage2, char *return1, char *return2, int power) { //usage1 = energy usage meter tariff 1, This is an incrementing counter //usage2 = energy usage meter tariff 2, This is an incrementing counter //return1 = energy return meter tariff 1, This is an incrementing counter //return2 = energy return meter tariff 2, This is an incrementing counter //power = if >= 0 actual usage power. if < 0 actual return power (Watt) int consumed = power; int produced = 0; if (power < 0) { consumed = 0; produced = -power; } char data[64]; snprintf_P(data, sizeof(data), PSTR("%s;%s;%s;%s;%d;%d"), usage1, usage2, return1, return2, consumed, produced); DomoticzSensor(DZ_P1_SMART_METER, data); } /*********************************************************************************************/ void DomoticzInit(void) { if (Settings->flag.mqtt_enabled) { // SetOption3 - Enable MQTT Domoticz = (Domoticz_t*)calloc(sizeof(Domoticz_t), 1); // Need calloc to reset registers to 0/false if (nullptr == Domoticz) { return; } Domoticz->update_flag = true; } } /*********************************************************************************************\ * Commands \*********************************************************************************************/ void CmndDomoticzIdx(void) { // DzIdx0 0 - Reset all disabling subscription too // DzIdx1 403 - Relate relay1 (=Power1) to Domoticz Idx 403 persistent // DzIdx5 403 - Relate relay5 (=Power5) to Domoticz Idx 403 non-persistent (need a rule at boot to become persistent) if ((XdrvMailbox.index >= 0) && (XdrvMailbox.index <= MAX_RELAYS_SET)) { if (XdrvMailbox.payload >= 0) { if (0 == XdrvMailbox.index) { for (uint32_t i = 0; i < MAX_RELAYS_SET; i++) { DomoticzSetRelayIdx(i, 0); } } else { DomoticzSetRelayIdx(XdrvMailbox.index -1, XdrvMailbox.payload); } DomoticzMqttSubscribe(); } ResponseCmndIdxNumber(DomoticzRelayIdx(XdrvMailbox.index -1)); } } void CmndDomoticzKeyIdx(void) { if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= MAX_DOMOTICZ_IDX)) { if (XdrvMailbox.payload >= 0) { Settings->domoticz_key_idx[XdrvMailbox.index -1] = XdrvMailbox.payload; } ResponseCmndIdxNumber(Settings->domoticz_key_idx[XdrvMailbox.index -1]); } } void CmndDomoticzSwitchIdx(void) { if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= MAX_DOMOTICZ_IDX)) { if (XdrvMailbox.payload >= 0) { Settings->domoticz_switch_idx[XdrvMailbox.index -1] = XdrvMailbox.payload; } ResponseCmndIdxNumber(Settings->domoticz_switch_idx[XdrvMailbox.index -1]); } } void CmndDomoticzSensorIdx(void) { if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= DZ_MAX_SENSORS)) { if (XdrvMailbox.payload >= 0) { Settings->domoticz_sensor_idx[XdrvMailbox.index -1] = XdrvMailbox.payload; } ResponseCmndIdxNumber(Settings->domoticz_sensor_idx[XdrvMailbox.index -1]); } } void CmndDomoticzUpdateTimer(void) { if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 3601)) { Settings->domoticz_update_timer = XdrvMailbox.payload; } ResponseCmndNumber(Settings->domoticz_update_timer); } void CmndDomoticzSend(void) { /* DzSend1 , - {\"idx\":,\"nvalue\":0,\"svalue\":\"\",\"Battery\":xx,\"RSSI\":yy} rule1 on power1#state do dzsend1 9001,%value% endon DzSend1 418,%var1%;%var2% or DzSend1 418,%var1%:%var2% - Notice colon as substitute to semi-colon DzSend2 , - USE_SHUTTER only - {\"idx\":,\"nvalue\":,\"svalue\":\"\",\"Battery\":xx,\"RSSI\":yy} DzSend3 , - {\"idx\":,\"nvalue\":,\"Battery\":xx,\"RSSI\":yy} DzSend4 , - {\"command\":\"switchlight\",\"idx\":,\"switchcmd\":\"\"} DzSend5 , - {\"command\":\"switchscene\",\"idx\":,\"switchcmd\":\"\"} */ if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= 5)) { if (XdrvMailbox.data_len > 0) { if (strchr(XdrvMailbox.data, ',') != nullptr) { // Process parameter entry char *data; uint32_t index = strtoul(strtok_r(XdrvMailbox.data, ",", &data), nullptr, 10); if ((index > 0) && (data != nullptr)) { ReplaceChar(data,':',';'); // As a workaround for command backlog inter-command separator if (XdrvMailbox.index > 3) { uint32_t state = strtoul(data, nullptr, 10); // 0, 1 or 2 DomoticzSendSwitch(XdrvMailbox.index -4, index, state); } else { uint32_t type = DZ_TEMP; if (2 == XdrvMailbox.index) { type = DZ_SHUTTER; } else if (3 == XdrvMailbox.index) { type = DZ_AIRQUALITY; } DomoticzSendData(type, index, data); } ResponseCmndDone(); } } } } } /*********************************************************************************************\ * Presentation \*********************************************************************************************/ #ifdef USE_WEBSERVER #define WEB_HANDLE_DOMOTICZ "dm" const char HTTP_BTN_MENU_DOMOTICZ[] PROGMEM = "

"; const char HTTP_FORM_DOMOTICZ[] PROGMEM = "
 " D_DOMOTICZ_PARAMETERS " " "
" ""; const char HTTP_FORM_DOMOTICZ_RELAY[] PROGMEM = "" ""; const char HTTP_FORM_DOMOTICZ_SWITCH[] PROGMEM = ""; const char HTTP_FORM_DOMOTICZ_SENSOR[] PROGMEM = ""; const char HTTP_FORM_DOMOTICZ_TIMER[] PROGMEM = ""; void HandleDomoticzConfiguration(void) { if (!HttpCheckPriviledgedAccess()) { return; } AddLog(LOG_LEVEL_DEBUG, PSTR(D_LOG_HTTP D_CONFIGURE_DOMOTICZ)); if (Webserver->hasArg(F("save"))) { DomoticzSaveSettings(); HandleConfiguration(); return; } char stemp[40]; WSContentStart_P(PSTR(D_CONFIGURE_DOMOTICZ)); WSContentSendStyle(); WSContentSend_P(HTTP_FORM_DOMOTICZ); for (uint32_t i = 0; i < MAX_DOMOTICZ_IDX; i++) { if (i < TasmotaGlobal.devices_present) { WSContentSend_P(HTTP_FORM_DOMOTICZ_RELAY, i +1, i, Settings->domoticz_relay_idx[i], i +1, i, Settings->domoticz_key_idx[i]); } if (PinUsed(GPIO_SWT1, i)) { WSContentSend_P(HTTP_FORM_DOMOTICZ_SWITCH, i +1, i, Settings->domoticz_switch_idx[i]); } #ifdef USE_SONOFF_IFAN if (IsModuleIfan() && (1 == i)) { break; } #endif // USE_SONOFF_IFAN } for (uint32_t i = 0; i < DZ_MAX_SENSORS; i++) { WSContentSend_P(HTTP_FORM_DOMOTICZ_SENSOR, i +1, GetTextIndexed(stemp, sizeof(stemp), i, kDomoticzSensors), i, Settings->domoticz_sensor_idx[i]); } WSContentSend_P(HTTP_FORM_DOMOTICZ_TIMER, Settings->domoticz_update_timer); WSContentSend_P(PSTR("
" D_DOMOTICZ_IDX " %d
" D_DOMOTICZ_KEY_IDX " %d
" D_DOMOTICZ_SWITCH_IDX " %d
" D_DOMOTICZ_SENSOR_IDX " %d %s
" D_DOMOTICZ_UPDATE_TIMER " (" STR(DOMOTICZ_UPDATE_TIMER) ")
")); WSContentSend_P(HTTP_FORM_END); WSContentSpaceButton(BUTTON_CONFIGURATION); WSContentStop(); } void DomoticzSaveSettings(void) { String cmnd = F(D_CMND_BACKLOG "0 "); cmnd += AddWebCommand(PSTR(D_PRFX_DOMOTICZ D_CMND_UPDATETIMER), PSTR("ut"), STR(DOMOTICZ_UPDATE_TIMER)); char arg_idx[5]; char cmnd2[24]; for (uint32_t i = 1; i <= MAX_DOMOTICZ_IDX; i++) { snprintf_P(cmnd2, sizeof(cmnd2), PSTR(D_PRFX_DOMOTICZ D_CMND_IDX "%d"), i); snprintf_P(arg_idx, sizeof(arg_idx), PSTR("r%d"), i -1); cmnd += AddWebCommand(cmnd2, arg_idx, PSTR("0")); snprintf_P(cmnd2, sizeof(cmnd2), PSTR(D_PRFX_DOMOTICZ D_CMND_KEYIDX "%d"), i); arg_idx[0] = 'k'; cmnd += AddWebCommand(cmnd2, arg_idx, PSTR("0")); snprintf_P(cmnd2, sizeof(cmnd2), PSTR(D_PRFX_DOMOTICZ D_CMND_SWITCHIDX "%d"), i); arg_idx[0] = 's'; cmnd += AddWebCommand(cmnd2, arg_idx, PSTR("0")); } for (uint32_t i = 1; i <= DZ_MAX_SENSORS; i++) { snprintf_P(cmnd2, sizeof(cmnd2), PSTR(D_PRFX_DOMOTICZ D_CMND_SENSORIDX "%d"), i); snprintf_P(arg_idx, sizeof(arg_idx), PSTR("l%d"), i -1); cmnd += AddWebCommand(cmnd2, arg_idx, PSTR("0")); } ExecuteWebCommand((char*)cmnd.c_str()); } #endif // USE_WEBSERVER /*********************************************************************************************\ * Interface \*********************************************************************************************/ bool Xdrv07(uint32_t function) { bool result = false; if (FUNC_PRE_INIT == function) { DomoticzInit(); } else if (Domoticz) { switch (function) { case FUNC_EVERY_SECOND: DomoticzMqttUpdate(); break; case FUNC_MQTT_DATA: result = DomoticzMqttData(); break; #ifdef USE_WEBSERVER case FUNC_WEB_ADD_BUTTON: WSContentSend_P(HTTP_BTN_MENU_DOMOTICZ); break; case FUNC_WEB_ADD_HANDLER: WebServer_on(PSTR("/" WEB_HANDLE_DOMOTICZ), HandleDomoticzConfiguration); break; #endif // USE_WEBSERVER case FUNC_MQTT_SUBSCRIBE: DomoticzMqttSubscribe(); #ifdef USE_SHUTTER if (Settings->domoticz_sensor_idx[DZ_SHUTTER]) { Domoticz->is_shutter = true; } #endif // USE_SHUTTER break; case FUNC_MQTT_INIT: Domoticz->update_timer = 2; break; case FUNC_SHOW_SENSOR: // DomoticzSendSensor(); break; case FUNC_COMMAND: result = DecodeCommand(kDomoticzCommands, DomoticzCommand); break; case FUNC_ACTIVE: result = true; break; } } return result; } #endif // USE_DOMOTICZ