Réalisez un « Sniffer » MQTT avec un écran TFT tactile
Objectif
Capturer, afficher et éventuellement enregistrer sur carte SD, tous les messages MQTT échangés sur un réseau.
Fonctionnement
L’écran tactile résistif affiche le menu et permet de choisir le mode de fonctionnement. Ensuite, il affiche les messages et, le cas échéant, les enregistre sur une carte SD dans un fichier CSV.
Le projet est facilement adaptable à un ensemble différent d’ESP32, d’un écran TFT tactile SPI et une interface carte SD SPI.
Il faut juste s’assurer que le microcontrôleur possède assez de GPIO pour les différents périphériques SPI.
Version 1 : Arduino UNO R3 et ESP8266
Réalisation
Un Arduino UNO r3 muni du bouclier (shield) comprenant un écran TFT 2.4″ et un connecteur pour carte SD micro.
J’avais cela en stock et je n’avais jamais utilisé le « shield » !
L’UNO ne possédant pas de puce wifi, un esp8266 gère la partie WiFi/MQTT.
Ce n’est clairement pas la meilleure solution !
Schéma
Les cavaliers permettent de déconnecter la liaison série entre les deux microcontrôleurs afin de permettre l’injection d’un nouveau code.

Boîtier
J’ai imprimé le boîtier en PLA d’après ce modèle publié sur le site.
Les fichiers 3D pourront bientôt être téléchargés en cliquant sur le bouton ci-dessous

Si vous souhaitez les obtenir avant, laissez-moi un message.
Vous pouvez adapter les dimensions et modifier les formes à partir du fichier FreeCAD.
Code
Côté Uno
platformio.ini
[env:uno]
platform = atmelavr
board = uno
framework = arduino
monitor_speed = 9600
lib_deps =
adafruit/Adafruit TouchScreen@^1.1.6
prenticedavid/MCUFRIEND_kbv@^3.1.0-Beta
olikraus/U8g2_for_Adafruit_GFX@^1.8.0
arduino-libraries/SD@^1.3.0
main.cpp
#include <Arduino.h>
// Carte SD
#include <SPI.h>
#include <SD.h>
char filename[13];
const uint8_t chipSelect = SS;
File myFile;
// Affichage
#include <MCUFRIEND_kbv.h>
#include <U8g2_for_Adafruit_GFX.h> // librairie permettant d'afficher les polices U8g2 sur Adafruit GFX displays
MCUFRIEND_kbv tft; // définition de l'objet pour afficher les graphiques
U8G2_FOR_ADAFRUIT_GFX ecran; // définition de l'objet pour afficher le texte
#define noir 0x0000
#define blanc 0xFFFF
#define rouge 0xF800
#define vert 0x07E0
#define cyan 0x07FF
#define jaune 0xFF00
#define bleulogo 0x20fa
#define vertclair 0x5761
// Arc-en-ciel
#define violet 0X614F
#define bleu 0x0211
#define bleuciel 0X061B
#define verta 0X05CE
#define jaunea 0XF6A3
#define orange 0xe2e2
#define rougea 0XF1A8
const uint8_t PROGMEM logo_bits[] = {
0x00, 0x00, 0xff, 0x01, 0x00, 0x00, 0xe0, 0xff, 0x0f, 0x07, 0x00, 0xf8,
0xff, 0xff, 0x07, 0x00, 0xfe, 0x83, 0xff, 0x07, 0x00, 0x7f, 0x00, 0xfe,
0x07, 0x80, 0x1f, 0x00, 0xf8, 0x07, 0xc0, 0x0f, 0x00, 0xf0, 0x07, 0xe0,
0x0f, 0x00, 0xe0, 0x07, 0xf0, 0x07, 0x00, 0xe0, 0x07, 0xf8, 0x03, 0x00,
0xc0, 0x07, 0xf8, 0x03, 0x00, 0xc0, 0x07, 0xfc, 0x01, 0x00, 0x80, 0x07,
0xfc, 0x01, 0x00, 0x80, 0x07, 0xfe, 0x01, 0x00, 0x80, 0x07, 0xfe, 0x00,
0x00, 0x00, 0x07, 0xfe, 0x00, 0x00, 0x00, 0x07, 0xfe, 0x00, 0x00, 0x00,
0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff,
0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00,
0x00, 0x00, 0xff, 0x00, 0xf8, 0xff, 0xff, 0xff, 0x00, 0xf8, 0xff, 0xff,
0xff, 0x00, 0xf8, 0xff, 0x7f, 0xff, 0x00, 0xe0, 0xff, 0x3f, 0xff, 0x00,
0x00, 0xff, 0x03, 0xfe, 0x00, 0x00, 0xfe, 0x01, 0xfe, 0x01, 0x00, 0xfe,
0x01, 0xfe, 0x01, 0x00, 0xfe, 0x01, 0xfc, 0x01, 0x00, 0xfe, 0x01, 0xfc,
0x03, 0x00, 0xfe, 0x01, 0xf8, 0x03, 0x00, 0xfe, 0x01, 0xf8, 0x07, 0x00,
0xfe, 0x01, 0xf0, 0x0f, 0x00, 0xfe, 0x01, 0xe0, 0x0f, 0x00, 0xfe, 0x01,
0xc0, 0x1f, 0x00, 0xfe, 0x01, 0x80, 0x7f, 0x00, 0xfe, 0x01, 0x00, 0xff,
0x01, 0xfe, 0x01, 0x00, 0xfe, 0xff, 0xff, 0x01, 0x00, 0xf8, 0xff, 0xff,
0x01, 0x00, 0xe0, 0xff, 0xff, 0x01, 0x00, 0x00, 0x3c, 0xfe, 0x01, 0x00,
0x00, 0x00, 0xfe, 0x01, 0x00, 0x00, 0x00, 0xfe, 0x01, 0x00, 0x00, 0x00,
0xfe, 0x01, 0x00, 0x00, 0x00, 0xfe, 0x01, 0x00, 0x00, 0x00, 0xfe, 0x01,
0x00, 0x00, 0x00, 0xfe, 0x01, 0xe0, 0x07, 0x00, 0xfe, 0x01, 0xf0, 0x1f,
0x00, 0xfe, 0x01, 0xf8, 0x1f, 0x00, 0xfe, 0x01, 0xfc, 0x3f, 0x00, 0xfe,
0x01, 0xfc, 0x3f, 0x00, 0xfe, 0x01, 0xfc, 0x1f, 0x00, 0xfe, 0x01, 0xfc,
0x1f, 0x00, 0xfe, 0x01, 0xfc, 0x0f, 0x00, 0xff, 0x00, 0xf8, 0x07, 0x00,
0xff, 0x00, 0xf8, 0x03, 0x80, 0x7f, 0x00, 0xf0, 0x03, 0xc0, 0x3f, 0x00,
0xe0, 0x0f, 0xf0, 0x1f, 0x00, 0x80, 0xff, 0xff, 0x0f, 0x00, 0x00, 0xfe,
0xff, 0x03, 0x00, 0x00, 0xf0, 0x3f, 0x00, 0x00};
// Tactile
#include <TouchScreen.h>
const int XP = 8, XM = A2, YP = A3, YM = 9; // ID=0x9341
const int TS_LEFT = 93, TS_RT = 910, TS_TOP = 936, TS_BOT = 114; // Obtenus par la calibration native ci-dessous
TouchScreen ts = TouchScreen(XP, YP, XM, YM, 300);
TSPoint tp;
#define MINPRESSURE 200
#define MAXPRESSURE 1000
int16_t xpos, ypos;
void menu();
void lireSerieESP();
bool sortir = false;
int fonction = 0;
char messageRX[128];
char messagesd[128];
uint8_t idxRX = 0;
bool messageComplet = false;
bool nouveauMessage = false;
char date[11];
char heure[12];
char topic[69];
char payload[36];
char *nextField(char *&p)
{
char *start = p;
while (*p && *p != ';')
p++;
if (*p)
*p++ = 0;
return start;
}
void tactile(int16_t *xpos, int16_t *ypos)
{
tp = ts.getPoint();
pinMode(XM, OUTPUT);
pinMode(YP, OUTPUT);
if (tp.z > MINPRESSURE && tp.z < MAXPRESSURE)
{
*xpos = map(tp.y, TS_BOT, TS_TOP, 0, tft.width());
*ypos = map(tp.x, TS_RT, TS_LEFT, 0, tft.height());
}
else
{
*xpos = 400;
*ypos = 300;
}
}
void setup(void)
{
tft.reset();
uint16_t ID = tft.readID();
tft.begin(ID);
ecran.begin(tft); // Liaison U8g2 à l'afficheur
Serial.begin(9600);
tft.setRotation(1);
tft.fillScreen(noir);
ecran.setFontDirection(0);
ecran.setFontMode(255);
ecran.setFont(u8g2_font_8x13_tr);
menu();
}
void loop()
{
tactile(&xpos, &ypos);
if (ypos < 74)
{
fonction = 1;
ypos = 120;
}
else if (ypos > 166 and ypos < 240)
{
fonction = 2;
ypos = 300;
}
if (fonction != 0)
{
if (fonction == 2)
{
if (!SD.begin(chipSelect))
{
Serial.println("initialization failed!");
ecran.setCursor(20, 200);
tft.fillScreen(noir);
ecran.setForegroundColor(rouge);
ecran.print("Erreur carte !");
delay(1000);
menu();
return;
}
Serial.println("initialization done.");
char *p = filename;
strcpy(p, "mqtt");
p += 4;
uint32_t num = (millis() / 1000) % 10000;
*p++ = '0' + (num / 1000) % 10;
*p++ = '0' + (num / 100) % 10;
*p++ = '0' + (num / 10) % 10;
*p++ = '0' + num % 10;
strcpy(p, ".txt");
myFile = SD.open(filename, FILE_WRITE);
if (myFile)
{
Serial.print("sd prête à écrire");
}
else
{
Serial.println("error opening test.txt");
}
}
tft.fillScreen(noir);
sortir = false;
int x = 10;
int y = 10;
while (sortir == false)
{
lireSerieESP();
tactile(&xpos, &ypos);
if (ypos < 240 and xpos < 360)
{
sortir = true;
ypos = 300;
xpos = 400;
}
if (nouveauMessage)
{
// Dessin croix exit
// vidage des deux prochaines lignes
tft.fillRect(0, y - 10, 320, 20, noir);
ecran.setCursor(x, y);
ecran.setForegroundColor(blanc);
ecran.print(topic);
ecran.setCursor(240, y);
ecran.setForegroundColor(jaune);
ecran.println(payload);
if (fonction == 2)
{
myFile.print(messagesd);
}
y = y + 10;
if (y > 240)
{
y = 10;
}
nouveauMessage = false;
}
}
myFile.close();
menu();
}
}
void lireSerieESP()
{
while (Serial.available())
{
char c = Serial.read();
if (c == '\n')
{
messageRX[idxRX] = '\0';
messageComplet = true;
idxRX = 0;
}
else if (idxRX < sizeof(messageRX) - 1)
{
messageRX[idxRX++] = c;
}
}
if (messageComplet)
{
strcpy(messagesd, messageRX);
char *p = messageRX;
strcpy(date, nextField(p));
strcpy(heure, nextField(p));
strcpy(topic, nextField(p));
strcpy(payload, nextField(p));
nouveauMessage = true;
messageComplet = false;
}
}
void menu()
{
fonction = 0;
tft.fillScreen(noir);
tft.drawXBitmap(260, 30, logo_bits, 40, 64, bleulogo);
tft.drawRect(0, 0, 319, 239, violet);
// tft.drawRect(1,1,317,237, violet);
tft.drawRect(2, 2, 315, 235, bleu);
// tft.drawRect(3,3,313,233, bleu);
tft.drawRect(4, 4, 311, 231, bleuciel);
// tft.drawRect(5,5,309,229, bleuciel);
tft.drawRect(6, 6, 307, 227, vert);
// tft.drawRect(7,7,305,225, vert);
tft.drawRect(8, 8, 303, 223, jaunea);
// tft.drawRect(9,9,301,221, jaunea);
tft.drawRect(10, 10, 299, 219, orange);
// tft.drawRect(11,11,297,217, orange);
tft.drawRect(12, 12, 295, 215, rouge);
// tft.drawRect(13,13,293,213, rouge);
//tft.drawEllipse(100,90,40,20,blanc);
//tft.fillRoundRect(100,90,40,20,5,blanc);
tft.drawRect(100,75,115,40,blanc);
ecran.setForegroundColor(cyan);
ecran.setCursor(110, 100);
ecran.setForegroundColor(vertclair);
ecran.print("Sniffer MQTT");
ecran.setCursor(45, 150);
ecran.setForegroundColor(orange);
ecran.print("Toucher pour revenir au menu");
ecran.setForegroundColor(cyan);
tft.drawRect(25, 34, 20, 20, blanc);
tft.drawRect(25, 186, 20, 20, blanc);
ecran.setCursor(74, 49);
ecran.print("Affichage seul");
ecran.setCursor(74, 199);
ecran.print("Affichage & Enregistrement");
}
/*
//////////////////////////////////////////////////////////////////
// CALIBRATION NATIVE //
//////////////////////////////////////////////////////////////////
//------------------------------------------------Fonctionne OK UNO et SHIELD
// TouchScreen_Calibr_native for MCUFRIEND UNO Display Shields
// adapted by David Prentice
// for Adafruit's <TouchScreen.h> Resistive Touch Screen Library
// from Henning Karlsen's original UTouch_Calibration program.
// Many Thanks.
#define PORTRAIT 0
#define LANDSCAPE 1
#define USE_XPT2046 0
#define USE_LOCAL_KBV 1
#define TOUCH_ORIENTATION PORTRAIT
#if defined(USB_PID) && USB_PID == 0x804E // Arduino M0 Native
#define Serial SerialUSB
#endif
#define SWAP(x, y) { int t = x; x = y; y = t; }
#define TITLE "TouchScreen.h GFX Calibration"
#include <Adafruit_GFX.h>
MCUFRIEND_kbv tft;
// MCUFRIEND UNO shield shares pins with the TFT.
int XP = 7, YP = A2, XM = A1, YM = 6; //next common configuration
TouchScreen ts(XP, YP, XM, YM, 300); //re-initialised after diagnose
TSPoint tp; //global point
void readResistiveTouch(void)
{
tp = ts.getPoint();
pinMode(YP, OUTPUT); //restore shared pins
pinMode(XM, OUTPUT);
//digitalWrite(YP, HIGH); //because TFT control pins
//digitalWrite(XM, HIGH);
// Serial.println("tp.x=" + String(tp.x) + ", tp.y=" + String(tp.y) + ", tp.z =" + String(tp.z));
}
uint16_t readID(void) {
uint16_t ID = tft.readID();
if (ID == 0xD3D3) ID = 0x9486;
return ID;
}
#define TFT_BEGIN() tft.begin(ID)
#define WHITE 0xFFFF
#define RED 0xF800
#define BLUE 0x001F
#define GREEN 0x07E0
#define BLACK 0x0000
//#define GRAY 0x2408 //un-highlighted cross-hair
#define GRAY BLUE //idle cross-hair colour
#define GRAY_DONE RED //finished cross-hair
bool ISPRESSED(void)
{
// .kbv this was too sensitive !!
// now touch has to be stable for 50ms
int count = 0;
bool state, oldstate;
while (count < 10) {
readResistiveTouch();
state = tp.z > 200; //ADJUST THIS VALUE TO SUIT YOUR SCREEN e.g. 20 ... 250
if (state == oldstate) count++;
else count = 0;
oldstate = state;
delay(5);
}
return oldstate;
}
uint32_t cx, cy, cz;
uint32_t rx[8], ry[8];
int32_t clx, crx, cty, cby;
float px, py;
int dispx, dispy, text_y_center, swapxy;
uint32_t calx, caly, cals;
char *Aval(int pin)
{
static char buf[2][10], cnt;
cnt = !cnt;
#if defined(ESP32)
sprintf(buf[cnt], "%d", pin);
#else
sprintf(buf[cnt], "A%d", pin - A0);
#endif
return buf[cnt];
}
void drawCrossHair(int x, int y, uint16_t color)
{
tft.drawRect(x - 10, y - 10, 20, 20, color);
tft.drawLine(x - 5, y, x + 5, y, color);
tft.drawLine(x, y - 5, x, y + 5, color);
}
void centerprint(const char *s, int y)
{
int len = strlen(s) * 6;
tft.setTextColor(WHITE, RED);
tft.setCursor((dispx - len) / 2, y);
tft.print(s);
}
void centertitle(const char *s)
{
tft.fillScreen(BLACK);
tft.fillRect(0, 0, dispx, 14, RED);
tft.fillRect(0, 14, dispx, 1, WHITE);
centerprint(s, 1);
tft.setCursor(0, 30);
tft.setTextColor(WHITE, BLACK);
}
void startup()
{
centertitle(TITLE);
tft.println(F("#define NUMSAMPLES 3 in Library\n"));
tft.println(F("Use a stylus or something"));
tft.println(F("similar to touch as close"));
tft.println(F("to the center of the WHITE"));
tft.println(F("crosshair. Keep holding"));
tft.println(F("until crosshair turns RED."));
tft.println(F("Repeat for all crosshairs.\n"));
tft.println(F("Report can be pasted from Serial\n"));
tft.println(F("Touch screen to continue"));
while (ISPRESSED() == false) {}
while (ISPRESSED() == true) {}
// waitForTouch();
}
void fail()
{
centertitle("Touch Calibration FAILED");
tft.println(F("Unable to read the position"));
tft.println(F("of the press. This is a"));
tft.println(F("hardware issue and can not"));
tft.println(F("be corrected in software."));
tft.println(F("check XP, XM pins with a multimeter"));
tft.println(F("check YP, YM pins with a multimeter"));
tft.println(F("should be about 300 ohms"));
while (true) {};
}
void showpins(int A, int D, int value, const char *msg)
{
char buf[40];
sprintf(buf, "%s (%s, D%d) = %d", msg, Aval(A), D, value);
Serial.println(buf);
}
void bofe(char *buf)
{
tft.println(buf);
Serial.println(buf);
}
#if USE_XPT2046 == 0
bool diagnose_pins()
{
uint8_t i, j, Apins[2], Dpins[2], found = 0;
uint16_t value, Values[2];
Serial.println(F("Making all control and bus pins INPUT_PULLUP"));
Serial.println(F("Typical 30k Analog pullup with corresponding pin"));
Serial.println(F("would read low when digital is written LOW"));
Serial.println(F("e.g. reads ~25 for 300R X direction"));
Serial.println(F("e.g. reads ~30 for 500R Y direction"));
Serial.println(F(""));
for (i = A0; i < A5; i++) pinMode(i, INPUT_PULLUP);
for (i = 2; i < 10; i++) pinMode(i, INPUT_PULLUP);
for (i = A0; i < A4; i++) {
pinMode(i, INPUT_PULLUP);
for (j = 5; j < 10; j++) {
pinMode(j, OUTPUT);
digitalWrite(j, LOW);
value = analogRead(i); // ignore first reading
value = analogRead(i);
if (value < 100 && value > 0) {
showpins(i, j, value, "Testing :");
if (found < 2) {
Apins[found] = i;
Dpins[found] = j;
Values[found] = value;
}
found++;
}
pinMode(j, INPUT_PULLUP);
}
pinMode(i, INPUT_PULLUP);
}
if (found == 2) {
int idx = Values[0] < Values[1];
Serial.println(F("Diagnosing as:-"));
for (i = 0; i < 2; i++) {
showpins(Apins[i], Dpins[i], Values[i],
(Values[i] < Values[!i]) ? "XM,XP: " : "YP,YM: ");
}
XM = Apins[!idx]; XP = Dpins[!idx]; YP = Apins[idx]; YM = Dpins[idx];
ts = TouchScreen(XP, YP, XM, YM, 300); //re-initialise with pins
return true; //success
}
if (found == 0) Serial.println(F("MISSING TOUCHSCREEN"));
//else Serial.println(F("BROKEN TOUCHSCREEN"));
return false;
}
#endif
void readCoordinates()
{
int iter = 5000;
int failcount = 0;
int cnt = 0;
uint32_t tx = 0;
uint32_t ty = 0;
uint32_t tz = 0;
bool OK = false;
while (OK == false)
{
centerprint("* PRESS *", text_y_center);
while (ISPRESSED() == false) {}
centerprint("* HOLD! *", text_y_center);
cnt = 0;
iter = 400;
do
{
readResistiveTouch();
if (tp.z > 200) //.kbv
{
tx += tp.x;
ty += tp.y;
tz += tp.z;
cnt++;
}
else
failcount++;
} while ((cnt < iter) && (failcount < 10000));
if (cnt >= iter)
{
OK = true;
}
else
{
tx = 0;
ty = 0;
tz = 0;
cnt = 0;
}
if (failcount >= 10000)
fail();
}
cx = tx / iter;
cy = ty / iter;
cz = tz / iter;
}
void calibrate(int x, int y, int i, String msg)
{
drawCrossHair(x, y, WHITE);
readCoordinates();
centerprint("* RELEASE *", text_y_center);
drawCrossHair(x, y, GRAY_DONE);
rx[i] = cx;
ry[i] = cy;
char buf[40];
sprintf(buf, "\r\ncx=%ld cy=%ld cz=%ld %s", cx, cy, cz, msg.c_str());
Serial.print(buf);
while (ISPRESSED() == true) {}
}
void report()
{
uint16_t TS_LEFT, TS_RT, TS_TOP, TS_BOT, TS_WID, TS_HT, TS_SWAP;
int16_t tmp;
char buf[60];
centertitle(TITLE);
tft.println(F("To use the new calibration"));
tft.println(F("settings you must map the values"));
tft.println(F("from Point p = ts.getPoint() e.g. "));
tft.println(F("x = map(p.x, LEFT, RT, 0, tft.width());"));
tft.println(F("y = map(p.y, TOP, BOT, 0, tft.height());"));
tft.println(F("swap p.x and p.y if diff ORIENTATION"));
//.kbv show human values
TS_LEFT = (calx >> 14) & 0x3FFF;
TS_RT = (calx >> 0) & 0x3FFF;
TS_TOP = (caly >> 14) & 0x3FFF;
TS_BOT = (caly >> 0) & 0x3FFF;
TS_WID = ((cals >> 12) & 0x0FFF) + 1;
TS_HT = ((cals >> 0) & 0x0FFF) + 1;
TS_SWAP = (cals >> 31);
if (TOUCH_ORIENTATION == LANDSCAPE) { //always show PORTRAIT first
tmp = TS_LEFT, TS_LEFT = TS_BOT, TS_BOT = TS_RT, TS_RT = TS_TOP, TS_TOP = tmp;
tmp = TS_WID, TS_WID = TS_HT, TS_HT = tmp;
}
sprintf(buf, "\n\n*** COPY-PASTE from Serial Terminal:");
bofe(buf);
bool ts_landscape = (TOUCH_ORIENTATION == LANDSCAPE) ^ swapxy;
#if (USE_XPT2046)
sprintf(buf, "const int TS_LANDSCAPE=%d; //%s", ts_landscape, TITLE);
bofe(buf);
#else
if (ts_landscape) {
SWAP(XM, YP);
SWAP(XP, YM);
SWAP(TS_LEFT, TS_RT);
SWAP(TS_TOP, TS_BOT);
}
sprintf(buf, "const int XP=%d,XM=%s,YP=%s,YM=%d; //%dx%d ID=0x%04X",
XP, Aval(XM), Aval(YP), YM, TS_WID, TS_HT, readID());
Serial.println(buf);
sprintf(buf, "\nTouch Pin Wiring XP=%d XM=%s YP=%s YM=%d",
XP, Aval(XM), Aval(YP), YM);
tft.println(buf);
#endif
sprintf(buf, "const int TS_LEFT=%d,TS_RT=%d,TS_TOP=%d,TS_BOT=%d;",
TS_LEFT, TS_RT, TS_TOP, TS_BOT);
Serial.println(buf);
#if !defined(ARDUINO_AVR_LEONARDO)
for (int orient = 0; orient < 2; orient++) {
sprintf(buf, "\n%s CALIBRATION %d x %d",
orient ? "LANDSCAPE" : "PORTRAIT ", TS_WID, TS_HT);
bofe(buf);
sprintf(buf, "x = map(p.%s, LEFT=%d, RT=%d, 0, %d)",
orient ? "y" : "x", TS_LEFT, TS_RT, TS_WID);
bofe(buf);
sprintf(buf, "y = map(p.%s, TOP=%d, BOT=%d, 0, %d)",
orient ? "x" : "y", TS_TOP, TS_BOT, TS_HT);
bofe(buf);
tmp = TS_LEFT, TS_LEFT = TS_TOP, TS_TOP = TS_RT, TS_RT = TS_BOT, TS_BOT = tmp;
tmp = TS_WID, TS_WID = TS_HT, TS_HT = tmp;
}
int16_t x_range = TS_LEFT - TS_RT, y_range = TS_TOP - TS_BOT;
if (abs(x_range) > 500 && abs(y_range) > 650) //LANDSCAPE
return;
sprintf(buf, "\n*** UNUSUAL CALIBRATION RANGES %d %d", x_range, y_range);
bofe(buf);
#endif
}
void setup()
{
char buf[40];
uint16_t ID = readID();
TFT_BEGIN();
tft.fillScreen(TFT_NAVY);
tft.println("Waiting for Serial");
delay(1000);
Serial.begin(9600);
while (!Serial);
tft.fillScreen(TFT_BLUE);
Serial.println(TITLE);
bool ret = true;
#if USE_XPT2046 || defined(__arm__) || defined(ESP32)
Serial.println(F("Not possible to diagnose Touch pins on ARM or ESP32"));
#else
ret = diagnose_pins(); //destroys TFT pin modes
TFT_BEGIN(); //start again
#endif
tft.setRotation(TOUCH_ORIENTATION);
dispx = tft.width();
dispy = tft.height();
text_y_center = (dispy / 2) - 6;
sprintf(buf, "ID = 0x%04x", ID);
Serial.println(buf);
if (ret == false) {
centerprint("BROKEN TOUCHSCREEN", text_y_center);
fail();
}
}
void loop()
{
startup();
int x, y, cnt, idx = 0;
tft.fillScreen(BLACK);
for (x = 10, cnt = 0; x < dispx; x += (dispx - 20) / 2) {
for (y = 10; y < dispy; y += (dispy - 20) / 2) {
if (++cnt != 5) drawCrossHair(x, y, GRAY);
}
}
centerprint("***********", text_y_center - 12);
centerprint("***********", text_y_center + 12);
for (x = 10, cnt = 0; x < dispx; x += (dispx - 20) / 2) {
for (y = 10; y < dispy; y += (dispy - 20) / 2) {
if (++cnt != 5) calibrate(x, y, idx++, F(" X, Y, Pressure"));
}
}
cals = (long(dispx - 1) << 12) + (dispy - 1);
swapxy = rx[2] - rx[0];
//else swapxy = ry[2] - ry[0];
swapxy = (swapxy < -400 || swapxy > 400);
if (swapxy != 0) {
clx = (ry[0] + ry[1] + ry[2]); //rotate 90
crx = (ry[5] + ry[6] + ry[7]);
cty = (rx[0] + rx[3] + rx[5]);
cby = (rx[2] + rx[4] + rx[7]);
} else {
clx = (rx[0] + rx[1] + rx[2]); //regular
crx = (rx[5] + rx[6] + rx[7]);
cty = (ry[0] + ry[3] + ry[5]);
cby = (ry[2] + ry[4] + ry[7]);
}
clx /= 3;
crx /= 3;
cty /= 3;
cby /= 3;
px = float(crx - clx) / (dispx - 20);
py = float(cby - cty) / (dispy - 20);
// px = 0;
clx -= px * 10;
crx += px * 10;
cty -= py * 10;
cby += py * 10;
calx = (long(clx) << 14) + long(crx);
caly = (long(cty) << 14) + long(cby);
if (swapxy)
cals |= (1L << 31);
report(); // report results
while (true) {} // tread water
}
*/
Côté ESP8266
platformio.ini
env:d1_mini]
platform = espressif8266
board = d1_mini
framework = arduino
monitor_speed = 9600
lib_deps =
marvinroger/AsyncMqttClient@^0.9.0
jagprod.h
#ifndef jagprod_h
#define jagprod_h
// WiFi
#define sid "votre SSID"
#define wifi_sesame "Votre mot de passe WiFi"
// IP (Si utilisation en DHCP, supprimer cette partie)
const uint8_t ip[4] = {Adresse IP de l'hôte dans votre réseau};
const uint8_t mask[4] = {Masque de sous-réseaux};
const uint8_t ns[4] = {Adresse IP du serveur DNS};
const uint8_t gw[4] = {Adresse de la passerelle};
// MQTT
const char *mqtt_serveur = "adresse IP du serveur MQTT";
const uint16_t mqtt_port = port MQTT;
const char *mqtt_identifiant = "identifiant MQTT";
const char *mqtt_sesame = "Mot de passe MQTT";
#endif
main.cpp
#include <Arduino.h>
#include <jagprod.h>
// Wifi
#include <ESP8266WiFi.h>
#include <WiFiUdp.h>
void WiFiEvent(WiFiEvent_t event);
bool wifi_statut = false;
bool demandentp = false;
void WiFiEvent(WiFiEvent_t event)
{
switch (event)
{
case WIFI_EVENT_STAMODE_CONNECTED:
Serial.println("Assoscié");
break;
case WIFI_EVENT_STAMODE_GOT_IP:
Serial.println("Connecté");
wifi_statut = true;
demandentp = true;
break;
case WIFI_EVENT_STAMODE_DISCONNECTED:
Serial.println("Wifi perdu");
wifi_statut = false;
break;
default:
break;
}
}
#include <AsyncMqttClient.h>
AsyncMqttClient mqtt_client;
unsigned long derniere_connexion_mqtt = 0;
unsigned long mqtt_timeout = 5000;
bool mqtt_statut = false;
static String message;
float valeur;
char val[7];
char ligne[128]; // taille suffisante
size_t pos = 0;
String mois;
String epoch2string;
unsigned int localPort = 2391;
const int NTP_PACKET_SIZE = 48;
byte packetBuffer[NTP_PACKET_SIZE];
IPAddress timeServerIP;
const char *ntpServerName = "time.vicou.be";
WiFiUDP udp;
int dst = 1;
unsigned long epoch;
unsigned long epochmars;
unsigned long epochoctobre;
unsigned long epochannee;
unsigned long epoch01012025 = 1735689600;
unsigned long nombredejours;
int nombredejoursannee;
int seconde;
int minute;
int heure;
int bis = 0;
int annee;
String joursemaine;
int queljoursemaine;
long debut_millis;
int jour;
int s;
int ds;
int m;
int dm;
int h;
int dh;
int mois_nombre;
char Date[23];
void quelleheure(IPAddress &address);
bool ntpfait = false;
float temps;
int centieme = 0;
// === MQTT EVENTS ===
void onMqttConnect(bool sessionPresent)
{
Serial.println("✅ Connecté au broker MQTT !");
mqtt_statut = true;
mqtt_client.subscribe("#", 0);
}
void onMqttDisconnect(AsyncMqttClientDisconnectReason reason)
{
Serial.printf("⚠️ Déconnecté du broker MQTT (raison: %d)\n", static_cast<uint8_t>(reason));
mqtt_statut = false;
}
void onMqttMessage(char *topic, char *payload, AsyncMqttClientMessageProperties properties, size_t len, size_t index, size_t total)
{
if (ntpfait == false)
{
return;
}
temps = (heure * 360000 + minute * 6000 + seconde * 100 + centieme + (millis() - debut_millis) / 10);
debut_millis = millis();
heure = temps / 360000;
minute = (temps / 360000 - heure) * 60;
seconde = ((temps / 360000 - heure) * 60 - minute) * 60;
centieme = ((((temps / 360000 - heure) * 60 - minute) * 60) - seconde) * 100;
// Formatage de la date
snprintf(Date, sizeof(Date), "%02d-%02d-%04d;%02d:%02d:%02d.%02d", jour, mois_nombre, annee, heure, minute, seconde, centieme);
pos = 0;
// Date + séparateur + topic + séparateur
pos += snprintf(ligne + pos, sizeof(ligne) - pos, "%s;%s;", Date, topic);
// Payload (NON terminé par \0)
if (pos + len < sizeof(ligne))
{
memcpy(ligne + pos, payload, len);
pos += len;
}
// Fin de chaîne
ligne[pos] = '\0';
Serial.println(ligne);
}
void setup()
{
Serial.begin(9600);
WiFi.setAutoReconnect(true);
WiFi.setSleep(false);
WiFi.config(ip, gw, mask, ns); // Mettre en remarque pour une utilisation en DHCP
WiFi.onEvent(WiFiEvent);
WiFi.begin(sid, wifi_sesame);
// MQTT
mqtt_client.setServer(mqtt_serveur, mqtt_port);
mqtt_client.setCredentials(mqtt_identifiant, mqtt_sesame);
mqtt_client.setClientId("sniffermqtt"); // nom du client mqtt
mqtt_client.onConnect(onMqttConnect);
mqtt_client.onDisconnect(onMqttDisconnect);
mqtt_client.onMessage(onMqttMessage);
}
void loop()
{
if (wifi_statut == true and mqtt_statut == false and millis() - derniere_connexion_mqtt > mqtt_timeout)
{
Serial.println("Tentative de connexion MQTT");
mqtt_client.connect();
derniere_connexion_mqtt = millis();
}
if (demandentp)
{
demandentp = false;
quelleheure(timeServerIP);
}
}
void quelleheure(IPAddress &address) // Synchronisation du temps via NTP
{
udp.begin(localPort);
WiFi.hostByName(ntpServerName, timeServerIP);
memset(packetBuffer, 0, NTP_PACKET_SIZE);
packetBuffer[0] = 0b11100011;
packetBuffer[1] = 0;
packetBuffer[2] = 6;
packetBuffer[3] = 0xEC;
packetBuffer[12] = 49;
packetBuffer[13] = 0x4E;
packetBuffer[14] = 49;
packetBuffer[15] = 52;
udp.beginPacket(address, 123);
udp.write(packetBuffer, NTP_PACKET_SIZE);
udp.endPacket();
delay(1000);
int cb = udp.parsePacket();
while (!cb)
{
cb = udp.parsePacket();
}
udp.read(packetBuffer, NTP_PACKET_SIZE); // read the packet into the buffer
debut_millis = millis();
unsigned long highWord = word(packetBuffer[40], packetBuffer[41]);
unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]);
unsigned long secsSince1900 = highWord << 16 | lowWord;
const unsigned long seventyYears = 2208988800UL;
unsigned long epoch = secsSince1900 - seventyYears - 1; // moins délais udp
annee = 2025;
bis = 0;
epochannee = epoch01012025;
nombredejours = (epoch - epoch01012025) / 86400L + 1; // car le 1 janvier correspond aux secondes 0 à 86400 soit le jour 0
nombredejoursannee = nombredejours;
while (nombredejoursannee > 365 + bis)
{
nombredejoursannee = nombredejoursannee - (365 + bis);
annee = annee + 1;
epochannee = epochannee + (365 + bis) * 86400L;
bis = 0;
if (annee % 400 == 0 or (annee % 4 == 0 and annee % 100 != 0))
{
bis = 1;
}
}
if (nombredejoursannee <= 31)
{
mois = "janvier";
mois_nombre = 1;
jour = nombredejoursannee;
}
else if (nombredejoursannee <= (59 + bis))
{
mois = "fevrier";
mois_nombre = 2;
jour = nombredejoursannee - 31;
}
else if (nombredejoursannee <= 90 + bis)
{
mois = "mars";
mois_nombre = 3;
jour = nombredejoursannee - (59 + bis);
}
else if (nombredejoursannee <= 120 + bis)
{
mois = "avril";
mois_nombre = 4;
jour = nombredejoursannee - (90 + bis);
}
else if (nombredejoursannee <= 151 + bis)
{
mois = "mai";
mois_nombre = 5;
jour = nombredejoursannee - (120 + bis);
}
else if (nombredejoursannee <= 181 + bis)
{
mois = "juin";
mois_nombre = 6;
jour = nombredejoursannee - (151 + bis);
}
else if (nombredejoursannee <= 212 + bis)
{
mois = "juillet";
mois_nombre = 7;
jour = nombredejoursannee - (181 + bis);
}
else if (nombredejoursannee <= 243 + bis)
{
mois = "aout";
mois_nombre = 8;
jour = nombredejoursannee - (212 + bis);
}
else if (nombredejoursannee <= 273 + bis)
{
mois = "septembre";
mois_nombre = 9;
jour = nombredejoursannee - (243 + bis);
}
else if (nombredejoursannee <= 304 + bis)
{
mois = "octobre";
mois_nombre = 10;
jour = nombredejoursannee - (273 + bis);
}
else if (nombredejoursannee <= 334 + bis)
{
mois = "novembre";
mois_nombre = 11;
jour = nombredejoursannee - (304 + bis);
}
else if (nombredejoursannee <= 365 + bis)
{
mois = "decembre";
mois_nombre = 12;
jour = nombredejoursannee - (334 + bis);
}
queljoursemaine = (epoch / 86400L + 4) % 7; // +4 car 1/1/1970 = jeudi = jour 4 (si dimanche = 0)
switch (queljoursemaine)
{
case 0:
joursemaine = "Dimanche";
break;
case 1:
joursemaine = "Lundi";
break;
case 2:
joursemaine = "Mardi";
break;
case 3:
joursemaine = "Mercredi";
break;
case 4:
joursemaine = "Jeudi";
break;
case 5:
joursemaine = "Vendredi";
break;
case 6:
joursemaine = "Samedi";
break;
}
epochmars = epochannee + (90 + bis) * 86400L; // correspond au dernier jour de de mars
queljoursemaine = (epochmars / 86400L + 4) % 7; // +4 car 1/1/1970 = jeudi = jour 4 (si dimanche = 0)
while (queljoursemaine != 0)
{
epochmars = epochmars - 86400L;
queljoursemaine = (epochmars / 86400L + 4) % 7;
}
epochmars = epochmars + 2 * 3600; // le changement se fait à UTC+1 soit +2H car UTC+1 +DST hiver
epochoctobre = epochannee + (304 + bis) * 86400L; // correspond au dernier jour d'octobre
queljoursemaine = (epochoctobre / 86400L + 4) % 7; // +4 car 1/1/1970 = jeudi = jour 4 (si dimanche = 0)
while (queljoursemaine != 0)
{
epochoctobre = epochoctobre - 86400L;
queljoursemaine = (epochoctobre / 86400L + 4) % 7;
}
epochoctobre = epochoctobre + 3 * 3600; // le changement se fait à UTC+1 soit +3H car UTC+1 +DST été
if (epoch >= epochmars and epoch < epochoctobre)
{
dst = 2;
}
else
{
dst = 1;
}
heure = ((epoch % 86400L) / 3600) + dst; // +1 = Heure d'hiver et +2 = Heure d'été
if (heure >= 24)
{
heure = heure - 24;
}
minute = (epoch % 3600) / 60;
seconde = (epoch % 60);
ntpfait = true;
}
Version 2 : CYD 2.8″
Schéma

Réalisation
Dans cette version, j’ai opté pour un « CYD » 2.8 pouces (ESP32-2432S028R).
En plus d’un affichage plus grand, il a la possibilité de se connecter sur un réseau choisi.
Ce module de développement comprend un esp32 double coeur à 240 MHz, un écran TFT tactile résistif de 320/240 pixels, une interface Carte SD, une LDR et une LED RVB.
Ici, pas de boîtier car le CYD était fourni avec des plaques de plexiglas transparentes.
Code
Fichier « platformio.ini »
[env:esp32doit-devkit-v1]
platform = espressif32
board = esp32doit-devkit-v1
framework = arduino
lib_deps =
bodmer/TFT_eSPI@^2.5.43
PaulStoffregen/XPT2046_Touchscreen@^1.4
marvinroger/AsyncMqttClient@^0.9.0
monitor_speed = 115200
Fichier « reseau.h »
Il y a lieu de remplacer les caractères en italique gras par les valeurs correspondant à vos propres réseaux !
#ifndef reseau_h
#define reseau_h
struct reseau_config
{
const char *sid;
const char *wifi_sesame;
IPAddress ip;
IPAddress gw;
IPAddress mask;
IPAddress ns;
const char *mqtt_serveur;
uint16_t mqtt_port;
const char *mqtt_identifiant;
const char *mqtt_sesame;
};
const reseau_config reseaux[] =
{
{
"SSID du premier réseau",
"Mot de passe du premier réseau",
IPAddress(x, x, x, x),
IPAddress(x, x, x, x),
IPAddress(x, x, x, x),
IPAddress(x, x, x, x),
"x.x.x.x",
x,
"Identifiant MQTT du premier réseau",
"Mot de passe MQTT du premier réseau"
},
{
"SSID du second réseau",
"Mot de passe du premier réseau",
IPAddress(x, x, x, x),
IPAddress(x, x, x, x),
IPAddress(x, x, x, x),
IPAddress(x, x, x, x),
"x.x.x.x",
x,
"Identifiant MQTT du second réseau",
"Identifiant MQTT du second réseau"
}
};
#endif
Fichier « main.cpp »
#include <Arduino.h>
#include <SPI.h>
// Affichage
#include <TFT_eSPI.h>
TFT_eSPI tft = TFT_eSPI();
#define SCREEN_WIDTH 320
#define SCREEN_HEIGHT 240
#define FONT_SIZE 2
#define noir 0x0000
#define blanc 0xFFFF
#define rouge 0xF800
#define vert 0x07E0
#define cyan 0x07FF
#define jaune 0xFF00
#define bleulogo 0x20fa
#define vertclair 0x5761
#define violet 0X614F
#define bleu 0x0211
#define bleuciel 0X061B
#define verta 0X05CE
#define jaunea 0XF6A3
#define orange 0xe2e2
#define rougea 0XF1A8
const uint8_t PROGMEM logo_bits[] = { // Logo
0x00, 0x00, 0xff, 0x01, 0x00, 0x00, 0xe0, 0xff, 0x0f, 0x07, 0x00, 0xf8,
0xff, 0xff, 0x07, 0x00, 0xfe, 0x83, 0xff, 0x07, 0x00, 0x7f, 0x00, 0xfe,
0x07, 0x80, 0x1f, 0x00, 0xf8, 0x07, 0xc0, 0x0f, 0x00, 0xf0, 0x07, 0xe0,
0x0f, 0x00, 0xe0, 0x07, 0xf0, 0x07, 0x00, 0xe0, 0x07, 0xf8, 0x03, 0x00,
0xc0, 0x07, 0xf8, 0x03, 0x00, 0xc0, 0x07, 0xfc, 0x01, 0x00, 0x80, 0x07,
0xfc, 0x01, 0x00, 0x80, 0x07, 0xfe, 0x01, 0x00, 0x80, 0x07, 0xfe, 0x00,
0x00, 0x00, 0x07, 0xfe, 0x00, 0x00, 0x00, 0x07, 0xfe, 0x00, 0x00, 0x00,
0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff,
0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00,
0x00, 0x00, 0xff, 0x00, 0xf8, 0xff, 0xff, 0xff, 0x00, 0xf8, 0xff, 0xff,
0xff, 0x00, 0xf8, 0xff, 0x7f, 0xff, 0x00, 0xe0, 0xff, 0x3f, 0xff, 0x00,
0x00, 0xff, 0x03, 0xfe, 0x00, 0x00, 0xfe, 0x01, 0xfe, 0x01, 0x00, 0xfe,
0x01, 0xfe, 0x01, 0x00, 0xfe, 0x01, 0xfc, 0x01, 0x00, 0xfe, 0x01, 0xfc,
0x03, 0x00, 0xfe, 0x01, 0xf8, 0x03, 0x00, 0xfe, 0x01, 0xf8, 0x07, 0x00,
0xfe, 0x01, 0xf0, 0x0f, 0x00, 0xfe, 0x01, 0xe0, 0x0f, 0x00, 0xfe, 0x01,
0xc0, 0x1f, 0x00, 0xfe, 0x01, 0x80, 0x7f, 0x00, 0xfe, 0x01, 0x00, 0xff,
0x01, 0xfe, 0x01, 0x00, 0xfe, 0xff, 0xff, 0x01, 0x00, 0xf8, 0xff, 0xff,
0x01, 0x00, 0xe0, 0xff, 0xff, 0x01, 0x00, 0x00, 0x3c, 0xfe, 0x01, 0x00,
0x00, 0x00, 0xfe, 0x01, 0x00, 0x00, 0x00, 0xfe, 0x01, 0x00, 0x00, 0x00,
0xfe, 0x01, 0x00, 0x00, 0x00, 0xfe, 0x01, 0x00, 0x00, 0x00, 0xfe, 0x01,
0x00, 0x00, 0x00, 0xfe, 0x01, 0xe0, 0x07, 0x00, 0xfe, 0x01, 0xf0, 0x1f,
0x00, 0xfe, 0x01, 0xf8, 0x1f, 0x00, 0xfe, 0x01, 0xfc, 0x3f, 0x00, 0xfe,
0x01, 0xfc, 0x3f, 0x00, 0xfe, 0x01, 0xfc, 0x1f, 0x00, 0xfe, 0x01, 0xfc,
0x1f, 0x00, 0xfe, 0x01, 0xfc, 0x0f, 0x00, 0xff, 0x00, 0xf8, 0x07, 0x00,
0xff, 0x00, 0xf8, 0x03, 0x80, 0x7f, 0x00, 0xf0, 0x03, 0xc0, 0x3f, 0x00,
0xe0, 0x0f, 0xf0, 0x1f, 0x00, 0x80, 0xff, 0xff, 0x0f, 0x00, 0x00, 0xfe,
0xff, 0x03, 0x00, 0x00, 0xf0, 0x3f, 0x00, 0x00};
int ligne_ecran;
// Touch Screen
#include <XPT2046_Touchscreen.h>
#define XPT2046_IRQ 36
#define XPT2046_MOSI 32
#define XPT2046_MISO 39
#define XPT2046_CLK 25
#define XPT2046_CS 33
SPIClass touchscreenSPI = SPIClass(HSPI);
XPT2046_Touchscreen touchscreen(XPT2046_CS, XPT2046_IRQ);
#define MINPRESSURE 200
#define MAXPRESSURE 1000
int16_t x, y;
// SD Card
#include "SD.h"
SPIClass spiSD(VSPI);
char filename[13];
const uint8_t chipSelect = 5;
File myFile;
void menu();
static String sujet;
int reseau = 2;
bool statut_journalisation = false;
bool statut_action = false;
bool statut_wifi = false;
bool startut_mqtt = false;
bool statut_sd = false;
bool statut_menu = false;
bool statut_nouveaumessage = false;
bool statut_ntp = false;
// Date et Heure NTP
String mois;
String epoch2string;
unsigned int localPort = 2391;
const int NTP_PACKET_SIZE = 48;
byte packetBuffer[NTP_PACKET_SIZE];
IPAddress timeServerIP;
const char *ntpServerName = "time.vicou.be";
int dst = 1;
unsigned long epoch;
unsigned long epochmars;
unsigned long epochoctobre;
unsigned long epochannee;
unsigned long epoch01012025 = 1735689600;
unsigned long nombredejours;
int nombredejoursannee;
int seconde;
int minute;
int heure;
int bis = 0;
int annee;
String joursemaine;
int queljoursemaine;
long debut_millis;
int jour;
int s;
int ds;
int m;
int dm;
int h;
int dh;
int mois_nombre;
char Date[23];
void quelleheure(IPAddress &address);
bool ntpfait = false;
float temps;
int centieme = 0;
//-----WIFI-----
#include <WiFi.h>
#include <jagprod.h>
#include <WiFiUdp.h>
unsigned long derniere_connexion_wifi = 0;
unsigned long wifi_timeout = 10000;
const reseau_config *reseau_choisi = nullptr;
WiFiUDP udp;
void WiFiEvent(WiFiEvent_t event)
{
switch (event)
{
case SYSTEM_EVENT_STA_CONNECTED:
Serial.println("Assoscié");
break;
case SYSTEM_EVENT_STA_GOT_IP:
Serial.println("Connecté");
statut_wifi = true;
break;
case SYSTEM_EVENT_STA_DISCONNECTED:
Serial.println("Wifi perdu");
statut_wifi = false;
break;
default:
break;
}
}
void connexion_wifi()
{
Serial.print("réseau : ");
Serial.println(reseau);
reseau_choisi = &reseaux[reseau];
WiFi.disconnect(true);
WiFi.config(
reseau_choisi->ip,
reseau_choisi->gw,
reseau_choisi->mask,
reseau_choisi->ns);
WiFi.begin(
reseau_choisi->sid,
reseau_choisi->wifi_sesame);
}
//-----MQTT-----
#include <AsyncMqttClient.h>
AsyncMqttClient mqtt_client;
unsigned long derniere_connexion_mqtt = 0;
unsigned long mqtt_timeout = 5000;
bool statut_mqtt = false;
static String message;
char ligne[128]; // taille suffisante
size_t pos = 0;
void connexion_mqtt()
{
mqtt_client.setServer(
reseau_choisi->mqtt_serveur,
reseau_choisi->mqtt_port);
mqtt_client.setCredentials(
reseau_choisi->mqtt_identifiant,
reseau_choisi->mqtt_sesame);
mqtt_client.connect();
}
// === MQTT EVENTS ===
void onMqttConnect(bool sessionPresent)
{
Serial.println("✅ Connecté au broker MQTT !");
statut_mqtt = true;
mqtt_client.subscribe("#", 0);
}
void onMqttDisconnect(AsyncMqttClientDisconnectReason reason)
{
Serial.printf("⚠️ Déconnecté du broker MQTT (raison: %d)\n", static_cast<uint8_t>(reason));
statut_mqtt = false;
}
void onMqttPublish(uint16_t packetId)
{
Serial.printf("📤 Publication MQTT confirmée (packetId: %u)\n", packetId);
}
void onMqttMessage(char *topic, char *payload, AsyncMqttClientMessageProperties properties, size_t len, size_t index, size_t total)
{
if (statut_ntp == false)
{
return;
}
sujet = topic;
message = "";
for (size_t i = 0; i < len; i++)
{
message += (char)payload[i];
}
statut_nouveaumessage = true;
temps = (heure * 360000 + minute * 6000 + seconde * 100 + centieme + (millis() - debut_millis) / 10);
debut_millis = millis();
heure = temps / 360000;
minute = (temps / 360000 - heure) * 60;
seconde = ((temps / 360000 - heure) * 60 - minute) * 60;
centieme = ((((temps / 360000 - heure) * 60 - minute) * 60) - seconde) * 100;
// Formatage du timestamp
snprintf(Date, sizeof(Date), "%02d-%02d-%04d;%02d:%02d:%02d.%02d", jour, mois_nombre, annee, heure, minute, seconde, centieme);
pos = 0;
pos += snprintf(ligne + pos, sizeof(ligne) - pos, "%s;%s;", Date, topic);
if (pos + len < sizeof(ligne))
{
memcpy(ligne + pos, payload, len);
pos += len;
}
ligne[pos] = '\0';
}
void setup()
{
Serial.begin(115200);
// Ecran
tft.init();
tft.setRotation(1);
tft.fillScreen(noir);
//----WIFI----
WiFi.setSleep(false);
WiFi.setAutoReconnect(true);
WiFi.onEvent(WiFiEvent); // Attache la fonction de gestion des événements
//----MQTT----
mqtt_client.setClientId("iotdevmqttTest"); // nom du client mqtt
mqtt_client.onConnect(onMqttConnect);
mqtt_client.onDisconnect(onMqttDisconnect);
mqtt_client.onMessage(onMqttMessage);
mqtt_client.onPublish(onMqttPublish);
// Touch Screen
touchscreenSPI.begin(XPT2046_CLK, XPT2046_MISO, XPT2046_MOSI, XPT2046_CS);
touchscreen.begin(touchscreenSPI);
touchscreen.setRotation(1);
// SD
spiSD.begin(18, 19, 23, 5);
}
void initialisation_sd()
{
if (!SD.begin(chipSelect, spiSD))
{
Serial.println("initialization failed!");
tft.setCursor(20, 100);
tft.fillScreen(noir);
tft.setTextColor(rouge);
tft.print("Erreur carte !");
delay(3000);
tft.fillScreen(noir);
statut_menu = false;
statut_action = false;
statut_journalisation = false;
return;
}
else
{
Serial.println("initialization done.");
sprintf(filename, "/mqtt%04lu.txt", (millis() / 1000) % 10000);
myFile = SD.open(filename, FILE_WRITE);
if (myFile)
{
Serial.print("sd prête à écrire");
Serial.println(filename);
statut_sd = true;
}
else
{
Serial.println("error opening txt file");
}
}
}
void loop()
{
if (statut_action == false and statut_menu == false)
{
menu();
statut_menu = true;
}
if (touchscreen.tirqTouched() && touchscreen.touched()) // Toched?
{
TS_Point p = touchscreen.getPoint();
x = map(p.x, 200, 3700, 1, SCREEN_WIDTH); // Calibrate
y = map(p.y, 240, 3800, 1, SCREEN_HEIGHT);
Serial.print("x : ");
Serial.println(x);
Serial.print("y : ");
Serial.println(y);
delay(250);
if (statut_action == false) // je définis les options de fonctionnement
{
if (x > 29 and x < 111 and y > 29 and y < 61)
{
reseau = 0;
tft.fillRect(31, 31, 78, 28, bleu);
tft.setTextColor(jaune);
tft.drawString("R&D", 55, 37, 2);
tft.fillRect(31, 81, 78, 28, noir);
tft.setTextColor(cyan);
tft.drawString("IOT Dev", 45, 87, 2);
}
else if (x > 29 and x < 111 and y > 79 and y < 111)
{
reseau = 1;
tft.fillRect(31, 31, 78, 28, noir);
tft.setTextColor(cyan);
tft.drawString("R&D", 55, 37, 2);
tft.fillRect(31, 81, 78, 28, bleu);
tft.setTextColor(jaune);
tft.drawString("IOT Dev", 45, 87, 2);
}
else if (x > 29 and x < 51 and y > 129 and y < 151)
{
statut_journalisation = !statut_journalisation;
if (statut_journalisation == true)
{
tft.fillRect(31, 131, 18, 18, bleu);
}
else
{
tft.fillRect(31, 131, 18, 18, noir);
}
}
else if (x > 99 and x < 221 and y > 164 and y < 196)
{
if (reseau > 1)
{
Serial.println("Pas de réseau sélectionné !");
}
else
{
statut_action = true;
statut_menu = false;
tft.fillScreen(noir);
ligne_ecran = 0;
if (statut_journalisation == true and statut_sd == false)
{
initialisation_sd();
}
}
}
}
else
{
statut_action = false;
statut_menu = false;
statut_journalisation = false;
statut_sd = false;
statut_wifi = false;
statut_mqtt = false;
statut_nouveaumessage = false;
statut_ntp = false;
myFile.close();
WiFi.disconnect(true);
mqtt_client.disconnect(true);
tft.fillScreen(noir);
}
}
if (statut_action == true)
{
if (statut_wifi == false and millis() - derniere_connexion_wifi > wifi_timeout)
{
connexion_wifi();
derniere_connexion_wifi = millis();
}
if (statut_wifi == true and statut_mqtt == false and millis() - derniere_connexion_mqtt > mqtt_timeout)
{
Serial.println("Tentative de connexion MQTT");
connexion_mqtt();
derniere_connexion_mqtt = millis();
}
if (statut_wifi == true and statut_ntp == false)
{
quelleheure(timeServerIP);
}
if (statut_nouveaumessage == true)
{ // Dessin croix exit
// vidage des deux prochaines lignes
tft.fillRect(0, ligne_ecran, 320, 20, noir);
tft.setCursor(0, ligne_ecran);
tft.setTextColor(blanc);
tft.print(sujet);
tft.setCursor(240, ligne_ecran);
tft.setTextColor(jaune);
tft.println(message);
Serial.print(sujet);
Serial.print(" - ");
Serial.println(message);
if (statut_journalisation == true and statut_sd == true)
{
Serial.println(ligne);
myFile.println(ligne);
myFile.flush();
}
ligne_ecran = ligne_ecran + 10;
if (ligne_ecran > 240)
{
ligne_ecran = 0;
}
statut_nouveaumessage = false;
}
}
}
void menu()
{
tft.fillScreen(noir);
tft.drawXBitmap(260, 30, logo_bits, 40, 64, bleulogo);
tft.drawRect(0, 0, 319, 239, violet);
tft.drawRect(2, 2, 315, 235, bleu);
tft.drawRect(4, 4, 311, 231, bleuciel);
tft.drawRect(6, 6, 307, 227, vert);
tft.drawRect(8, 8, 303, 223, jaunea);
tft.drawRect(10, 10, 299, 219, orange);
tft.drawRect(12, 12, 295, 215, rouge);
tft.setTextColor(vertclair);
tft.drawString("Sniffer MQTT", 125, 50, 2);
tft.drawRect(30, 30, 80, 30, blanc);
tft.setTextColor(cyan);
tft.drawString("R&D", 55, 37, 2);
tft.drawRect(30, 80, 80, 30, blanc);
tft.drawString("IOT Dev", 45, 87, 2);
tft.drawRect(30, 130, 20, 20, blanc);
tft.setCursor(70, 135);
tft.print("Journalisation");
tft.drawRect(100, 165, 120, 30, vertclair);
tft.drawCentreString("VALIDER", 160, 172, 2);
tft.setCursor(45, 210);
tft.setTextColor(orange);
tft.print("Toucher l'ecran pour revenir au menu");
}
void quelleheure(IPAddress &address) // Synchronisation du temps via NTP
{
udp.begin(localPort);
WiFi.hostByName(ntpServerName, timeServerIP);
memset(packetBuffer, 0, NTP_PACKET_SIZE);
packetBuffer[0] = 0b11100011;
packetBuffer[1] = 0;
packetBuffer[2] = 6;
packetBuffer[3] = 0xEC;
packetBuffer[12] = 49;
packetBuffer[13] = 0x4E;
packetBuffer[14] = 49;
packetBuffer[15] = 52;
udp.beginPacket(address, 123);
udp.write(packetBuffer, NTP_PACKET_SIZE);
udp.endPacket();
delay(1000);
int cb = udp.parsePacket();
while (!cb)
{
cb = udp.parsePacket();
}
udp.read(packetBuffer, NTP_PACKET_SIZE); // read the packet into the buffer
debut_millis = millis();
unsigned long highWord = word(packetBuffer[40], packetBuffer[41]);
unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]);
unsigned long secsSince1900 = highWord << 16 | lowWord;
const unsigned long seventyYears = 2208988800UL;
unsigned long epoch = secsSince1900 - seventyYears - 1; // moins délais udp
annee = 2025;
bis = 0;
epochannee = epoch01012025;
nombredejours = (epoch - epoch01012025) / 86400L + 1; // car le 1 janvier correspond aux secondes 0 à 86400 soit le jour 0
nombredejoursannee = nombredejours;
while (nombredejoursannee > 365 + bis)
{
nombredejoursannee = nombredejoursannee - (365 + bis);
annee = annee + 1;
epochannee = epochannee + (365 + bis) * 86400L;
bis = 0;
if (annee % 400 == 0 or (annee % 4 == 0 and annee % 100 != 0))
{
bis = 1;
}
}
if (nombredejoursannee <= 31)
{
mois = "janvier";
mois_nombre = 1;
jour = nombredejoursannee;
}
else if (nombredejoursannee <= (59 + bis))
{
mois = "fevrier";
mois_nombre = 2;
jour = nombredejoursannee - 31;
}
else if (nombredejoursannee <= 90 + bis)
{
mois = "mars";
mois_nombre = 3;
jour = nombredejoursannee - (59 + bis);
}
else if (nombredejoursannee <= 120 + bis)
{
mois = "avril";
mois_nombre = 4;
jour = nombredejoursannee - (90 + bis);
}
else if (nombredejoursannee <= 151 + bis)
{
mois = "mai";
mois_nombre = 5;
jour = nombredejoursannee - (120 + bis);
}
else if (nombredejoursannee <= 181 + bis)
{
mois = "juin";
mois_nombre = 6;
jour = nombredejoursannee - (151 + bis);
}
else if (nombredejoursannee <= 212 + bis)
{
mois = "juillet";
mois_nombre = 7;
jour = nombredejoursannee - (181 + bis);
}
else if (nombredejoursannee <= 243 + bis)
{
mois = "aout";
mois_nombre = 8;
jour = nombredejoursannee - (212 + bis);
}
else if (nombredejoursannee <= 273 + bis)
{
mois = "septembre";
mois_nombre = 9;
jour = nombredejoursannee - (243 + bis);
}
else if (nombredejoursannee <= 304 + bis)
{
mois = "octobre";
mois_nombre = 10;
jour = nombredejoursannee - (273 + bis);
}
else if (nombredejoursannee <= 334 + bis)
{
mois = "novembre";
mois_nombre = 11;
jour = nombredejoursannee - (304 + bis);
}
else if (nombredejoursannee <= 365 + bis)
{
mois = "decembre";
mois_nombre = 12;
jour = nombredejoursannee - (334 + bis);
}
queljoursemaine = (epoch / 86400L + 4) % 7; // +4 car 1/1/1970 = jeudi = jour 4 (si dimanche = 0)
switch (queljoursemaine)
{
case 0:
joursemaine = "Dimanche";
break;
case 1:
joursemaine = "Lundi";
break;
case 2:
joursemaine = "Mardi";
break;
case 3:
joursemaine = "Mercredi";
break;
case 4:
joursemaine = "Jeudi";
break;
case 5:
joursemaine = "Vendredi";
break;
case 6:
joursemaine = "Samedi";
break;
}
epochmars = epochannee + (90 + bis) * 86400L; // correspond au dernier jour de de mars
queljoursemaine = (epochmars / 86400L + 4) % 7; // +4 car 1/1/1970 = jeudi = jour 4 (si dimanche = 0)
while (queljoursemaine != 0)
{
epochmars = epochmars - 86400L;
queljoursemaine = (epochmars / 86400L + 4) % 7;
}
epochmars = epochmars + 2 * 3600; // le changement se fait à UTC+1 soit +2H car UTC+1 +DST hiver
epochoctobre = epochannee + (304 + bis) * 86400L; // correspond au dernier jour d'octobre
queljoursemaine = (epochoctobre / 86400L + 4) % 7; // +4 car 1/1/1970 = jeudi = jour 4 (si dimanche = 0)
while (queljoursemaine != 0)
{
epochoctobre = epochoctobre - 86400L;
queljoursemaine = (epochoctobre / 86400L + 4) % 7;
}
epochoctobre = epochoctobre + 3 * 3600; // le changement se fait à UTC+1 soit +3H car UTC+1 +DST été
if (epoch >= epochmars and epoch < epochoctobre)
{
dst = 2;
}
else
{
dst = 1;
}
heure = ((epoch % 86400L) / 3600) + dst; // +1 = Heure d'hiver et +2 = Heure d'été
if (heure >= 24)
{
heure = heure - 24;
}
minute = (epoch % 3600) / 60;
seconde = (epoch % 60);
statut_ntp = true;
Serial.print(heure);
Serial.print(":");
Serial.println(minute);
}
Améliorations
Le programme est basique. Il serait intéressant d’ajouter des fonctions :
– de filtrage pour n’afficher que les sujets (topic) et/ou les messages (payload) souhaités
– de mise en couleur de certains sujets et/ou messages
…







