5.0.5 20170508
* Add command FullTopic with tokens %topic% (replaced by command Topic
value) and
*  %prefix% (replaced by command Prefix<x> values) for more flexible
topic definitions (#244)
*  See wiki > MQTT Features
https://github.com/arendst/Sonoff-Tasmota/wiki/MQTT-Features for more
information
This commit is contained in:
arendst 2017-05-08 13:21:45 +02:00
parent 09acb77277
commit bc9d44d74a
8 changed files with 112 additions and 95 deletions

View File

@ -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 ****

Binary file not shown.

View File

@ -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<x> 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)

View File

@ -175,6 +175,9 @@ struct SYSCFG {
// 5.0.4
unsigned long hlw_kWhtotal;
// 5.0.4a
char mqtt_fulltopic[101];
} sysCfg;
struct RTCMEM {

View File

@ -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;
}
}

View File

@ -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));

View File

@ -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)

View File

@ -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;
}