diff --git a/tasmota/settings.h b/tasmota/settings.h index b23d6dd0e..9e8b31d26 100644 --- a/tasmota/settings.h +++ b/tasmota/settings.h @@ -121,13 +121,13 @@ typedef union { // Restricted by MISRA-C Rule 18.4 bu uint32_t zerocross_dimmer : 1; // bit 17 (v8.3.1.4) - SetOption99 - (PWM Dimmer) Enable zerocross dimmer (1) uint32_t remove_zbreceived : 1; // bit 18 (v8.3.1.7) - SetOption100 - (Zigbee) Remove ZbReceived form JSON message (1) uint32_t zb_index_ep : 1; // bit 19 (v8.3.1.7) - SetOption101 - (Zigbee) Add the source endpoint as suffix to attributes, ex `Power3` (1) instead of `Power` (0) if sent from endpoint 3 - uint32_t teleinfo_baudrate : 1; // bit 20 (v8.4.0.1) - SetOption102 - (Teleinfo) Set Baud rate for Teleinfo communication to 1200 (0) or 9600 (1) + uint32_t obsolete1 : 1; // bit 20 (v9.3.1.3) - SetOption102 - teleinfo_baudrate Obsolete Teleinfo config has now a dedicated bit field uint32_t mqtt_tls : 1; // bit 21 (v8.4.0.1) - SetOption103 - (MQTT TLS) Enable TLS mode (1) (requires TLS version) uint32_t mqtt_no_retain : 1; // bit 22 (v8.4.0.1) - SetOption104 - (MQTT) No Retain (1) - disable all MQTT retained messages, some brokers don't support it: AWS IoT, Losant uint32_t white_blend_mode : 1; // bit 23 (v8.4.0.1) - SetOption105 - (Light) White Blend Mode (1) - used to be `RGBWWTable` last value `0`, now deprecated in favor of this option uint32_t virtual_ct : 1; // bit 24 (v8.4.0.1) - SetOption106 - (Light) Virtual CT (1) - Creates a virtual White ColorTemp for RGBW lights uint32_t virtual_ct_cw : 1; // bit 25 (v8.4.0.1) - SetOption107 - (Light) Virtual CT Channel (1) - signals whether the hardware white is cold CW (true) or warm WW (false) - uint32_t teleinfo_rawdata : 1; // bit 26 (v8.4.0.2) - SetOption108 - (Teleinfo) Enable Teleinfo + Tasmota Energy device (0) or Teleinfo raw data only (1) + uint32_t obsolete2 : 1; // bit 26 (v9.3.1.3) - SetOption108 - teleinfo_rawdata Obsolete Teleinfo config has now a dedicated bit field uint32_t alexa_gen_1 : 1; // bit 27 (v8.4.0.3) - SetOption109 - (Alexa) Gen1 mode (1) - if you only have Echo Dot 2nd gen devices uint32_t zb_disable_autobind : 1; // bit 28 (v8.5.0.1) - SetOption110 - (Zigbee) Disable auto-config (1) when pairing new devices uint32_t buzzer_freq_mode : 1; // bit 29 (v8.5.0.1) - SetOption111 - (Buzzer) Use frequency output (1) for buzzer pin instead of on/off signal (0) diff --git a/tasmota/xnrg_15_teleinfo.ino b/tasmota/xnrg_15_teleinfo.ino index c1e0fc9d9..75ff81cef 100755 --- a/tasmota/xnrg_15_teleinfo.ino +++ b/tasmota/xnrg_15_teleinfo.ino @@ -38,6 +38,27 @@ #define TINFO_READ_TIMEOUT 400 +#define D_NAME_TELEINFO "Teleinfo" + +// Json Command +//const char S_JSON_TELEINFO_COMMAND_STRING[] PROGMEM = "{\"" D_NAME_TELEINFO "\":{\"%s\":%s}}"; +//const char S_JSON_TELEINFO_COMMAND_NVALUE[] PROGMEM = "{\"" D_NAME_TELEINFO "\":{\"%s\":%d}}"; +const char TELEINFO_COMMAND_SETTINGS[] PROGMEM = "TIC: Settings Mode:%s, Raw:%s, Skip:%d, Limit:%d"; + +#define MAX_TINFO_COMMAND_NAME 16+1 // Change this if one of the following kTInfo_Commands is higher then 16 char +const char kTInfo_Commands[] PROGMEM = "historique|standard|noraw|full|changed|skip|limit"; + +enum TInfoCommands { // commands for Console + CMND_TELEINFO_HISTORIQUE=0, // Set Legacy mode + CMND_TELEINFO_STANDARD, // Set Standard Mode + CMND_TELEINFO_RAW_DISABLE, // Disable Raw frame sending + CMND_TELEINFO_RAW_FULL, // Enable all RAW frame send + CMND_TELEINFO_RAW_CHANGE, // Enable only changed values RAW frame send + CMND_TELEINFO_SKIP, // Set number of frame to skip when raw mode is enabled + CMND_TELEINFO_LIMIT // Limit RAW frame to values subject to fast change (Power, Current, ...), TBD +}; + + // All contract type for legacy, standard mode has in clear text enum TInfoContrat{ CONTRAT_BAS = 1, // BASE => Option Base. @@ -126,6 +147,7 @@ bool tinfo_found = false; int contrat; int tarif; int isousc; +int raw_skip; /*********************************************************************************************/ @@ -366,15 +388,17 @@ void DataCallback(struct _ValueList * me, uint8_t flags) Function: responseDumpTInfo Purpose : add teleinfo values into JSON response Input : 1st separator space if begining of JSON, else comma -Output : - + : select if append all data or just changed one +Output : false if asked for changed value and none has changed else true Comments: - ====================================================================== */ -void ResponseAppendTInfo(char sep) +bool ResponseAppendTInfo(char sep, bool all) { struct _ValueList * me = tinfo.getList(); char * p ; - boolean isNumber ; + bool isNumber ; + bool hasValue = false; // Loop thru all the teleinfo frame but // always check we don't buffer overflow of MQTT data @@ -383,34 +407,41 @@ void ResponseAppendTInfo(char sep) me = me->next; if (me->name && me->value && *me->name && *me->value) { - isNumber = true; - p = me->value; - // Specific treatment serial number don't convert to number later - if (strcmp(me->name, "ADCO")==0 || strcmp(me->name, "ADSC")==0) { - isNumber = false; - } else { - // check if value is number - while (*p && isNumber) { - if ( *p < '0' || *p > '9' ) { - isNumber = false; + // Add values only if we want all data or if data has changed + if (all || ( Settings.teleinfo.raw_report_changed && (me->flags & (TINFO_FLAGS_UPDATED | TINFO_FLAGS_ADDED | TINFO_FLAGS_ALERT) ) ) ) { + + isNumber = true; + hasValue = true; + p = me->value; + + // Specific treatment serial number don't convert to number later + if (strcmp(me->name, "ADCO")==0 || strcmp(me->name, "ADSC")==0) { + isNumber = false; + } else { + // check if value is number + while (*p && isNumber) { + if ( *p < '0' || *p > '9' ) { + isNumber = false; + } + p++; } - p++; } + + ResponseAppend_P( PSTR("%c\"%s\":"), sep, me->name ); + + if (!isNumber) { + ResponseAppend_P( PSTR("\"%s\""), me->value ); + } else { + ResponseAppend_P( PSTR("%d"), atoi(me->value)); + } + + // Now JSON separator is needed + sep =','; } - - ResponseAppend_P( PSTR("%c\"%s\":"), sep, me->name ); - - if (!isNumber) { - ResponseAppend_P( PSTR("\"%s\""), me->value ); - } else { - ResponseAppend_P( PSTR("%d"), atoi(me->value)); - } - - // Now JSON separator is needed - sep =','; } } + return hasValue; } /* ====================================================================== @@ -425,15 +456,29 @@ void NewFrameCallback(struct _ValueList * me) // Reset Energy Watchdog Energy.data_valid[0] = 0; - // send teleinfo full frame only if setup like that - // see setOption108 - if (Settings.flag4.teleinfo_rawdata) { - Response_P(PSTR("{")); - ResponseAppendTInfo(' '); - ResponseJsonEnd(); - // Publish adding ADCO serial number into the topic - // Need setOption4 to be enabled - MqttPublishPrefixTopicRulesProcess_P(RESULT_OR_TELE, serialNumber, false); + // Deprecated see setOption108 + // send teleinfo raw data only if setup like that + if (Settings.teleinfo.raw_send) { + // Do we need to skip this frame + if (raw_skip == 0 ) { + Response_P(PSTR("{")); + // send teleinfo full frame or only changed data + bool hasData = ResponseAppendTInfo(' ', Settings.teleinfo.raw_report_changed ? false : true ); + ResponseJsonEnd(); + + // Publish adding ADCO serial number into the topic + // Need setOption4 to be enabled + // No need to send empty payload + if (hasData) { + MqttPublishPrefixTopicRulesProcess_P(RESULT_OR_TELE, serialNumber, false); + } + + // Reset frame skip counter (if 0 it's disabled) + raw_skip = Settings.teleinfo.raw_skip; + } else { + AddLog(LOG_LEVEL_DEBUG, PSTR("TIC: not sending yet, will do in %d frame(s)"), raw_skip); + raw_skip--; + } } } @@ -463,9 +508,9 @@ void TInfoInit(void) int baudrate; int serial_buffer_size; - - // SetOption102 - Set Baud rate for Teleinfo serial communication (0 = 1200 or 1 = 9600) - if (Settings.flag4.teleinfo_baudrate) { + // Deprecated SetOption102 - Set Baud rate for Teleinfo serial communication (0 = 1200 or 1 = 9600) + // now set in bit field TeleinfoCfg + if (Settings.teleinfo.mode_standard) { baudrate = 9600; tinfo_mode = TINFO_MODE_STANDARD; serial_buffer_size = TELEINFO_SERIAL_BUFFER_STANDARD; @@ -475,6 +520,7 @@ void TInfoInit(void) serial_buffer_size = TELEINFO_SERIAL_BUFFER_HISTORIQUE; } + if (PinUsed(GPIO_TELEINFO_RX)) { int8_t rx_pin = Pin(GPIO_TELEINFO_RX); AddLog(LOG_LEVEL_INFO, PSTR("TIC: RX on GPIO%d, baudrate %d"), rx_pin, baudrate); @@ -533,11 +579,180 @@ void TInfoInit(void) tinfo.attachNewFrame(NewFrameCallback); tinfo_found = true; + if (Settings.teleinfo.raw_send) { + raw_skip = Settings.teleinfo.raw_skip; + AddLog(LOG_LEVEL_INFO, PSTR("TIC: Raw mode enabled")); + if (raw_skip) { + AddLog(LOG_LEVEL_INFO, PSTR("TIC: Sending only one frame over %d "), raw_skip+1); + } + } AddLog(LOG_LEVEL_INFO, PSTR("TIC: Ready")); } } } + +/* ====================================================================== +Function: TInfoCmd +Purpose : Tasmota core command engine for Teleinfo commands +Input : - +Output : - +Comments: - +====================================================================== */ +bool TInfoCmd(void) { + bool serviced = false; + char command[CMDSZ]; + //uint8_t name_len = strlen(D_NAME_TELEINFO); + + // At least "EnergyConfig" + if (CMND_ENERGYCONFIG == Energy.command_code) { + + AddLog_P(LOG_LEVEL_DEBUG, PSTR("TIC: len %d, data '%s'"), XdrvMailbox.data_len, XdrvMailbox.data ? XdrvMailbox.data : "null" ); + + // Just "EnergyConfig" no more parameter + // Show Teleinfo configuration + if (XdrvMailbox.data_len == 0) { + + char mode_name[MAX_TINFO_COMMAND_NAME]; + char raw_name[MAX_TINFO_COMMAND_NAME]; + int index_mode = Settings.teleinfo.mode_standard ? CMND_TELEINFO_STANDARD : CMND_TELEINFO_HISTORIQUE; + int index_raw = Settings.teleinfo.raw_send ? CMND_TELEINFO_RAW_FULL : CMND_TELEINFO_RAW_DISABLE; + if (Settings.teleinfo.raw_send && Settings.teleinfo.raw_report_changed) { + index_raw = CMND_TELEINFO_RAW_CHANGE; + } + // Get the mode and raw name + GetTextIndexed(mode_name, MAX_TINFO_COMMAND_NAME, index_mode, kTInfo_Commands); + GetTextIndexed(raw_name, MAX_TINFO_COMMAND_NAME, index_raw, kTInfo_Commands); + + AddLog_P(LOG_LEVEL_INFO, TELEINFO_COMMAND_SETTINGS, mode_name, raw_name, Settings.teleinfo.raw_skip, Settings.teleinfo.raw_limit); + + serviced = true; + + // At least "EnergyConfig xyz" plus one space and one (or more) char + // so "EnergyConfig 0" or "EnergyConfig Teleinfo Standard" + } else if (XdrvMailbox.data_len) { + // Now point on parameter + char *pParam = XdrvMailbox.data; + char *p = pParam; + char *pValue = nullptr; + // Check for sub parameter ie : EnergyConfig Teleinfo Skip value + while(*p) { + if (*p == ' ') { + if (*(p+1)) { + // Skip parameter by emptying th string so below getcommandcode works + *p++ = 0x00; + // Save parameter value for later + pValue = p; + } + break; + } + p++; + } + + int command_code = GetCommandCode(command, sizeof(command), pParam, kTInfo_Commands); + + AddLog_P(LOG_LEVEL_DEBUG, PSTR("TIC: param '%s' cmnd %d"), pParam, command_code); + + switch (command_code) { + case CMND_TELEINFO_STANDARD: + case CMND_TELEINFO_HISTORIQUE: { + char mode_name[MAX_TINFO_COMMAND_NAME]; + + // Get the mode name + GetTextIndexed(mode_name, MAX_TINFO_COMMAND_NAME, command_code, kTInfo_Commands); + + // Only if current settings is different than previous + if ( (tinfo_mode==TINFO_MODE_STANDARD && command_code==CMND_TELEINFO_HISTORIQUE) || + (tinfo_mode==TINFO_MODE_HISTORIQUE && command_code==CMND_TELEINFO_STANDARD) ) { + + // Cleanup Serial not sure it will works since + // there is no end() or close() on tasmotaserial class + if (TInfoSerial) { + TInfoSerial->flush(); + //TInfoSerial->end(); + free(TInfoSerial); + } + + // Change mode + Settings.teleinfo.mode_standard = command_code == CMND_TELEINFO_STANDARD ? 1 : 0; + + AddLog_P(LOG_LEVEL_INFO, PSTR("TIC: '%s' mode"), mode_name); + + // Re init teleinfo (LibTeleinfo always free linked list on init) + TInfoInit(); + + serviced = true; + + } else { + AddLog_P(LOG_LEVEL_INFO, PSTR("TIC: No change to '%s' mode"), mode_name); + } + } + break; + + case CMND_TELEINFO_RAW_DISABLE: + case CMND_TELEINFO_RAW_FULL: + case CMND_TELEINFO_RAW_CHANGE: { + + // Enable all RAW frame send + char raw_name[MAX_TINFO_COMMAND_NAME]; + + // Get the raw name + GetTextIndexed(raw_name, MAX_TINFO_COMMAND_NAME, command_code, kTInfo_Commands); + + if (command_code == CMND_TELEINFO_RAW_DISABLE) { + // disable raw mode + Settings.teleinfo.raw_send = 0; + } else { + // enable raw mode + Settings.teleinfo.raw_send = 1; + Settings.teleinfo.raw_report_changed = command_code == CMND_TELEINFO_RAW_CHANGE ? 1 : 0; + } + + AddLog_P(LOG_LEVEL_INFO, PSTR("TIC: Raw to '%s'"), raw_name); + serviced = true; + } + break; + + case CMND_TELEINFO_SKIP: { + // Set Raw mode skip frame number + char skip_name[MAX_TINFO_COMMAND_NAME]; + // Get the raw name + GetTextIndexed(skip_name, MAX_TINFO_COMMAND_NAME, command_code, kTInfo_Commands); + int l = strlen(skip_name); + + // At least "EnergyConfig Teleinfo skip" plus one space and one (or more) digit + // so "EnergyConfig Skip 0" or "EnergyConfig Skip 123" + if ( pValue ) { + int value = atoi(pValue); + if (value >= 0 && value <= 255) { + raw_skip = value; + Settings.teleinfo.raw_skip = raw_skip; + + if (raw_skip ==0) { + AddLog_P(LOG_LEVEL_INFO, PSTR("TIC: Raw no skip")); + } else { + AddLog_P(LOG_LEVEL_INFO, PSTR("TIC: Raw each %d frame(s)"), raw_skip+1); + } + serviced = true; + } else { + AddLog_P(LOG_LEVEL_INFO, PSTR("TIC: skip can be 0 to 255")); + } + } else { + AddLog_P(LOG_LEVEL_INFO, PSTR("TIC: no skip value")); + } + } + break; + + default: + AddLog_P(LOG_LEVEL_INFO, PSTR("TIC: bad cmd param '%s'"), pParam); + break; + + } + } + } + return serviced ; +} + /* ====================================================================== Function: TInfoProcess Purpose : Tasmota callback executed often enough to read serial @@ -614,7 +829,7 @@ void TInfoShow(bool json) } // add teleinfo full frame - ResponseAppendTInfo(','); + ResponseAppendTInfo(',', true); #ifdef USE_WEBSERVER } @@ -696,11 +911,16 @@ void TInfoShow(bool json) \*********************************************************************************************/ bool Xnrg15(uint8_t function) { + bool result = false; switch (function) { case FUNC_EVERY_250_MSECOND: TInfoProcess(); break; + case FUNC_COMMAND: + result = TInfoCmd(); + break; + case FUNC_JSON_APPEND: TInfoShow(1); break; @@ -716,7 +936,7 @@ bool Xnrg15(uint8_t function) TInfoDrvInit(); break; } - return false; + return result; } #endif // USE_TELEINFO