diff --git a/README.md b/README.md index 13dc9afc2..ad865c067 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ ## Sonoff-Tasmota Provide ESP8266 based Sonoff by [iTead Studio](https://www.itead.cc/) and ElectroDragon IoT Relay with Serial, Web and MQTT control allowing 'Over the Air' or OTA firmware updates using Arduino IDE. -Current version is **5.0.4** - See [sonoff/_releasenotes.ino](https://github.com/arendst/Sonoff-Tasmota/blob/master/sonoff/_releasenotes.ino) for change information. +Current version is **5.0.5** - See [sonoff/_releasenotes.ino](https://github.com/arendst/Sonoff-Tasmota/blob/master/sonoff/_releasenotes.ino) for change information. ### **** ATTENTION Version 5.0.x specific information **** diff --git a/api/arduino/sonoff.ino.bin b/api/arduino/sonoff.ino.bin index 48b5253e2..610f18bd4 100644 Binary files a/api/arduino/sonoff.ino.bin and b/api/arduino/sonoff.ino.bin differ diff --git a/sonoff/_releasenotes.ino b/sonoff/_releasenotes.ino index cd22875ff..540f3fb09 100644 --- a/sonoff/_releasenotes.ino +++ b/sonoff/_releasenotes.ino @@ -1,4 +1,9 @@ -/* 5.0.4 20170505 +/* 5.0.5 20170508 + * Add command FullTopic with tokens %topic% (replaced by command Topic value) and + * %prefix% (replaced by command Prefix values) for more flexible topic definitions (#244) + * See wiki > MQTT Features https://github.com/arendst/Sonoff-Tasmota/wiki/MQTT-Features for more information + * + * 5.0.4 20170505 * Add Sonoff Pow Energy Total up to 40 MWh * Add command EnergyReset 1|2|3 to reset Energy counters (#406) * Fix Domoticz Energy logging (#411) diff --git a/sonoff/settings.h b/sonoff/settings.h index 25ed2b426..1f7e21c08 100644 --- a/sonoff/settings.h +++ b/sonoff/settings.h @@ -175,6 +175,9 @@ struct SYSCFG { // 5.0.4 unsigned long hlw_kWhtotal; + // 5.0.4a + char mqtt_fulltopic[101]; + } sysCfg; struct RTCMEM { diff --git a/sonoff/settings.ino b/sonoff/settings.ino index 1fd329a95..877ff6e85 100644 --- a/sonoff/settings.ino +++ b/sonoff/settings.ino @@ -452,6 +452,10 @@ void CFG_DefaultSet2() // 5.0.4 // sysCfg.hlw_kWhtotal = 0; rtcMem.hlw_kWhtotal = 0; + + // 5.0.4a + strlcpy(sysCfg.mqtt_fulltopic, MQTT_FULLTOPIC, sizeof(sysCfg.mqtt_fulltopic)); + } /********************************************************************************************/ @@ -630,6 +634,9 @@ void CFG_Delta() sysCfg.hlw_kWhtotal = 0; rtcMem.hlw_kWhtotal = 0; } + if (sysCfg.version < 0x05000500) { + strlcpy(sysCfg.mqtt_fulltopic, MQTT_FULLTOPIC, sizeof(sysCfg.mqtt_fulltopic)); + } sysCfg.version = VERSION; } } diff --git a/sonoff/sonoff.ino b/sonoff/sonoff.ino index 38f2e3ccc..8704b1965 100644 --- a/sonoff/sonoff.ino +++ b/sonoff/sonoff.ino @@ -10,7 +10,7 @@ * ==================================================== */ -#define VERSION 0x05000400 // 5.0.4 +#define VERSION 0x05000500 // 5.0.5 enum log_t {LOG_LEVEL_NONE, LOG_LEVEL_ERROR, LOG_LEVEL_INFO, LOG_LEVEL_DEBUG, LOG_LEVEL_DEBUG_MORE, LOG_LEVEL_ALL}; enum week_t {Last, First, Second, Third, Fourth}; @@ -119,7 +119,8 @@ enum emul_t {EMUL_NONE, EMUL_WEMO, EMUL_HUE, EMUL_MAX}; #define OTA_ATTEMPTS 10 // Number of times to try fetching the new firmware #define INPUT_BUFFER_SIZE 100 // Max number of characters in serial buffer -#define TOPSZ 60 // Max number of characters in topic string +#define CMDSZ 20 // Max number of characters in command +#define TOPSZ 100 // Max number of characters in topic string #define LOGSZ 128 // Max number of characters in log string #ifdef USE_MQTT_TLS #define MAX_LOG_LINES 10 // Max number of lines in weblog @@ -173,6 +174,8 @@ const char commands[MAX_BUTTON_COMMANDS][14] PROGMEM = { const char wificfg[5][12] PROGMEM = { "Restart", "Smartconfig", "Wifimanager", "WPSconfig", "Retry" }; +const char PREFIXES[3][5] PROGMEM = { "cmnd", "stat", "tele" }; + struct TIME_T { uint8_t Second; uint8_t Minute; @@ -363,59 +366,36 @@ void setLed(uint8_t state) /********************************************************************************************/ -void json2legacy(char* stopic, char* svalue) +void getTopic_P(char *stopic, byte idx, char *topic, const char* subtopic) { - char *p; - char *token; - uint16_t i; - uint16_t j; + char romram[CMDSZ]; - if (!strstr(svalue, "{\"")) { - return; // No JSON + snprintf_P(romram, sizeof(romram), subtopic); + String fulltopic = sysCfg.mqtt_fulltopic; + if ((0 == idx) && (-1 == fulltopic.indexOf(F("%prefix%")))) { + fulltopic += F("/%prefix%"); // Need prefix for commands to handle mqtt topic loops } - -// stopic = stat/sonoff/RESULT -// svalue = {"POWER2":"ON"} -// --> stopic = "stat/sonoff/POWER2", svalue = "ON" -// svalue = {"Upgrade":{"Version":"2.1.2", "OtaUrl":"%s"}} -// --> stopic = "stat/sonoff/UPGRADE", svalue = "2.1.2" -// svalue = {"SerialLog":2} -// --> stopic = "stat/sonoff/SERIALLOG", svalue = "2" -// svalue = {"POWER":""} -// --> stopic = "stat/sonoff/POWER", svalue = "" - - token = strtok(svalue, "{\""); // Topic - p = strrchr(stopic, '/') +1; - i = p - stopic; - for (j = 0; j < strlen(token)+1; j++) { - stopic[i+j] = toupper(token[j]); - } - token = strtok(NULL, "\""); // : or :3} or :3, or :{ - if (strstr(token, ":{")) { - token = strtok(NULL, "\""); // Subtopic - token = strtok(NULL, "\""); // : or :3} or :3, - } - if (strlen(token) > 1) { - token++; - p = strchr(token, ','); - if (!p) { - p = strchr(token, '}'); - } - i = p - token; - token[i] = '\0'; // Value - } else { - token = strtok(NULL, "\""); // Value or , or } - if ((token[0] == ',') || (token[0] == '}')) { // Empty parameter - token = NULL; + for (byte i = 0; i < 3; i++) { + if ('\0' == sysCfg.mqtt_prefix[i][0]) { + snprintf_P(sysCfg.mqtt_prefix[i], sizeof(sysCfg.mqtt_prefix[i]), PREFIXES[i]); } } - if (token == NULL) { - svalue[0] = '\0'; - } else { - memcpy(svalue, token, strlen(token)+1); + fulltopic.replace(F("%prefix%"), sysCfg.mqtt_prefix[idx]); + fulltopic.replace(F("%topic%"), topic); + fulltopic.replace(F("#"), ""); + fulltopic.replace(F("//"), "/"); + if (!fulltopic.endsWith("/")) { + fulltopic += "/"; } + snprintf_P(stopic, TOPSZ, PSTR("%s%s"), fulltopic.c_str(), romram); +/* + char log[LOGSZ]; + snprintf_P(log, sizeof(log), PSTR("MTPC: %s"), stopic); + addLog(LOG_LEVEL_DEBUG, log); +*/ } + char* getStateText(byte state) { if (state > 2) { @@ -472,7 +452,7 @@ void mqtt_publish_topic_P(uint8_t prefix, const char* subtopic, const char* data snprintf_P(romram, sizeof(romram), ((prefix > 3) && !sysCfg.flag.mqtt_response) ? PSTR("RESULT") : subtopic); prefix &= 1; - snprintf_P(stopic, sizeof(stopic), PSTR("%s/%s/%s"), sysCfg.mqtt_prefix[prefix +1], sysCfg.mqtt_topic, romram); + getTopic_P(stopic, prefix +1, sysCfg.mqtt_topic, romram); mqtt_publish(stopic, data, retained); } @@ -485,18 +465,21 @@ void mqtt_publishPowerState(byte device) { char stopic[TOPSZ]; char sdevice[10]; + char scommand[10]; char svalue[64]; // was MESSZ if ((device < 1) || (device > Maxdevice)) { device = 1; } snprintf_P(sdevice, sizeof(sdevice), PSTR("%d"), device); - snprintf_P(stopic, sizeof(stopic), PSTR("%s/%s/%s"), - sysCfg.mqtt_prefix[1], sysCfg.mqtt_topic, (sysCfg.flag.mqtt_response)?"POWER":"RESULT"); - snprintf_P(svalue, sizeof(svalue), PSTR("{\"POWER%s\":\"%s\"}"), - (Maxdevice > 1) ? sdevice : "", getStateText(bitRead(power, device -1))); + snprintf_P(scommand, sizeof(scommand), PSTR("POWER%s"), (Maxdevice > 1) ? sdevice : ""); + + getTopic_P(stopic, 1, sysCfg.mqtt_topic, (sysCfg.flag.mqtt_response)?"POWER":"RESULT"); + snprintf_P(svalue, sizeof(svalue), PSTR("{\"%s\":\"%s\"}"), scommand, getStateText(bitRead(power, device -1))); mqtt_publish(stopic, svalue); - json2legacy(stopic, svalue); + + getTopic_P(stopic, 1, sysCfg.mqtt_topic, scommand); + snprintf_P(svalue, sizeof(svalue), PSTR("%s"), getStateText(bitRead(power, device -1))); mqtt_publish(stopic, svalue, sysCfg.flag.mqtt_power_retain); } @@ -522,17 +505,19 @@ void mqtt_connected() if (sysCfg.flag.mqtt_enabled) { // Satisfy iobroker (#299) - snprintf_P(stopic, sizeof(stopic), PSTR("%s/%s/POWER"), sysCfg.mqtt_prefix[0], sysCfg.mqtt_topic); + getTopic_P(stopic, 0, sysCfg.mqtt_topic, PSTR("POWER")); svalue[0] ='\0'; mqtt_publish(stopic, svalue); - snprintf_P(stopic, sizeof(stopic), PSTR("%s/%s/#"), sysCfg.mqtt_prefix[0], sysCfg.mqtt_topic); + getTopic_P(stopic, 0, sysCfg.mqtt_topic, PSTR("#")); mqttClient.subscribe(stopic); mqttClient.loop(); // Solve LmacRxBlk:1 messages - snprintf_P(stopic, sizeof(stopic), PSTR("%s/%s/#"), sysCfg.mqtt_prefix[0], sysCfg.mqtt_grptopic); + + getTopic_P(stopic, 0, sysCfg.mqtt_grptopic, PSTR("#")); mqttClient.subscribe(stopic); mqttClient.loop(); // Solve LmacRxBlk:1 messages - snprintf_P(stopic, sizeof(stopic), PSTR("%s/%s/#"), sysCfg.mqtt_prefix[0], MQTTClient); // Fall back topic + + getTopic_P(stopic, 0, MQTTClient, PSTR("#")); mqttClient.subscribe(stopic); mqttClient.loop(); // Solve LmacRxBlk:1 messages #ifdef USE_DOMOTICZ @@ -610,7 +595,8 @@ void mqtt_reconnect() #endif // USE_DISCOVERY #endif // USE_MQTT_TLS mqttClient.setServer(sysCfg.mqtt_host, sysCfg.mqtt_port); - snprintf_P(stopic, sizeof(stopic), PSTR("%s/%s/LWT"), sysCfg.mqtt_prefix[2], sysCfg.mqtt_topic); + + getTopic_P(stopic, 2, sysCfg.mqtt_topic, PSTR("LWT")); snprintf_P(svalue, sizeof(svalue), PSTR("Offline")); if (mqttClient.connect(MQTTClient, sysCfg.mqtt_user, sysCfg.mqtt_pwd, stopic, 1, true, svalue)) { addLog_P(LOG_LEVEL_INFO, PSTR("MQTT: Connected")); @@ -632,6 +618,7 @@ boolean mqtt_command(boolean grpflg, char *type, uint16_t index, char *dataBuf, boolean serviced = true; char stemp1[TOPSZ]; char stemp2[10]; + char scommand[CMDSZ]; uint16_t i; if (!strcmp_P(type,PSTR("MQTTHOST"))) { @@ -695,6 +682,23 @@ boolean mqtt_command(boolean grpflg, char *type, uint16_t index, char *dataBuf, } snprintf_P(svalue, ssvalue, PSTR("{\"MqttPassword\":\"%s\"}"), sysCfg.mqtt_pwd); } + else if (!grpflg && !strcmp_P(type,PSTR("FULLTOPIC"))) { + if ((data_len > 0) && (data_len < sizeof(sysCfg.mqtt_fulltopic))) { + for (i = 0; i <= data_len; i++) { + if ((dataBuf[i] == '+') || (dataBuf[i] == '#') || (dataBuf[i] == ' ')) { + for (byte j = i; j <= data_len; j++) { + dataBuf[j] = dataBuf[j +1]; + } + } + } + if (!strcmp(dataBuf, MQTTClient)) { + payload = 1; + } + strlcpy(sysCfg.mqtt_fulltopic, (1 == payload) ? MQTT_FULLTOPIC : dataBuf, sizeof(sysCfg.mqtt_fulltopic)); + restartflag = 2; + } + snprintf_P(svalue, ssvalue, PSTR("{\"FullTopic\":\"%s\"}"), sysCfg.mqtt_fulltopic); + } else if (!strcmp_P(type,PSTR("PREFIX")) && (index > 0) && (index <= 3)) { if ((data_len > 0) && (data_len < sizeof(sysCfg.mqtt_prefix[0]))) { for(i = 0; i <= data_len; i++) { @@ -795,7 +799,12 @@ boolean mqtt_command(boolean grpflg, char *type, uint16_t index, char *dataBuf, if (!payload) { for(i = 1; i <= Maxdevice; i++) { // Clear MQTT retain in broker snprintf_P(stemp2, sizeof(stemp2), PSTR("%d"), i); - snprintf_P(stemp1, sizeof(stemp1), PSTR("%s/%s/POWER%s"), sysCfg.mqtt_prefix[1], sysCfg.mqtt_topic, (Maxdevice > 1) ? stemp2 : ""); + + snprintf_P(scommand, sizeof(scommand), PSTR("POWER%s"), (Maxdevice > 1) ? stemp2 : ""); + getTopic_P(stemp1, 1, sysCfg.mqtt_topic, scommand); + +// snprintf_P(stemp1, sizeof(stemp1), PSTR("%s/%s/POWER%s"), sysCfg.mqtt_prefix[1], sysCfg.mqtt_topic, (Maxdevice > 1) ? stemp2 : ""); + mqtt_publish(stemp1, "", sysCfg.flag.mqtt_power_retain); } } @@ -872,21 +881,8 @@ void mqttDataCb(char* topic, byte* data, unsigned int data_len) } #endif // USE_DOMOTICZ - memmove(topicBuf, topicBuf+strlen(sysCfg.mqtt_prefix[0]), sizeof(topicBuf)-strlen(sysCfg.mqtt_prefix[0])); // Remove SUB_PREFIX - - i = 0; - for (str = strtok_r(topicBuf, "/", &p); str && i < 2; str = strtok_r(NULL, "/", &p)) { - switch (i++) { - case 0: // Topic / GroupTopic / DVES_123456 - mtopic = str; - break; - case 1: // TopicIndex / Text - type = str; - } - } - if (!strcmp(mtopic, sysCfg.mqtt_grptopic)) { - grpflg = 1; - } + grpflg = (strstr(topicBuf, sysCfg.mqtt_grptopic) != NULL); + type = strrchr(topicBuf, '/') +1; index = 1; if (type != NULL) { @@ -906,8 +902,8 @@ void mqttDataCb(char* topic, byte* data, unsigned int data_len) dataBufUc[i] = toupper(dataBuf[i]); } - snprintf_P(svalue, sizeof(svalue), PSTR("RSLT: DataCb Topic %s, Group %d, Index %d, Type %s, Data %s (%s)"), - mtopic, grpflg, index, type, dataBuf, dataBufUc); + snprintf_P(svalue, sizeof(svalue), PSTR("RSLT: DataCb Group %d, Index %d, Type %s, Data %s (%s)"), + grpflg, index, type, dataBuf, dataBufUc); addLog(LOG_LEVEL_DEBUG, svalue); // snprintf_P(stopic, sizeof(stopic), PSTR("%s/%s/RESULT"), PUB_PREFIX, sysCfg.mqtt_topic); @@ -1496,6 +1492,7 @@ void send_button_power(byte key, byte device, byte state) // key 1 = switch_topic char stopic[TOPSZ]; + char scommand[CMDSZ]; char svalue[TOPSZ]; char stemp1[10]; @@ -1503,8 +1500,8 @@ void send_button_power(byte key, byte device, byte state) device = 1; } snprintf_P(stemp1, sizeof(stemp1), PSTR("%d"), device); - snprintf_P(stopic, sizeof(stopic), PSTR("%s/%s/POWER%s"), - sysCfg.mqtt_prefix[0], (key) ? sysCfg.switch_topic : sysCfg.button_topic, (key || (Maxdevice > 1)) ? stemp1 : ""); + snprintf_P(scommand, sizeof(scommand), PSTR("POWER%s"), (key || (Maxdevice > 1)) ? stemp1 : ""); + getTopic_P(stopic, 0, (key) ? sysCfg.switch_topic : sysCfg.button_topic, scommand); if (3 == state) { svalue[0] = '\0'; @@ -1598,7 +1595,7 @@ void stop_all_power_blink() void do_cmnd(char *cmnd) { - char stopic[TOPSZ]; + char stopic[CMDSZ]; char svalue[128]; char *start; char *token; @@ -1607,10 +1604,10 @@ void do_cmnd(char *cmnd) if (token != NULL) { start = strrchr(token, '/'); // Skip possible cmnd/sonoff/ preamble if (start) { - token = start; + token = start +1; } } - snprintf_P(stopic, sizeof(stopic), PSTR("%s/%s/%s"), sysCfg.mqtt_prefix[0], sysCfg.mqtt_topic, token); + snprintf_P(stopic, sizeof(stopic), PSTR("/%s"), token); token = strtok(NULL, ""); snprintf_P(svalue, sizeof(svalue), PSTR("%s"), (token == NULL) ? "" : token); mqttDataCb(stopic, (byte*)svalue, strlen(svalue)); diff --git a/sonoff/user_config.h b/sonoff/user_config.h index 251917b8c..f8940f850 100644 --- a/sonoff/user_config.h +++ b/sonoff/user_config.h @@ -57,14 +57,6 @@ #define MQTT_PASS "DVES_PASS" // [MqttPassword] Optional password #endif -#define MQTT_CLIENT_ID "DVES_%06X" // [MqttClient] Also fall back topic using Chip Id = last 6 characters of MAC address - -#define SUB_PREFIX "cmnd" // [Prefix1] Sonoff devices subscribe to:- SUB_PREFIX/MQTT_TOPIC and SUB_PREFIX/MQTT_GRPTOPIC -#define PUB_PREFIX "stat" // [Prefix2] Sonoff devices publish to:- PUB_PREFIX/MQTT_TOPIC -#define PUB_PREFIX2 "tele" // [Prefix3] Sonoff devices publish telemetry data to:- PUB_PREFIX2/MQTT_TOPIC/UPTIME, POWER/LIGHT and TIME - // May be named the same as PUB_PREFIX -#define MQTT_TOPIC PROJECT // [Topic] (unique) MQTT device topic -#define MQTT_GRPTOPIC "sonoffs" // [GroupTopic] MQTT Group topic #define MQTT_BUTTON_RETAIN 0 // [ButtonRetain] Button may send retain flag (0 = off, 1 = on) #define MQTT_POWER_RETAIN 0 // [PowerRetain] Power status message may send retain flag (0 = off, 1 = on) #define MQTT_SWITCH_RETAIN 0 // [SwitchRetain] Switch may send retain flag (0 = off, 1 = on) @@ -73,6 +65,20 @@ #define MQTT_STATUS_ON "ON" // [StateText2] Command or Status result when turned on (needs to be a string like "1" or "On") #define MQTT_CMND_TOGGLE "TOGGLE" // [StateText3] Command to send when toggling (needs to be a string like "2" or "Toggle") +// -- MQTT topics --------------------------------- +//#define MQTT_FULLTOPIC "tasmota/bedroom/%topic%/%prefix%/" // Up to max 80 characers +#define MQTT_FULLTOPIC "%prefix%/%topic%/" // [FullTopic] Subscribe and Publish full topic name - Legacy topic + +// %prefix% token options +#define SUB_PREFIX "cmnd" // [Prefix1] Sonoff devices subscribe to %prefix%/%topic% being SUB_PREFIX/MQTT_TOPIC and SUB_PREFIX/MQTT_GRPTOPIC +#define PUB_PREFIX "stat" // [Prefix2] Sonoff devices publish to %prefix%/%topic% being PUB_PREFIX/MQTT_TOPIC +#define PUB_PREFIX2 "tele" // [Prefix3] Sonoff devices publish telemetry data to %prefix%/%topic% being PUB_PREFIX2/MQTT_TOPIC/UPTIME, POWER and TIME + // May be named the same as PUB_PREFIX +// %topic% token options (also ButtonTopic and SwitchTopic) +#define MQTT_TOPIC PROJECT // [Topic] (unique) MQTT device topic +#define MQTT_GRPTOPIC "sonoffs" // [GroupTopic] MQTT Group topic +#define MQTT_CLIENT_ID "DVES_%06X" // [MqttClient] Also fall back topic using Chip Id = last 6 characters of MAC address + // -- MQTT - Telemetry ---------------------------- #define TELE_PERIOD 300 // [TelePeriod] Telemetry (0 = disable, 10 - 3600 seconds) diff --git a/sonoff/xdrv_domoticz.ino b/sonoff/xdrv_domoticz.ino index 283d85668..a7965ee83 100644 --- a/sonoff/xdrv_domoticz.ino +++ b/sonoff/xdrv_domoticz.ino @@ -138,6 +138,7 @@ boolean domoticz_mqttData(char *topicBuf, uint16_t stopicBuf, char *dataBuf, uin { char log[LOGSZ]; char stemp1[10]; + char scommand[10]; unsigned long idx = 0; int16_t nvalue; int16_t found = 0; @@ -173,16 +174,14 @@ boolean domoticz_mqttData(char *topicBuf, uint16_t stopicBuf, char *dataBuf, uin if ((SONOFF_LED == sysCfg.module) && (sysCfg.led_dimmer[i] == nvalue)) { return 1; } - snprintf_P(topicBuf, stopicBuf, PSTR("%s/%s/DIMMER%s"), - sysCfg.mqtt_prefix[0], sysCfg.mqtt_topic, (Maxdevice > 1) ? stemp1 : ""); + snprintf_P(topicBuf, stopicBuf, PSTR("/DIMMER%s"), (Maxdevice > 1) ? stemp1 : ""); snprintf_P(dataBuf, sdataBuf, PSTR("%d"), nvalue); found = 1; } else { if (((power >> i) &1) == nvalue) { return 1; } - snprintf_P(topicBuf, stopicBuf, PSTR("%s/%s/POWER%s"), - sysCfg.mqtt_prefix[0], sysCfg.mqtt_topic, (Maxdevice > 1) ? stemp1 : ""); + snprintf_P(topicBuf, stopicBuf, PSTR("/POWER%s"), (Maxdevice > 1) ? stemp1 : ""); snprintf_P(dataBuf, sdataBuf, PSTR("%d"), nvalue); found = 1; }