2020-06-12 15:31:23 +01:00
|
|
|
/*
|
|
|
|
xnrg_15_Teleinfo.ino - Teleinfo support for Tasmota
|
|
|
|
|
|
|
|
Copyright (C) 2020 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
|
2020-06-12 15:31:23 +01:00
|
|
|
* Source: http://hallard.me/category/tinfo/
|
|
|
|
*
|
2020-06-17 18:54:40 +01:00
|
|
|
* 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
|
|
|
*
|
2020-06-17 18:54:40 +01: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}
|
2020-08-10 16:43:07 +01:00
|
|
|
* {"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-06-12 15:31:23 +01:00
|
|
|
\*********************************************************************************************/
|
2020-10-28 16:32:07 +00:00
|
|
|
#define XNRG_15 15
|
2020-06-12 15:31:23 +01:00
|
|
|
|
|
|
|
#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
|
|
|
|
2020-08-11 18:58:24 +01:00
|
|
|
// 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.
|
2020-06-17 18:54:40 +01:00
|
|
|
CONTRAT_BBR, // BBRx => Option Tempo
|
|
|
|
CONTRAT_END
|
2020-06-14 21:04:19 +01:00
|
|
|
};
|
|
|
|
|
2020-08-11 18:58:24 +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 =
|
2020-06-17 18:54:40 +01:00
|
|
|
"|Base|Heures Creuses|EJP|Bleu Blanc Rouge"
|
|
|
|
;
|
|
|
|
|
2020-08-11 18:58:24 +01:00
|
|
|
// Received current contract value for legacy, standard mode has in clear text
|
2020-10-28 16:32:07 +00:00
|
|
|
const char kContratValue[] PROGMEM =
|
2020-06-17 18:54:40 +01:00
|
|
|
"|BASE|HC..|EJP.|BBR"
|
|
|
|
;
|
|
|
|
|
2020-08-11 18:58:24 +01:00
|
|
|
// 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,
|
2020-06-17 18:54:40 +01:00
|
|
|
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
|
|
|
};
|
|
|
|
|
2020-10-28 16:32:07 +00:00
|
|
|
// Received current tariff values
|
2020-08-11 18:58:24 +01:00
|
|
|
// for legacy, standard mode has in clear text
|
2020-10-28 16:32:07 +00:00
|
|
|
const char kTarifValue[] PROGMEM =
|
|
|
|
"|TH..|HC..|HP.."
|
|
|
|
"|HN..|PM.."
|
|
|
|
"|HCJB|HCJW|HCJR"
|
|
|
|
"|HPJB|HPJW|HPJR"
|
2020-06-17 18:54:40 +01:00
|
|
|
;
|
|
|
|
|
2020-08-11 18:58:24 +01:00
|
|
|
// tariff displayed name (for legacy, standard mode has in clear text)
|
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"
|
2020-06-17 18:54:40 +01:00
|
|
|
;
|
|
|
|
|
2020-08-11 18:58:24 +01:00
|
|
|
// Label used to do some post processing and/or calculation
|
2020-06-17 18:54:40 +01:00
|
|
|
enum TInfoLabel{
|
2020-10-28 16:32:07 +00:00
|
|
|
LABEL_BASE = 1,
|
2020-08-11 18:58:24 +01:00
|
|
|
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,
|
2020-06-17 18:54:40 +01:00
|
|
|
LABEL_DEMAIN,
|
|
|
|
LABEL_END
|
2020-06-14 23:52:49 +01:00
|
|
|
};
|
2020-06-12 15:31:23 +01:00
|
|
|
|
2020-10-28 16:32:07 +00:00
|
|
|
const char kLabel[] PROGMEM =
|
2020-08-11 18:58:24 +01:00
|
|
|
"|BASE|ADCO|ADSC"
|
|
|
|
"|HCHC|HCHP|EAST|EASF01|EASF02"
|
|
|
|
"|OPTARIF|NGTF|ISOUSC|PREF|PTEC|LTARF|NTARF"
|
|
|
|
"|PAPP|SINSTS|IINST|IRMS1|TENSION|URMS1"
|
|
|
|
"|IMAX|PMAX|SMAXSN"
|
2020-06-17 18:54:40 +01:00
|
|
|
"|DEMAIN"
|
|
|
|
;
|
|
|
|
|
2020-06-12 15:31:23 +01:00
|
|
|
TInfo tinfo; // Teleinfo object
|
|
|
|
TasmotaSerial *TInfoSerial = nullptr;
|
2020-08-11 18:58:24 +01:00
|
|
|
_Mode_e tinfo_mode = TINFO_MODE_HISTORIQUE;
|
2020-08-16 19:53:28 +01:00
|
|
|
char serialNumber[13] = ""; // Serial number is 12 char long
|
2020-06-12 15:31:23 +01:00
|
|
|
bool tinfo_found = false;
|
2020-08-10 16:43:07 +01:00
|
|
|
int contrat;
|
|
|
|
int tarif;
|
|
|
|
int isousc;
|
2020-06-12 15:31:23 +01:00
|
|
|
|
|
|
|
/*********************************************************************************************/
|
2020-06-14 21:04:19 +01:00
|
|
|
|
2020-06-17 18:54:40 +01:00
|
|
|
/* ======================================================================
|
2020-10-28 16:32:07 +00:00
|
|
|
Function: getValueFromLabelIndex
|
2020-06-17 18:54:40 +01:00
|
|
|
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-06-12 15:31:23 +01:00
|
|
|
/* ======================================================================
|
2020-10-28 16:32:07 +00:00
|
|
|
Function: ADPSCallback
|
2020-06-12 15:31:23 +01:00
|
|
|
Purpose : called by library when we detected a ADPS on any phased
|
2020-10-28 16:32:07 +00:00
|
|
|
Input : phase number
|
2020-06-12 15:31:23 +01:00
|
|
|
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 : -
|
2020-08-10 16:43:07 +01:00
|
|
|
Comments: should have been initialised with a
|
2020-06-12 15:31:23 +01:00
|
|
|
tinfo.attachADPSCallback(ADPSCallback())
|
|
|
|
====================================================================== */
|
|
|
|
void ADPSCallback(uint8_t phase)
|
|
|
|
{
|
2020-08-10 16:43:07 +01:00
|
|
|
// n = phase number 1 to 3
|
|
|
|
if (phase == 0){
|
|
|
|
phase = 1;
|
|
|
|
}
|
2020-08-11 18:58:24 +01:00
|
|
|
|
2020-08-16 19:53:28 +01:00
|
|
|
Response_P(PSTR("{"));
|
|
|
|
ResponseAppend_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"%s\":{\"ADPS\":%i}}"), serialNumber, phase );
|
|
|
|
ResponseJsonEnd();
|
|
|
|
|
|
|
|
// Publish adding ADCO serial number into the topic
|
|
|
|
MqttPublishPrefixTopic_P(RESULT_OR_TELE, serialNumber, false);
|
2020-08-10 16:43:07 +01:00
|
|
|
|
|
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR("ADPS on phase %d"), phase);
|
2020-06-12 15:31:23 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/* ======================================================================
|
2020-10-28 16:32:07 +00:00
|
|
|
Function: DataCallback
|
2020-06-12 15:31:23 +01:00
|
|
|
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 : -
|
2020-06-12 15:31:23 +01:00
|
|
|
Comments: -
|
|
|
|
====================================================================== */
|
|
|
|
void DataCallback(struct _ValueList * me, uint8_t flags)
|
|
|
|
{
|
2020-06-14 21:04:19 +01:00
|
|
|
char c = ' ';
|
2020-06-17 18:54:40 +01:00
|
|
|
int ilabel ;
|
2020-06-14 21:04:19 +01:00
|
|
|
|
|
|
|
// Does this value is new or changed?
|
2020-06-17 18:54:40 +01:00
|
|
|
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)) {
|
2020-06-17 18:54:40 +01:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2020-06-12 15:31:23 +01:00
|
|
|
|
2020-08-16 19:53:28 +01:00
|
|
|
if (flags & TINFO_FLAGS_ADDED) { c = '#'; }
|
|
|
|
if (flags & TINFO_FLAGS_UPDATED) { c = '*'; }
|
|
|
|
AddLog_P2(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)) {
|
2020-08-16 19:53:28 +01:00
|
|
|
break;
|
|
|
|
}
|
2020-06-17 18:54:40 +01:00
|
|
|
}
|
2020-08-16 19:53:28 +01:00
|
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TIC: Tarif changed, now '%s' (%d)"), me->value, tarif);
|
2020-10-28 16:32:07 +00:00
|
|
|
}
|
2020-08-16 19:53:28 +01:00
|
|
|
|
|
|
|
// Current tariff (standard is in clear text in value)
|
|
|
|
else if (ilabel == LABEL_LTARF)
|
|
|
|
{
|
|
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TIC: Tarif name changed, now '%s'"), me->value);
|
2020-10-28 16:32:07 +00:00
|
|
|
}
|
2020-08-16 19:53:28 +01:00
|
|
|
// Current tariff (standard index is is in clear text in value)
|
|
|
|
else if (ilabel == LABEL_NTARF)
|
|
|
|
{
|
|
|
|
tarif = atoi(me->value);
|
|
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TIC: Tarif index changed, now '%d'"), tarif);
|
2020-10-28 16:32:07 +00:00
|
|
|
}
|
2020-08-16 19:53:28 +01: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);
|
|
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TIC: Voltage %s, now %d"), me->value, (int) Energy.voltage[0]);
|
2020-06-17 18:54:40 +01:00
|
|
|
}
|
|
|
|
|
2020-08-16 19:53:28 +01:00
|
|
|
// Current I
|
|
|
|
else if (ilabel == LABEL_IINST || ilabel == LABEL_IRMS1)
|
|
|
|
{
|
|
|
|
Energy.current[0] = (float) atoi(me->value);
|
|
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TIC: Current %s, now %d"), me->value, (int) Energy.current[0]);
|
|
|
|
}
|
2020-06-14 23:52:49 +01:00
|
|
|
|
2020-08-16 19:53:28 +01:00
|
|
|
// Power P
|
|
|
|
else if (ilabel == LABEL_PAPP || ilabel == LABEL_SINSTS)
|
|
|
|
{
|
|
|
|
Energy.active_power[0] = (float) atoi(me->value);;
|
|
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TIC: Power %s, now %d"), me->value, (int) Energy.active_power[0]);
|
|
|
|
}
|
2020-06-17 18:54:40 +01:00
|
|
|
|
2020-08-16 19:53:28 +01:00
|
|
|
// Wh indexes (legacy)
|
2020-08-21 11:32:27 +01:00
|
|
|
else if ( ilabel == LABEL_HCHC || ilabel == LABEL_HCHP || ilabel == LABEL_BASE)
|
2020-08-16 19:53:28 +01:00
|
|
|
{
|
|
|
|
char value[32];
|
|
|
|
uint32_t hc = 0;
|
|
|
|
uint32_t hp = 0;
|
|
|
|
uint32_t total = 0;
|
2020-06-17 18:54:40 +01:00
|
|
|
|
2020-08-21 11:32:27 +01:00
|
|
|
// Base, un seul index
|
|
|
|
if (ilabel == LABEL_BASE) {
|
|
|
|
total = atoi(me->value);
|
2020-08-23 10:17:33 +01:00
|
|
|
AddLog_P2(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;
|
|
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TIC: HC:%u HP:%u Total:%u"), hc, hp, total);
|
|
|
|
}
|
2020-08-14 18:47:13 +01:00
|
|
|
|
2020-10-28 16:32:07 +00:00
|
|
|
if (!Settings.flag4.teleinfo_rawdata) {
|
|
|
|
EnergyUpdateTotal(total/1000.0f, true);
|
2020-08-16 19:53:28 +01:00
|
|
|
}
|
2020-08-14 18:47:13 +01:00
|
|
|
}
|
2020-06-17 18:54:40 +01:00
|
|
|
|
2020-10-28 16:32:07 +00:00
|
|
|
// Wh total index (standard)
|
2020-08-16 19:53:28 +01:00
|
|
|
else if ( ilabel == LABEL_EAST)
|
|
|
|
{
|
|
|
|
uint32_t total = atoi(me->value);
|
2020-10-28 16:32:07 +00:00
|
|
|
if (!Settings.flag4.teleinfo_rawdata) {
|
|
|
|
EnergyUpdateTotal(total/1000.0f, true);
|
2020-08-16 19:53:28 +01:00
|
|
|
}
|
|
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TIC: Total:%uWh"), total);
|
2020-08-14 18:47:13 +01:00
|
|
|
}
|
2020-08-11 18:58:24 +01:00
|
|
|
|
2020-08-16 19:53:28 +01:00
|
|
|
// Wh indexes (standard)
|
|
|
|
else if ( ilabel == LABEL_EASF01)
|
|
|
|
{
|
|
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TIC: HC:%u"), atoi(me->value));
|
|
|
|
}
|
|
|
|
else if ( ilabel == LABEL_EASF02)
|
|
|
|
{
|
|
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TIC: HP:%u"), atoi(me->value));
|
|
|
|
}
|
2020-08-11 18:58:24 +01:00
|
|
|
|
2020-08-16 19:53:28 +01:00
|
|
|
// 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)) {
|
2020-08-16 19:53:28 +01:00
|
|
|
break;
|
|
|
|
}
|
2020-06-17 18:54:40 +01:00
|
|
|
}
|
2020-08-16 19:53:28 +01:00
|
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TIC: Contract changed, now '%s' (%d)"), me->value, contrat);
|
2020-10-28 16:32:07 +00:00
|
|
|
}
|
2020-08-16 19:53:28 +01:00
|
|
|
// Contract subscribed (standard is in clear text in value)
|
|
|
|
else if (ilabel == LABEL_NGTF)
|
|
|
|
{
|
|
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TIC: Contract changed, now '%s'"), me->value);
|
2020-10-28 16:32:07 +00:00
|
|
|
}
|
2020-08-16 19:53:28 +01:00
|
|
|
|
|
|
|
// Contract subscribed (Power)
|
|
|
|
else if (ilabel == LABEL_ISOUSC || ilabel == LABEL_PREF)
|
|
|
|
{
|
|
|
|
isousc = atoi( me->value);
|
|
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TIC: ISousc set to %d"), isousc);
|
2020-10-28 16:32:07 +00:00
|
|
|
}
|
2020-08-16 19:53:28 +01:00
|
|
|
|
|
|
|
// Serial Number of device
|
|
|
|
else if (ilabel == LABEL_ADCO || ilabel == LABEL_ADSC)
|
|
|
|
{
|
|
|
|
strcpy(serialNumber, me->value);
|
|
|
|
AddLog_P2(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-08-16 19:53:28 +01:00
|
|
|
}
|
2020-06-14 21:04:19 +01:00
|
|
|
}
|
2020-06-17 18:54:40 +01:00
|
|
|
|
2020-10-28 16:32:07 +00:00
|
|
|
|
2020-08-16 19:53:28 +01:00
|
|
|
|
|
|
|
|
2020-06-12 15:31:23 +01:00
|
|
|
}
|
|
|
|
|
2020-06-14 23:52:49 +01:00
|
|
|
/* ======================================================================
|
2020-10-28 16:32:07 +00:00
|
|
|
Function: responseDumpTInfo
|
2020-08-16 19:53:28 +01:00
|
|
|
Purpose : add teleinfo values into JSON response
|
2020-08-23 10:17:33 +01:00
|
|
|
Input : 1st separator space if begining of JSON, else comma
|
2020-10-28 16:32:07 +00:00
|
|
|
Output : -
|
2020-06-14 23:52:49 +01:00
|
|
|
Comments: -
|
|
|
|
====================================================================== */
|
2020-08-23 10:17:33 +01:00
|
|
|
void ResponseAppendTInfo(char sep)
|
2020-06-14 23:52:49 +01:00
|
|
|
{
|
2020-08-16 19:53:28 +01:00
|
|
|
struct _ValueList * me = tinfo.getList();
|
2020-08-14 18:47:13 +01:00
|
|
|
|
2020-08-16 19:53:28 +01:00
|
|
|
char * p ;
|
|
|
|
boolean isNumber ;
|
2020-08-14 18:47:13 +01:00
|
|
|
|
2020-08-16 19:53:28 +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
|
|
|
|
2020-08-16 19:53:28 +01:00
|
|
|
if (me->name && me->value && *me->name && *me->value) {
|
|
|
|
isNumber = true;
|
|
|
|
p = me->value;
|
2020-08-14 18:47:13 +01:00
|
|
|
|
2020-08-16 19:53:28 +01:00
|
|
|
// Specific treatment serial number don't convert to number later
|
|
|
|
if (strcmp(me->name, "ADCO")==0 || strcmp(me->name, "ADSC")==0) {
|
|
|
|
isNumber = false;
|
|
|
|
} else {
|
2020-08-14 18:47:13 +01:00
|
|
|
// check if value is number
|
|
|
|
while (*p && isNumber) {
|
|
|
|
if ( *p < '0' || *p > '9' ) {
|
|
|
|
isNumber = false;
|
|
|
|
}
|
|
|
|
p++;
|
|
|
|
}
|
2020-08-16 19:53:28 +01:00
|
|
|
}
|
2020-08-14 18:47:13 +01:00
|
|
|
|
2020-08-16 19:53:28 +01:00
|
|
|
ResponseAppend_P( PSTR("%c\"%s\":"), sep, me->name );
|
2020-08-14 18:47:13 +01:00
|
|
|
|
2020-08-18 08:02:27 +01:00
|
|
|
if (!isNumber) {
|
2020-08-16 19:53:28 +01:00
|
|
|
ResponseAppend_P( PSTR("\"%s\""), me->value );
|
|
|
|
} else {
|
|
|
|
ResponseAppend_P( PSTR("%d"), atoi(me->value));
|
2020-08-14 18:47:13 +01:00
|
|
|
}
|
|
|
|
|
2020-10-28 16:32:07 +00:00
|
|
|
// Now JSON separator is needed
|
2020-08-16 19:53:28 +01:00
|
|
|
sep =',';
|
|
|
|
}
|
2020-08-14 18:47:13 +01:00
|
|
|
}
|
2020-08-16 19:53:28 +01:00
|
|
|
}
|
2020-08-14 18:47:13 +01:00
|
|
|
|
2020-08-16 19:53:28 +01:00
|
|
|
/* ======================================================================
|
2020-10-28 16:32:07 +00:00
|
|
|
Function: NewFrameCallback
|
2020-08-16 19:53:28 +01:00
|
|
|
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 : -
|
2020-08-16 19:53:28 +01:00
|
|
|
Comments: -
|
|
|
|
====================================================================== */
|
|
|
|
void NewFrameCallback(struct _ValueList * me)
|
|
|
|
{
|
|
|
|
// Reset Energy Watchdog
|
|
|
|
Energy.data_valid[0] = 0;
|
2020-08-14 18:47:13 +01:00
|
|
|
|
2020-08-16 19:53:28 +01:00
|
|
|
// send teleinfo full frame only if setup like that
|
|
|
|
// see setOption108
|
2020-10-28 16:32:07 +00:00
|
|
|
if (Settings.flag4.teleinfo_rawdata) {
|
2020-08-16 19:53:28 +01:00
|
|
|
Response_P(PSTR("{"));
|
2020-08-23 10:17:33 +01:00
|
|
|
ResponseAppendTInfo(' ');
|
2020-08-16 19:53:28 +01:00
|
|
|
ResponseJsonEnd();
|
|
|
|
// Publish adding ADCO serial number into the topic
|
|
|
|
// Need setOption4 to be enabled
|
|
|
|
MqttPublishPrefixTopic_P(RESULT_OR_TELE, serialNumber, false);
|
|
|
|
}
|
2020-06-14 23:52:49 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/* ======================================================================
|
2020-10-28 16:32:07 +00:00
|
|
|
Function: TInfoDrvInit
|
|
|
|
Purpose : Tasmota core driver init
|
2020-06-17 18:54:40 +01:00
|
|
|
Input : -
|
2020-10-28 16:32:07 +00:00
|
|
|
Output : -
|
2020-06-14 23:52:49 +01:00
|
|
|
Comments: -
|
|
|
|
====================================================================== */
|
2020-06-12 15:31:23 +01:00
|
|
|
void TInfoDrvInit(void) {
|
|
|
|
if (PinUsed(GPIO_TELEINFO_RX)) {
|
2020-08-14 18:47:13 +01:00
|
|
|
energy_flg = XNRG_15;
|
|
|
|
Energy.voltage_available = false;
|
2020-06-12 15:31:23 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-17 18:54:40 +01:00
|
|
|
/* ======================================================================
|
2020-10-28 16:32:07 +00:00
|
|
|
Function: TInfoInit
|
|
|
|
Purpose : Tasmota core device init
|
2020-06-17 18:54:40 +01:00
|
|
|
Input : -
|
2020-10-28 16:32:07 +00:00
|
|
|
Output : -
|
2020-06-17 18:54:40 +01:00
|
|
|
Comments: -
|
|
|
|
====================================================================== */
|
2020-06-12 15:31:23 +01:00
|
|
|
void TInfoInit(void)
|
|
|
|
{
|
2020-08-11 18:58:24 +01:00
|
|
|
int baudrate;
|
|
|
|
|
|
|
|
// SetOption102 - Set Baud rate for Teleinfo serial communication (0 = 1200 or 1 = 9600)
|
2020-10-28 16:32:07 +00:00
|
|
|
if (Settings.flag4.teleinfo_baudrate) {
|
|
|
|
baudrate = 9600;
|
2020-08-11 18:58:24 +01:00
|
|
|
tinfo_mode = TINFO_MODE_STANDARD;
|
|
|
|
} else {
|
2020-10-28 16:32:07 +00:00
|
|
|
baudrate = 1200;
|
2020-08-11 18:58:24 +01:00
|
|
|
tinfo_mode = TINFO_MODE_HISTORIQUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TIC: inferface speed %d bps"),baudrate);
|
2020-10-28 16:32:07 +00:00
|
|
|
|
2020-06-17 18:54:40 +01:00
|
|
|
if (PinUsed(GPIO_TELEINFO_RX)) {
|
|
|
|
uint8_t rx_pin = Pin(GPIO_TELEINFO_RX);
|
|
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR("TIC: RX on GPIO%d"), rx_pin);
|
|
|
|
|
|
|
|
// Enable Teleinfo pin used, control it
|
|
|
|
if (PinUsed(GPIO_TELEINFO_ENABLE)) {
|
|
|
|
uint8_t en_pin = Pin(GPIO_TELEINFO_ENABLE);
|
2020-10-28 16:32:07 +00:00
|
|
|
pinMode(en_pin, OUTPUT);
|
2020-06-17 18:54:40 +01:00
|
|
|
digitalWrite(en_pin, HIGH);
|
|
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR("TIC: Enable with GPIO%d"), en_pin);
|
|
|
|
} else {
|
2020-06-12 15:31:23 +01:00
|
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR("TIC: always enabled"));
|
|
|
|
}
|
|
|
|
|
2020-08-11 18:58:24 +01:00
|
|
|
#if defined (ESP8266)
|
|
|
|
// Allow GPIO3 AND GPIO13 with hardware fallback to 2
|
|
|
|
TInfoSerial = new TasmotaSerial(rx_pin, -1, 2);
|
2020-08-10 16:43:07 +01:00
|
|
|
//pinMode(rx_pin, INPUT_PULLUP);
|
2020-08-11 18:58:24 +01:00
|
|
|
#else
|
|
|
|
TInfoSerial = new TasmotaSerial(rx_pin, -1, 1);
|
|
|
|
#endif
|
2020-06-12 15:31:23 +01:00
|
|
|
|
2020-06-17 18:54:40 +01:00
|
|
|
// 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
|
2020-08-11 18:58:24 +01:00
|
|
|
if (TInfoSerial->begin(baudrate)) {
|
2020-08-10 16:43:07 +01:00
|
|
|
|
2020-06-17 18:54:40 +01:00
|
|
|
|
|
|
|
#if defined (ESP8266)
|
|
|
|
if (TInfoSerial->hardwareSerial() ) {
|
2020-06-12 15:31:23 +01:00
|
|
|
ClaimSerial();
|
2020-08-10 16:43:07 +01:00
|
|
|
|
|
|
|
// 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
|
2020-08-10 16:43:07 +01:00
|
|
|
// working only on Serial RX pin (Hardware Serial) for now
|
2020-10-28 16:32:07 +00:00
|
|
|
|
2020-08-23 10:17:33 +01:00
|
|
|
//SetSerialConfig(TS_SERIAL_7E1);
|
|
|
|
//TInfoSerial->setTimeout(TINFO_READ_TIMEOUT);
|
2020-08-10 16:43:07 +01:00
|
|
|
|
2020-06-17 18:54:40 +01:00
|
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR("TIC: using hardware serial"));
|
|
|
|
} else {
|
|
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR("TIC: using software serial"));
|
2020-06-14 21:04:19 +01:00
|
|
|
}
|
2020-06-12 15:31:23 +01:00
|
|
|
|
2020-06-17 18:54:40 +01:00
|
|
|
#elif defined (ESP32)
|
|
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR("TIC: using ESP32 hardware serial"));
|
|
|
|
#endif
|
2020-06-12 15:31:23 +01:00
|
|
|
// Init teleinfo
|
2020-08-11 18:58:24 +01:00
|
|
|
tinfo.init(tinfo_mode);
|
2020-06-12 15:31:23 +01:00
|
|
|
// Attach needed callbacks
|
|
|
|
tinfo.attachADPS(ADPSCallback);
|
2020-10-28 16:32:07 +00:00
|
|
|
tinfo.attachData(DataCallback);
|
|
|
|
tinfo.attachNewFrame(NewFrameCallback);
|
2020-06-12 15:31:23 +01:00
|
|
|
tinfo_found = true;
|
2020-08-10 16:43:07 +01:00
|
|
|
|
2020-06-12 15:31:23 +01:00
|
|
|
AddLog_P2(LOG_LEVEL_INFO, PSTR("TIC: Ready"));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-17 18:54:40 +01:00
|
|
|
/* ======================================================================
|
2020-10-28 16:32:07 +00:00
|
|
|
Function: TInfoEvery250ms
|
2020-06-17 18:54:40 +01:00
|
|
|
Purpose : Tasmota callback executed every 250ms
|
|
|
|
Input : -
|
2020-10-28 16:32:07 +00:00
|
|
|
Output : -
|
2020-06-17 18:54:40 +01:00
|
|
|
Comments: -
|
|
|
|
====================================================================== */
|
|
|
|
void TInfoEvery250ms(void)
|
2020-06-12 15:31:23 +01:00
|
|
|
{
|
2020-08-14 18:47:13 +01:00
|
|
|
if (!tinfo_found) {
|
2020-06-17 18:54:40 +01:00
|
|
|
return;
|
2020-08-14 18:47:13 +01:00
|
|
|
}
|
2020-06-17 18:54:40 +01:00
|
|
|
|
|
|
|
if (TInfoSerial->available()) {
|
2020-08-14 18:47:13 +01:00
|
|
|
unsigned long start = millis();
|
|
|
|
char c;
|
|
|
|
|
|
|
|
// We received some data, process but never more than 100ms ?
|
|
|
|
while (TInfoSerial->available()>8 && millis()-start < 100) {
|
2020-06-14 21:04:19 +01:00
|
|
|
// get char
|
|
|
|
c = TInfoSerial->read();
|
|
|
|
// data processing
|
2020-08-23 10:17:33 +01:00
|
|
|
tinfo.process(c & 0x7F);
|
2020-06-17 18:54:40 +01:00
|
|
|
}
|
|
|
|
}
|
2020-06-12 15:31:23 +01:00
|
|
|
}
|
|
|
|
|
2020-06-17 18:54:40 +01:00
|
|
|
/* ======================================================================
|
2020-10-28 16:32:07 +00:00
|
|
|
Function: TInfoShow
|
2020-06-17 18:54:40 +01:00
|
|
|
Purpose : Tasmota callback executed to send telemetry or WEB display
|
|
|
|
Input : -
|
2020-10-28 16:32:07 +00:00
|
|
|
Output : -
|
2020-06-17 18:54:40 +01:00
|
|
|
Comments: -
|
|
|
|
====================================================================== */
|
2020-06-14 23:52:49 +01:00
|
|
|
#ifdef USE_WEBSERVER
|
2020-08-16 19:53:28 +01:00
|
|
|
const char HTTP_ENERGY_ID_TELEINFO[] PROGMEM = "{s}ID{m}%s{e}" ;
|
2020-06-17 18:54:40 +01:00
|
|
|
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}" ;
|
2020-08-10 16:43:07 +01:00
|
|
|
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
|
|
|
|
|
2020-06-12 15:31:23 +01:00
|
|
|
void TInfoShow(bool json)
|
|
|
|
{
|
2020-10-28 16:32:07 +00:00
|
|
|
// Since it's an Energy device , current, voltage and power are
|
2020-06-17 18:54:40 +01:00
|
|
|
// already present on the telemetry frame. No need to add here
|
2020-08-10 16:43:07 +01:00
|
|
|
// Just add the raw label/values of the teleinfo frame
|
2020-06-17 18:54:40 +01:00
|
|
|
if (json)
|
|
|
|
{
|
2020-08-10 16:43:07 +01:00
|
|
|
// 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
|
|
|
|
2020-08-14 18:47:13 +01:00
|
|
|
// add teleinfo full frame only if no teleinfo raw data setup
|
2020-10-28 16:32:07 +00:00
|
|
|
if (!Settings.flag4.teleinfo_rawdata) {
|
2020-08-23 10:17:33 +01:00
|
|
|
ResponseAppendTInfo(',');
|
2020-06-18 16:12:23 +01:00
|
|
|
}
|
|
|
|
|
2020-08-10 16:43:07 +01:00
|
|
|
|
2020-06-12 15:31:23 +01:00
|
|
|
#ifdef USE_WEBSERVER
|
2020-06-17 18:54:40 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-08-16 19:53:28 +01:00
|
|
|
char name[32];
|
|
|
|
char value[32];
|
|
|
|
|
2020-08-21 11:32:27 +01:00
|
|
|
if (getValueFromLabelIndex(LABEL_BASE, value) ) {
|
|
|
|
GetTextIndexed(name, sizeof(name), LABEL_BASE, kLabel);
|
|
|
|
WSContentSend_PD(HTTP_ENERGY_INDEX_TELEINFO, name, value);
|
|
|
|
}
|
2020-06-17 18:54:40 +01:00
|
|
|
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);
|
|
|
|
}
|
2020-08-10 16:43:07 +01:00
|
|
|
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));
|
|
|
|
}
|
|
|
|
|
2020-08-14 18:47:13 +01:00
|
|
|
if (tinfo_mode==TINFO_MODE_HISTORIQUE ) {
|
2020-08-11 18:58:24 +01:00
|
|
|
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);
|
|
|
|
}
|
2020-08-14 18:47:13 +01:00
|
|
|
} else if (tinfo_mode==TINFO_MODE_STANDARD ) {
|
2020-08-11 18:58:24 +01:00
|
|
|
if (getValueFromLabelIndex(LABEL_LTARF, name) ) {
|
|
|
|
WSContentSend_PD(HTTP_ENERGY_TARIF_TELEINFO, name);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (getValueFromLabelIndex(LABEL_NGTF, name) ) {
|
|
|
|
if (isousc) {
|
|
|
|
int percent = (int) ((Energy.current[0]*100.0f) / isousc) ;
|
|
|
|
WSContentSend_PD(HTTP_ENERGY_CONTRAT_TELEINFO, name, isousc);
|
|
|
|
WSContentSend_PD(HTTP_ENERGY_LOAD_TELEINFO, percent);
|
|
|
|
}
|
|
|
|
}
|
2020-06-17 18:54:40 +01:00
|
|
|
}
|
2020-08-16 19:53:28 +01:00
|
|
|
|
2020-08-23 10:17:33 +01:00
|
|
|
// Serial number ADCO or ADSC if found
|
|
|
|
if (*serialNumber) {
|
|
|
|
WSContentSend_PD(HTTP_ENERGY_ID_TELEINFO, serialNumber);
|
|
|
|
}
|
2020-08-16 19:53:28 +01:00
|
|
|
|
2020-06-12 15:31:23 +01:00
|
|
|
#endif // USE_WEBSERVER
|
2020-06-17 18:54:40 +01:00
|
|
|
}
|
2020-06-12 15:31:23 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/*********************************************************************************************\
|
|
|
|
* Interface
|
|
|
|
\*********************************************************************************************/
|
|
|
|
bool Xnrg15(uint8_t function)
|
|
|
|
{
|
2020-06-17 18:54:40 +01:00
|
|
|
switch (function)
|
|
|
|
{
|
|
|
|
case FUNC_EVERY_250_MSECOND:
|
2020-10-28 16:32:07 +00:00
|
|
|
if (TasmotaGlobal.uptime > 4) { TInfoEvery250ms(); }
|
2020-06-17 18:54:40 +01:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
return false;
|
2020-06-12 15:31:23 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
#endif // USE_TELEINFO
|
2020-06-17 18:54:40 +01:00
|
|
|
#endif // USE_ENERGY_SENSOR
|