11. WiFi - STA-подключение
Событийная state-machine поверх (режим STA). Включается PIPCORE_ENABLE_WIFI. Радио работает, пока запрошено wifiRequest(true); при wifiRequest(false) железо полностью деинициализируется.
Важно: таймауты подключения и повторные попытки продвигаются только внутри wifiService() - вызывайте её регулярно (например, каждый проход главного цикла).
Конфигурация
struct WifiConfig
{
char ssid[kWifiSsidCap] = {}; // kWifiSsidCap = 33
char password[kWifiPasswordCap] = {}; // kWifiPasswordCap = 65
bool disableSleep = false; // true → WIFI_PS_NONE (максимальная
// пропускная способность)
bool fullScan = false; // true → сканировать все каналы
// (медленнее, надёжнее)
bool autoReconnect = true; // автоматический реконнект с
// нарастающей паузой
uint32_t connectTimeoutMs = 15'000; // таймаут одной попытки
uint32_t retryDelayMs = 2'500; // базовая пауза между попытками
uint32_t staticIp = 0; // статический IPv4, формат - см. ниже
uint32_t gateway = 0; // если staticIp, gateway и subnet
// все != 0 - DHCP отключается
uint32_t subnet = 0;
uint32_t dns1 = 0; // необязательные DNS
uint32_t dns2 = 0;
void setCredentials(const char *ssidIn, const char *passwordIn); // безопасное копирование
};
enum class WifiState : uint8_t
{
Off = 0, Connecting = 1, Connected = 2, Failed = 3, Unsupported = 4
};
Функции
| Функция | Описание |
|---|---|
void wifiConfigure(const WifiConfig &cfg) |
Задать креды и параметры. Вызывайте до включения радио |
void wifiRequest(bool enabled) |
Включить или выключить радио |
void wifiService() |
Прокачка state-machine. Вызывайте в цикле |
WifiState wifiState() |
Текущее состояние |
bool wifiConnected() |
state == Connected |
uint32_t wifiLocalIpV4() |
Локальный IPv4 (0 - адреса нет) |
WifiState |
Значение |
|---|---|
Off |
Радио выключено |
Connecting |
Идёт подключение или пауза перед повторной попыткой |
Connected |
Подключено, IP получен |
Failed |
Ошибка (в том числе пустой SSID); повторная попытка - по retryDelayMs |
Unsupported |
WiFi-бэкенд недоступен |
Пример
net::WifiConfig cfg = {};
cfg.setCredentials("MyWiFi", "password");
cfg.disableSleep = true;
net::wifiConfigure(cfg);
net::wifiRequest(true);
const uint32_t start = plat->nowMs();
while (!net::wifiConnected() && plat->nowMs() - start < 20'000)
{
net::wifiService();
plat->delayMs(10);
}
if (net::wifiConnected())
{
const uint32_t ip = net::wifiLocalIpV4();
log::info("IP: %u.%u.%u.%u",
unsigned(ip >> 24), unsigned((ip >> 16) & 0xFF),
unsigned((ip >> 8) & 0xFF), unsigned(ip & 0xFF));
}
Формат IP-адресов
Адреса хранятся как uint32_t в порядке «старший октет - в старших битах»: 192.168.1.10 → 0xC0A8010A; ip >> 24 - первый октет. Это относится и к staticIp, gateway, subnet, dns1, dns2.
Повторные попытки
Если попытка подключения не удалась (таймаут или обрыв), состояние сменяется на Failed / Connecting, и через retryDelayMs начинается новая попытка. При автореконнекте пауза удваивается с каждой неудачей до 16× от базовой - сеть не «заваливается» запросами при лежащем роутере. Ручной реконнект - снова wifiRequest(true).
12. OTA - обновления прошивки
Обновление по манифесту с криптографической проверкой. Включается PIPCORE_ENABLE_OTA (требует WiFi). Сервис сам включает радио на время работы, поэтому WiFi-креды достаточно задать через net::wifiConfigure() - вызывать wifiRequest(true) не нужно.
Поток обновления
GET {URL}/index.json
→ выбор канала (stable / beta) и более нового build
GET {URL}/<релиз>/manifest.json
→ проверка Ed25519-подписи
→ состояние UpdateAvailable
requestInstall()
→ потоковая загрузка с подсчётом SHA-256
→ запись во второй OTA-слот
→ смена загрузочного раздела
→ перезагрузка
Anti-replay: build не может быть меньше сохранённого для канала (хранится в NVS).
Типы
struct Options
{
uint16_t currentVerMajor = 0, currentVerMinor = 0, currentVerPatch = 0;
uint64_t currentBuild = 0; // монотонный счётчик сборки
const char *ed25519PubkeyHex = nullptr; // открытый ключ, 64 hex-символа
};
enum class Channel : uint8_t { Stable = 0, Beta = 1 };
enum class CheckMode : uint8_t { NewerOnly = 0, AllowDowngrade = 1 };
enum class State : uint8_t
{
Idle = 0, WifiStarting = 1, FetchingManifest = 2, UpdateAvailable = 3,
Downloading = 4, Installing = 5, Success = 6, Error = 7, UpToDate = 8
};
struct Manifest
{
char title[64];
uint16_t verMajor, verMinor, verPatch;
char version[32];
uint64_t build;
uint32_t size; // размер прошивки, байт
char url[512];
char desc[256];
uint8_t sha256[32]; // контрольная сумма образа
uint8_t sigEd25519[64]; // подпись манифеста
bool hasSig;
};
struct Status
{
State state; Error error;
int httpCode; // HTTP-код последнего запроса
int platformCode; // код для диагностики
uint32_t downloaded, total; // прогресс загрузки, байт
uint32_t lastChangeMs; // время последней смены состояния
int8_t versionCmp, buildCmp; // сравнение с текущей прошивкой
bool pendingVerify; // прошивка ждёт подтверждения
Channel channel;
Manifest manifest;
};
using StatusCallback = void (*)(const Status &st, void *user);
Коды ошибок (ota::Error)
| Код | Значение | Причина |
|---|---|---|
None (0) |
- | Ошибки нет |
WifiNotEnabled (1) |
WiFi недоступен | OTA-сервису не удалось получить WiFi-бэкенд |
WifiNotConnected (2) |
Нет подключения | WiFi перешёл в Failed (проверьте креды через net::wifiConfigure) |
HttpBeginFailed (3) |
Не удалось начать HTTP-запрос | Проверьте URL |
HttpStatusNotOk (4) |
Сервер вернул не 200 | См. Status::httpCode |
ManifestTooLarge (5) |
Манифест слишком большой | - |
ManifestParseFailed (6) |
Манифест не разобран | Неверный JSON/поля |
ManifestReplay (7) |
Build меньше сохранённого | Защита от отката на старую версию |
SignatureMissing (8) |
Нет подписи | - |
SignatureInvalid (9) |
Подпись неверна | Не тот ключ или манифест изменён |
FlashLayoutInvalid (10) |
Неподходящая разметка флеш | Проверьте таблицу разделов: нужен OTA-слот под образ |
RollbackUnavailable (11) |
Откат невозможен | Зарезервирован |
UpdateBeginFailed (12) |
Не удалось начать запись | - |
UpdateWriteFailed (13) |
Ошибка записи | - |
HashPipelineFailed (14) |
Ошибка подсчёта SHA-256 | - |
DownloadTruncated (15) |
Загрузка оборвана | - |
PayloadSizeMismatch (16) |
Размер не совпал с манифестом | - |
HashMismatch (17) |
Контрольная сумма не совпала | - |
UpdateEndFailed (18) |
Не удалось завершить обновление | - |
UrlTooLong (19) |
URL длиннее буфера | - |
Функции
| Функция | Описание |
|---|---|
void configure(const Options &opt, StatusCallback cb = nullptr, void *user = nullptr) |
Инициализация. Базовый URL берётся из PIPCORE_OTA_PROJECT_URL. cb вызывается на каждое изменение статуса |
void requestCheck() / void requestCheck(CheckMode mode) |
Проверить обновления. NewerOnly - только более новые build; AllowDowngrade - разрешить и более старые |
void requestInstall() |
Установить найденное обновление (после UpdateAvailable) |
void requestStableList() |
Запросить список стабильных версий сервера |
bool stableListReady() |
Список получен |
uint8_t stableListCount() |
Число версий в списке |
const char *stableListVersion(uint8_t idx) |
Версия по индексу |
void requestInstallStableVersion(const char *version) |
Установить конкретную версию (откат) |
void cancel() |
Прервать текущую операцию |
void service() |
Прокачка state-machine. Вызывайте в цикле. Блокируется не дольше PIPCORE_OTA_HTTP_TIMEOUT_MS за вызов |
const Status &status() |
Полный статус |
void markAppValid() |
Подтвердить работоспособность прошивки после OTA-перезагрузки |
Пример
ota::Options opt = {};
opt.currentVerMajor = 2; opt.currentVerMinor = 0; opt.currentVerPatch = 0;
opt.currentBuild = 2000000;
opt.ed25519PubkeyHex = PIPCORE_PUBKEY; // ваш публичный ключ, 64 hex-символа
ota::configure(opt, onStatus, nullptr);
ota::requestCheck();
// в главном цикле:
ota::service();
const ota::Status &st = ota::status();
if (st.state == ota::State::UpdateAvailable)
ota::requestInstall(); // например, после подтверждения пользователя
else if (st.state == ota::State::Error)
log::error("ota: error %d (http %d)", int(st.error), st.httpCode);
Подтверждение после обновления
Новая прошивка стартует в состоянии pending-verify. Если приложение не вызовет markAppValid(), загрузчик IDF откатится на предыдущую версию. Вызывайте markAppValid(), когда убедились, что всё работает (сеть поднялась, железо ответило):
extern "C" void app_main()
{
// ... инициализация железа ...
if (initOk)
ota::markAppValid();
}
Безопасность
- TLS: сертификат сервера проверяется системным bundle ESP-IDF.
- Подпись: манифест подписывается Ed25519 по каноническому представлению полей (префикс
pipcore-ota-manifest,title,version,build,size,sha256,url,desc). Меняйте эти поля только инструментом публикации - иначе подпись станет недействительной. - Целостность: образ проверяется по SHA-256 из подписанного манифеста.