Merge branch 'cursor/igiRadio-ios-app' into HEAD

This commit is contained in:
2026-08-28 01:12:09 +02:00
4 changed files with 111 additions and 8 deletions
@@ -17,6 +17,8 @@
*/
#pragma once
#include "core/DeviceIdentity.hpp"
#include "core/ISecureStore.hpp"
#include "core/WifiCredentials.hpp"
#include "net/NetError.hpp"
@@ -94,8 +96,20 @@ public:
* @brief connect — join the network described by creds.
*
* @dname connect
* @param creds Validated domain credentials from ISecureStore.
* @param hostname STA hostname / mDNS label (no .local suffix).
* @param creds Validated domain credentials from
* ISecureStore.
* @param hostname STA hostname / mDNS label (no .local
* suffix).
* @param bleFallbackStore When set alongside bleFallbackIdentity,
* a link lost for over a minute *after*
* this call already returned successfully
* starts BLE provisioning (additive,
* STA reconnect attempts keep running) so
* the app can reconfigure Wi-Fi without a
* power cycle. Null skips this behaviour.
* @param bleFallbackIdentity Device identity for the BLE fallback's
* advertising name; must outlive this
* StaClient when bleFallbackStore is set.
* @return Ok on success, or NetError::StaConnectTimeout /
* NetError::StaConnectFailed.
* @pubstate writes connected_ on success; uses creds via Secret.
@@ -105,7 +119,9 @@ public:
*/
[[nodiscard]] std::expected<void, NetError>
connect(const core::WifiCredentials& creds,
std::string_view hostname = {});
std::string_view hostname = {},
core::ISecureStore* bleFallbackStore = nullptr,
const core::DeviceIdentity* bleFallbackIdentity = nullptr);
private:
bool connected_;
+2 -1
View File
@@ -189,7 +189,8 @@ startStaMode(core::ISecureStore& store, tuner::TunerService& tuner,
StaClient sta;
if (auto staResult =
sta.connect(creds, deviceIdentity.hostname());
sta.connect(creds, deviceIdentity.hostname(), &store,
&deviceIdentity);
!staResult) {
ESP_LOGW(kTag, "STA connect failed for SSID %.*s — credentials kept in NVS",
static_cast<int>(creds.ssid().value().size()),
+48 -1
View File
@@ -18,6 +18,8 @@
#include "net/StaClient.hpp"
#include "net/BleProvisioning.hpp"
#include "esp_event.h"
#include "esp_log.h"
#include "esp_netif.h"
@@ -39,10 +41,20 @@ constexpr int kFailedBit = BIT1;
constexpr int kMaxConnectRetries = 10;
constexpr TickType_t kConnectTimeout = pdMS_TO_TICKS(45000);
constexpr TickType_t kRetryDelay = pdMS_TO_TICKS(800);
/** How long a post-boot link loss must persist before BLE fallback starts. */
constexpr TickType_t kBleFallbackThreshold = pdMS_TO_TICKS(60000);
EventGroupHandle_t s_wifiEventGroup = nullptr;
int s_connectRetries = 0;
// Set once by connect() on success when the caller opted in; used only by
// the post-boot branch of wifiEventHandler() below (s_wifiEventGroup is
// null there, since the bounded connect() call has already returned).
core::ISecureStore* s_bleFallbackStore = nullptr;
const core::DeviceIdentity* s_bleFallbackIdentity = nullptr;
TickType_t s_disconnectedSinceTick = 0;
bool s_bleFallbackActive = false;
/**
* @brief disconnectReasonString — map ESP-IDF Wi-Fi disconnect reason codes.
*
@@ -136,6 +148,32 @@ void wifiEventHandler(void* arg,
return;
}
// Post-boot link loss (the bounded connect() call above already
// succeeded once): esp_wifi_connect() below retries forever on its
// own, which used to be the whole story -- if the AP never comes
// back (moved, replaced, password changed), the device retried
// silently forever with no way for the app to reach it and no way
// for the user to reconfigure Wi-Fi short of a power cycle. After
// a sustained ~1 minute outage, start BLE provisioning alongside
// the ongoing reconnect attempts (does not stop them) so the app
// can push new credentials over Bluetooth.
if (s_disconnectedSinceTick == 0) {
s_disconnectedSinceTick = xTaskGetTickCount();
} else if (!s_bleFallbackActive && s_bleFallbackStore != nullptr
&& s_bleFallbackIdentity != nullptr
&& (xTaskGetTickCount() - s_disconnectedSinceTick)
>= kBleFallbackThreshold) {
ESP_LOGW(kTag, "STA link lost for over a minute — starting BLE "
"provisioning fallback");
if (auto bleResult = ble_provisioning::start(
*s_bleFallbackStore, *s_bleFallbackIdentity);
!bleResult) {
ESP_LOGW(kTag, "BLE fallback provisioning failed to start");
} else {
s_bleFallbackActive = true;
}
}
ESP_LOGW(kTag, "STA link lost — reconnecting");
esp_wifi_connect();
} else if (eventBase == IP_EVENT && eventId == IP_EVENT_STA_GOT_IP) {
@@ -145,6 +183,7 @@ void wifiEventHandler(void* arg,
ESP_LOGI(kTag, "STA IP " IPSTR, IP2STR(&event->ip_info.ip));
}
applyStaLinkTuning();
s_disconnectedSinceTick = 0;
if (s_wifiEventGroup != nullptr) {
xEventGroupSetBits(s_wifiEventGroup, kConnectedBit);
}
@@ -212,12 +251,20 @@ StaClient& StaClient::operator=(StaClient&& other) noexcept
}
std::expected<void, NetError>
StaClient::connect(const core::WifiCredentials& creds, std::string_view hostname)
StaClient::connect(const core::WifiCredentials& creds,
std::string_view hostname,
core::ISecureStore* bleFallbackStore,
const core::DeviceIdentity* bleFallbackIdentity)
{
if (connected_) {
return {};
}
s_bleFallbackStore = bleFallbackStore;
s_bleFallbackIdentity = bleFallbackIdentity;
s_disconnectedSinceTick = 0;
s_bleFallbackActive = false;
std::string hostLabel;
if (!hostname.empty()) {
hostLabel.assign(hostname.begin(), hostname.end());
+42 -3
View File
@@ -5,9 +5,48 @@ un suggerimento. Se qualcosa non è chiaro o sembra in conflitto con codice
esistente, chiedi prima di improvvisare una soluzione diversa — non inventare
comportamenti non specificati qui.
Dispositivo di test reale: `http://192.168.1.62` (mDNS `digiradio-CC4DB4.local`).
Verifica ogni funzionalità contro il dispositivo vero prima di considerarla
finita, non solo con dati mock.
Dispositivo di test reale: `http://192.168.1.62` (mDNS **`igiradio-CC4DB4.local`**
— vedi §0bis, è cambiato oggi). Verifica ogni funzionalità contro il
dispositivo vero prima di considerarla finita, non solo con dati mock.
---
## 0bis. Identità dispositivo rinominata "DigiRadio" → "igiRadio" — OBBLIGATORIO
Oggi il firmware è stato rinominato per coerenza con il nome dell'app. Tre
stringhe sono cambiate:
| Cosa | Prima | Ora |
|---|---|---|
| SSID WiFi di setup | `DigiRadio-<seriale>` | `igiRadio-<seriale>` |
| Nome Bluetooth | `DigiRadio` | `igiRadio` |
| Hostname mDNS | `digiradio-<seriale>.local` | `igiradio-<seriale>.local` |
**Cerca in tutto il progetto app ogni occorrenza letterale di `"DigiRadio"` o
`"digiradio"`** usata per riconoscere/filtrare il dispositivo (discovery WiFi
locale, scan Bluetooth, matching SSID durante il provisioning) — non solo
testo di UI. Candidati sospetti da controllare: `DigiRadioDiscoveryService`,
`BLEProvisioningService`, `LocalNetworkScanner`. Se il filtro cerca ancora
"DigiRadio", l'app smette di trovare il dispositivo reale.
---
## 0ter. Nuovo: fallback Bluetooth se il WiFi cade durante l'uso normale
Prima, se il WiFi si perdeva DOPO essersi già connesso una volta (router
riavviato, password cambiata, spostato di posto), il firmware ritentava
all'infinito senza mai avvisare né dare un modo per riconfigurare — bisognava
staccare la corrente. Ora, dopo **circa un minuto** di disconnessione
continua, il dispositivo attiva automaticamente il provisioning Bluetooth
(stesso protocollo BLE già usato al primo setup) **senza smettere di
ritentare il WiFi in background**.
Non serve un nuovo endpoint HTTP (il dispositivo potrebbe non essere
raggiungibile via WiFi in quel momento, è proprio il punto). **Azione
consigliata**: se l'app ha già una schermata "Cambia rete WiFi" che passa da
Bluetooth (dovrebbe già esistere per il primo setup), verifica che scatti
anche quando l'app perde la connessione HTTP al dispositivo per un po' — è
il segnale che potrebbe essere entrato in questa modalità.
---