diff --git a/src/main.cpp b/src/main.cpp index 458a7d3..4f00fe9 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -4,6 +4,9 @@ #include #include +#include +#include +#include #define RELAY1_PIN 1 #define RELAY2_PIN 2 @@ -16,8 +19,16 @@ #define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361f26d8" #define UUID_CHAR_UUID "beb5483e-36e1-4688-b7f5-ea07361f26d9" #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 ESP_UUID_LEN 16 #define NVS_NAMESPACE "hmac" @@ -33,6 +44,13 @@ bool relay1State = false; bool relay2State = false; 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 deviceUuid[ESP_UUID_LEN]; 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 { void onWrite(NimBLECharacteristic *pChar, NimBLEConnInfo &connInfo) override { + if (otaInProgress) return; std::string value = pChar->getValue(); if (value.length() != HMAC_PKT_SIZE) { Serial.printf("BLE: bad packet length %d (expected %d)\n", @@ -209,6 +341,12 @@ void setup() { ); pVersionChar->setValue(FIRMWARE_VERSION); + NimBLECharacteristic *pOtaChar = pService->createCharacteristic( + OTA_UUID, + NIMBLE_PROPERTY::WRITE | NIMBLE_PROPERTY::NOTIFY + ); + pOtaChar->setCallbacks(new OtaCharacteristicCallbacks()); + NimBLEAdvertising *pAdvertising = NimBLEDevice::getAdvertising(); pAdvertising->setName("SylDa_Server"); pAdvertising->addServiceUUID(SERVICE_UUID); @@ -238,7 +376,7 @@ void setup() { } void loop() { - if (Serial.available()) { + if (!otaInProgress && Serial.available()) { static String serialBuf; char c = Serial.read(); serialBuf += c; @@ -312,8 +450,14 @@ void loop() { bool t2Active = (millis() - t2Change > DEBOUNCE_MS) && t2Stable; bool btnActive = (millis() - btnChange > DEBOUNCE_MS) && btnStable; - relay1State = t1Active || btnActive || (deviceConnected && bleRelay1); - relay2State = t2Active || (deviceConnected && bleRelay2); + if (!otaInProgress) { + relay1State = t1Active || btnActive || (deviceConnected && bleRelay1); + relay2State = t2Active || (deviceConnected && bleRelay2); + } else { + relay1State = false; + relay2State = false; + ledOtaBusy(); + } digitalWrite(RELAY1_PIN, relay1State ? HIGH : LOW); digitalWrite(RELAY2_PIN, relay2State ? HIGH : LOW); @@ -336,7 +480,7 @@ void loop() { } 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", relay1State ? "ON" : "OFF", relay2State ? "ON" : "OFF",