Cardputer Wake-up clock with wav sound from SD
-
// Bird song wake-up for Cardputer
// string must be translated
// time zone idem
// SSID and Pass must be changed if you nuse one ssid #define MAX_ROUTERS 6 -> #define MAX_ROUTERS 1
// wav file must be put on SDcard of Cardputer#include <ArduinoJson.h>
#include <LittleFS.h>
#include <M5Cardputer.h>
#include <M5GFX.h>
#include <WiFi.h>
#include <WiFiUdp.h>
#include <NTPClient.h>
#include <SD.h>
#include <SPI.h>
#include <HTTPClient.h>
#include <Wire.h>
#include <esp_log.h>#include <TimeLib.h>
#include <Timezone.h>TimeChangeRule* tcr;
// *******************************************************
// to be adjusted according to your criteria
// *******************************************************
#define MAX_ROUTERS 6
#define SCREEN_TIMEOUT 90000
#define MOVING_DISPLAY_DURATION 3000
#define TRIGGER_HOUR 6
#define WAV_FILENAME "/2380.wav"// RÚgles pour l'heure d'été/d'hiver en Europe (UTC+1/UTC+2)
// *******************************************************
// to be adjusted according to your criteria
// *******************************************************
TimeChangeRule CEST = { "CEST", Last, Sun, Mar, 2, 120 }; // UTC+2
TimeChangeRule CET = { "CET", Last, Sun, Oct, 3, 60 }; // UTC+1
Timezone CE(CEST, CET);int TRIGGER_MINUTE = 25;
// --- Variables globales ---
float batteryLevel;
unsigned long lastActivityTime = 0;
unsigned long lastSyncMinute = 9999;
static unsigned long lastNotificationTime = 0;
const unsigned long NOTIFICATION_DELAY = 5000;
int lastConnectedRouterIndex = -1;
int contrast = 33;
int volume = 22;
bool screenOn = true;
bool isWaitingForInput = false;
bool lowPowerMode = false;
bool screenInitDone = false;static constexpr const char* files[] = {
"/2380.wav",
};static constexpr const size_t buf_num = 3;
static constexpr const size_t buf_size = 1024;
static uint8_t wav_data[buf_num][buf_size];struct attribute((packed)) wav_header_t {
char RIFF[4];
uint32_t chunk_size;
char WAVEfmt[8];
uint32_t fmt_chunk_size;
uint16_t audiofmt;
uint16_t channel;
uint32_t sample_rate;
uint32_t byte_per_sec;
uint16_t block_size;
uint16_t bit_per_sample;
};struct attribute((packed)) sub_chunk_t {
char identifier[4];
uint32_t chunk_size;
uint8_t data[1];
};struct Router {
char ssid[32];
char password[32];
};Router routers[MAX_ROUTERS];
int routerCount = 0;// --- Variables affichage ---
String TxtEnTete = "Bird-Alarm ";
String TxtCorps = "";
String TxtPied = "";
String TxtPiedII = "";
String data = "> ";
M5Canvas canvas(&M5Cardputer.Display);// --- Variables NTP ---
const char* ntpServer = "pool.ntp.org";
const long utcOffsetSeconds = 0; // UTCWiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP, ntpServer, utcOffsetSeconds);// --- Variables alarme ---
SemaphoreHandle_t resourceMutex = NULL;
TaskHandle_t audioTaskHandle = NULL;
volatile bool audioRequested = false;
volatile bool audioPlaying = false;
volatile bool triggeredToday = false;// *******************************************
static bool playSdWav(const char* filename) {
File file = SD.open(filename);wav_header_t wav_header;
file.read((uint8_t*)&wav_header, sizeof(wav_header_t));// Vérifier la validité du fichier
if (memcmp(wav_header.RIFF, "RIFF", 4) || memcmp(wav_header.WAVEfmt, "WAVEfmt ", 8) || wav_header.audiofmt != 1 || wav_header.bit_per_sample < 8 || wav_header.bit_per_sample > 16 || wav_header.channel == 0 || wav_header.channel > 2) {
file.close();
Serial.println("[playSdWav] Invalid WAV file!");
return false;
}file.seek(offsetof(wav_header_t, audiofmt) + wav_header.fmt_chunk_size);
sub_chunk_t sub_chunk;
file.read((uint8_t*)&sub_chunk, 8);
while (memcmp(sub_chunk.identifier, "data", 4)) {
file.seek(sub_chunk.chunk_size, SeekCur);
file.read((uint8_t*)&sub_chunk, 8);
}bool flg_16bit = (wav_header.bit_per_sample >> 4);
int32_t data_len = sub_chunk.chunk_size;
size_t idx = 0;M5Cardputer.Speaker.begin();
M5Cardputer.Speaker.setVolume(volume);while (data_len > 0) {
size_t len = data_len < buf_size ? data_len : buf_size;
len = file.read(wav_data[idx], len);
data_len -= len;if (flg_16bit) { M5Cardputer.Speaker.playRaw((const int16_t*)wav_data[idx], len >> 1, wav_header.sample_rate, wav_header.channel > 1, 1, false); } else { M5Cardputer.Speaker.playRaw((const uint8_t*)wav_data[idx], len, wav_header.sample_rate, wav_header.channel > 1, 1, false); } idx = idx < (buf_num - 1) ? idx + 1 : 0;}
M5Cardputer.Speaker.end();
file.close();
return true;
}// *********************************************
void EnTete() {
ClearEnTete();
M5.Display.setCursor(5, 2);
M5.Display.setFont(&fonts::FreeMono9pt7b);
M5.Display.print(TxtEnTete);
batteryLevel = M5.Power.getBatteryLevel();
drawBattery(200, 5, batteryLevel);
M5.Display.drawLine(0, 19, M5.Display.width(), 19, YELLOW);
M5.Display.drawLine(0, 20, M5.Display.width(), 20, DARKGREY);
M5.Display.drawLine(0, 21, M5.Display.width(), 21, TFT_ORANGE);
}
// *********************************************
void ClearEnTete() {
M5.Display.setTextColor(WHITE, BLACK);
}// *********************************************
void Corps() {
ClearCorps();
M5.Display.setTextSize(1);
M5.Display.setFont(&fonts::FreeMonoOblique9pt7b);
int largeurTexte = TxtCorps.length() * 10;
int x = (M5.Display.width() - largeurTexte) / 2;
M5.Display.setCursor(x, 25);
M5.Display.print(TxtCorps);
}
// *********************************************
void ClearCorps() {
M5.Display.fillRect(0, 22, 240, 91, BLACK);
}// *********************************************
void Pied() {
ClearPied();
M5.Display.setTextSize(1);
M5.Display.setFont(&fonts::FreeMonoOblique9pt7b);
M5.Display.setTextColor(LIGHTGREY, BLACK);
M5.Display.setCursor(20, 107);
M5.Display.print(TxtPied);
}
// *********************************************
void ClearPied() {
M5.Display.fillRect(0, 107, 240, 14, BLACK);
}// *********************************************
void PiedII() {
ClearPiedII();
M5.Display.setTextSize(1);
M5.Display.setFont(&fonts::FreeMonoOblique9pt7b);
M5.Display.setTextColor(LIGHTGREY, BLACK);M5.Display.drawLine(0, 116, M5.Display.width(), 116, YELLOW);
char buffer[20];
snprintf(buffer, sizeof(buffer), " Wake-up @ %02dh%02dm | In: %s", TRIGGER_HOUR, TRIGGER_MINUTE, getTimeUntilAlarm().c_str());
TxtPiedII = buffer;M5.Display.setCursor(3, 120);
M5.Display.print(TxtPiedII);
}// *********************************************
void ClearPiedII() {
M5.Display.fillRect(0, 120, 240, 14, BLACK);
}// *********************************************
void drawBattery(int x, int y, float percentage) {
int width = 30;
int height = 11;
int terminalWidth = 2;
uint16_t color = (percentage > 70) ? BLUE : (percentage > 30) ? YELLOW
: RED;
M5.Display.drawRect(x, y, width, height, WHITE);
M5.Display.fillRect(x + width, y + (height / 3), terminalWidth, height / 3, WHITE);
int chargeWidth = (int)((percentage / 100.0) * (width - 2));
M5.Display.fillRect(x + 1, y + 1, chargeWidth, height - 2, color);
}// *********************************************
void drawProgressBar(int percentage) {
int barWidth = 180;
int barHeight = 10;
int x = 20;
int y = 50;
M5.Display.fillRect(x, y, barWidth, barHeight, BLACK);
M5.Display.drawRect(x, y, barWidth, barHeight, WHITE);
int fillWidth = (percentage * barWidth) / 100;
M5.Display.fillRect(x + 1, y + 1, fillWidth, barHeight - 2, GREEN);
M5.Display.setCursor(x + barWidth + 5, y);
M5.Display.printf("%d%%", percentage);
}// *********************************************
void connectToStrongestWiFi() {
TxtCorps = " ";
Corps();
M5.Display.setTextColor(LIGHTGREY, BLACK);
M5.Display.setCursor(10, 26);
M5.Display.println("Scanning Wi-Fi...");drawProgressBar(0);
int numNetworks = WiFi.scanNetworks();
if (numNetworks <= 0) {
ClearCorps();
M5.Display.setTextColor(YELLOW, BLACK);
M5.Display.setCursor(10, 40);
M5.Display.println("No Wi-Fi found");
return;
}for (int i = 0; i <= 100; i += 5) {
drawProgressBar(i);
delay(28);
}struct Network {
String ssid;
int rssi;
};Network networks[numNetworks];
for (int i = 0; i < numNetworks; i++) {
networks[i].ssid = WiFi.SSID(i);
networks[i].rssi = WiFi.RSSI(i);
}for (int i = 0; i < numNetworks - 1; i++) {
for (int j = i + 1; j < numNetworks; j++) {
if (networks[i].rssi < networks[j].rssi) {
Network temp = networks[i];
networks[i] = networks[j];
networks[j] = temp;
}
}
}TxtCorps = "SSID found (" + String(numNetworks) + "):\n";
for (int i = 0; i < min(4, numNetworks); i++) {
TxtCorps += String(i + 1) + ". " + networks[i].ssid + "\n";
}
delay(9);
Corps();for (int n = 0; n < numNetworks; n++) {
String candidateSSID = networks[n].ssid;int foundIndex = -1; for (int i = 0; i < routerCount; i++) { if (String(routers[i].ssid) == candidateSSID) { foundIndex = i; break; } } if (foundIndex == -1) { continue; } ClearCorps(); M5.Display.setCursor(10, 26); M5.Display.setTextColor(LIGHTGREY, BLACK); M5.Display.println("Connecting to:"); M5.Display.setCursor(10, 46); M5.Display.setTextColor(SKYBLUE, BLACK); M5.Display.println(routers[foundIndex].ssid); // Démarrer la connexion WiFi.begin(routers[foundIndex].ssid, routers[foundIndex].password); unsigned long start = millis(); bool connected = false; while (millis() - start < 10000) { delay(194); M5.Display.print("."); if (WiFi.status() == WL_CONNECTED) { connected = true; break; } } ClearCorps(); if (connected) { M5.Display.setCursor(10, 66); M5.Display.setTextColor(GREEN, BLACK); M5.Display.println("Connected @ "); M5.Display.setCursor(10, 86); M5.Display.print("IP: "); M5.Display.println(WiFi.localIP()); lastConnectedRouterIndex = foundIndex; return; } else { M5.Display.setCursor(10, 66); M5.Display.setTextColor(RED, BLACK); M5.Display.println("Connection failed"); delay(1940); ClearCorps(); }}
// Aucun réseau connu trouvé ou aucune connexion réussie
M5.Display.setCursor(10, 26);
M5.Display.setTextColor(YELLOW, BLACK);
M5.Display.println("No SSID found");
}// *********************************************
void audioTask(void* param) {
(void)param;
while (true) {
if (!audioRequested) {
vTaskDelay(pdMS_TO_TICKS(200));
continue;
}xSemaphoreTake(resourceMutex, portMAX_DELAY); audioPlaying = true; Serial.println("[AudioTask] Playing WAV file..."); if (!playSdWav(WAV_FILENAME)) { M5.Display.fillRect(0, 22, 240, 91, BLACK); M5.Display.setCursor(10, 26); M5.Display.setTextColor(RED, BLACK); M5.Display.println("Playback failed!"); } else { M5.Display.fillRect(0, 22, 240, 91, BLACK); M5.Display.setCursor(10, 26); M5.Display.setTextColor(GREEN, BLACK); M5.Display.println("Alarm ended !"); } audioRequested = false; audioPlaying = false; xSemaphoreGive(resourceMutex);}
}// *********************************************
String Clavier(String TxtClavier) {
M5Cardputer.update();
M5.Display.fillRect(0, 120, 240, 14, BLACK);
data = TxtClavier + "> ";
M5.Display.drawString(data, 3, 120);unsigned long startTime = millis();
const unsigned long TIMEOUT = 30000;while (millis() - startTime < TIMEOUT) {
M5Cardputer.update();
if (M5Cardputer.Keyboard.isChange() && M5Cardputer.Keyboard.isPressed()) {
Keyboard_Class::KeysState status = M5Cardputer.Keyboard.keysState();for (auto i : status.word) { data += i; } if (status.del && data.length() > (TxtClavier.length() + 2)) { data.remove(data.length() - 1); } if (status.enter) { data.remove(0, TxtClavier.length() + 2); String result = data; data = ""; return result; } M5.Display.fillRect(0, 120, 240, 14, BLACK); M5.Display.drawString(data, 3, 120); } delay(10);}
return "";
}// *********************************************
void saveSettingsToLittleFS() {
DynamicJsonDocument doc(256);
doc["contrast"] = contrast;
doc["volume"] = volume;
doc["minutes"] = TRIGGER_MINUTE;File file = LittleFS.open("/settings.json", "w");
if (!file) {
Serial.println("Ăchec sauvegarde sur LittleFS.");
return;
}serializeJson(doc, file);
file.close();
Serial.println("[LittleFS] ParamÚtres sauvegardés :");
Serial.print(" - Contraste: ");
Serial.println(contrast);
Serial.print(" - Volume: ");
Serial.println(volume);
Serial.print(" - Minutes de réveil: ");
Serial.println(TRIGGER_MINUTE);
}// *********************************************
void handleButtonA() {
if (!isWaitingForInput && M5Cardputer.BtnA.wasPressed()) {
isWaitingForInput = true;
ClearCorps();
M5.Display.setTextColor(WHITE, BLACK);M5.Display.setCursor(10, 27); M5.Display.println("1. Contrast 0-255"); M5.Display.setCursor(10, 44); M5.Display.println("2. Volume. 0-255"); M5.Display.setCursor(10, 61); M5.Display.println("3. Test WAV"); M5.Display.setCursor(10, 78); M5.Display.println("4. Rv: 6h 0-59m."); M5.Display.setCursor(10, 95); M5.Display.println("CR = Cancel"); String choice = Clavier("Selection "); choice.trim(); if (choice == "1") { ClearCorps(); M5.Display.setCursor(10, 30); M5.Display.println("Contrast " + String(contrast)); M5.Display.setCursor(10, 50); M5.Display.println("Input 0 -> 255"); M5.Display.setCursor(10, 90); M5.Display.println("CR => Cancel"); String contrastStr = Clavier("Contrast: "); if (contrastStr.length() > 0) { bool isNumeric = true; for (int i = 0; i < contrastStr.length(); i++) { if (!isdigit(contrastStr.charAt(i))) { isNumeric = false; break; } } if (isNumeric) { int newContrast = contrastStr.toInt(); if (newContrast >= 0 && newContrast <= 255) { contrast = newContrast; M5.Display.setBrightness(contrast); saveSettingsToLittleFS(); M5.Display.setCursor(10, 90); M5.Display.println("Updated ! "); delay(1000); } else { M5.Display.setCursor(10, 90); M5.Display.println("Out of range ! "); delay(1000); } } else { M5.Display.setCursor(10, 90); M5.Display.println("Wrong input ! "); delay(1000); } } else { M5.Display.setCursor(10, 90); M5.Display.println("Canceled ! "); delay(1000); } } else if (choice == "2") { ClearCorps(); M5.Display.setCursor(10, 30); M5.Display.println("Volume actuel: " + String(M5Cardputer.Speaker.getVolume())); M5.Display.setCursor(10, 50); M5.Display.println("Input 0 -> 255"); M5.Display.setCursor(10, 90); M5.Display.println("CR => Cancel"); String volumeStr = Clavier("Volume: "); if (volumeStr.length() > 0) { bool isNumeric = true; for (int i = 0; i < volumeStr.length(); i++) { if (!isdigit(volumeStr.charAt(i))) { isNumeric = false; break; } } if (isNumeric) { int inputVolume = volumeStr.toInt(); if (inputVolume >= 0 && inputVolume <= 255) { volume = inputVolume; M5Cardputer.Speaker.setVolume(volume); saveSettingsToLittleFS(); M5.Display.setCursor(10, 90); M5.Display.println("Updated ! "); delay(1000); } else { M5.Display.setCursor(10, 90); M5.Display.println("Out of range ! "); delay(1000); } } else { M5.Display.setCursor(10, 90); M5.Display.println("Wrong input ! "); delay(1000); } } else { M5.Display.setCursor(10, 90); M5.Display.println("Canceled ! "); delay(1000); } } else if (choice == "3") { ClearCorps(); M5.Display.setCursor(10, 30); M5.Display.println("Test WAV file..."); M5.Display.setCursor(10, 50); M5.Display.println("Playing: " + String(WAV_FILENAME)); if (SD.exists(WAV_FILENAME)) { M5Cardputer.Speaker.setVolume(volume); audioRequested = true; } else { M5.Display.setCursor(10, 70); M5.Display.setTextColor(RED, BLACK); M5.Display.println("WAV not found ! "); delay(1500); } } else if (choice == "4") { int newMinutes; if (newMinutes >= 0 && newMinutes <= 59) { TRIGGER_MINUTE = newMinutes; saveSettingsToLittleFS(); PiedII(); } ClearCorps(); M5.Display.setCursor(10, 30); char buffer[20]; snprintf(buffer, sizeof(buffer), "Wake-up @ %02dh %02dm", TRIGGER_HOUR, TRIGGER_MINUTE); M5.Display.println(buffer); M5.Display.setCursor(10, 50); M5.Display.println("Input 00' -> 59'"); M5.Display.setCursor(10, 90); M5.Display.println("CR => Cancel"); String minutesStr = Clavier("Minutes: "); if (minutesStr.length() > 0) { bool isNumeric = true; for (int i = 0; i < minutesStr.length(); i++) { if (!isdigit(minutesStr.charAt(i))) { isNumeric = false; break; } } if (isNumeric) { int newMinutes = minutesStr.toInt(); if (newMinutes >= 0 && newMinutes <= 59) { TRIGGER_MINUTE = newMinutes; saveSettingsToLittleFS(); M5.Display.setCursor(10, 90); M5.Display.println("Updated ! "); delay(1000); } else { M5.Display.setCursor(10, 90); M5.Display.println("Out of range !"); delay(1000); } } else { M5.Display.setCursor(10, 90); M5.Display.println("Wrong input ! "); delay(1000); } } else { M5.Display.setCursor(10, 90); M5.Display.println("Canceled ! "); delay(1000); } } Corps(); Pied(); PiedII(); isWaitingForInput = false;}
}// ==============================================
void turnScreenOn() {
if (lowPowerMode || !screenOn) {
M5.Display.setBrightness(contrast);
M5.Display.clear();
EnTete();
Corps();
Pied();
PiedII();
lowPowerMode = false;
screenOn = true;
}
lastActivityTime = millis();
}// ==============================================
void turnScreenToLowPower() {
if (!lowPowerMode) {
M5.Display.setBrightness(5);
lowPowerMode = true;
}
}// ==============================================
void checkScreenTimeout() {
if (millis() - lastActivityTime > SCREEN_TIMEOUT) {
turnScreenToLowPower();
}
}// ==============================================
String getTimeUntilAlarm() {
timeClient.update();time_t utc = timeClient.getEpochTime();
time_t local = CE.toLocal(utc, &tcr);int currentHour = hour(local);
int currentMinute = minute(local);
int currentSecond = second(local);int alarmHour = TRIGGER_HOUR;
int alarmMinute = TRIGGER_MINUTE;int hoursUntilAlarm = alarmHour - currentHour;
int minutesUntilAlarm = alarmMinute - currentMinute;
int secondsUntilAlarm = 0 - currentSecond;if (hoursUntilAlarm < 0) {
hoursUntilAlarm += 24;
}if (minutesUntilAlarm < 0) {
minutesUntilAlarm += 60;
hoursUntilAlarm--;
}if (secondsUntilAlarm < 0) {
secondsUntilAlarm += 60;
minutesUntilAlarm--;
}if (minutesUntilAlarm < 0) {
minutesUntilAlarm = 0;
}char timeStr[20];
snprintf(timeStr, sizeof(timeStr), "%02dh%02dm", hoursUntilAlarm, minutesUntilAlarm);
return String(timeStr);TxtPiedII = " Alarm in: " + getTimeUntilAlarm();
PiedII();
}// ==============================================
void updateDisplay() {
if (!screenOn) return;timeClient.update();
time_t utc = timeClient.getEpochTime();
time_t local = CE.toLocal(utc, &tcr);int hh = hour(local);
int mm = minute(local);
int ss = second(local);
int currentDay = day(local);
int currentMonth = month(local);
int currentYear = year(local);M5.Display.fillRect(0, 22, 240, 54, BLACK);
char timeBuf[16];
snprintf(timeBuf, sizeof(timeBuf), "%02d:%02d:%02d", hh, mm, ss);
M5.Display.setFont(&fonts::FreeSansBold18pt7b);
M5.Display.setTextSize(1);
M5.Display.setTextColor(YELLOW, BLACK);
int tw = M5.Display.textWidth(timeBuf);
int tx = (M5.Display.width() - tw) / 2;
M5.Display.setCursor(tx, 25);
M5.Display.print(timeBuf);char dateBuf[24];
snprintf(dateBuf, sizeof(dateBuf), "%02d/%02d/%04d", currentDay, currentMonth, currentYear);
M5.Display.setFont(&fonts::FreeMonoOblique9pt7b);
M5.Display.setTextSize(1);
M5.Display.setTextColor(LIGHTGREY, BLACK);
int dw = M5.Display.textWidth(dateBuf);
int dx = (M5.Display.width() - dw) / 2;
M5.Display.setCursor(dx, 58);
M5.Display.print(dateBuf);M5.Display.setTextColor(LIGHTGREY, BLACK);
M5.Display.setCursor(2, 76);
M5.Display.print("Wi-Fi ");
if (WiFi.status() == WL_CONNECTED) {
M5.Display.setTextColor(SKYBLUE, BLACK);
M5.Display.setCursor(69, 76);
M5.Display.println(WiFi.SSID());
M5.Display.setTextColor(LIGHTGREY, BLACK);
M5.Display.setCursor(2, 96);
M5.Display.print("IP ");
M5.Display.setTextColor(SKYBLUE, BLACK);
M5.Display.setCursor(69, 96);
M5.Display.println(WiFi.localIP());
} else {
M5.Display.setTextColor(YELLOW, BLACK);
M5.Display.println("No Wifi");
}
}// ==============================================
void loadSettingsFromLittleFS() {
if (!LittleFS.exists("/settings.json")) {
Serial.println("Fichier settings.json introuvable.");
return;
}
File file = LittleFS.open("/settings.json", "r");
if (!file) {
Serial.println("Ăchec ouverture fichier settings.json.");
return;
}
DynamicJsonDocument doc(256);
DeserializationError err = deserializeJson(doc, file);
file.close();
if (err) {
Serial.println("Erreur parsing JSON (settings).");
return;
}
if (doc.containsKey("contrast")) {
contrast = doc["contrast"];
Serial.print("[LittleFS] Contraste chargé: ");
Serial.println(contrast);
}
if (doc.containsKey("volume")) {
volume = doc["volume"];
Serial.print("[LittleFS] Volume chargé: ");
Serial.println(volume);
}
if (doc.containsKey("minutes")) {
TRIGGER_MINUTE = doc["minutes"];
Serial.print("[LittleFS] Minutes de réveil chargées: ");
Serial.println(TRIGGER_MINUTE);
}M5.Display.setBrightness(contrast);
M5Cardputer.Speaker.setVolume(volume);
}// ==============================================
void loadDefaultRouters() {
routerCount = 5;
routers[0] = { "SSID1", "Pass1" };
routers[1] = { "SSID2", "Pass2" };
routers[2] = { "SSID3", "Pass3" };
routers[3] = { "SSID4", "Pass4" };
routers[4] = { "SSID5", "Pass5" };
Serial.println("[LittleFS] Liste de routers par défaut chargée (5 entrées).");
}// ==============================================
bool loadRoutersFromLittleFS() {
if (!LittleFS.exists("/routers.json")) {
Serial.println("Fichier routers.json introuvable.");
return false;
}File file = LittleFS.open("/routers.json", "r");
if (!file) {
Serial.println("Ăchec ouverture fichier routers.json.");
return false;
}
DynamicJsonDocument doc(1024);
DeserializationError err = deserializeJson(doc, file);
file.close();if (err) {
Serial.println("Erreur parsing JSON (routers).");
return false;
}JsonArray arr = doc["routers"];
routerCount = 0;
Serial.println("[LittleFS] Liste des routers chargés :");
for (JsonObject o : arr) {
if (routerCount >= MAX_ROUTERS) break;
strncpy(routers[routerCount].ssid, o["ssid"], sizeof(routers[routerCount].ssid) - 1);
strncpy(routers[routerCount].password, o["pwd"], sizeof(routers[routerCount].password) - 1);
Serial.print(" - SSID: ");
Serial.print(routers[routerCount].ssid);
Serial.print(", Password: ");
Serial.println(routers[routerCount].password);
routerCount++;
}
return true;
}// ==============================================
void setup() {
Serial.begin(115200);
Serial.println("Démarrage du M5Cardputer...");auto cfg = M5.config();
M5Cardputer.begin(cfg, true);M5.Display.setRotation(1);
M5Cardputer.Display.setTextSize(1);
M5.Display.setFont(&fonts::FreeMonoOblique9pt7b);
M5.Display.setBrightness(contrast);
M5.Display.clear();canvas.setTextFont(&fonts::FreeMonoOblique9pt7b);
canvas.setTextSize(1);
canvas.pushSprite(3, 120);if (!LittleFS.begin(true)) {
Serial.println("Erreur LittleFS");
} else {
Serial.println("LittleFS initialisé.");
delay(999);
loadSettingsFromLittleFS();
Serial.println("Chargement des paramÚtres depuis LittleFS terminé.");
if (!loadRoutersFromLittleFS()) {
loadDefaultRouters();
}
Serial.println("Chargement des routers terminé.");
}EnTete();
Serial.println("OLED initialisé.");M5Cardputer.Speaker.begin();
Serial.println("Speaker initialisé.");if (!SD.begin()) {
Serial.println("SD init failed");
} else {
Serial.println("SD ready");
}WiFi.mode(WIFI_STA);
connectToStrongestWiFi();timeClient.begin();
timeClient.update();
setTime(timeClient.getEpochTime());EnTete();
Corps();
Pied();
PiedII();resourceMutex = xSemaphoreCreateMutex();
xTaskCreatePinnedToCore(audioTask, "audioTask", 4096, NULL, 2, &audioTaskHandle, 1);lastActivityTime = millis();
}// ==============================================
void loop() {
checkScreenTimeout();
handleButtonA();timeClient.update();
time_t utc = timeClient.getEpochTime();
time_t local = CE.toLocal(utc, &tcr);
int hh = hour(local);
int mm = minute(local);bool shouldSync = (mm == 0) && (hh == 9 || hh == 12 || hh == 15 || hh == 18);
if (shouldSync && lastSyncMinute != (hh * 60 + mm)) {
lastSyncMinute = hh * 60 + mm;
if (WiFi.status() == WL_CONNECTED) {
if (timeClient.forceUpdate()) {
unsigned long epoch = timeClient.getEpochTime();
time_t t = (time_t)epoch;
struct tm tm;
gmtime_r(&t, &tm);
M5.Rtc.setDateTime(&tm);
}
}
}
if (!triggeredToday && hh == TRIGGER_HOUR && mm == TRIGGER_MINUTE) {
if (!audioPlaying && SD.exists(WAV_FILENAME)) {
audioRequested = true;
triggeredToday = true;
}
}if (hh == 0 && mm == 0) {
triggeredToday = false;
}if (screenOn) {
updateDisplay();
}M5Cardputer.update();
if (M5Cardputer.Keyboard.isChange() && M5Cardputer.Keyboard.isPressed()) {
lastActivityTime = millis();
turnScreenOn();
}
PiedII();
delay(940);
}
Hello! It looks like you're interested in this conversation, but you don't have an account yet.
Getting fed up of having to scroll through the same posts each visit? When you register for an account, you'll always come back to exactly where you were before, and choose to be notified of new replies (either via email, or push notification). You'll also be able to save bookmarks and upvote posts to show your appreciation to other community members.
With your input, this post could be even better đ
Register Login