From 319242c467253632ad6299c8651ad266f48d125c Mon Sep 17 00:00:00 2001 From: "nagham.kheir25" Date: Sat, 30 May 2026 20:48:36 +0300 Subject: [PATCH 01/29] Pin MQTT to Krake PubInv and clean 1-100 volume handling --- Firmware/GPAD_API/GPAD_API/DFPlayer.cpp | 29 ++-- Firmware/GPAD_API/GPAD_API/DFPlayer.h | 2 +- Firmware/GPAD_API/GPAD_API/GPAD_API.ino | 171 +++++---------------- Firmware/GPAD_API/GPAD_API/GPAD_HAL.cpp | 15 +- Firmware/GPAD_API/GPAD_API/GPAD_menu.cpp | 16 +- Firmware/GPAD_API/data/PMD_GPAD_API.html | 2 +- Firmware/GPAD_API/data/device-monitor.html | 6 +- Firmware/GPAD_API/data/js/pmd.js | 2 +- Firmware/GPAD_API/data/js/settings.js | 3 +- Firmware/GPAD_API/data/settings.html | 4 +- 10 files changed, 71 insertions(+), 179 deletions(-) diff --git a/Firmware/GPAD_API/GPAD_API/DFPlayer.cpp b/Firmware/GPAD_API/GPAD_API/DFPlayer.cpp index 5e27f2e1..308d1f3e 100644 --- a/Firmware/GPAD_API/GPAD_API/DFPlayer.cpp +++ b/Firmware/GPAD_API/GPAD_API/DFPlayer.cpp @@ -8,9 +8,13 @@ extern HardwareSerial uartSerial2; const int LED_PIN = 13; // Krake const int nDFPlayer_BUSY = 4; // active LOW BUSY pin from DFPlayer +const int MIN_VOLUME_PERCENT = 1; +const int MAX_VOLUME_PERCENT = 100; +const int MIN_DFPLAYER_VOLUME = 1; +const int MAX_DFPLAYER_VOLUME = 30; bool isDFPlayerDetected = false; -int volumeDFPlayer = 20; // Range: 1 to 30 +int volumeDFPlayer = 20; // Range: 1 to 100 (%) int numberFilesDF = 0; // Number of audio files found on SD card extern bool currentlyMuted; char command; @@ -80,17 +84,19 @@ void checkSerial(void) if (command == '+') { - dfPlayer.volumeUp(); + setVolume(volumeDFPlayer + 1); DBG_PRINT(F("Current volume: ")); - DBG_PRINTLN(dfPlayer.readVolume()); + DBG_PRINT(volumeDFPlayer); + DBG_PRINTLN(F("%")); menu_opcoes(); } if (command == '-') { - dfPlayer.volumeDown(); + setVolume(volumeDFPlayer - 1); DBG_PRINT(F("Current volume: ")); - DBG_PRINTLN(dfPlayer.readVolume()); + DBG_PRINT(volumeDFPlayer); + DBG_PRINTLN(F("%")); menu_opcoes(); } @@ -156,7 +162,7 @@ void setupDFPlayer() DBG_PRINTLN(F("Warning: unusual DFPlayer state. Possible clone/module variant, continuing test.")); } - dfPlayer.volume(volumeDFPlayer); + setVolume(volumeDFPlayer); delayWithYield(300); numberFilesDF = dfPlayer.readFileCounts(); @@ -176,15 +182,16 @@ void setupDFPlayer() menu_opcoes(); } -void setVolume(int oneToThirty) +void setVolume(int oneToHundred) { - if (oneToThirty < 1) oneToThirty = 1; - if (oneToThirty > 30) oneToThirty = 30; + if (oneToHundred < MIN_VOLUME_PERCENT) oneToHundred = MIN_VOLUME_PERCENT; + if (oneToHundred > MAX_VOLUME_PERCENT) oneToHundred = MAX_VOLUME_PERCENT; - volumeDFPlayer = oneToThirty; + volumeDFPlayer = oneToHundred; + const int dfpVolume = map(volumeDFPlayer, MIN_VOLUME_PERCENT, MAX_VOLUME_PERCENT, MIN_DFPLAYER_VOLUME, MAX_DFPLAYER_VOLUME); if (isDFPlayerDetected) { - dfPlayer.volume(volumeDFPlayer); + dfPlayer.volume(dfpVolume); } } diff --git a/Firmware/GPAD_API/GPAD_API/DFPlayer.h b/Firmware/GPAD_API/GPAD_API/DFPlayer.h index f80f00d2..0c9ae2c8 100644 --- a/Firmware/GPAD_API/GPAD_API/DFPlayer.h +++ b/Firmware/GPAD_API/GPAD_API/DFPlayer.h @@ -21,7 +21,7 @@ void checkSerial(void); void menu_opcoes(); void serialSplashDFP(); -void setVolume(int oneToThirty); +void setVolume(int oneToHundred); extern int volumeDFPlayer; extern int numberFilesDF; diff --git a/Firmware/GPAD_API/GPAD_API/GPAD_API.ino b/Firmware/GPAD_API/GPAD_API/GPAD_API.ino index 0b9d4a84..9fe1bd2d 100644 --- a/Firmware/GPAD_API/GPAD_API/GPAD_API.ino +++ b/Firmware/GPAD_API/GPAD_API/GPAD_API.ino @@ -193,30 +193,24 @@ WifiOTA::Manager wifiManager(WiFi, debugSerial); // MQTT Broker const char *DEFAULT_MQTT_BROKER_NAME = "krakepubinv.cloud.shiftr.io"; -const char *mqtt_user = "krakepubinv"; -const char *mqtt_password = "DlDmkWjp4I4kgDcA"; +const char *const MQTT_USER = "krakepubinv"; +const char *const MQTT_PASSWORD = "DlDmkWjp4I4kgDcA"; const size_t MQTT_BROKER_MAX_LEN = 64; const char *MQTT_CONFIG_PATH = "/mqtt.json"; char mqtt_broker_name[MQTT_BROKER_MAX_LEN] = {0}; -char mqtt_user_storage[32] = {0}; -char mqtt_password_storage[64] = {0}; struct BrokerOption { uint8_t index; const char *name; const char *host; uint16_t port; - const char *username; - const char *password; const char *notes; }; const BrokerOption brokerOptions[] = { - {2, "Public Shiftr", "public.cloud.shiftr.io", 1883, "public", "public", "Public test broker"}, - {1, "Krake PubInv", "krakepubinv.cloud.shiftr.io", 1883, "krakepubinv", "DlDmkWjp4I4kgDcA", "Project broker"}, + {1, "Krake PubInv", DEFAULT_MQTT_BROKER_NAME, 1883, "Project broker"}, }; const uint8_t BROKER_OPTION_COUNT = sizeof(brokerOptions) / sizeof(brokerOptions[0]); -const uint8_t DEFAULT_BROKER_INDEX = 1; -const uint8_t MQTT_FAILOVER_THRESHOLD = 5; +const uint8_t DEFAULT_BROKER_INDEX = 0; uint8_t selectedBrokerIndex = DEFAULT_BROKER_INDEX; uint8_t activeBrokerIndex = DEFAULT_BROKER_INDEX; uint8_t mqttFailCount = 0; @@ -236,6 +230,8 @@ const uint8_t MAX_WATCH_TOPICS = 12; char watchedTopics[MAX_WATCH_TOPICS][MAX_TOPIC_LEN]; uint8_t watchedTopicCount = 0; unsigned long wifiResetRequestedAtMs = 0; +unsigned long lastWiFiReconnectAttemptMs = 0; +const unsigned long WIFI_RECONNECT_INTERVAL_MS = 5000; const unsigned long MQTT_RECONNECT_INTERVAL_MS = 3000; const uint16_t MQTT_SOCKET_TIMEOUT_SECONDS = 1; unsigned long lastMqttReconnectAttemptMs = 0; @@ -276,7 +272,6 @@ bool writeMqttConfig(); bool loadMqttConfig(); void applyActiveMqttBrokerConfig(); bool selectMqttBrokerOption(uint8_t index); -int8_t findBrokerOptionByHost(const char *host); bool parseCsvIntoTopics(const String &rawTopics, char dest[][MAX_TOPIC_LEN], uint8_t &count, uint8_t maxTopics); String joinTopicsCsv(const char topics[][MAX_TOPIC_LEN], uint8_t count); @@ -323,12 +318,9 @@ void onWiFiDisconnect(WiFiEvent_t event, WiFiEventInfo_t info) { (void)event; (void)info; #endif - - // OPTION A: Simple Reconnect - //WiFi.begin(); - - // OPTION B: Re-trigger WiFiManager Portal - // wm.startConfigPortal("AutoConnectAP"); + + // Force immediate reconnect attempt in loop (including AP mode). + lastWiFiReconnectAttemptMs = 0; } @@ -420,8 +412,6 @@ void applyActiveMqttBrokerConfig() const BrokerOption &broker = brokerOptions[activeBrokerIndex]; strncpy(mqtt_broker_name, broker.host, MQTT_BROKER_MAX_LEN - 1); mqtt_broker_name[MQTT_BROKER_MAX_LEN - 1] = '\0'; - mqtt_user = broker.username; - mqtt_password = broker.password; client.setServer(broker.host, broker.port); client.setSocketTimeout(MQTT_SOCKET_TIMEOUT_SECONDS); } @@ -441,23 +431,6 @@ bool selectMqttBrokerOption(uint8_t index) return true; } -int8_t findBrokerOptionByHost(const char *host) -{ - if (host == nullptr || host[0] == '\0') - { - return -1; - } - - for (uint8_t i = 0; i < BROKER_OPTION_COUNT; i++) - { - if (strcmp(host, brokerOptions[i].host) == 0) - { - return static_cast(i); - } - } - return -1; -} - bool reconnect(bool force = false) { if (client.connected()) @@ -492,7 +465,7 @@ bool reconnect(bool force = false) debugSerial.print(" broker="); debugSerial.print(mqtt_broker_name); debugSerial.print(" user="); - debugSerial.print((mqtt_user != nullptr && mqtt_user[0] != '\0') ? mqtt_user : ""); + debugSerial.print(MQTT_USER); debugSerial.print(" ip="); debugSerial.print(WiFi.localIP()); debugSerial.print(" rssi="); @@ -504,7 +477,7 @@ bool reconnect(bool force = false) debugSerial.print(" pub="); debugSerial.print(publish_Ack_Topic); debugSerial.print(" ... "); - if (client.connect(clientId, mqtt_user, mqtt_password, publish_Ack_Topic, 1, true, willPayload)) + if (client.connect(clientId, MQTT_USER, MQTT_PASSWORD, publish_Ack_Topic, 1, true, willPayload)) { debugSerial.print("success at: "); debugSerial.println(millis()); @@ -540,17 +513,6 @@ bool reconnect(bool force = false) debugSerial.println(mqttStateDescription(client.state())); mqttFailCount++; brokerState = BROKER_FAILED; - if (mqttFailCount >= MQTT_FAILOVER_THRESHOLD) - { - mqttFailCount = 0; - brokerState = BROKER_RETRYING; - activeBrokerIndex = (activeBrokerIndex + 1) % BROKER_OPTION_COUNT; - const BrokerOption &nextBroker = brokerOptions[activeBrokerIndex]; - debugSerial.print("MQTT failover to "); - debugSerial.print(nextBroker.name); - debugSerial.print(" host="); - debugSerial.println(nextBroker.host); - } yield(); return false; } @@ -1144,10 +1106,6 @@ bool writeMqttConfig() } String payload = "{"; - payload += "\"broker\":\"" + jsonEscape(String(mqtt_broker_name)) + "\","; - payload += "\"selectedBrokerIndex\":\"" + String(selectedBrokerIndex) + "\","; - payload += "\"mqttUser\":\"" + jsonEscape(String(mqtt_user != nullptr ? mqtt_user : "")) + "\","; - payload += "\"mqttPassword\":\"" + jsonEscape(String(mqtt_password != nullptr ? mqtt_password : "")) + "\","; payload += "\"subscribeTopic\":\"" + jsonEscape(String(subscribe_Alarm_Topic)) + "\","; payload += "\"publishTopic\":\"" + jsonEscape(String(publish_Ack_Topic)) + "\","; payload += "\"subscribeTopics\":\"" + jsonEscape(joinedExtraTopics()) + "\","; @@ -1180,47 +1138,6 @@ bool loadMqttConfig() file.close(); String value; - bool loadedBrokerIndex = false; - if (extractJsonString(content, "broker", value)) - { - value.trim(); - if (value.length() > 0 && value.length() < MQTT_BROKER_MAX_LEN) - { - value.toCharArray(mqtt_broker_name, MQTT_BROKER_MAX_LEN); - } - } - - if (extractJsonString(content, "selectedBrokerIndex", value)) - { - value.trim(); - const int parsedIndex = value.toInt(); - if (value.length() > 0 && parsedIndex >= 0 && parsedIndex < BROKER_OPTION_COUNT) - { - selectedBrokerIndex = static_cast(parsedIndex); - activeBrokerIndex = selectedBrokerIndex; - loadedBrokerIndex = true; - } - } - - if (extractJsonString(content, "mqttUser", value)) - { - value.trim(); - if (value.length() < sizeof(mqtt_user_storage)) - { - value.toCharArray(mqtt_user_storage, sizeof(mqtt_user_storage)); - mqtt_user = mqtt_user_storage; - } - } - - if (extractJsonString(content, "mqttPassword", value)) - { - if (value.length() < sizeof(mqtt_password_storage)) - { - value.toCharArray(mqtt_password_storage, sizeof(mqtt_password_storage)); - mqtt_password = mqtt_password_storage; - } - } - if (extractJsonString(content, "subscribeTopic", value)) { value.trim(); @@ -1267,15 +1184,6 @@ bool loadMqttConfig() value.toCharArray(device_role, DEVICE_ROLE_MAX_LEN); } } - if (!loadedBrokerIndex) - { - const int8_t matchedIndex = findBrokerOptionByHost(mqtt_broker_name); - if (matchedIndex >= 0) - { - selectedBrokerIndex = static_cast(matchedIndex); - activeBrokerIndex = selectedBrokerIndex; - } - } applyActiveMqttBrokerConfig(); return true; } @@ -1772,7 +1680,6 @@ void setupOTA() { String payload = "{"; payload += "\"broker\":\"" + jsonEscape(String(mqtt_broker_name)) + "\","; - payload += "\"mqttUser\":\"" + jsonEscape(String(mqtt_user != nullptr ? mqtt_user : "")) + "\","; payload += "\"mqttConnected\":" + String(client.connected() ? "true" : "false") + ","; payload += "\"mqttState\":" + String(client.state()) + ","; payload += "\"mqttStateText\":\"" + jsonEscape(String(mqttStateDescription(client.state()))) + "\","; @@ -1791,8 +1698,6 @@ void setupOTA() payload += "\"name\":\"" + jsonEscape(String(brokerOptions[i].name)) + "\","; payload += "\"host\":\"" + jsonEscape(String(brokerOptions[i].host)) + "\","; payload += "\"port\":" + String(brokerOptions[i].port) + ","; - payload += "\"username\":\"" + jsonEscape(String(brokerOptions[i].username)) + "\","; - payload += "\"password\":\"" + jsonEscape(String(brokerOptions[i].password)) + "\","; payload += "\"notes\":\"" + jsonEscape(String(brokerOptions[i].notes)) + "\","; payload += "\"active\":" + String(i == selectedBrokerIndex ? "true" : "false"); payload += "}"; @@ -1829,35 +1734,6 @@ void setupOTA() } } - if (request->hasParam("mqttUser", true)) - { - String user = request->getParam("mqttUser", true)->value(); - user.trim(); - if (user.length() >= sizeof(mqtt_user_storage)) - { - errorMessage += "invalid mqttUser;"; - } - else - { - user.toCharArray(mqtt_user_storage, sizeof(mqtt_user_storage)); - mqtt_user = mqtt_user_storage; - } - } - - if (request->hasParam("mqttPassword", true)) - { - String password = request->getParam("mqttPassword", true)->value(); - if (password.length() >= sizeof(mqtt_password_storage)) - { - errorMessage += "invalid mqttPassword;"; - } - else - { - password.toCharArray(mqtt_password_storage, sizeof(mqtt_password_storage)); - mqtt_password = mqtt_password_storage; - } - } - if (request->hasParam("role", true)) { if (!applyRoleSetting(request->getParam("role", true)->value())) @@ -2304,6 +2180,30 @@ void setup() bool running_menu = false; bool menu_just_exited = false; +void serviceWiFiReconnect() +{ +#if defined HMWK || defined KRAKE + if (WiFi.status() == WL_CONNECTED) + { + return; + } + + if (wifiManager.getMode() != wifi_mode_t::WIFI_MODE_AP && wifiManager.getMode() != wifi_mode_t::WIFI_MODE_APSTA) + { + return; + } + + const unsigned long now = millis(); + if (lastWiFiReconnectAttemptMs != 0 && (now - lastWiFiReconnectAttemptMs) < WIFI_RECONNECT_INTERVAL_MS) + { + return; + } + + lastWiFiReconnectAttemptMs = now; + WiFi.reconnect(); +#endif +} + void serviceMqttClient() { #if defined HMWK || defined KRAKE @@ -2398,6 +2298,7 @@ void loop() { // MQTT gets the first and most frequent slices so inbound bursts are drained // before comparatively slow LCD/menu/audio work is serviced. + serviceWiFiReconnect(); serviceMqttClient(); serviceDeferredReset(); diff --git a/Firmware/GPAD_API/GPAD_API/GPAD_HAL.cpp b/Firmware/GPAD_API/GPAD_API/GPAD_HAL.cpp index 941800f8..177907d3 100644 --- a/Firmware/GPAD_API/GPAD_API/GPAD_HAL.cpp +++ b/Firmware/GPAD_API/GPAD_API/GPAD_HAL.cpp @@ -353,7 +353,7 @@ namespace case PAGE_WIFI: return 1; case PAGE_BROKER: - return 3; + return 2; case PAGE_MUTE: return 3; case PAGE_MAIN: @@ -958,7 +958,7 @@ namespace } else if (lcdPage == PAGE_BROKER) { - optionRow = (lcdPageOption == 0) ? 1 : (lcdPageOption == 1 ? 2 : 3); + optionRow = (lcdPageOption == 0) ? 1 : 3; } else if (lcdPage == PAGE_MUTE) { @@ -1911,11 +1911,6 @@ bool alarmActionSelectorHandlePress() else if (lcdPage == PAGE_BROKER) { if (lcdPageOption == 0) - { - resetLcdUiToMainPage(); - showActionFeedback(selectMqttBrokerOption(1) ? "Broker selected" : "Broker failed"); - } - else if (lcdPageOption == 1) { resetLcdUiToMainPage(); showActionFeedback(selectMqttBrokerOption(0) ? "Broker selected" : "Broker failed"); @@ -2128,9 +2123,9 @@ void renderWifiPage(char rows[LCD_ROWS][LCD_COLS + 1]) void renderBrokerPage(char rows[LCD_ROWS][LCD_COLS + 1]) { formatFullRow(rows[0], "Broker Select"); - formatFullRow(rows[1], "%c1 Krake PubInv", lcdPageOption == 0 ? '>' : ' '); - formatFullRow(rows[2], "%c2 Public Shiftr", lcdPageOption == 1 ? '>' : ' '); - formatFullRow(rows[3], "%cBack", lcdPageOption == 2 ? '>' : ' '); + formatFullRow(rows[1], "%cKrake PubInv", lcdPageOption == 0 ? '>' : ' '); + formatFullRow(rows[2], " Fixed broker"); + formatFullRow(rows[3], "%cBack", lcdPageOption == 1 ? '>' : ' '); } void renderMutePage(char rows[LCD_ROWS][LCD_COLS + 1]) diff --git a/Firmware/GPAD_API/GPAD_API/GPAD_menu.cpp b/Firmware/GPAD_API/GPAD_API/GPAD_menu.cpp index e529bd30..3f103d05 100644 --- a/Firmware/GPAD_API/GPAD_API/GPAD_menu.cpp +++ b/Firmware/GPAD_API/GPAD_API/GPAD_menu.cpp @@ -273,20 +273,11 @@ bool selectBroker(uint8_t index) return selected; } -result actionBrokerPublic(eventMask e) -{ - if (e == eventMask::enterEvent) - { - selectBroker(0); - } - return proceed; -} - result actionBrokerKrake(eventMask e) { if (e == eventMask::enterEvent) { - selectBroker(1); + selectBroker(0); } return proceed; } @@ -335,8 +326,7 @@ MENU(wifiMenu, "WiFi", Menu::doNothing, Menu::noEvent, Menu::wrapStyle, ); MENU(brokerMenu, "Broker", Menu::doNothing, Menu::noEvent, Menu::wrapStyle, - OP("1 Krake PubInv", actionBrokerKrake, enterEvent), - OP("2 Public Shiftr", actionBrokerPublic, enterEvent), + OP("Krake PubInv", actionBrokerKrake, enterEvent), OP("Back", actionBack, enterEvent) ); @@ -357,7 +347,7 @@ MENU(developerMenu, "Developer Mode", Menu::doNothing, Menu::noEvent, Menu::wrap MENU(mainMenu, "Settings", Menu::doNothing, Menu::noEvent, Menu::wrapStyle, OP("Info", actionInfo, enterEvent), SUBMENU(wifiMenu), - FIELD(volumeDFPlayer, "Volume", "%", 1, 30, 20, 1, action4, enterEvent, wrapStyle), + FIELD(volumeDFPlayer, "Volume", "%", 1, 100, 20, 1, action4, enterEvent, wrapStyle), SUBMENU(muteMenu), SUBMENU(developerMenu), SUBMENU(resetConfirmMenu), diff --git a/Firmware/GPAD_API/data/PMD_GPAD_API.html b/Firmware/GPAD_API/data/PMD_GPAD_API.html index 3d9a019f..319922a9 100644 --- a/Firmware/GPAD_API/data/PMD_GPAD_API.html +++ b/Firmware/GPAD_API/data/PMD_GPAD_API.html @@ -11,7 +11,7 @@
-
Program: PMD_Processing_MQTT (V0.37)
Role: Krake
Broker: public.cloud.shiftr.io
+
Program: PMD_Processing_MQTT (V0.37)
Role: Krake
Broker: krakepubinv.cloud.shiftr.io

Issue Alarm (multi-topic)

Select one or more topics from Settings and send PMD alarm payloads to all selected topics.

Selected topics: -
Result: -

Send Message / Alarm

Publish result: -
diff --git a/Firmware/GPAD_API/data/device-monitor.html b/Firmware/GPAD_API/data/device-monitor.html index f1aac381..29f93ca3 100644 --- a/Firmware/GPAD_API/data/device-monitor.html +++ b/Firmware/GPAD_API/data/device-monitor.html @@ -30,9 +30,9 @@

Shiftr MQTT Broker Monitor

Broker Connection

- - - + + +
diff --git a/Firmware/GPAD_API/data/js/pmd.js b/Firmware/GPAD_API/data/js/pmd.js index 66edb5f1..7dd9e126 100644 --- a/Firmware/GPAD_API/data/js/pmd.js +++ b/Firmware/GPAD_API/data/js/pmd.js @@ -12,7 +12,7 @@ async function loadTopics() { const data = await KrakeUI.getJson('/settings-data'); KrakeUI.setText('deviceRole', data.role || 'Krake'); - KrakeUI.setText('brokerName', data.broker || 'public.cloud.shiftr.io'); + KrakeUI.setText('brokerName', data.broker || 'krakepubinv.cloud.shiftr.io'); const list = KrakeUI.getPublishTopics(data); const select = KrakeUI.byId('topicSelect'); select.innerHTML = ''; diff --git a/Firmware/GPAD_API/data/js/settings.js b/Firmware/GPAD_API/data/js/settings.js index 22b6adf9..7d5ff47e 100644 --- a/Firmware/GPAD_API/data/js/settings.js +++ b/Firmware/GPAD_API/data/js/settings.js @@ -48,12 +48,11 @@ async function saveMqttConfig() { try { const role = 'Krake'; - const broker = getInputValue('broker').trim(); const subscribeTopicUi = getInputValue('subscribeTopic').trim(); const publishTopicUi = getInputValue('publishTopic').trim(); const subscribeTopics = getInputValue('topics'); const publishTopics = getInputValue('publishTopics'); - await KrakeUI.postForm('/config', { role, broker, subscribeTopic: publishTopicUi, publishTopic: subscribeTopicUi, subscribeTopics, publishTopics, publishDefaultTopic: publishTopicUi }); + await KrakeUI.postForm('/config', { role, subscribeTopic: publishTopicUi, publishTopic: subscribeTopicUi, subscribeTopics, publishTopics, publishDefaultTopic: publishTopicUi }); KrakeUI.showMessage('MQTT config updated and saved to /mqtt.json.'); await refreshSettings(); } catch (e) { KrakeUI.showMessage('Failed to save MQTT config: ' + e.message, true); } diff --git a/Firmware/GPAD_API/data/settings.html b/Firmware/GPAD_API/data/settings.html index 7acf46ef..79a8d62b 100644 --- a/Firmware/GPAD_API/data/settings.html +++ b/Firmware/GPAD_API/data/settings.html @@ -49,14 +49,14 @@

Sound

MQTT

-

Dynamic broker/topic/role assignment stored in /mqtt.json.

+

Krake PubInv is the fixed broker. Topic settings are stored in /mqtt.json.

- + From be362c2858193ee4dd858bfd9d03e0a9879d4199 Mon Sep 17 00:00:00 2001 From: "nagham.kheir25" Date: Sat, 30 May 2026 22:50:09 +0300 Subject: [PATCH 02/29] Revert web data-file changes causing UI regressions --- Firmware/GPAD_API/data/PMD_GPAD_API.html | 2 +- Firmware/GPAD_API/data/device-monitor.html | 6 +++--- Firmware/GPAD_API/data/js/pmd.js | 2 +- Firmware/GPAD_API/data/js/settings.js | 3 ++- Firmware/GPAD_API/data/settings.html | 4 ++-- 5 files changed, 9 insertions(+), 8 deletions(-) diff --git a/Firmware/GPAD_API/data/PMD_GPAD_API.html b/Firmware/GPAD_API/data/PMD_GPAD_API.html index 319922a9..3d9a019f 100644 --- a/Firmware/GPAD_API/data/PMD_GPAD_API.html +++ b/Firmware/GPAD_API/data/PMD_GPAD_API.html @@ -11,7 +11,7 @@
-
Program: PMD_Processing_MQTT (V0.37)
Role: Krake
Broker: krakepubinv.cloud.shiftr.io
+
Program: PMD_Processing_MQTT (V0.37)
Role: Krake
Broker: public.cloud.shiftr.io

Issue Alarm (multi-topic)

Select one or more topics from Settings and send PMD alarm payloads to all selected topics.

Selected topics: -
Result: -

Send Message / Alarm

Publish result: -
diff --git a/Firmware/GPAD_API/data/device-monitor.html b/Firmware/GPAD_API/data/device-monitor.html index 29f93ca3..f1aac381 100644 --- a/Firmware/GPAD_API/data/device-monitor.html +++ b/Firmware/GPAD_API/data/device-monitor.html @@ -30,9 +30,9 @@

Shiftr MQTT Broker Monitor

Broker Connection

- - - + + +
diff --git a/Firmware/GPAD_API/data/js/pmd.js b/Firmware/GPAD_API/data/js/pmd.js index 7dd9e126..66edb5f1 100644 --- a/Firmware/GPAD_API/data/js/pmd.js +++ b/Firmware/GPAD_API/data/js/pmd.js @@ -12,7 +12,7 @@ async function loadTopics() { const data = await KrakeUI.getJson('/settings-data'); KrakeUI.setText('deviceRole', data.role || 'Krake'); - KrakeUI.setText('brokerName', data.broker || 'krakepubinv.cloud.shiftr.io'); + KrakeUI.setText('brokerName', data.broker || 'public.cloud.shiftr.io'); const list = KrakeUI.getPublishTopics(data); const select = KrakeUI.byId('topicSelect'); select.innerHTML = ''; diff --git a/Firmware/GPAD_API/data/js/settings.js b/Firmware/GPAD_API/data/js/settings.js index 7d5ff47e..22b6adf9 100644 --- a/Firmware/GPAD_API/data/js/settings.js +++ b/Firmware/GPAD_API/data/js/settings.js @@ -48,11 +48,12 @@ async function saveMqttConfig() { try { const role = 'Krake'; + const broker = getInputValue('broker').trim(); const subscribeTopicUi = getInputValue('subscribeTopic').trim(); const publishTopicUi = getInputValue('publishTopic').trim(); const subscribeTopics = getInputValue('topics'); const publishTopics = getInputValue('publishTopics'); - await KrakeUI.postForm('/config', { role, subscribeTopic: publishTopicUi, publishTopic: subscribeTopicUi, subscribeTopics, publishTopics, publishDefaultTopic: publishTopicUi }); + await KrakeUI.postForm('/config', { role, broker, subscribeTopic: publishTopicUi, publishTopic: subscribeTopicUi, subscribeTopics, publishTopics, publishDefaultTopic: publishTopicUi }); KrakeUI.showMessage('MQTT config updated and saved to /mqtt.json.'); await refreshSettings(); } catch (e) { KrakeUI.showMessage('Failed to save MQTT config: ' + e.message, true); } diff --git a/Firmware/GPAD_API/data/settings.html b/Firmware/GPAD_API/data/settings.html index 79a8d62b..7acf46ef 100644 --- a/Firmware/GPAD_API/data/settings.html +++ b/Firmware/GPAD_API/data/settings.html @@ -49,14 +49,14 @@

Sound

MQTT

-

Krake PubInv is the fixed broker. Topic settings are stored in /mqtt.json.

+

Dynamic broker/topic/role assignment stored in /mqtt.json.

- + From a07002a3e75ce8560122caa28f452d16525cf5f6 Mon Sep 17 00:00:00 2001 From: "nagham.kheir25" Date: Sat, 30 May 2026 23:11:22 +0300 Subject: [PATCH 03/29] Return settings menu to main page after inactivity --- Firmware/GPAD_API/GPAD_API/GPAD_menu.cpp | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/Firmware/GPAD_API/GPAD_API/GPAD_menu.cpp b/Firmware/GPAD_API/GPAD_API/GPAD_menu.cpp index 3f103d05..76f7d015 100644 --- a/Firmware/GPAD_API/GPAD_API/GPAD_menu.cpp +++ b/Firmware/GPAD_API/GPAD_API/GPAD_menu.cpp @@ -24,6 +24,13 @@ extern bool selectMqttBrokerOption(uint8_t index); #define MAX_DEPTH 3 static bool settingsExitRequested = false; +static unsigned long lastMenuInteractionMs = 0; +const unsigned long MENU_INACTIVITY_TIMEOUT_MS = 120000; + +static void noteMenuInteraction() +{ + lastMenuInteractionMs = millis(); +} void reset_menu_navigation(); static void finishReturnToMainPage(); @@ -376,6 +383,7 @@ NAVROOT(nav, mainMenu, MAX_DEPTH, in, out); void registerRotationEvent(bool CW) { + noteMenuInteraction(); DBG_PRINT(F("CW: ")); DBG_PRINTLN(CW); reIn.registerEvent(CW ? RotaryEventIn::EventType::ROTARY_CW @@ -384,6 +392,7 @@ void registerRotationEvent(bool CW) void registerRotaryEncoderPress() { + noteMenuInteraction(); reIn.registerEvent(RotaryEventIn::EventType::BUTTON_CLICKED); } @@ -397,6 +406,13 @@ void setup_GPAD_menu() void poll_GPAD_menu() { + if (running_menu && (millis() - lastMenuInteractionMs) >= MENU_INACTIVITY_TIMEOUT_MS) + { + DBG_PRINTLN(F("Menu inactivity timeout. Returning to main page.")); + finishReturnToMainPage(); + return; + } + nav.poll(); if (settingsExitRequested) { @@ -435,6 +451,7 @@ void open_settings_menu_at(int n) void reset_menu_navigation() { running_menu = true; + noteMenuInteraction(); nav.reset(); } From 4bb3117f7e3dc87456dcdadd26a2d95f726a01ba Mon Sep 17 00:00:00 2001 From: "nagham.kheir25" Date: Sat, 30 May 2026 23:11:26 +0300 Subject: [PATCH 04/29] Add safe LittleFS diagnostics and mount failure handling --- Firmware/GPAD_API/GPAD_API/GPAD_API.ino | 44 +++++++++++ Firmware/GPAD_API/GPAD_API/WiFiManagerOTA.cpp | 77 +++++++++++++++++-- Firmware/GPAD_API/GPAD_API/WiFiManagerOTA.h | 4 +- 3 files changed, 117 insertions(+), 8 deletions(-) diff --git a/Firmware/GPAD_API/GPAD_API/GPAD_API.ino b/Firmware/GPAD_API/GPAD_API/GPAD_API.ino index 9fe1bd2d..3c33e3b0 100644 --- a/Firmware/GPAD_API/GPAD_API/GPAD_API.ino +++ b/Firmware/GPAD_API/GPAD_API/GPAD_API.ino @@ -1098,6 +1098,12 @@ bool parseCsvIntoTopics(const String &rawTopics, char dest[][MAX_TOPIC_LEN], uin bool writeMqttConfig() { + if (!WifiOTA::isLittleFSMounted()) + { + debugSerial.println(F("Cannot save /mqtt.json: LittleFS is unavailable.")); + return false; + } + File file = LittleFS.open(MQTT_CONFIG_PATH, "w"); if (!file) { @@ -1122,6 +1128,12 @@ bool writeMqttConfig() bool loadMqttConfig() { + if (!WifiOTA::isLittleFSMounted()) + { + debugSerial.println(F("Cannot load /mqtt.json: LittleFS is unavailable.")); + return false; + } + if (!LittleFS.exists(MQTT_CONFIG_PATH)) { return false; @@ -1525,6 +1537,12 @@ bool isNoStorePath(const char *path) void sendStaticFile(AsyncWebServerRequest *request, const char *path, const char *contentType) { + if (!WifiOTA::isLittleFSMounted()) + { + sendTextResponse(request, 503, "text/plain", "LittleFS unavailable"); + return; + } + char gzipPath[64]; snprintf(gzipPath, sizeof(gzipPath), "%s.gz", path); if (LittleFS.exists(gzipPath)) @@ -1552,6 +1570,17 @@ void sendStaticFile(AsyncWebServerRequest *request, const char *path) void sendTemplateFile(AsyncWebServerRequest *request, const char *path) { + if (!WifiOTA::isLittleFSMounted()) + { + sendTextResponse(request, 503, "text/plain", "LittleFS unavailable"); + return; + } + if (!LittleFS.exists(path)) + { + sendTextResponse(request, 404, "text/plain", "not found"); + return; + } + AsyncWebServerResponse *response = request->beginResponse(LittleFS, path, contentTypeForPath(path), false, templateProcessor); addWebUiHeaders(response); request->send(response); @@ -1563,6 +1592,21 @@ void setupOTA() { // Route for root / web page + server.on("/littlefs-status", HTTP_GET, [](AsyncWebServerRequest *request) + { + String payload = "{\"mounted\":"; + payload += WifiOTA::isLittleFSMounted() ? "true" : "false"; + if (WifiOTA::isLittleFSMounted()) + { + payload += ",\"totalBytes\":" + String(LittleFS.totalBytes()); + payload += ",\"usedBytes\":" + String(LittleFS.usedBytes()); + payload += ",\"indexPresent\":" + String(LittleFS.exists("/index.html") ? "true" : "false"); + payload += ",\"wifiConfigPresent\":" + String(LittleFS.exists("/wifi.json") ? "true" : "false"); + payload += ",\"mqttConfigPresent\":" + String(LittleFS.exists(MQTT_CONFIG_PATH) ? "true" : "false"); + } + payload += "}"; + sendTextResponse(request, 200, "application/json", payload); }); + server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) { sendTemplateFile(request, "/index.html"); }); diff --git a/Firmware/GPAD_API/GPAD_API/WiFiManagerOTA.cpp b/Firmware/GPAD_API/GPAD_API/WiFiManagerOTA.cpp index 3354f230..9a5f2190 100644 --- a/Firmware/GPAD_API/GPAD_API/WiFiManagerOTA.cpp +++ b/Firmware/GPAD_API/GPAD_API/WiFiManagerOTA.cpp @@ -4,6 +4,7 @@ namespace { const char *WIFI_CREDENTIALS_PATH = "/wifi.json"; + bool littleFsMounted = false; String jsonEscape(const String &value) { @@ -200,6 +201,11 @@ void Manager::startConfigPortal(const char *const accessPointSsid, unsigned long bool Manager::forgetSavedCredentials() { this->wifiManager.resetSettings(); + if (!littleFsMounted) + { + this->print.println(F("Cannot remove wifi.json: LittleFS is unavailable.")); + return false; + } if (LittleFS.exists(WIFI_CREDENTIALS_PATH)) { return LittleFS.remove(WIFI_CREDENTIALS_PATH); @@ -222,6 +228,12 @@ IPAddress Manager::getAddress() bool Manager::saveCredentials(const String &ssid, const String &password) { + if (!littleFsMounted) + { + this->print.println(F("Cannot save wifi.json: LittleFS is unavailable.")); + return false; + } + String trimmedSsid = ssid; String trimmedPassword = password; trimmedSsid.trim(); @@ -290,6 +302,11 @@ bool Manager::loadCredentials(String &ssid, String &password) bool Manager::loadCredentialsList(CredentialList &credentials) { credentials.count = 0; + if (!littleFsMounted) + { + this->print.println(F("Cannot load wifi.json: LittleFS is unavailable.")); + return false; + } if (!LittleFS.exists(WIFI_CREDENTIALS_PATH)) { return false; @@ -447,18 +464,64 @@ void Manager::apStarted() } } -void WifiOTA::initLittleFS() +bool WifiOTA::initLittleFS() { - if (!LittleFS.begin(true)) + littleFsMounted = LittleFS.begin(false); + if (!littleFsMounted) { - Serial.println(F("An error occurred while mounting LittleFS.")); + Serial.println(F("LittleFS mount failed. Filesystem unavailable; refusing to auto-format.")); + Serial.println(F("Upload the LittleFS image or format explicitly during service.")); + return false; } - else + + printLittleFSDiagnostics(Serial); + return true; +} + +bool WifiOTA::isLittleFSMounted() +{ + return littleFsMounted; +} + +void WifiOTA::printLittleFSDiagnostics(Print &print) +{ + print.println(F("=== LittleFS diagnostics ===")); + print.print(F("Mounted: ")); + print.println(littleFsMounted ? F("yes") : F("no")); + if (!littleFsMounted) { -#if (DEBUG > 1) - Serial.println("LittleFS mounted successfully."); -#endif + print.println(F("============================")); + return; + } + + print.print(F("Total bytes: ")); + print.println(LittleFS.totalBytes()); + print.print(F("Used bytes: ")); + print.println(LittleFS.usedBytes()); + + File root = LittleFS.open("/"); + if (!root || !root.isDirectory()) + { + print.println(F("Unable to list filesystem root.")); + print.println(F("============================")); + return; + } + + File file = root.openNextFile(); + if (!file) + { + print.println(F("Filesystem root is empty. Upload the LittleFS image.")); + } + while (file) + { + print.print(F(" ")); + print.print(file.name()); + print.print(F(" (")); + print.print(file.size()); + print.println(F(" bytes)")); + file = root.openNextFile(); } + print.println(F("============================")); } String WifiOTA::processor(const String &var) diff --git a/Firmware/GPAD_API/GPAD_API/WiFiManagerOTA.h b/Firmware/GPAD_API/GPAD_API/WiFiManagerOTA.h index 08887592..6dd11d37 100644 --- a/Firmware/GPAD_API/GPAD_API/WiFiManagerOTA.h +++ b/Firmware/GPAD_API/GPAD_API/WiFiManagerOTA.h @@ -56,7 +56,9 @@ namespace WifiOTA void startPortal(const char *const accessPointSsid); }; - void initLittleFS(); + bool initLittleFS(); + bool isLittleFSMounted(); + void printLittleFSDiagnostics(Print &print); String processor(const String &var); }; From 31aead1c4a2729c381f0816fb5f3607901604f61 Mon Sep 17 00:00:00 2001 From: "nagham.kheir25" Date: Sat, 30 May 2026 23:28:46 +0300 Subject: [PATCH 05/29] Remove remaining Public Shiftr broker defaults --- Firmware/GPAD_API/data/PMD_GPAD_API.html | 2 +- Firmware/GPAD_API/data/device-monitor.html | 6 +++--- Firmware/GPAD_API/data/js/pmd.js | 2 +- Firmware/GPAD_API/data/settings.html | 2 +- .../MQTT/Krake_MQTT/Krake_MQTT.ino | 6 +++--- .../MQTT/mqtt_timing/mqtt_timing.ino | 6 ++---- pages/PMD_GPAD_API.html | 16 ++++++++-------- 7 files changed, 19 insertions(+), 21 deletions(-) diff --git a/Firmware/GPAD_API/data/PMD_GPAD_API.html b/Firmware/GPAD_API/data/PMD_GPAD_API.html index 3d9a019f..319922a9 100644 --- a/Firmware/GPAD_API/data/PMD_GPAD_API.html +++ b/Firmware/GPAD_API/data/PMD_GPAD_API.html @@ -11,7 +11,7 @@
-
Program: PMD_Processing_MQTT (V0.37)
Role: Krake
Broker: public.cloud.shiftr.io
+
Program: PMD_Processing_MQTT (V0.37)
Role: Krake
Broker: krakepubinv.cloud.shiftr.io

Issue Alarm (multi-topic)

Select one or more topics from Settings and send PMD alarm payloads to all selected topics.

Selected topics: -
Result: -

Send Message / Alarm

Publish result: -
diff --git a/Firmware/GPAD_API/data/device-monitor.html b/Firmware/GPAD_API/data/device-monitor.html index f1aac381..398c1b55 100644 --- a/Firmware/GPAD_API/data/device-monitor.html +++ b/Firmware/GPAD_API/data/device-monitor.html @@ -30,9 +30,9 @@

Shiftr MQTT Broker Monitor

Broker Connection

- - - + + +
diff --git a/Firmware/GPAD_API/data/js/pmd.js b/Firmware/GPAD_API/data/js/pmd.js index 66edb5f1..7dd9e126 100644 --- a/Firmware/GPAD_API/data/js/pmd.js +++ b/Firmware/GPAD_API/data/js/pmd.js @@ -12,7 +12,7 @@ async function loadTopics() { const data = await KrakeUI.getJson('/settings-data'); KrakeUI.setText('deviceRole', data.role || 'Krake'); - KrakeUI.setText('brokerName', data.broker || 'public.cloud.shiftr.io'); + KrakeUI.setText('brokerName', data.broker || 'krakepubinv.cloud.shiftr.io'); const list = KrakeUI.getPublishTopics(data); const select = KrakeUI.byId('topicSelect'); select.innerHTML = ''; diff --git a/Firmware/GPAD_API/data/settings.html b/Firmware/GPAD_API/data/settings.html index 7acf46ef..5c1244a8 100644 --- a/Firmware/GPAD_API/data/settings.html +++ b/Firmware/GPAD_API/data/settings.html @@ -56,7 +56,7 @@

MQTT

- + diff --git a/Firmware/factoryTest/component-Focused/MQTT/Krake_MQTT/Krake_MQTT.ino b/Firmware/factoryTest/component-Focused/MQTT/Krake_MQTT/Krake_MQTT.ino index e9f775a6..0cf889e3 100644 --- a/Firmware/factoryTest/component-Focused/MQTT/Krake_MQTT/Krake_MQTT.ino +++ b/Firmware/factoryTest/component-Focused/MQTT/Krake_MQTT/Krake_MQTT.ino @@ -36,9 +36,9 @@ const char* password = "textinsert"; // MQTT Broker -const char* mqtt_broker_name = "public.cloud.shiftr.io"; -const char* mqtt_user = "public"; -const char* mqtt_password = "public"; +const char* mqtt_broker_name = "krakepubinv.cloud.shiftr.io"; +const char* mqtt_user = "krakepubinv"; +const char* mqtt_password = "DlDmkWjp4I4kgDcA"; // MQTT Topics // User must modify the device serial number. In this case change the part "USA4" as approprate. diff --git a/Firmware/factoryTest/component-Focused/MQTT/mqtt_timing/mqtt_timing.ino b/Firmware/factoryTest/component-Focused/MQTT/mqtt_timing/mqtt_timing.ino index 0b3535b8..6a6c4951 100644 --- a/Firmware/factoryTest/component-Focused/MQTT/mqtt_timing/mqtt_timing.ino +++ b/Firmware/factoryTest/component-Focused/MQTT/mqtt_timing/mqtt_timing.ino @@ -53,9 +53,7 @@ void connect() { Serial.print("\nconnecting..."); #define ClientName "mqtt_timing" -// while (!client.connect("arduino", "public", "public")) { -// while (!client.connect("mqtt_timing", "public", "public")) { - while (!client.connect(ClientName, "public", "public")) { + while (!client.connect(ClientName, "krakepubinv", "DlDmkWjp4I4kgDcA")) { Serial.print("."); delay(1000); } @@ -111,7 +109,7 @@ void setup() { WiFi.begin(ssid, password); // Note: Local domain names (e.g. "Computer.local" on OSX) are not supported // by Arduino. You need to set the IP address directly. - client.begin("public.cloud.shiftr.io", net); + client.begin("krakepubinv.cloud.shiftr.io", net); client.onMessage(messageReceived); connect(); diff --git a/pages/PMD_GPAD_API.html b/pages/PMD_GPAD_API.html index 01caff3a..25daefd2 100644 --- a/pages/PMD_GPAD_API.html +++ b/pages/PMD_GPAD_API.html @@ -149,24 +149,24 @@

PMD_GPAD_API

The PMD publishes to a "topic" which is made up of the model name, KRAKE, and serial number of the KRAKE. It is contatinated by an underscored.

Set MQTT Broker

- The default example MQTT broker is: mqtt://public:public@public.cloud.shiftr.io + The MQTT broker is fixed to: mqtt://krakepubinv@krakepubinv.cloud.shiftr.io
- Set up another broker with this form. + Use the Krake PubInv broker credentials below.

Setup MQTT Broker

- +

- +
- +
@@ -229,10 +229,10 @@
Public Invention, LICENSE "GNU Affero General Public License, version 3 "