🤖Have you ever tried Chat.M5Stack.com before asking??😎

Categories

  • Firmware updates, Hardware Revisions, New Product info can be found here.

    75 Topics
    826 Posts
    KhanFromNorthK
    You can continue developing on the M5Stack Core2 reliably. The key is to bypass UIFlow entirely and use MicroPython directly through Thonny or VS Code.
  • M5Stack Events

    8 Topics
    31 Posts
    kurikoK
    @Apex574R Does the Userdemo firmware running normally?
  • Wish for a feature that doesn't exist yet? this is the place to ask for it. we will collect all the requirements and enquiry's and who knows ... sometime wish might come true!

    223 Topics
    680 Posts
    S
    Hello! I really love my Cardputer ADV, but I just wanted to mention something that, as a new user, didn't feel very intuitive. The original cardputer and the ADV differ to the point where firmware written for one often won't work on the other. As a new user, it was confusing that M5Burner doesn't have separate sections for the ADV and original, instead opting to have one section. Firmware creators (and even m5stack themselves), end up making two version of the firmware (one for the ADV and one for the original), and posting them in the same cardputer section. New users could miss the correct firmware version, and install the wrong one, or get frustrated when firmware that only supports the original model doesn't work on their ADV. I feel a lot of this confusion could be avoided if the m5stack menu had two different sections for the original and ADV model.
  • Forum rules - Announcements - General chit chat (ESP-32, ESP-8266, IoT, Raspberry Pi etc...)

    976 Topics
    3k Posts
    S
    Can anyone help me find a replacement smd button as the one on the pcb the A button (the big button that says m5 on) fell off
  • Core and Modules info.

    3k Topics
    14k Posts
    V
    The same thing. AI mode didn't work, no leds on, no reply on startup phrase
  • Projects Sharing.

    498 Topics
    2k Posts
    M
    // 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; // UTC WiFiUDP 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); }
  • This is the place to discuss driver issues, M5 burner troubleshooting, and development of software compatible with M5Stack

    3k Topics
    10k Posts
    L
    I turned an M5Stack Cardputer-Adv into a physical control surface for an AI coding agent (Claude Code) over BLE + MCP. Built on top of cardputer-claude-os. [image: device.jpg] Physical approval gate — a hook routes the agent's shell commands and file edits to the Cardputer. Read-only commands pass through; ordinary ones take a single Enter on the device; destructive ones (rm -rf, git push, sudo, editing secrets) need a sustained hold-Y gesture that prompt-injection can't fake. If the device is away, it falls back to the terminal — the Cardputer is an optional gate, never a dependency. Ordinary action — one Enter: [image: approve.jpg] Destructive action — hold Y for ~3s: [image: danger.jpg] Other bits Always-on usage dashboard: today's spend, 5h/7d subscription utilization, battery %, and a resident pixel-crab mascot. notify / ask / confirm tools so the agent can buzz a banner, ask a multiple-choice question, or demand confirmation. Notes for fellow Adv owners The Adv's ES8311 codec only makes sound if your main loop calls M5.update() every iteration — took me a while to track down. The app is big, so it ships as compiled .mpy (source-form import OOMs the launcher), deployed into the cardputer-claude-os launcher bundle. Code (MIT) + setup: https://github.com/loml13/cardputer-claude-mcp Feedback welcome
  • 264 Topics
    575 Posts
    J
    Hola, me llego la pingequa 2 in 1 con 433 mhz, instale bruce adv nrf24 mod v.2.0 de alienses creo que es el desarrolador, pero no me reconoce la placa, pense que era plug and play ese mod. me ayudan y gracias