Compare commits

..

No commits in common. "main" and "sleepmode" have entirely different histories.

10 changed files with 187 additions and 389 deletions

1
.gitignore vendored
View File

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

View File

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

View File

@ -172,14 +172,6 @@ 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.
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).

Binary file not shown.

View File

@ -1,43 +1,22 @@
#include "serial.h"
#include "secret.h"
#include "tic.h"
#include "ota.h"
#include <Arduino.h>
#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <ESP8266WebServer.h>
#include <ElegantOTA.h>
#include <PubSubClient.h>
// Activer le Wi-Fi
#define WIFI_ENABLE
// Initialiser le serveur web si le Wi-Fi est activé
#ifdef WIFI_ENABLE
ESP8266WebServer server(HTTP_PORT);
WiFiClient espClient;
PubSubClient mqttclient(espClient);
#endif
unsigned long previousMillis = 0;
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);
}
}
}
// Durée de sommeil en microsecondes (par exemple, 5 secondes)
const int sleepDuration = 5 * 1000000;
// Fonction pour configurer et connecter au réseau Wi-Fi
void setup_wifi() {
@ -45,10 +24,10 @@ void setup_wifi() {
// Connexion au réseau Wi-Fi
DebugPort.println();
DebugPort.print("Connecting to ");
DebugPort.println(STASSID);
DebugPort.println(ssid);
WiFi.mode(WIFI_STA);
WiFi.begin(STASSID, STAPSK);
WiFi.begin(ssid, passPhrase);
int c = 0;
// Attendre la connexion Wi-Fi
@ -135,6 +114,12 @@ void setup_serial() {
#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
void setup() {
@ -145,61 +130,20 @@ void setup() {
// Configurer les routes du serveur
restServerRouting();
ElegantOTA.begin(&server);
// ElegantOTA callbacks
ElegantOTA.onStart(onOTAStart);
ElegantOTA.onProgress(onOTAProgress);
ElegantOTA.onEnd(onOTAEnd);
// Démarrer le serveur HTTP
DebugPort.println("Start HTTP server");
server.begin();
DebugPort.println("HTTP server started");
//Démarrer le serveur MQTT
mqttclient.setServer(MQTT_SERVER, MQTT_PORT);
#endif
}
// Boucle principale
void loop() {
ElegantOTA.loop();
server.handleClient();
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
if (server.client().available() == 0) {
goToDeepSleep();
}*/
}
}

32
ota.cpp
View File

@ -1,32 +0,0 @@
#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
View File

@ -1,12 +0,0 @@
#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,11 +16,3 @@
const char *ssid = STASSID;
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-"

392
tic.cpp
View File

@ -1,99 +1,60 @@
#include "lwip/ip.h"
#include "tic.h"
#include "serial.h"
#include "secret.h"
#include <Arduino.h>
#include <PubSubClient.h>
// #define DEBUG 1
struct GroupDetail TicValues[NB_ETIQUETTE] = {};
// La lecture / ecriture des données tic s'effectue sur les variables data{1,2}.
// Pour éviter des pb, il y a un swap. ce qui permet d'avoir une variable en lecture seule, une en écriture seule.
String data1 = ""; // Variable pour stocker la trame complète
String data2 = ""; // Variable pour stocker la trame complète
String data1 = ""; // Variable pour stocker la trame complète
String data2 = ""; // Variable pour stocker la trame complète
int nActiveData = 1;
boolean isReceiving = false; // Indicateur pour savoir si on est dans une trame
RegistreStatus regStatus; // definition du registre status
RelaisStatus relaisStatus; // definition du relais status
Action actionJp1[11]; // actions définie pour jour +1
boolean isReceiving = false; // Indicateur pour savoir si on est dans une trame
RegistreStatus regStatus; // definition du registre status
RelaisStatus relaisStatus; // definition du relais status
Action actionJp1[11]; // actions définie pour jour +1
int nbActions;
/**
* 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) {
static struct GroupDetail processGroup(String group)
{
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);
gd.name = group.substring(0, indexgrp);
// Move to the value part
group = group.substring(indexgrp + 1);
indexgrp = group.indexOf(HT);
gd.value = group.substring(0, indexgrp);
// Move to the horodate part, if it exists
group = group.substring(indexgrp + 1);
indexgrp = group.indexOf(HT);
if (indexgrp != -1) // Check if there is an horodate part
String key = group.substring(0, indexgrp);
group = group.substring(indexgrp + 1);
indexgrp = group.indexOf(HT);
if (indexgrp != -1) // some parameters may have hour recording.
{
gd.horodate = gd.value;
gd.value = group.substring(0, indexgrp);
group = group.substring(indexgrp + 1);
gd.value = key;
}
// Verify the checksum
gd.checkok = (group[0] == computedChecksum);
return gd;
}
static void processStge(RegistreStatus *rs, String value) {
static void processStge(RegistreStatus *rs, String value)
{
char stge[9] = "";
// copy in the char array
strncpy(stge, value.c_str(), 8);
stge[8] = '\0';
unsigned long l = strtoul(stge, NULL, 16); // Convert hex pair to unsigned long
unsigned long l = strtoul(stge, NULL, 16); // Convert hex pair to unsigned long
rs->uli = l;
}
static void processRelais(RelaisStatus *rs, String value) {
static void processRelais(RelaisStatus *rs, String value)
{
char stge[4] = "";
// copy in the char array
strncpy(stge, value.c_str(), 3);
@ -101,19 +62,24 @@ static void processRelais(RelaisStatus *rs, String value) {
rs->ui = strtoul(stge, NULL, 16);
}
static void processActionsCalendrier(String value) {
static void processActionsCalendrier(String value)
{
nbActions = 0;
String s = value;
while (s.length() > 0) {
while (s.length() > 0)
{
int index = s.indexOf(SP);
if (index == -1) // No space found
if (index == -1) // No space found
{
break;
} else {
}
else
{
char data[9] = "";
data[8] = '\0';
strncpy(data, s.substring(0, index).c_str(), 8);
if (strncmp(data, NONUTILE, 8) != 0) {
if (strncmp(data, NONUTILE, 8) != 0)
{
char stge[5] = "";
// copy ssss field
memcpy(stge, &data[4], 4);
@ -136,14 +102,19 @@ static void processActionsCalendrier(String value) {
*
* @param data A reference to a String containing the data frame to be processed.
*/
static void processTrame(String &data) {
while (data.length() > 0) {
static void processTrame(String &data)
{
while (data.length() > 0)
{
// Find the position of the next carriage return (CR) character
int index = data.indexOf(CR);
// If no CR is found, exit the loop
if (index == -1) {
if (index == -1)
{
break;
} else {
}
else
{
// Extract the group string between the start and the CR character
String group = data.substring(1, index);
// Process the group to extract detailed information
@ -151,47 +122,26 @@ static void processTrame(String &data) {
// Check if the extracted group name matches any user-selected etiquette
int t = 0;
while ((SelectedEtiquette[t] != gd.name) && (t < NB_ETIQUETTE)) {
while ((SelectedEtiquette[t] != gd.name) && (t < NB_ETIQUETTE))
{
++t;
}
// If a match is found, update the corresponding TicValues entry if the group confirms the checksum
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;
// Depending on the group name, call the appropriate processing function
if (gd.name == "STGE") {
processStge(&regStatus, gd.value);
} else if (gd.name == "RELAIS") {
processRelais(&relaisStatus, gd.value);
} else if (gd.name == "PJOURF+1") {
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;
}
// If a match is found, update the corresponding TicValues entry
if (t < NB_ETIQUETTE)
{
TicValues[t] = gd;
// Depending on the group name, call the appropriate processing function
if (gd.name == "STGE")
{
processStge(&regStatus, gd.value);
}
else if (gd.name == "RELAIS")
{
processRelais(&relaisStatus, gd.value);
}
else if (gd.name == "PJOURF+1")
{
processActionsCalendrier(gd.value);
}
}
data = data.substring(index + 1);
@ -199,30 +149,33 @@ static void processTrame(String &data) {
}
}
static char *actionJp1AsJson() {
static char *actionJp1AsJson()
{
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\": [");
for (int i = 0; i < nbActions; i++) {
for (int i = 0; i < nbActions; i++)
{
// Format each action
char actionJson[256]; // To store individual action JSON string
char actionJson[256]; // To store individual action JSON string
String relaisSec = "";
switch ((unsigned int)actionJp1[i].action.bits.relaisSec) {
case 0:
relaisSec = "no change";
break;
case 1:
relaisSec = "tempo";
break;
case 2:
relaisSec = "open";
break;
case 3:
relaisSec = "closed";
break;
default:
relaisSec = "unknown";
switch ((unsigned int)actionJp1[i].action.bits.relaisSec)
{
case 0:
relaisSec = "no change";
break;
case 1:
relaisSec = "tempo";
break;
case 2:
relaisSec = "open";
break;
case 3:
relaisSec = "closed";
break;
default:
relaisSec = "unknown";
}
snprintf(actionJson, sizeof(actionJson),
" { \"startTime\": \"%c%c%c%c\", "
@ -236,9 +189,12 @@ static char *actionJp1AsJson() {
actionJp1[i].action.bits.relais1, actionJp1[i].action.bits.index);
// Append the current action's JSON to the overall JSON buffer
if (i == (nbActions - 1)) { // Last item, no comma at the end
if (i == (nbActions - 1))
{ // Last item, no comma at the end
strncat(jsonBuffer, actionJson, bufferSize - strlen(jsonBuffer) - 1);
} else {
}
else
{
strncat(jsonBuffer, actionJson, bufferSize - strlen(jsonBuffer) - 1);
strncat(jsonBuffer, ",", bufferSize - strlen(jsonBuffer) - 1);
}
@ -249,9 +205,10 @@ static char *actionJp1AsJson() {
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
static char response[150]; // Adjust size as needed
static char response[150]; // Adjust size as needed
// Use snprintf to construct the JSON string efficiently
snprintf(response, sizeof(response),
"\"RELAIS\": "
@ -278,9 +235,10 @@ static char *relaisStatusAsJson(RelaisStatusBits *status, String rawValue) {
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
static char response[1000]; // Adjust size as needed
static char response[1000]; // Adjust size as needed
// Use snprintf to construct the JSON string efficiently
snprintf(response, sizeof(response),
@ -328,38 +286,39 @@ static char *registreStatusAsJson(RegistreStatusBits *status, String rawValue) {
return response;
}
String ticValuesAsJson() {
String ticValuesAsJson()
{
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);
} else if (SelectedEtiquette[i] == "RELAIS") {
}
else if (SelectedEtiquette[i] == "RELAIS")
{
response += relaisStatusAsJson(&relaisStatus.bits, TicValues[i].value);
} else if (SelectedEtiquette[i] == "PJOURF+1") {
}
else if (SelectedEtiquette[i] == "PJOURF+1")
{
response += actionJp1AsJson();
} else {
static char jres[150]; // Adjust size as needed
}
else
{
static char jres[150]; // Adjust size as needed
// Use snprintf to construct the JSON string efficiently
if (TicValues[i].horodate.isEmpty()) {
snprintf(jres, sizeof(jres),
"\"%s\": \"%s\"",
SelectedEtiquette[i].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());
}
snprintf(jres, sizeof(jres),
"\"%s\": \"%s\"",
SelectedEtiquette[i].c_str(),
TicValues[i].value.c_str());
response += jres;
}
if (i < (NB_ETIQUETTE - 1)) {
if (i < (NB_ETIQUETTE - 1))
{
response += ',';
}
}
@ -367,16 +326,20 @@ String ticValuesAsJson() {
return response;
}
String ticBasicValuesAsJson() {
String ticBasicValuesAsJson()
{
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 += ",";
}
@ -392,55 +355,6 @@ String ticBasicValuesAsJson() {
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.
*
@ -455,56 +369,76 @@ void mqttForcePublish(PubSubClient *mqttclient) {
*
* The built-in LED is used to indicate the state of data reception.
*/
void readTicPort() {
void readTicPort()
{
// Check TicPort availability
if (TicPort.available()) {
byte incomingByte = TicPort.read(); // Read a byte from the TicPort
if (TicPort.available())
{
byte incomingByte = TicPort.read(); // Read a byte from the TicPort
// Check if the incoming byte is the End Of Transmission (EOT) character
if (incomingByte == EOT) {
if (incomingByte == EOT)
{
// Force the end of transmission
// Reject everything
isReceiving = false;
digitalWrite(LED_BUILTIN, HIGH); // Turn the built-in LED to indicate the end of transmission
digitalWrite(LED_BUILTIN, HIGH); // Turn the built-in LED to indicate the end of transmission
}
// Check if the system is currently receiving data
if (isReceiving) {
if (isReceiving)
{
// Check if the end of the frame is reached (ETX character)
if (incomingByte == ETX) {
if (incomingByte == ETX)
{
// Extract the useful part of the frame
if (nActiveData == 1) {
processTrame(data1); // Process the data in data1
} else {
processTrame(data2); // Process the data in data2
if (nActiveData == 1)
{
processTrame(data1); // Process the data in data1
}
else
{
processTrame(data2); // Process the data in data2
}
// Indicate that the data reception is complete
isReceiving = false;
digitalWrite(LED_BUILTIN, HIGH);
// Debugging information: Print the extracted data
#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(":");
DebugPort.println(TicValues[i].value);
}
#endif
} else {
}
else
{
// Add the incoming byte to the current frame
if (nActiveData == 1) {
data1 += (char)incomingByte; // Append the byte to data1
} else {
data2 += (char)incomingByte; // Append the byte to data2
if (nActiveData == 1)
{
data1 += (char)incomingByte; // Append the byte to data1
}
else
{
data2 += (char)incomingByte; // Append the byte to data2
}
}
} else {
}
else
{
// Look for the start of the frame (STX character)
if (incomingByte == STX) {
if (incomingByte == STX)
{
isReceiving = true;
digitalWrite(LED_BUILTIN, LOW);
if (nActiveData == 1) {
if (nActiveData == 1)
{
data2 = "";
nActiveData = 2;
} else {
}
else
{
data1 = "";
nActiveData = 1;
}

27
tic.h
View File

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