Compare commits

..

12 Commits

10 changed files with 389 additions and 187 deletions

1
.gitignore vendored
View File

@ -1,5 +1,6 @@
#specific files #specific files
secret.h secret.h
build/
# ---> C++ # ---> C++
# Prerequisites # Prerequisites

8
.theia/launch.json Normal file
View File

@ -0,0 +1,8 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
"version": "0.2.0",
"configurations": [
]
}

View File

@ -172,6 +172,14 @@ Les étiquettes sont celles définies dans le document ENEDIS.
L'objectif initial de cette liste est de n'inclure dans le JSON que les informations pertinentes, permettant ainsi de gagner quelques millisecondes de traitement, quelques kilo-octets de données et quelques milliwatts de consommation. Bien que le JSON puisse contenir toutes les informations lues, cela n'est pas nécessairement utile pour un utilisateur standard, car jusqu'à 50 % des champs peuvent être vides ou sans réelle utilité domotique. L'objectif initial de cette liste est de n'inclure dans le JSON que les informations pertinentes, permettant ainsi de gagner quelques millisecondes de traitement, quelques kilo-octets de données et quelques milliwatts de consommation. Bien que le JSON puisse contenir toutes les informations lues, cela n'est pas nécessairement utile pour un utilisateur standard, car jusqu'à 50 % des champs peuvent être vides ou sans réelle utilité domotique.
En résumé, vous pouvez adapter cette liste selon vos besoins. En résumé, vous pouvez adapter cette liste selon vos besoins.
## Mode sleep
L'ESP est mis en mode deep sleep, via la fonction goToDeepSleep, pendant un temps 'sleepDuration' configurable. L'ESP8266 se réveille ainsi ériodiquement pour vérifier s'il y a des requêtes en attente.
Cette approche permet de réduire la consommation d'énergie en mettant l'ESP8266 en veille lorsqu'il n'est pas utilisé.
Attention à bien paramétrer le 'timeout' et le 'scan_interval' dans [Home Assistant](https://www.home-assistant.io/integrations/rest#timeout) ainsi que le 'sleepDuration' pour ne pas avoir de timeout intempestif.
La configuration par défaut (5 secondes) ne demande pas de modification du 'timeout' de Home Assistant (10 secondes par défault).
La mise en place du deep sleep occasione un délai de réponse (ici, 5 secondes au max), faisant apparaître un 'jitter'. J'estime qu'une valeur 'sleepDuration' entre 10 et 20% du scan_interval est approprié (sachant que le refresh du Linky est de l'ordre de la seconde).

BIN
docs/Enedis-NOI-CPT_54E.pdf Normal file

Binary file not shown.

View File

@ -1,22 +1,43 @@
#include "serial.h" #include "serial.h"
#include "secret.h" #include "secret.h"
#include "tic.h" #include "tic.h"
#include "ota.h"
#include <Arduino.h> #include <Arduino.h>
#include <ESP8266WiFi.h> #include <ESP8266WiFi.h>
#include <WiFiClient.h> #include <WiFiClient.h>
#include <ESP8266WebServer.h> #include <ESP8266WebServer.h>
#include <ElegantOTA.h>
#include <PubSubClient.h>
// Activer le Wi-Fi // Activer le Wi-Fi
#define WIFI_ENABLE #define WIFI_ENABLE
// Initialiser le serveur web si le Wi-Fi est activé // Initialiser le serveur web si le Wi-Fi est activé
#ifdef WIFI_ENABLE
ESP8266WebServer server(HTTP_PORT); ESP8266WebServer server(HTTP_PORT);
#endif WiFiClient espClient;
PubSubClient mqttclient(espClient);
// Durée de sommeil en microsecondes (par exemple, 5 secondes) unsigned long previousMillis = 0;
const int sleepDuration = 5 * 1000000; unsigned long previousForceMillis = 0;
const long interval = 1000; //interval in ms
void mqttConnect() {
// Loop until we're reconnected
while (!mqttclient.connected()) {
// Create a random client ID
String clientId = MQTT_CLIENTID;
clientId += String(random(0xffff), HEX);
// Attempt to connect
if (mqttclient.connect(clientId.c_str(), MQTT_USERNAME, MQTT_PASSWORD)) {
// Once connected, publish an announcement...
mqttclient.publish(MQTT_TOPIC, "MQTT TIC interface online");
} else {
delay(1000);
}
}
}
// Fonction pour configurer et connecter au réseau Wi-Fi // Fonction pour configurer et connecter au réseau Wi-Fi
void setup_wifi() { void setup_wifi() {
@ -24,10 +45,10 @@ void setup_wifi() {
// Connexion au réseau Wi-Fi // Connexion au réseau Wi-Fi
DebugPort.println(); DebugPort.println();
DebugPort.print("Connecting to "); DebugPort.print("Connecting to ");
DebugPort.println(ssid); DebugPort.println(STASSID);
WiFi.mode(WIFI_STA); WiFi.mode(WIFI_STA);
WiFi.begin(ssid, passPhrase); WiFi.begin(STASSID, STAPSK);
int c = 0; int c = 0;
// Attendre la connexion Wi-Fi // Attendre la connexion Wi-Fi
@ -114,12 +135,6 @@ void setup_serial() {
#endif #endif
} }
// Fonction pour mettre l'ESP8266 en mode deep sleep
void goToDeepSleep() {
DebugPort.println("Going to deep sleep...");
ESP.deepSleep(sleepDuration);
}
// Fonction d'initialisation principale // Fonction d'initialisation principale
void setup() { void setup() {
@ -130,20 +145,61 @@ void setup() {
// Configurer les routes du serveur // Configurer les routes du serveur
restServerRouting(); restServerRouting();
ElegantOTA.begin(&server);
// ElegantOTA callbacks
ElegantOTA.onStart(onOTAStart);
ElegantOTA.onProgress(onOTAProgress);
ElegantOTA.onEnd(onOTAEnd);
// Démarrer le serveur HTTP // Démarrer le serveur HTTP
DebugPort.println("Start HTTP server"); DebugPort.println("Start HTTP server");
server.begin(); server.begin();
DebugPort.println("HTTP server started"); DebugPort.println("HTTP server started");
//Démarrer le serveur MQTT
mqttclient.setServer(MQTT_SERVER, MQTT_PORT);
#endif #endif
} }
// Boucle principale // Boucle principale
void loop() { void loop() {
ElegantOTA.loop();
server.handleClient(); server.handleClient();
readTicPort(); readTicPort();
unsigned long currentMillis = millis();
if (currentMillis - previousMillis >= interval) {
// save the last time you blinked the LED
previousMillis = currentMillis;
//Interface MQTT
if (!mqttclient.connected()) {
mqttConnect();
}
mqttPublish(&mqttclient);
}
if (currentMillis - previousForceMillis >= (30 * interval)) {
// save the last time you blinked the LED
previousForceMillis = currentMillis;
//Interface MQTT
if (!mqttclient.connected()) {
mqttConnect();
}
mqttForcePublish(&mqttclient);
}
mqttclient.loop();
/*
// Si aucune requête n'est en cours, mettre l'ESP8266 en mode deep sleep // Si aucune requête n'est en cours, mettre l'ESP8266 en mode deep sleep
if (server.client().available() == 0) { if (server.client().available() == 0) {
goToDeepSleep(); goToDeepSleep();
} }*/
} }

32
ota.cpp Normal file
View File

@ -0,0 +1,32 @@
#include "ota.h"
#include "serial.h"
#include <Arduino.h>
#include <stddef.h>
unsigned long ota_progress_millis = 0;
void onOTAStart() {
// Log when OTA has started
DebugPort.println("OTA update started!");
// <Add your own code here>
}
void onOTAProgress(size_t current, size_t final) {
// Log every 1 second
if (millis() - ota_progress_millis > 1000) {
ota_progress_millis = millis();
DebugPort.printf("OTA Progress Current: %u bytes, Final: %u bytes\n", current, final);
}
}
void onOTAEnd(bool success) {
// Log when OTA has finished
if (success) {
DebugPort.println("OTA update finished successfully!");
} else {
DebugPort.println("There was an error during OTA update!");
}
// <Add your own code here>
}

12
ota.h Normal file
View File

@ -0,0 +1,12 @@
#ifndef OTA
#define OTA
#include <stddef.h>
void onOTAStart();
void onOTAProgress(size_t current, size_t final);
void onOTAEnd(bool success);
#endif

View File

@ -16,3 +16,11 @@
const char *ssid = STASSID; const char *ssid = STASSID;
const char *passPhrase = STAPSK; const char *passPhrase = STAPSK;
// MQTT Broker settings
const char *mqtt_broker = "broker.emqx.io"; // EMQX broker endpoint
const char *mqtt_topic = "emqx/esp8266/led"; // MQTT topic
const char *mqtt_username = "emqx"; // MQTT username for authentication
const char *mqtt_password = "public"; // MQTT password for authentication
const int mqtt_port = 1883; // MQTT port (TCP)
const char *mqtt_clientId = "tic_client-"

306
tic.cpp
View File

@ -1,9 +1,9 @@
#include "lwip/ip.h" #include "lwip/ip.h"
#include "tic.h" #include "tic.h"
#include "serial.h" #include "serial.h"
#include "secret.h"
#include <Arduino.h> #include <Arduino.h>
#include <PubSubClient.h>
// #define DEBUG 1
struct GroupDetail TicValues[NB_ETIQUETTE] = {}; struct GroupDetail TicValues[NB_ETIQUETTE] = {};
@ -19,32 +19,72 @@ RelaisStatus relaisStatus; // definition du relais status
Action actionJp1[11]; // actions définie pour jour +1 Action actionJp1[11]; // actions définie pour jour +1
int nbActions; int nbActions;
static struct GroupDetail processGroup(String group) /**
{ * Calculates the checksum for a given data string according to ENEDIS specifications.
* Supports standard data mode (not history mode). [well to be honest, who has interest to keep the history mode.]
* Group format in standard mode with timestamp (horodatage) - Last HT included in checksum
* LF etiquette HT horodatage HT donnee HT Chk CR
* 0A 09 09 09 0D
* \____________checkum_______________/
* Group format in standard mode without timestamp (horodatage) - Last HT included in checksum
* LF etiquette HT donnee HT Chk CR
* 0A 09 09 0D
* \_____checkum________/
*
*
* @param data The input string for which the checksum is to be calculated.
* @return The calculated checksum as an unsigned char.
*/
unsigned char calcCheckSum(const String &data) {
unsigned int sum = 0;
// Calculate the sum of ASCII values, excluding the checksum character
// The string does not contain the CR char. The char before CR is the checksum.
for (size_t i = 0; i < data.length() - 1; ++i) {
sum += data[i];
}
// Truncate the sum to 6 bits
sum &= 0x3F;
// Add 0x20 to get the final checksum
return (unsigned char)sum + 0x20;
}
static struct GroupDetail processGroup(String group) {
struct GroupDetail gd; struct GroupDetail gd;
//gd.globale = group; // Store the entire group for reference during debug
// Calculate the checksum for the entire group
unsigned char computedChecksum = calcCheckSum(group);
// Extract the name (etiquette) from the group
int indexgrp = group.indexOf(HT); int indexgrp = group.indexOf(HT);
gd.name = group.substring(0, indexgrp); gd.name = group.substring(0, indexgrp);
// Move to the value part
group = group.substring(indexgrp + 1); group = group.substring(indexgrp + 1);
indexgrp = group.indexOf(HT); indexgrp = group.indexOf(HT);
gd.value = group.substring(0, indexgrp); gd.value = group.substring(0, indexgrp);
// Move to the horodate part, if it exists
group = group.substring(indexgrp + 1); group = group.substring(indexgrp + 1);
indexgrp = group.indexOf(HT); indexgrp = group.indexOf(HT);
String key = group.substring(0, indexgrp); if (indexgrp != -1) // Check if there is an horodate part
group = group.substring(indexgrp + 1);
indexgrp = group.indexOf(HT);
if (indexgrp != -1) // some parameters may have hour recording.
{ {
gd.horodate = gd.value; gd.horodate = gd.value;
gd.value = key; gd.value = group.substring(0, indexgrp);
group = group.substring(indexgrp + 1);
} }
// Verify the checksum
gd.checkok = (group[0] == computedChecksum);
return gd; return gd;
} }
static void processStge(RegistreStatus *rs, String value) static void processStge(RegistreStatus *rs, String value) {
{
char stge[9] = ""; char stge[9] = "";
// copy in the char array // copy in the char array
strncpy(stge, value.c_str(), 8); strncpy(stge, value.c_str(), 8);
@ -53,8 +93,7 @@ static void processStge(RegistreStatus *rs, String value)
rs->uli = l; rs->uli = l;
} }
static void processRelais(RelaisStatus *rs, String value) static void processRelais(RelaisStatus *rs, String value) {
{
char stge[4] = ""; char stge[4] = "";
// copy in the char array // copy in the char array
strncpy(stge, value.c_str(), 3); strncpy(stge, value.c_str(), 3);
@ -62,24 +101,19 @@ static void processRelais(RelaisStatus *rs, String value)
rs->ui = strtoul(stge, NULL, 16); rs->ui = strtoul(stge, NULL, 16);
} }
static void processActionsCalendrier(String value) static void processActionsCalendrier(String value) {
{
nbActions = 0; nbActions = 0;
String s = value; String s = value;
while (s.length() > 0) while (s.length() > 0) {
{
int index = s.indexOf(SP); int index = s.indexOf(SP);
if (index == -1) // No space found if (index == -1) // No space found
{ {
break; break;
} } else {
else
{
char data[9] = ""; char data[9] = "";
data[8] = '\0'; data[8] = '\0';
strncpy(data, s.substring(0, index).c_str(), 8); strncpy(data, s.substring(0, index).c_str(), 8);
if (strncmp(data, NONUTILE, 8) != 0) if (strncmp(data, NONUTILE, 8) != 0) {
{
char stge[5] = ""; char stge[5] = "";
// copy ssss field // copy ssss field
memcpy(stge, &data[4], 4); memcpy(stge, &data[4], 4);
@ -102,19 +136,14 @@ static void processActionsCalendrier(String value)
* *
* @param data A reference to a String containing the data frame to be processed. * @param data A reference to a String containing the data frame to be processed.
*/ */
static void processTrame(String &data) static void processTrame(String &data) {
{ while (data.length() > 0) {
while (data.length() > 0)
{
// Find the position of the next carriage return (CR) character // Find the position of the next carriage return (CR) character
int index = data.indexOf(CR); int index = data.indexOf(CR);
// If no CR is found, exit the loop // If no CR is found, exit the loop
if (index == -1) if (index == -1) {
{
break; break;
} } else {
else
{
// Extract the group string between the start and the CR character // Extract the group string between the start and the CR character
String group = data.substring(1, index); String group = data.substring(1, index);
// Process the group to extract detailed information // Process the group to extract detailed information
@ -122,46 +151,64 @@ static void processTrame(String &data)
// Check if the extracted group name matches any user-selected etiquette // Check if the extracted group name matches any user-selected etiquette
int t = 0; int t = 0;
while ((SelectedEtiquette[t] != gd.name) && (t < NB_ETIQUETTE)) while ((SelectedEtiquette[t] != gd.name) && (t < NB_ETIQUETTE)) {
{
++t; ++t;
} }
// If a match is found, update the corresponding TicValues entry // If a match is found, update the corresponding TicValues entry if the group confirms the checksum
if (t < NB_ETIQUETTE) if (t < NB_ETIQUETTE) {
{ //If there is a value update or an horodate change, the value is identified as "updated"
if (TicValues[t].value.compareTo(gd.value) != 0 || TicValues[t].horodate.compareTo(gd.horodate) != 0) {
//There is some noise on instantaneous values, make a basic filter. Also helps to reduce MQTT load
if (SelectedEtiquette[t] == "SINSTS" || SelectedEtiquette[t] == "SINSTS1" || SelectedEtiquette[t] == "SINSTS2" || SelectedEtiquette[t] == "SINSTS3" | SelectedEtiquette[t] == "SINSTI") {
int oldval = TicValues[t].value.toInt();
int newcal = (gd.value.toInt() + oldval) / 2;
//there is a significant change, so update
//consider a change if delta is > 2%
if (newcal < oldval * 0.98 || newcal > oldval * 1.02) {
gd.updated = true;
TicValues[t] = gd;
} else {
//the change is limited. Just record the mean to keep it fresh,
//but doesn't set the updated flag.
TicValues[t].value = String(newcal);
}
} else {
gd.updated = true;
TicValues[t] = gd; TicValues[t] = gd;
// Depending on the group name, call the appropriate processing function // Depending on the group name, call the appropriate processing function
if (gd.name == "STGE") if (gd.name == "STGE") {
{
processStge(&regStatus, gd.value); processStge(&regStatus, gd.value);
} } else if (gd.name == "RELAIS") {
else if (gd.name == "RELAIS")
{
processRelais(&relaisStatus, gd.value); processRelais(&relaisStatus, gd.value);
} } else if (gd.name == "PJOURF+1") {
else if (gd.name == "PJOURF+1")
{
processActionsCalendrier(gd.value); processActionsCalendrier(gd.value);
} }
} }
} else {
//there is no value update, but the checksum is not ok (strange case, but...)
if (TicValues[t].checkok != gd.checkok) {
TicValues[t] = gd;
TicValues[t].updated = true;
} else {
TicValues[t].updated = false;
}
}
}
data = data.substring(index + 1); data = data.substring(index + 1);
} }
} }
} }
static char *actionJp1AsJson() static char *actionJp1AsJson() {
{
const int bufferSize = 1000; const int bufferSize = 1000;
static char jsonBuffer[bufferSize]; // Adjust size as needed static char jsonBuffer[bufferSize]; // Adjust size as needed
snprintf(jsonBuffer, bufferSize, "\"PJOURF+1\": ["); snprintf(jsonBuffer, bufferSize, "\"PJOURF+1\": [");
for (int i = 0; i < nbActions; i++) for (int i = 0; i < nbActions; i++) {
{
// Format each action // Format each action
char actionJson[256]; // To store individual action JSON string char actionJson[256]; // To store individual action JSON string
String relaisSec = ""; String relaisSec = "";
switch ((unsigned int)actionJp1[i].action.bits.relaisSec) switch ((unsigned int)actionJp1[i].action.bits.relaisSec) {
{
case 0: case 0:
relaisSec = "no change"; relaisSec = "no change";
break; break;
@ -189,12 +236,9 @@ static char *actionJp1AsJson()
actionJp1[i].action.bits.relais1, actionJp1[i].action.bits.index); actionJp1[i].action.bits.relais1, actionJp1[i].action.bits.index);
// Append the current action's JSON to the overall JSON buffer // Append the current action's JSON to the overall JSON buffer
if (i == (nbActions - 1)) if (i == (nbActions - 1)) { // Last item, no comma at the end
{ // Last item, no comma at the end
strncat(jsonBuffer, actionJson, bufferSize - strlen(jsonBuffer) - 1); strncat(jsonBuffer, actionJson, bufferSize - strlen(jsonBuffer) - 1);
} } else {
else
{
strncat(jsonBuffer, actionJson, bufferSize - strlen(jsonBuffer) - 1); strncat(jsonBuffer, actionJson, bufferSize - strlen(jsonBuffer) - 1);
strncat(jsonBuffer, ",", bufferSize - strlen(jsonBuffer) - 1); strncat(jsonBuffer, ",", bufferSize - strlen(jsonBuffer) - 1);
} }
@ -205,8 +249,7 @@ static char *actionJp1AsJson()
return jsonBuffer; return jsonBuffer;
} }
static char *relaisStatusAsJson(RelaisStatusBits *status, String rawValue) static char *relaisStatusAsJson(RelaisStatusBits *status, String rawValue) {
{
// Pre-allocate buffer large enough to hold the JSON string // Pre-allocate buffer large enough to hold the JSON string
static char response[150]; // Adjust size as needed static char response[150]; // Adjust size as needed
// Use snprintf to construct the JSON string efficiently // Use snprintf to construct the JSON string efficiently
@ -235,8 +278,7 @@ static char *relaisStatusAsJson(RelaisStatusBits *status, String rawValue)
return response; return response;
} }
static char *registreStatusAsJson(RegistreStatusBits *status, String rawValue) static char *registreStatusAsJson(RegistreStatusBits *status, String rawValue) {
{
// Pre-allocate buffer large enough to hold the JSON string // Pre-allocate buffer large enough to hold the JSON string
static char response[1000]; // Adjust size as needed static char response[1000]; // Adjust size as needed
@ -286,39 +328,38 @@ static char *registreStatusAsJson(RegistreStatusBits *status, String rawValue)
return response; return response;
} }
String ticValuesAsJson() String ticValuesAsJson() {
{
String response = "{"; String response = "{";
for (int i = 0; i < NB_ETIQUETTE; ++i) for (int i = 0; i < NB_ETIQUETTE; ++i) {
{
if (SelectedEtiquette[i] == "STGE") if (SelectedEtiquette[i] == "STGE") {
{
response += registreStatusAsJson(&regStatus.bits, TicValues[i].value); response += registreStatusAsJson(&regStatus.bits, TicValues[i].value);
} } else if (SelectedEtiquette[i] == "RELAIS") {
else if (SelectedEtiquette[i] == "RELAIS")
{
response += relaisStatusAsJson(&relaisStatus.bits, TicValues[i].value); response += relaisStatusAsJson(&relaisStatus.bits, TicValues[i].value);
} } else if (SelectedEtiquette[i] == "PJOURF+1") {
else if (SelectedEtiquette[i] == "PJOURF+1")
{
response += actionJp1AsJson(); response += actionJp1AsJson();
} } else {
else
{
static char jres[150]; // Adjust size as needed static char jres[150]; // Adjust size as needed
// Use snprintf to construct the JSON string efficiently // Use snprintf to construct the JSON string efficiently
if (TicValues[i].horodate.isEmpty()) {
snprintf(jres, sizeof(jres), snprintf(jres, sizeof(jres),
"\"%s\": \"%s\"", "\"%s\": \"%s\"",
SelectedEtiquette[i].c_str(), SelectedEtiquette[i].c_str(),
TicValues[i].value.c_str()); TicValues[i].value.c_str());
} else {
// Include horodate if it is not empty
snprintf(jres, sizeof(jres),
"\"%s\": {\"value\": \"%s\", \"horodate\": \"%s\"}",
SelectedEtiquette[i].c_str(),
TicValues[i].value.c_str(),
TicValues[i].horodate.c_str());
}
response += jres; response += jres;
} }
if (i < (NB_ETIQUETTE - 1)) if (i < (NB_ETIQUETTE - 1)) {
{
response += ','; response += ',';
} }
} }
@ -326,20 +367,16 @@ String ticValuesAsJson()
return response; return response;
} }
String ticBasicValuesAsJson() String ticBasicValuesAsJson() {
{
String response = "{"; String response = "{";
for (int i = 0; i < NB_ETIQUETTE; ++i) for (int i = 0; i < NB_ETIQUETTE; ++i) {
{
if (SelectedEtiquette[i] == "LTARF" || SelectedEtiquette[i] == "EAST" || SelectedEtiquette[i] == "EASF01" || SelectedEtiquette[i] == "EASF02" || SelectedEtiquette[i] == "EASF03" || SelectedEtiquette[i] == "EASF04" || SelectedEtiquette[i] == "EASD01" || SelectedEtiquette[i] == "EASD02" || SelectedEtiquette[i] == "EASD03" || SelectedEtiquette[i] == "EASD04" || SelectedEtiquette[i] == "EAIT" || SelectedEtiquette[i] == "ERQ1" || SelectedEtiquette[i] == "ERQ2" || SelectedEtiquette[i] == "ERQ3" || SelectedEtiquette[i] == "ERQ4" || SelectedEtiquette[i] == "IRMS1" || SelectedEtiquette[i] == "IRMS2" || SelectedEtiquette[i] == "IRMS3" || SelectedEtiquette[i] == "URMS1" || SelectedEtiquette[i] == "URMS2" || SelectedEtiquette[i] == "URMS3" || SelectedEtiquette[i] == "SINSTS" || SelectedEtiquette[i] == "SINSTSI" || SelectedEtiquette[i] == "SINSTS1" || SelectedEtiquette[i] == "SINSTS2" || SelectedEtiquette[i] == "SINSTS3" || SelectedEtiquette[i] == "SINSTSI") if (SelectedEtiquette[i] == "LTARF" || SelectedEtiquette[i] == "EAST" || SelectedEtiquette[i] == "EASF01" || SelectedEtiquette[i] == "EASF02" || SelectedEtiquette[i] == "EASF03" || SelectedEtiquette[i] == "EASF04" || SelectedEtiquette[i] == "EASD01" || SelectedEtiquette[i] == "EASD02" || SelectedEtiquette[i] == "EASD03" || SelectedEtiquette[i] == "EASD04" || SelectedEtiquette[i] == "EAIT" || SelectedEtiquette[i] == "ERQ1" || SelectedEtiquette[i] == "ERQ2" || SelectedEtiquette[i] == "ERQ3" || SelectedEtiquette[i] == "ERQ4" || SelectedEtiquette[i] == "IRMS1" || SelectedEtiquette[i] == "IRMS2" || SelectedEtiquette[i] == "IRMS3" || SelectedEtiquette[i] == "URMS1" || SelectedEtiquette[i] == "URMS2" || SelectedEtiquette[i] == "URMS3" || SelectedEtiquette[i] == "SINSTS" || SelectedEtiquette[i] == "SINSTSI" || SelectedEtiquette[i] == "SINSTS1" || SelectedEtiquette[i] == "SINSTS2" || SelectedEtiquette[i] == "SINSTS3" || SelectedEtiquette[i] == "SINSTSI") {
{
static char jres[150]; // Adjust size as needed static char jres[150]; // Adjust size as needed
if (response != "{") if (response != "{") {
{
response += ","; response += ",";
} }
@ -355,6 +392,55 @@ String ticBasicValuesAsJson()
return response; return response;
} }
/**
* Publishes the list of updated TIC values to the MQTT broker.
*
* This function iterates over the TicValues array and publishes the values
* that have been updated to the corresponding MQTT topics.
* After the publication, the values are not anymore identified as Updated.
* @param mqttclient Pointer to the PubSubClient instance used to publish messages.
*/
void mqttPublish(PubSubClient *mqttclient) {
for (int i = 0; i < NB_ETIQUETTE; ++i) {
String topic = MQTT_TOPIC;
topic += "/",
topic += SelectedEtiquette[i];
if (TicValues[i].updated && TicValues[i].checkok) {
mqttclient->publish(topic.c_str(), TicValues[i].value.c_str());
if (!TicValues[i].horodate.isEmpty()) {
mqttclient->publish((topic + "/date").c_str(), TicValues[i].horodate.c_str());
}
TicValues[i].updated = false;
}
if (TicValues[i].updated && !TicValues[i].checkok) {
topic += "/status";
mqttclient->publish(topic.c_str(), TicValues[i].checkok ? "Ok" : "Not Ok");
}
}
}
/**
* Forces the publication of all TIC values to the MQTT broker.
*
* This function iterates over all TIC values and publishes them to their
* corresponding MQTT topics, regardless of whether they have been updated.
*
* @param mqttclient Pointer to the PubSubClient instance used to publish messages.
*/
void mqttForcePublish(PubSubClient *mqttclient) {
for (int i = 0; i < NB_ETIQUETTE; ++i) {
String topic = MQTT_TOPIC;
topic += "/",
topic += SelectedEtiquette[i];
mqttclient->publish(topic.c_str(), TicValues[i].value.c_str());
topic += "/status";
mqttclient->publish(topic.c_str(), TicValues[i].checkok ? "Ok" : "Not Ok");
}
}
/** /**
* Reads data from the TicPort and processes it according to specific control characters. * Reads data from the TicPort and processes it according to specific control characters.
* *
@ -369,15 +455,12 @@ String ticBasicValuesAsJson()
* *
* The built-in LED is used to indicate the state of data reception. * The built-in LED is used to indicate the state of data reception.
*/ */
void readTicPort() void readTicPort() {
{
// Check TicPort availability // Check TicPort availability
if (TicPort.available()) if (TicPort.available()) {
{
byte incomingByte = TicPort.read(); // Read a byte from the TicPort byte incomingByte = TicPort.read(); // Read a byte from the TicPort
// Check if the incoming byte is the End Of Transmission (EOT) character // Check if the incoming byte is the End Of Transmission (EOT) character
if (incomingByte == EOT) if (incomingByte == EOT) {
{
// Force the end of transmission // Force the end of transmission
// Reject everything // Reject everything
isReceiving = false; isReceiving = false;
@ -385,18 +468,13 @@ void readTicPort()
} }
// Check if the system is currently receiving data // Check if the system is currently receiving data
if (isReceiving) if (isReceiving) {
{
// Check if the end of the frame is reached (ETX character) // Check if the end of the frame is reached (ETX character)
if (incomingByte == ETX) if (incomingByte == ETX) {
{
// Extract the useful part of the frame // Extract the useful part of the frame
if (nActiveData == 1) if (nActiveData == 1) {
{
processTrame(data1); // Process the data in data1 processTrame(data1); // Process the data in data1
} } else {
else
{
processTrame(data2); // Process the data in data2 processTrame(data2); // Process the data in data2
} }
// Indicate that the data reception is complete // Indicate that the data reception is complete
@ -404,41 +482,29 @@ void readTicPort()
digitalWrite(LED_BUILTIN, HIGH); digitalWrite(LED_BUILTIN, HIGH);
// Debugging information: Print the extracted data // Debugging information: Print the extracted data
#ifdef DEBUG #ifdef DEBUG
for (int i = 0; i < NB_ETIQUETTE; ++i) for (int i = 0; i < NB_ETIQUETTE; ++i) {
{
DebugPort.print(TicValues[i].name); DebugPort.print(TicValues[i].name);
DebugPort.print(":"); DebugPort.print(":");
DebugPort.println(TicValues[i].value); DebugPort.println(TicValues[i].value);
} }
#endif #endif
} } else {
else
{
// Add the incoming byte to the current frame // Add the incoming byte to the current frame
if (nActiveData == 1) if (nActiveData == 1) {
{
data1 += (char)incomingByte; // Append the byte to data1 data1 += (char)incomingByte; // Append the byte to data1
} } else {
else
{
data2 += (char)incomingByte; // Append the byte to data2 data2 += (char)incomingByte; // Append the byte to data2
} }
} }
} } else {
else
{
// Look for the start of the frame (STX character) // Look for the start of the frame (STX character)
if (incomingByte == STX) if (incomingByte == STX) {
{
isReceiving = true; isReceiving = true;
digitalWrite(LED_BUILTIN, LOW); digitalWrite(LED_BUILTIN, LOW);
if (nActiveData == 1) if (nActiveData == 1) {
{
data2 = ""; data2 = "";
nActiveData = 2; nActiveData = 2;
} } else {
else
{
data1 = ""; data1 = "";
nActiveData = 1; nActiveData = 1;
} }

21
tic.h
View File

@ -4,6 +4,7 @@
#define TIC_DEF #define TIC_DEF
#include <Arduino.h> #include <Arduino.h>
#include <PubSubClient.h>
#define TIC #define TIC
@ -12,6 +13,7 @@
#define HTTP_PORT 80 //port #define HTTP_PORT 80 //port
// Définition des constantes pour les délimiteurs de trame TIC // Définition des constantes pour les délimiteurs de trame TIC
//Uniquement le mode standard est supporté
//CF document ENEDIS //CF document ENEDIS
#define STX 0x02 // Début de la trame : 0x02 (<STX>) #define STX 0x02 // Début de la trame : 0x02 (<STX>)
#define ETX 0x03 // Fin de la trame : 0x03 (<ETX>) End Of Text #define ETX 0x03 // Fin de la trame : 0x03 (<ETX>) End Of Text
@ -24,13 +26,16 @@
// Constantes pour la taille des étiquettes et le nombre d'étiquettes // Constantes pour la taille des étiquettes et le nombre d'étiquettes
#define MAX_CHAR_ETIQUETTE 9 //CF doc ENEDIS #define MAX_CHAR_ETIQUETTE 9 //CF doc ENEDIS
#define NB_ETIQUETTE 45 #define NB_ETIQUETTE 49
// Structure pour stocker les détails d'un groupe TIC // Structure pour stocker les détails d'un groupe TIC
struct GroupDetail { struct GroupDetail {
//String globale;
String name; // Nom de l'étiquette String name; // Nom de l'étiquette
String value; // Valeur associée à l'étiquette String value; // Valeur associée à l'étiquette
String horodate; // Horodatage de la valeur String horodate; // Horodatage de la valeur
bool updated;
bool checkok; //status of checksum
}; };
// Structure pour les bits de statut du registre // Structure pour les bits de statut du registre
@ -108,9 +113,9 @@ union RegistreStatus {
}; };
const static String SelectedEtiquette[NB_ETIQUETTE] = { const static String SelectedEtiquette[NB_ETIQUETTE] = {
"ADSC", // Adresse du compteur //"ADSC", // Adresse du compteur
"DATE", // Date et heure courantes //"DATE", // Date et heure courantes
"NGTF", // Numéro de gestionnaire de réseau de transport //"NGTF", // Numéro de gestionnaire de réseau de transport
"LTARF", // Libellé du tarif en cours "LTARF", // Libellé du tarif en cours
"EAST", // Énergie active soutirée totale "EAST", // Énergie active soutirée totale
"EASF01", "EASF02", "EASF03", "EASF04", // Énergie active soutirée par période tarifaire "EASF01", "EASF02", "EASF03", "EASF04", // Énergie active soutirée par période tarifaire
@ -128,7 +133,10 @@ const static String SelectedEtiquette[NB_ETIQUETTE] = {
"NTARF", // Nom du tarif en cours "NTARF", // Nom du tarif en cours
"NJOURF", "NJOURF+1", "PJOURF+1", // Couleur du jour et du lendemain "NJOURF", "NJOURF+1", "PJOURF+1", // Couleur du jour et du lendemain
"MSG1", "MSG2", // Messages d'information "MSG1", "MSG2", // Messages d'information
"PPOINTE" // Préavis de pointe mobile "PPOINTE", // Préavis de pointe mobile
"SMAXSN", "SMAXSN1", "SMAXSN2", "SMAXSN3", //Puissance app max soutiree
"SMAXIN", //Puissance app max injectée
"CCASN", "CCAIN" // point de la courbe de charge soustirée / injectée
}; };
// Tableau pour stocker les valeurs des groupes TIC identifiés // Tableau pour stocker les valeurs des groupes TIC identifiés
@ -159,4 +167,7 @@ const static String kPointeMobile[4] = { "no", "PM1", "PM2", "PM3" };
void readTicPort(); void readTicPort();
String ticValuesAsJson(); String ticValuesAsJson();
String ticBasicValuesAsJson(); String ticBasicValuesAsJson();
// Prototypes des fonctions pour envoyer les infos MQTT
void mqttPublish(PubSubClient *mqttclient);
void mqttForcePublish(PubSubClient *mqttclient);
#endif #endif