Tasmota/tasmota/xnrg_15_teleinfo.ino

842 lines
31 KiB
Arduino
Raw Normal View History

/*
xnrg_15_Teleinfo.ino - Teleinfo support for Tasmota
2021-01-01 12:44:04 +00:00
Copyright (C) 2021 Charles-Henri Hallard
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 <http://www.gnu.org/licenses/>.
*/
#ifdef USE_ENERGY_SENSOR
#ifdef USE_TELEINFO
/*********************************************************************************************\
2020-10-28 16:32:07 +00:00
* Teleinfo : French energy provider metering telemety data
* Source: http://hallard.me/category/tinfo/
*
* Denky ESP32 Teleinfo Template
2020-06-19 01:28:09 +01:00
* {"NAME":"Denky (Teleinfo)","GPIO":[1,1,1,1,5664,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,0,1376,1,1,0,0,0,0,1,5632,1,1,1,0,0,1],"FLAG":0,"BASE":1}
2020-10-28 16:32:07 +00:00
*
* Denky (aka WifInfo) ESP8266 Teleinfo Template
2020-08-14 18:47:13 +01:00
* {"NAME":"WifInfo v1.0a","GPIO":[17,255,255,255,6,5,255,255,7,210,255,255,255],"FLAG":15,"BASE":18}
* {"NAME":"WifInfo","GPIO":[7,255,255,210,6,5,255,255,255,255,255,255,255],"FLAG":15,"BASE":18}
2020-10-28 16:32:07 +00:00
*
\*********************************************************************************************/
2020-10-28 16:32:07 +00:00
#define XNRG_15 15
#include "LibTeleinfo.h"
#include <TasmotaSerial.h>
2020-10-28 16:32:07 +00:00
#define TINFO_READ_TIMEOUT 400
2020-06-14 21:04:19 +01:00
2021-04-08 19:02:19 +01:00
#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 S_JSON_TELEINFO_COMMAND_SETTINGS[] PROGMEM = "{\"TIC_Settings\":{\"Mode\":%s,\"Raw\":%s,\"Skip\":%d,\"Limit\":%d}}";
#define MAX_TINFO_COMMAND_NAME 17 // Change this if one of the following kTInfo_Commands is higher then 16 char
const char kTInfo_Commands[] PROGMEM = "historique|standard|none|full|changed|skip|limit|config";
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
CMND_TELEINFO_CONFIG // Show Teleinfo current settings
};
// All contract type for legacy, standard mode has in clear text
2020-06-14 21:04:19 +01:00
enum TInfoContrat{
2020-10-28 16:32:07 +00:00
CONTRAT_BAS = 1, // BASE => Option Base.
CONTRAT_HC, // HC.. => Option Heures Creuses.
CONTRAT_EJP, // EJP. => Option EJP.
CONTRAT_BBR, // BBRx => Option Tempo
CONTRAT_END
2020-06-14 21:04:19 +01:00
};
// contract displayed name for legacy, standard mode has in clear text
2020-10-28 16:32:07 +00:00
const char kContratName[] PROGMEM =
"|Base|Heures Creuses|EJP|Bleu Blanc Rouge"
;
// Received current contract value for legacy, standard mode has in clear text
2020-10-28 16:32:07 +00:00
const char kContratValue[] PROGMEM =
"|BASE|HC..|EJP.|BBR"
;
// all tariff type for legacy, standard mode has in clear text
2020-06-14 21:04:19 +01:00
enum TInfoTarif{
2020-10-28 16:32:07 +00:00
TARIF_TH = 1,
TARIF_HC, TARIF_HP,
TARIF_HN, TARIF_PM,
TARIF_CB, TARIF_CW, TARIF_CR,
TARIF_PB, TARIF_PW, TARIF_PR,
TARIF_END
2020-06-14 21:04:19 +01:00
};
2021-04-06 01:23:06 +01:00
// Legacy mode Received current tariff values
2020-10-28 16:32:07 +00:00
const char kTarifValue[] PROGMEM =
"|TH..|HC..|HP.."
"|HN..|PM.."
"|HCJB|HCJW|HCJR"
"|HPJB|HPJW|HPJR"
;
2021-04-06 01:23:06 +01:00
// legacy mode tariff displayed name
2020-10-28 16:32:07 +00:00
const char kTarifName[] PROGMEM =
"|Toutes|Creuses|Pleines"
"|Normales|Pointe Mobile"
"|Creuses Bleu|Creuses Blanc|Creuse Rouges"
"|Pleines Bleu|Pleines Blanc|Pleines Rouges"
;
2021-04-06 01:23:06 +01:00
// contract name for standard mode
#define TELEINFO_STD_CONTRACT_BASE PSTR("BASE")
#define TELEINFO_STD_CONTRACT_HCHP PSTR("H CREUSE/PLEINE")
// tariff values for standard mode
#define TELEINFO_STD_TARIFF_BASE PSTR("BASE")
#define TELEINFO_STD_TARIFF_HC PSTR("HEURE CREUSE")
#define TELEINFO_STD_TARIFF_HP PSTR("HEURE PLEINE")
// Label used to do some post processing and/or calculation
enum TInfoLabel{
2020-10-28 16:32:07 +00:00
LABEL_BASE = 1,
LABEL_ADCO, LABEL_ADSC,
LABEL_HCHC, LABEL_HCHP, LABEL_EAST, LABEL_EASF01, LABEL_EASF02,
LABEL_OPTARIF, LABEL_NGTF, LABEL_ISOUSC, LABEL_PREF, LABEL_PTEC, LABEL_LTARF, LABEL_NTARF,
LABEL_PAPP, LABEL_SINSTS, LABEL_IINST, LABEL_IRMS1, LABEL_TENSION, LABEL_URMS1,
LABEL_IMAX, LABEL_PMAX, LABEL_SMAXSN,
LABEL_DEMAIN,
LABEL_END
2020-06-14 23:52:49 +01:00
};
2020-10-28 16:32:07 +00:00
const char kLabel[] PROGMEM =
"|BASE|ADCO|ADSC"
"|HCHC|HCHP|EAST|EASF01|EASF02"
"|OPTARIF|NGTF|ISOUSC|PREF|PTEC|LTARF|NTARF"
"|PAPP|SINSTS|IINST|IRMS1|TENSION|URMS1"
"|IMAX|PMAX|SMAXSN"
"|DEMAIN"
;
2021-02-10 20:08:59 +00:00
#define TELEINFO_SERIAL_BUFFER_STANDARD 512 // Receive buffer size for Standard mode
#define TELEINFO_SERIAL_BUFFER_HISTORIQUE 512 // Receive buffer size for Legacy mode
#define TELEINFO_PROCESS_BUFFER 32 // Local processing buffer
TInfo tinfo; // Teleinfo object
TasmotaSerial *TInfoSerial = nullptr;
_Mode_e tinfo_mode = TINFO_MODE_HISTORIQUE;
char serialNumber[13] = ""; // Serial number is 12 char long
bool tinfo_found = false;
int contrat;
int tarif;
int isousc;
2021-04-08 19:02:19 +01:00
int raw_skip;
/*********************************************************************************************/
2020-06-14 21:04:19 +01:00
/* ======================================================================
2020-10-28 16:32:07 +00:00
Function: getValueFromLabelIndex
Purpose : return label value from label index
Input : label index to search for
Output : value filled
Comments: -
====================================================================== */
char * getValueFromLabelIndex(int labelIndex, char * value)
{
char labelName[16];
// Get the label name
GetTextIndexed(labelName, sizeof(labelName), labelIndex, kLabel);
// Get value of label name
return tinfo.valueGet(labelName, value) ;
}
/* ======================================================================
2020-10-28 16:32:07 +00:00
Function: ADPSCallback
Purpose : called by library when we detected a ADPS on any phased
2020-10-28 16:32:07 +00:00
Input : phase number
0 for ADPS (monophase)
1 for ADIR1 triphase
2 for ADIR2 triphase
3 for ADIR3 triphase
2020-10-28 16:32:07 +00:00
Output : -
Comments: should have been initialised with a
tinfo.attachADPSCallback(ADPSCallback())
====================================================================== */
void ADPSCallback(uint8_t phase)
{
// n = phase number 1 to 3
if (phase == 0){
phase = 1;
}
Response_P(PSTR("{"));
2020-10-30 11:29:48 +00:00
ResponseAppend_P(TasmotaGlobal.mqtt_data, sizeof(TasmotaGlobal.mqtt_data), PSTR("{\"%s\":{\"ADPS\":%i}}"), serialNumber, phase );
ResponseJsonEnd();
// Publish adding ADCO serial number into the topic
2021-04-07 14:07:05 +01:00
MqttPublishPrefixTopicRulesProcess_P(RESULT_OR_TELE, serialNumber, false);
2021-01-23 16:10:06 +00:00
AddLog(LOG_LEVEL_INFO, PSTR("ADPS on phase %d"), phase);
}
/* ======================================================================
2020-10-28 16:32:07 +00:00
Function: DataCallback
Purpose : callback when we detected new or modified data received
Input : linked list pointer on the concerned data
current flags value
2020-10-28 16:32:07 +00:00
Output : -
Comments: -
====================================================================== */
void DataCallback(struct _ValueList * me, uint8_t flags)
{
2020-06-14 21:04:19 +01:00
char c = ' ';
int ilabel ;
2020-06-14 21:04:19 +01:00
// Does this value is new or changed?
if (flags & (TINFO_FLAGS_ADDED | TINFO_FLAGS_UPDATED) ) {
char labelName[16];
// Find the label index
for ( ilabel = 1 ; ilabel < LABEL_END ; ilabel++) {
GetTextIndexed(labelName, sizeof(labelName), ilabel, kLabel);
2020-10-28 16:32:07 +00:00
if (!strcmp(labelName, me->name)) {
break;
}
}
if (flags & TINFO_FLAGS_ADDED) { c = '#'; }
if (flags & TINFO_FLAGS_UPDATED) { c = '*'; }
2021-01-23 16:10:06 +00:00
AddLog(LOG_LEVEL_DEBUG, PSTR("TIC: [%d]%c %s=%s"), ilabel, c , me->name, me->value);
if (ilabel<LABEL_END) {
// Current tariff (legacy)
if (ilabel == LABEL_PTEC)
{
char tarif_value[] = " "; // 4 spaces
// Find the tariff index
for (tarif = TARIF_TH ; tarif < TARIF_END ; tarif++) {
GetTextIndexed(tarif_value, sizeof(tarif_value), tarif-1, kTarifValue);
2020-10-28 16:32:07 +00:00
if (!strcmp(tarif_value, me->value)) {
break;
}
}
2021-02-10 20:08:59 +00:00
AddLog(LOG_LEVEL_DEBUG, PSTR("TIC: Tariff changed, now '%s' (%d)"), me->value, tarif);
2020-10-28 16:32:07 +00:00
}
2021-04-06 01:23:06 +01:00
// Current tariff (standard)
else if (ilabel == LABEL_LTARF)
{
2021-04-06 01:23:06 +01:00
if (!strcmp_P(TELEINFO_STD_TARIFF_BASE, me->value)) {
tarif = TARIF_TH;
} else if (!strcmp_P(TELEINFO_STD_TARIFF_HC, me->value)) {
tarif = TARIF_HC;
} else if (!strcmp_P(TELEINFO_STD_TARIFF_HP, me->value)) {
tarif = TARIF_HP;
}
2021-02-10 20:08:59 +00:00
AddLog(LOG_LEVEL_DEBUG, PSTR("TIC: Tariff name changed, now '%s'"), me->value);
2020-10-28 16:32:07 +00:00
}
2021-04-06 01:23:06 +01:00
// Current tariff index (standard)
// This is the index on pointer counter (not used just for information)
else if (ilabel == LABEL_NTARF)
{
2021-04-06 01:23:06 +01:00
int index = atoi(me->value);
AddLog(LOG_LEVEL_DEBUG, PSTR("TIC: Tariff index changed, now '%d'"), index);
2020-10-28 16:32:07 +00:00
}
// Voltage V (not present on all Smart Meter)
else if ( ilabel == LABEL_TENSION || ilabel == LABEL_URMS1)
{
Energy.voltage_available = true;
Energy.voltage[0] = (float) atoi(me->value);
2021-01-23 16:10:06 +00:00
AddLog(LOG_LEVEL_DEBUG, PSTR("TIC: Voltage %s, now %d"), me->value, (int) Energy.voltage[0]);
}
// Current I
else if (ilabel == LABEL_IINST || ilabel == LABEL_IRMS1)
{
2021-04-02 21:57:23 +01:00
Energy.current_available = true;
Energy.current[0] = (float) atoi(me->value);
2021-01-23 16:10:06 +00:00
AddLog(LOG_LEVEL_DEBUG, PSTR("TIC: Current %s, now %d"), me->value, (int) Energy.current[0]);
}
2020-06-14 23:52:49 +01:00
// Power P
else if (ilabel == LABEL_PAPP || ilabel == LABEL_SINSTS)
{
Energy.active_power[0] = (float) atoi(me->value);;
2021-01-23 16:10:06 +00:00
AddLog(LOG_LEVEL_DEBUG, PSTR("TIC: Power %s, now %d"), me->value, (int) Energy.active_power[0]);
}
// Wh indexes (legacy)
2020-08-21 11:32:27 +01:00
else if ( ilabel == LABEL_HCHC || ilabel == LABEL_HCHP || ilabel == LABEL_BASE)
{
char value[32];
uint32_t hc = 0;
uint32_t hp = 0;
uint32_t total = 0;
2020-08-21 11:32:27 +01:00
// Base, un seul index
if (ilabel == LABEL_BASE) {
total = atoi(me->value);
2021-01-23 16:10:06 +00:00
AddLog(LOG_LEVEL_DEBUG, PSTR("TIC: Base:%u"), total);
2020-08-21 11:32:27 +01:00
// Heures creuses/pleines calculer total
} else {
// Heures creuses get heures pleines
if (ilabel == LABEL_HCHC) {
hc = atoi(me->value);
2020-10-28 16:32:07 +00:00
if ( getValueFromLabelIndex(LABEL_HCHP, value) ) {
2020-08-21 11:32:27 +01:00
hp = atoi(value);
}
2020-10-28 16:32:07 +00:00
2020-08-21 11:32:27 +01:00
// Heures pleines, get heures creuses
} else if (ilabel == LABEL_HCHP) {
hp = atoi(me->value);
2020-10-28 16:32:07 +00:00
if ( getValueFromLabelIndex(LABEL_HCHC, value) ) {
2020-08-21 11:32:27 +01:00
hc = atoi(value);
}
}
total = hc + hp;
2021-01-23 16:10:06 +00:00
AddLog(LOG_LEVEL_DEBUG, PSTR("TIC: HC:%u HP:%u Total:%u"), hc, hp, total);
2020-08-21 11:32:27 +01:00
}
2020-08-14 18:47:13 +01:00
2021-04-02 21:57:23 +01:00
EnergyUpdateTotal(total/1000.0f, true);
2020-08-14 18:47:13 +01:00
}
2020-10-28 16:32:07 +00:00
// Wh total index (standard)
else if ( ilabel == LABEL_EAST)
{
uint32_t total = atoi(me->value);
2021-04-06 01:23:06 +01:00
if (contrat != CONTRAT_BAS) {
EnergyUpdateTotal(total/1000.0f, true);
AddLog(LOG_LEVEL_DEBUG, PSTR("TIC: Total:%uWh"), total);
}
2020-08-14 18:47:13 +01:00
}
// Wh indexes (standard)
else if ( ilabel == LABEL_EASF01)
{
2021-04-06 01:23:06 +01:00
if (contrat == CONTRAT_BAS) {
EnergyUpdateTotal(atoi(me->value)/1000.0f, true);
}
2021-01-23 16:10:06 +00:00
AddLog(LOG_LEVEL_DEBUG, PSTR("TIC: HC:%u"), atoi(me->value));
}
else if ( ilabel == LABEL_EASF02)
{
2021-01-23 16:10:06 +00:00
AddLog(LOG_LEVEL_DEBUG, PSTR("TIC: HP:%u"), atoi(me->value));
}
// Contract subscribed (legacy)
else if (ilabel == LABEL_OPTARIF)
{
char contrat_value[] = " "; // 4 spaces
// Find the contract index
for (contrat = CONTRAT_BAS ; contrat < CONTRAT_END ; contrat++) {
GetTextIndexed(contrat_value, sizeof(contrat_value), contrat, kContratValue);
2020-10-28 16:32:07 +00:00
if (!strcmp(contrat_value, me->value)) {
break;
}
}
2021-01-23 16:10:06 +00:00
AddLog(LOG_LEVEL_DEBUG, PSTR("TIC: Contract changed, now '%s' (%d)"), me->value, contrat);
2020-10-28 16:32:07 +00:00
}
// Contract subscribed (standard is in clear text in value)
else if (ilabel == LABEL_NGTF)
{
2021-04-06 01:23:06 +01:00
if (!strcmp_P(TELEINFO_STD_CONTRACT_BASE, me->value)) {
contrat = CONTRAT_BAS;
} else if (!strcmp_P(TELEINFO_STD_CONTRACT_HCHP, me->value)) {
contrat = CONTRAT_HC;
}
2021-01-23 16:10:06 +00:00
AddLog(LOG_LEVEL_DEBUG, PSTR("TIC: Contract changed, now '%s'"), me->value);
2020-10-28 16:32:07 +00:00
}
// Contract subscribed (Power)
else if (ilabel == LABEL_ISOUSC || ilabel == LABEL_PREF)
{
isousc = atoi( me->value);
2021-01-23 16:10:06 +00:00
AddLog(LOG_LEVEL_DEBUG, PSTR("TIC: ISousc set to %d"), isousc);
2020-10-28 16:32:07 +00:00
}
// Serial Number of device
else if (ilabel == LABEL_ADCO || ilabel == LABEL_ADSC)
{
strcpy(serialNumber, me->value);
2021-01-23 16:10:06 +00:00
AddLog(LOG_LEVEL_DEBUG, PSTR("TIC: %s set to %s"), me->name, serialNumber);
2020-10-28 16:32:07 +00:00
}
2020-06-18 15:10:48 +01:00
}
2020-06-14 21:04:19 +01:00
}
}
2020-06-14 23:52:49 +01:00
/* ======================================================================
2020-10-28 16:32:07 +00:00
Function: responseDumpTInfo
Purpose : add teleinfo values into JSON response
Input : 1st separator space if begining of JSON, else comma
2021-04-08 19:02:19 +01:00
: select if append all data or just changed one
2020-10-28 16:32:07 +00:00
Output : -
2020-06-14 23:52:49 +01:00
Comments: -
====================================================================== */
2021-04-08 19:02:19 +01:00
void ResponseAppendTInfo(char sep, bool all)
2020-06-14 23:52:49 +01:00
{
struct _ValueList * me = tinfo.getList();
2020-08-14 18:47:13 +01:00
char * p ;
boolean isNumber ;
2020-08-14 18:47:13 +01:00
// Loop thru all the teleinfo frame but
// always check we don't buffer overflow of MQTT data
while (me->next) {
// go to next node
me = me->next;
2020-08-14 18:47:13 +01:00
if (me->name && me->value && *me->name && *me->value) {
2020-08-14 18:47:13 +01:00
2021-04-08 19:02:19 +01:00
// 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;
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++;
2020-08-14 18:47:13 +01:00
}
}
2021-04-08 19:02:19 +01:00
ResponseAppend_P( PSTR("%c\"%s\":"), sep, me->name );
2020-08-14 18:47:13 +01:00
2021-04-08 19:02:19 +01:00
if (!isNumber) {
ResponseAppend_P( PSTR("\"%s\""), me->value );
} else {
ResponseAppend_P( PSTR("%d"), atoi(me->value));
}
2020-08-14 18:47:13 +01:00
2021-04-08 19:02:19 +01:00
// Now JSON separator is needed
sep =',';
}
}
2020-08-14 18:47:13 +01:00
}
}
2020-08-14 18:47:13 +01:00
/* ======================================================================
2020-10-28 16:32:07 +00:00
Function: NewFrameCallback
Purpose : callback when we received a complete Teleinfo frama
Input : linked list pointer on the concerned data
2020-10-28 16:32:07 +00:00
Output : -
Comments: -
====================================================================== */
void NewFrameCallback(struct _ValueList * me)
{
// Reset Energy Watchdog
Energy.data_valid[0] = 0;
2020-08-14 18:47:13 +01:00
2021-04-08 19:02:19 +01:00
// 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) {
raw_skip--;
}
if (raw_skip == 0 ) {
Response_P(PSTR("{"));
// send teleinfo full frame or only changed data
ResponseAppendTInfo(' ', Settings.teleinfo.raw_report_changed ? false : true );
ResponseJsonEnd();
// Publish adding ADCO serial number into the topic
// Need setOption4 to be enabled
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_INFO, PSTR("TIC: not sending yet, will do in %d frame(s)"), raw_skip);
}
}
2020-06-14 23:52:49 +01:00
}
/* ======================================================================
2020-10-28 16:32:07 +00:00
Function: TInfoDrvInit
Purpose : Tasmota core driver init
Input : -
2020-10-28 16:32:07 +00:00
Output : -
2020-06-14 23:52:49 +01:00
Comments: -
====================================================================== */
void TInfoDrvInit(void) {
if (PinUsed(GPIO_TELEINFO_RX)) {
2020-10-30 11:29:48 +00:00
TasmotaGlobal.energy_driver = XNRG_15;
2020-08-14 18:47:13 +01:00
Energy.voltage_available = false;
}
}
/* ======================================================================
2020-10-28 16:32:07 +00:00
Function: TInfoInit
Purpose : Tasmota core device init
Input : -
2020-10-28 16:32:07 +00:00
Output : -
Comments: -
====================================================================== */
void TInfoInit(void)
{
int baudrate;
2021-02-10 20:08:59 +00:00
int serial_buffer_size;
2021-04-08 19:02:19 +01:00
// 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) {
2020-10-28 16:32:07 +00:00
baudrate = 9600;
tinfo_mode = TINFO_MODE_STANDARD;
2021-02-10 20:08:59 +00:00
serial_buffer_size = TELEINFO_SERIAL_BUFFER_STANDARD;
} else {
2020-10-28 16:32:07 +00:00
baudrate = 1200;
tinfo_mode = TINFO_MODE_HISTORIQUE;
2021-02-10 20:08:59 +00:00
serial_buffer_size = TELEINFO_SERIAL_BUFFER_HISTORIQUE;
}
2021-04-08 19:02:19 +01:00
if (PinUsed(GPIO_TELEINFO_RX)) {
int8_t rx_pin = Pin(GPIO_TELEINFO_RX);
2021-02-10 20:08:59 +00:00
AddLog(LOG_LEVEL_INFO, PSTR("TIC: RX on GPIO%d, baudrate %d"), rx_pin, baudrate);
// Enable Teleinfo pin used, control it
if (PinUsed(GPIO_TELEINFO_ENABLE)) {
int8_t en_pin = Pin(GPIO_TELEINFO_ENABLE);
2020-10-28 16:32:07 +00:00
pinMode(en_pin, OUTPUT);
digitalWrite(en_pin, HIGH);
2021-01-23 16:10:06 +00:00
AddLog(LOG_LEVEL_INFO, PSTR("TIC: Enable with GPIO%d"), en_pin);
} else {
2021-01-23 16:10:06 +00:00
AddLog(LOG_LEVEL_INFO, PSTR("TIC: always enabled"));
}
2020-11-28 16:00:15 +00:00
#ifdef ESP8266
// Allow GPIO3 AND GPIO13 with hardware fallback to 2
2021-02-10 20:08:59 +00:00
// Set buffer to nnn char to support 250ms loop at 9600 baud
TInfoSerial = new TasmotaSerial(rx_pin, -1, 2, 0, serial_buffer_size);
//pinMode(rx_pin, INPUT_PULLUP);
2020-11-28 16:00:15 +00:00
#endif // ESP8266
#ifdef ESP32
2021-02-10 20:08:59 +00:00
// Set buffer to nnn char to support 250ms loop at 9600 baud
TInfoSerial = new TasmotaSerial(rx_pin, -1, 1, 0, serial_buffer_size);
2020-11-28 16:00:15 +00:00
#endif // ESP32
// Trick here even using SERIAL_7E1 or TS_SERIAL_7E1
2020-10-28 16:32:07 +00:00
// this is not working, need to call SetSerialConfig after
if (TInfoSerial->begin(baudrate)) {
2020-11-28 16:00:15 +00:00
#ifdef ESP8266
if (TInfoSerial->hardwareSerial() ) {
ClaimSerial();
// This is a hack, looks like begin does not take into account
2020-10-28 16:32:07 +00:00
// the TS_SERIAL_7E1 configuration so on ESP8266 this is
// working only on Serial RX pin (Hardware Serial) for now
2020-10-28 16:32:07 +00:00
//SetSerialConfig(TS_SERIAL_7E1);
//TInfoSerial->setTimeout(TINFO_READ_TIMEOUT);
2021-01-23 16:10:06 +00:00
AddLog(LOG_LEVEL_INFO, PSTR("TIC: using hardware serial"));
} else {
2021-01-23 16:10:06 +00:00
AddLog(LOG_LEVEL_INFO, PSTR("TIC: using software serial"));
2020-06-14 21:04:19 +01:00
}
2020-11-28 16:00:15 +00:00
#endif // ESP8266
#ifdef ESP32
2021-02-10 20:08:59 +00:00
SetSerialConfig(TS_SERIAL_7E1);
2021-01-23 16:10:06 +00:00
AddLog(LOG_LEVEL_INFO, PSTR("TIC: using ESP32 hardware serial"));
2020-11-28 16:00:15 +00:00
#endif // ESP32
// Init teleinfo
tinfo.init(tinfo_mode);
// Attach needed callbacks
tinfo.attachADPS(ADPSCallback);
2020-10-28 16:32:07 +00:00
tinfo.attachData(DataCallback);
tinfo.attachNewFrame(NewFrameCallback);
tinfo_found = true;
2021-04-08 19:02:19 +01:00
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);
}
}
2021-01-23 16:10:06 +00:00
AddLog(LOG_LEVEL_INFO, PSTR("TIC: Ready"));
}
}
}
2021-04-08 19:02:19 +01:00
/* ======================================================================
Function: TInfoCmd
Purpose : Tasmota core command engine for Teleinfo commands
Input : -
Output : -
Comments: -
====================================================================== */
bool TInfoCmd(void) {
char command[CMDSZ];
uint8_t name_len = strlen(D_NAME_TELEINFO);
if (!strncasecmp_P(XdrvMailbox.topic, PSTR(D_NAME_TELEINFO), name_len)) {
uint32_t command_code = GetCommandCode(command, sizeof(command), XdrvMailbox.topic + name_len, kTInfo_Commands);
switch (command_code) {
case CMND_TELEINFO_STANDARD:
case CMND_TELEINFO_HISTORIQUE: {
char mode_name[MAX_TINFO_COMMAND_NAME];
if (XdrvMailbox.data_len) {
Settings.teleinfo.mode_standard = command_code == CMND_TELEINFO_STANDARD ? 1 : 0;
// Get the mode and raw name
GetTextIndexed(mode_name, MAX_TINFO_COMMAND_NAME, command_code, kTInfo_Commands);
}
Response_P(S_JSON_TELEINFO_COMMAND_STRING, command, mode_name);
}
break;
case CMND_TELEINFO_CONFIG: {
if (!XdrvMailbox.data_len) {
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);
Response_P(S_JSON_TELEINFO_COMMAND_SETTINGS, mode_name, raw_name, Settings.teleinfo.raw_skip, Settings.teleinfo.raw_limit );
}
}
break;
default:
return false;
}
return true;
} else {
return false;
}
}
/* ======================================================================
2021-02-10 20:08:59 +00:00
Function: TInfoProcess
Purpose : Tasmota callback executed often enough to read serial
Input : -
2020-10-28 16:32:07 +00:00
Output : -
Comments: -
====================================================================== */
2021-02-10 20:08:59 +00:00
//#define MEASURE_PERF // Define to enable performance measurments
void TInfoProcess(void)
{
2021-02-10 20:08:59 +00:00
static char buff[TELEINFO_PROCESS_BUFFER];
#ifdef MEASURE_PERF
static unsigned long max_time = 0;
unsigned long duration = millis();
static int max_size = 0;
int tmp_size = 0;
#endif
2020-08-14 18:47:13 +01:00
if (!tinfo_found) {
return;
2020-08-14 18:47:13 +01:00
}
2021-02-10 20:08:59 +00:00
int size = TInfoSerial->read(buff,TELEINFO_PROCESS_BUFFER);
while ( size ) {
#ifdef MEASURE_PERF
tmp_size += size;
#endif
// Process as many bytes as available in serial buffer
for(int i = 0; size; i++, size--)
{
buff[i] &= 0x7F;
2020-06-14 21:04:19 +01:00
// data processing
2021-02-10 20:08:59 +00:00
tinfo.process(buff[i]);
}
2021-02-10 20:08:59 +00:00
size = TInfoSerial->read(buff,TELEINFO_PROCESS_BUFFER);
}
2021-02-10 20:08:59 +00:00
#ifdef MEASURE_PERF
duration = millis()-duration;
if (duration > max_time) { max_time = duration; AddLog(LOG_LEVEL_INFO,PSTR("TIC: max_time=%lu"), max_time); }
if (tmp_size > max_size) { max_size = tmp_size; AddLog(LOG_LEVEL_INFO,PSTR("TIC: max_size=%d"), max_size); }
#endif
}
/* ======================================================================
2020-10-28 16:32:07 +00:00
Function: TInfoShow
Purpose : Tasmota callback executed to send telemetry or WEB display
Input : -
2020-10-28 16:32:07 +00:00
Output : -
Comments: -
====================================================================== */
2020-06-14 23:52:49 +01:00
#ifdef USE_WEBSERVER
const char HTTP_ENERGY_ID_TELEINFO[] PROGMEM = "{s}ID{m}%s{e}" ;
const char HTTP_ENERGY_INDEX_TELEINFO[] PROGMEM = "{s}%s{m}%s " D_UNIT_WATTHOUR "{e}" ;
2020-06-14 23:52:49 +01:00
const char HTTP_ENERGY_PAPP_TELEINFO[] PROGMEM = "{s}" D_POWERUSAGE "{m}%d " D_UNIT_WATT "{e}" ;
const char HTTP_ENERGY_IINST_TELEINFO[] PROGMEM = "{s}" D_CURRENT "{m}%d " D_UNIT_AMPERE "{e}" ;
const char HTTP_ENERGY_TARIF_TELEINFO[] PROGMEM = "{s}" D_CURRENT_TARIFF "{m}Heures %s{e}" ;
const char HTTP_ENERGY_CONTRAT_TELEINFO[] PROGMEM = "{s}" D_CONTRACT "{m}%s %d" D_UNIT_AMPERE "{e}" ;
const char HTTP_ENERGY_LOAD_TELEINFO[] PROGMEM = "{s}" D_POWER_LOAD "{m}%d" D_UNIT_PERCENT "{e}" ;
const char HTTP_ENERGY_IMAX_TELEINFO[] PROGMEM = "{s}" D_MAX_CURRENT "{m}%d" D_UNIT_AMPERE "{e}" ;
const char HTTP_ENERGY_PMAX_TELEINFO[] PROGMEM = "{s}" D_MAX_POWER "{m}%d" D_UNIT_WATT "{e}" ;
2020-06-14 23:52:49 +01:00
#endif // USE_WEBSERVER
void TInfoShow(bool json)
{
2020-10-28 16:32:07 +00:00
// Since it's an Energy device , current, voltage and power are
// already present on the telemetry frame. No need to add here
// Just add the raw label/values of the teleinfo frame
if (json)
{
// Calculated values
if (isousc) {
ResponseAppend_P(PSTR(",\"Load\":%d"),(int) ((Energy.current[0]*100.0f) / isousc));
2020-06-14 23:52:49 +01:00
}
2020-06-18 16:12:23 +01:00
2021-04-02 16:37:27 +01:00
// add teleinfo full frame
2021-04-08 19:02:19 +01:00
ResponseAppendTInfo(',', true);
2020-06-18 16:12:23 +01:00
#ifdef USE_WEBSERVER
}
else
{
2021-04-07 12:23:31 +01:00
char name[33];
char value[33];
2020-08-14 18:47:13 +01:00
if (tinfo_mode==TINFO_MODE_HISTORIQUE ) {
2021-04-06 01:23:06 +01:00
if (getValueFromLabelIndex(LABEL_BASE, value) ) {
GetTextIndexed(name, sizeof(name), LABEL_BASE, kLabel);
WSContentSend_PD(HTTP_ENERGY_INDEX_TELEINFO, name, value);
}
if (getValueFromLabelIndex(LABEL_HCHC, value) ) {
GetTextIndexed(name, sizeof(name), LABEL_HCHC, kLabel);
WSContentSend_PD(HTTP_ENERGY_INDEX_TELEINFO, name, value);
}
if (getValueFromLabelIndex(LABEL_HCHP, value) ) {
GetTextIndexed(name, sizeof(name), LABEL_HCHP, kLabel);
WSContentSend_PD(HTTP_ENERGY_INDEX_TELEINFO, name, value);
}
if (getValueFromLabelIndex(LABEL_IMAX, value) ) {
WSContentSend_PD(HTTP_ENERGY_IMAX_TELEINFO, atoi(value));
}
if (getValueFromLabelIndex(LABEL_PMAX, value) ) {
WSContentSend_PD(HTTP_ENERGY_PMAX_TELEINFO, atoi(value));
}
if (tarif) {
GetTextIndexed(name, sizeof(name), tarif-1, kTarifName);
WSContentSend_PD(HTTP_ENERGY_TARIF_TELEINFO, name);
}
if (contrat && isousc) {
int percent = (int) ((Energy.current[0]*100.0f) / isousc) ;
GetTextIndexed(name, sizeof(name), contrat, kContratName);
WSContentSend_PD(HTTP_ENERGY_CONTRAT_TELEINFO, name, isousc);
WSContentSend_PD(HTTP_ENERGY_LOAD_TELEINFO, percent);
}
2021-04-06 01:23:06 +01:00
2020-08-14 18:47:13 +01:00
} else if (tinfo_mode==TINFO_MODE_STANDARD ) {
2021-04-06 01:23:06 +01:00
if (getValueFromLabelIndex(LABEL_EAST, value) ) {
GetTextIndexed(name, sizeof(name), LABEL_EAST, kLabel);
WSContentSend_PD(HTTP_ENERGY_INDEX_TELEINFO, name, value);
}
if (getValueFromLabelIndex(LABEL_EASF01, value) ) {
GetTextIndexed(name, sizeof(name), LABEL_EASF01, kLabel);
WSContentSend_PD(HTTP_ENERGY_INDEX_TELEINFO, name, value);
}
if (getValueFromLabelIndex(LABEL_EASF02, value) ) {
GetTextIndexed(name, sizeof(name), LABEL_EASF02, kLabel);
WSContentSend_PD(HTTP_ENERGY_INDEX_TELEINFO, name, value);
}
if (getValueFromLabelIndex(LABEL_SMAXSN, value) ) {
WSContentSend_PD(HTTP_ENERGY_PMAX_TELEINFO, atoi(value));
}
2021-04-07 12:23:31 +01:00
if (getValueFromLabelIndex(LABEL_LTARF, value) ) {
WSContentSend_PD(HTTP_ENERGY_TARIF_TELEINFO, value);
}
2021-04-07 12:23:31 +01:00
if (getValueFromLabelIndex(LABEL_NGTF, value) ) {
if (isousc) {
int percent = (int) ((Energy.current[0]*100.0f) / isousc) ;
2021-04-07 12:23:31 +01:00
WSContentSend_PD(HTTP_ENERGY_CONTRAT_TELEINFO, value, isousc);
WSContentSend_PD(HTTP_ENERGY_LOAD_TELEINFO, percent);
}
}
}
// Serial number ADCO or ADSC if found
if (*serialNumber) {
WSContentSend_PD(HTTP_ENERGY_ID_TELEINFO, serialNumber);
}
#endif // USE_WEBSERVER
}
}
/*********************************************************************************************\
* Interface
\*********************************************************************************************/
bool Xnrg15(uint8_t function)
{
2021-04-08 19:02:19 +01:00
bool result = false;
switch (function)
{
case FUNC_EVERY_250_MSECOND:
2021-02-10 20:08:59 +00:00
TInfoProcess();
break;
2021-04-08 19:02:19 +01:00
case FUNC_COMMAND:
AddLog(LOG_LEVEL_INFO, PSTR("TIC: FUNC_COMMAND"));
result = TInfoCmd();
break;
case FUNC_JSON_APPEND:
TInfoShow(1);
break;
#ifdef USE_WEBSERVER
case FUNC_WEB_SENSOR:
TInfoShow(0);
break;
#endif // USE_WEBSERVER
case FUNC_INIT:
TInfoInit();
break;
case FUNC_PRE_INIT:
TInfoDrvInit();
break;
}
2021-04-08 19:02:19 +01:00
return result;
}
#endif // USE_TELEINFO
2021-02-10 20:08:59 +00:00
#endif // USE_ENERGY_SENSOR