feat: add BLE OTA characteristic with esp_ota integration
This commit is contained in:
parent
d34dfec1ce
commit
a1b0daee37
1 changed files with 150 additions and 6 deletions
154
src/main.cpp
154
src/main.cpp
|
|
@ -4,6 +4,9 @@
|
||||||
|
|
||||||
#include <mbedtls/md.h>
|
#include <mbedtls/md.h>
|
||||||
#include <Preferences.h>
|
#include <Preferences.h>
|
||||||
|
#include <esp_ota_ops.h>
|
||||||
|
#include <esp_partition.h>
|
||||||
|
#include <nvs_flash.h>
|
||||||
|
|
||||||
#define RELAY1_PIN 1
|
#define RELAY1_PIN 1
|
||||||
#define RELAY2_PIN 2
|
#define RELAY2_PIN 2
|
||||||
|
|
@ -16,8 +19,16 @@
|
||||||
#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361f26d8"
|
#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361f26d8"
|
||||||
#define UUID_CHAR_UUID "beb5483e-36e1-4688-b7f5-ea07361f26d9"
|
#define UUID_CHAR_UUID "beb5483e-36e1-4688-b7f5-ea07361f26d9"
|
||||||
#define VERSION_UUID "beb5483e-36e1-4688-b7f5-ea07361f26db"
|
#define VERSION_UUID "beb5483e-36e1-4688-b7f5-ea07361f26db"
|
||||||
|
#define OTA_UUID "beb5483e-36e1-4688-b7f5-ea07361f26da"
|
||||||
|
|
||||||
#define HMAC_PKT_SIZE 37 // 1 cmd + 4 seq + 32 hmac
|
#define HMAC_PKT_SIZE 37
|
||||||
|
#define OTA_CHUNK_SIZE 500
|
||||||
|
#define OTA_CMD_START 0x10
|
||||||
|
#define OTA_CMD_END 0x11
|
||||||
|
#define OTA_STATUS_ACK 0x00
|
||||||
|
#define OTA_STATUS_NAK 0x01
|
||||||
|
#define OTA_STATUS_COMPLETE 0x10
|
||||||
|
#define OTA_STATUS_ERROR 0x11 // 1 cmd + 4 seq + 32 hmac
|
||||||
#define HMAC_KEY_LEN 32
|
#define HMAC_KEY_LEN 32
|
||||||
#define ESP_UUID_LEN 16
|
#define ESP_UUID_LEN 16
|
||||||
#define NVS_NAMESPACE "hmac"
|
#define NVS_NAMESPACE "hmac"
|
||||||
|
|
@ -33,6 +44,13 @@ bool relay1State = false;
|
||||||
bool relay2State = false;
|
bool relay2State = false;
|
||||||
uint32_t lastSeq = 0;
|
uint32_t lastSeq = 0;
|
||||||
|
|
||||||
|
bool otaInProgress = false;
|
||||||
|
esp_ota_handle_t otaHandle = 0;
|
||||||
|
const esp_partition_t *otaPartition = nullptr;
|
||||||
|
uint32_t otaExpectedSize = 0;
|
||||||
|
uint32_t otaWritten = 0;
|
||||||
|
uint8_t otaExpectedSha[32];
|
||||||
|
|
||||||
uint8_t hmacKey[HMAC_KEY_LEN];
|
uint8_t hmacKey[HMAC_KEY_LEN];
|
||||||
uint8_t deviceUuid[ESP_UUID_LEN];
|
uint8_t deviceUuid[ESP_UUID_LEN];
|
||||||
bool keyProvisioned = false;
|
bool keyProvisioned = false;
|
||||||
|
|
@ -101,8 +119,122 @@ static void parseHexKey(const String &hex, uint8_t *out) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void ledOtaBusy() {
|
||||||
|
digitalWrite(LED_PIN, (millis() / 100) % 2 ? HIGH : LOW);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void otaReset() {
|
||||||
|
otaInProgress = false;
|
||||||
|
otaHandle = 0;
|
||||||
|
otaPartition = nullptr;
|
||||||
|
otaExpectedSize = 0;
|
||||||
|
otaWritten = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void sendOtaNotify(NimBLECharacteristic *otaChar, uint8_t status, uint32_t seq) {
|
||||||
|
uint8_t buf[5];
|
||||||
|
buf[0] = status;
|
||||||
|
buf[1] = (seq >> 24) & 0xFF;
|
||||||
|
buf[2] = (seq >> 16) & 0xFF;
|
||||||
|
buf[3] = (seq >> 8) & 0xFF;
|
||||||
|
buf[4] = seq & 0xFF;
|
||||||
|
otaChar->setValue(buf, 5);
|
||||||
|
otaChar->notify();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void abortOta(NimBLECharacteristic *otaChar, const char *reason) {
|
||||||
|
Serial.printf("OTA: abort — %s\n", reason);
|
||||||
|
if (otaHandle != 0) {
|
||||||
|
esp_ota_end(otaHandle);
|
||||||
|
}
|
||||||
|
otaReset();
|
||||||
|
sendOtaNotify(otaChar, OTA_STATUS_ERROR, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
class OtaCharacteristicCallbacks : public NimBLECharacteristicCallbacks {
|
||||||
|
void onWrite(NimBLECharacteristic *pChar, NimBLEConnInfo &connInfo) override {
|
||||||
|
std::string value = pChar->getValue();
|
||||||
|
if (value.empty()) return;
|
||||||
|
const uint8_t *data = (const uint8_t *)value.data();
|
||||||
|
size_t len = value.length();
|
||||||
|
|
||||||
|
if (len == 5 && data[0] == OTA_CMD_START) {
|
||||||
|
if (otaInProgress) {
|
||||||
|
esp_ota_end(otaHandle);
|
||||||
|
otaReset();
|
||||||
|
}
|
||||||
|
otaExpectedSize = ((uint32_t)data[1] << 24) | ((uint32_t)data[2] << 16) |
|
||||||
|
((uint32_t)data[3] << 8) | (uint32_t)data[4];
|
||||||
|
otaPartition = esp_ota_get_next_update_partition(nullptr);
|
||||||
|
if (otaPartition == nullptr) {
|
||||||
|
Serial.println("OTA: no update partition found");
|
||||||
|
sendOtaNotify(pChar, OTA_STATUS_ERROR, 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
esp_err_t err = esp_ota_begin(otaPartition, otaExpectedSize, &otaHandle);
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
Serial.printf("OTA: begin failed (%d)\n", err);
|
||||||
|
sendOtaNotify(pChar, OTA_STATUS_ERROR, 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
otaInProgress = true;
|
||||||
|
otaWritten = 0;
|
||||||
|
digitalWrite(RELAY1_PIN, LOW);
|
||||||
|
digitalWrite(RELAY2_PIN, LOW);
|
||||||
|
relay1State = false;
|
||||||
|
relay2State = false;
|
||||||
|
bleRelay1 = false;
|
||||||
|
bleRelay2 = false;
|
||||||
|
Serial.printf("OTA: started, expecting %lu bytes\n", otaExpectedSize);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (len == 32 && otaInProgress) {
|
||||||
|
memcpy(otaExpectedSha, data, 32);
|
||||||
|
esp_err_t err = esp_ota_end(otaHandle);
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
Serial.printf("OTA: end failed (%d)\n", err);
|
||||||
|
otaReset();
|
||||||
|
sendOtaNotify(pChar, OTA_STATUS_ERROR, 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
err = esp_ota_set_boot_partition(otaPartition);
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
Serial.printf("OTA: set boot partition failed (%d)\n", err);
|
||||||
|
otaReset();
|
||||||
|
sendOtaNotify(pChar, OTA_STATUS_ERROR, 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
digitalWrite(LED_PIN, HIGH);
|
||||||
|
sendOtaNotify(pChar, OTA_STATUS_COMPLETE, 0);
|
||||||
|
Serial.println("OTA: complete, rebooting...");
|
||||||
|
delay(500);
|
||||||
|
esp_restart();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!otaInProgress) return;
|
||||||
|
|
||||||
|
if (data[0] < 0x10) {
|
||||||
|
uint32_t seq = ((uint32_t)data[0] << 24) | ((uint32_t)data[1] << 16) |
|
||||||
|
((uint32_t)data[2] << 8) | (uint32_t)data[3];
|
||||||
|
size_t dataLen = len - 4;
|
||||||
|
esp_err_t err = esp_ota_write(otaHandle, data + 4, dataLen);
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
Serial.printf("OTA: write err at seq %lu (%d)\n", seq, err);
|
||||||
|
sendOtaNotify(pChar, OTA_STATUS_NAK, seq);
|
||||||
|
abortOta(pChar, "write error");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
otaWritten += dataLen;
|
||||||
|
sendOtaNotify(pChar, OTA_STATUS_ACK, seq);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
class MyCharacteristicCallbacks : public NimBLECharacteristicCallbacks {
|
class MyCharacteristicCallbacks : public NimBLECharacteristicCallbacks {
|
||||||
void onWrite(NimBLECharacteristic *pChar, NimBLEConnInfo &connInfo) override {
|
void onWrite(NimBLECharacteristic *pChar, NimBLEConnInfo &connInfo) override {
|
||||||
|
if (otaInProgress) return;
|
||||||
std::string value = pChar->getValue();
|
std::string value = pChar->getValue();
|
||||||
if (value.length() != HMAC_PKT_SIZE) {
|
if (value.length() != HMAC_PKT_SIZE) {
|
||||||
Serial.printf("BLE: bad packet length %d (expected %d)\n",
|
Serial.printf("BLE: bad packet length %d (expected %d)\n",
|
||||||
|
|
@ -209,6 +341,12 @@ void setup() {
|
||||||
);
|
);
|
||||||
pVersionChar->setValue(FIRMWARE_VERSION);
|
pVersionChar->setValue(FIRMWARE_VERSION);
|
||||||
|
|
||||||
|
NimBLECharacteristic *pOtaChar = pService->createCharacteristic(
|
||||||
|
OTA_UUID,
|
||||||
|
NIMBLE_PROPERTY::WRITE | NIMBLE_PROPERTY::NOTIFY
|
||||||
|
);
|
||||||
|
pOtaChar->setCallbacks(new OtaCharacteristicCallbacks());
|
||||||
|
|
||||||
NimBLEAdvertising *pAdvertising = NimBLEDevice::getAdvertising();
|
NimBLEAdvertising *pAdvertising = NimBLEDevice::getAdvertising();
|
||||||
pAdvertising->setName("SylDa_Server");
|
pAdvertising->setName("SylDa_Server");
|
||||||
pAdvertising->addServiceUUID(SERVICE_UUID);
|
pAdvertising->addServiceUUID(SERVICE_UUID);
|
||||||
|
|
@ -238,7 +376,7 @@ void setup() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void loop() {
|
void loop() {
|
||||||
if (Serial.available()) {
|
if (!otaInProgress && Serial.available()) {
|
||||||
static String serialBuf;
|
static String serialBuf;
|
||||||
char c = Serial.read();
|
char c = Serial.read();
|
||||||
serialBuf += c;
|
serialBuf += c;
|
||||||
|
|
@ -312,8 +450,14 @@ void loop() {
|
||||||
bool t2Active = (millis() - t2Change > DEBOUNCE_MS) && t2Stable;
|
bool t2Active = (millis() - t2Change > DEBOUNCE_MS) && t2Stable;
|
||||||
bool btnActive = (millis() - btnChange > DEBOUNCE_MS) && btnStable;
|
bool btnActive = (millis() - btnChange > DEBOUNCE_MS) && btnStable;
|
||||||
|
|
||||||
relay1State = t1Active || btnActive || (deviceConnected && bleRelay1);
|
if (!otaInProgress) {
|
||||||
relay2State = t2Active || (deviceConnected && bleRelay2);
|
relay1State = t1Active || btnActive || (deviceConnected && bleRelay1);
|
||||||
|
relay2State = t2Active || (deviceConnected && bleRelay2);
|
||||||
|
} else {
|
||||||
|
relay1State = false;
|
||||||
|
relay2State = false;
|
||||||
|
ledOtaBusy();
|
||||||
|
}
|
||||||
|
|
||||||
digitalWrite(RELAY1_PIN, relay1State ? HIGH : LOW);
|
digitalWrite(RELAY1_PIN, relay1State ? HIGH : LOW);
|
||||||
digitalWrite(RELAY2_PIN, relay2State ? HIGH : LOW);
|
digitalWrite(RELAY2_PIN, relay2State ? HIGH : LOW);
|
||||||
|
|
@ -336,7 +480,7 @@ void loop() {
|
||||||
}
|
}
|
||||||
|
|
||||||
static uint32_t heartbeat = 0;
|
static uint32_t heartbeat = 0;
|
||||||
if (millis() - heartbeat > 5000) {
|
if (!otaInProgress && millis() - heartbeat > 5000) {
|
||||||
Serial.printf("[heartbeat] R1=%s R2=%s t1=%s t2=%s btn=%s ble1=%s ble2=%s\n",
|
Serial.printf("[heartbeat] R1=%s R2=%s t1=%s t2=%s btn=%s ble1=%s ble2=%s\n",
|
||||||
relay1State ? "ON" : "OFF",
|
relay1State ? "ON" : "OFF",
|
||||||
relay2State ? "ON" : "OFF",
|
relay2State ? "ON" : "OFF",
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue