moki 1 päivä sitten
vanhempi
sitoutus
0c815f1b89
1 muutettua tiedostoa jossa 169 lisäystä ja 26 poistoa
  1. 169 26
      firmware/ai_light/ai_light.ino

+ 169 - 26
firmware/ai_light/ai_light.ino

@@ -6,6 +6,8 @@
 #include <BLEUtils.h>
 #include <BLE2902.h>
 #include <Preferences.h>
+#include <HTTPClient.h>
+#include <Update.h>
 
 // =====================================================
 // ESP32-C3 SuperMini + 原玩具公共正极灯板:BLE + MQTT 双模式
@@ -30,7 +32,8 @@
 //     "mqtt_pass": "pass",
 //     "mqtt_client": "AI-Light",
 //     "mqtt_topic": "opencode/status",
-//     "mqtt_status": "openCodeLight/status"
+//     "mqtt_status": "openCodeLight/status",
+//     "ota_url": "https://example.com/firmware.bin"
 //   }
 //
 // 接线方式(V3 版本):
@@ -44,18 +47,19 @@
 // BLE 配置
 // =====================================================
 const char* BLE_DEVICE_NAME = "AI-Light";
+const char* FW_VERSION = "1.0.0";
 #define SERVICE_UUID        "b8b7e001-7a6b-4f4f-9a8b-11c0ffee0001"
 #define MODE_CHAR_UUID      "b8b7e002-7a6b-4f4f-9a8b-11c0ffee0001"
 #define CONFIG_CHAR_UUID    "b8b7e003-7a6b-4f4f-9a8b-11c0ffee0001"
 
 // =====================================================
-// 引脚定义(V3 版本
+// 引脚定义(红绿黄可通过配置动态修改,状态灯和按钮固定
 // =====================================================
-const int RED_PIN = 4;
-const int GREEN_PIN = 3;
-const int YELLOW_PIN = 2;
-const int WIFI_LED_PIN = 8;
-const int BTN_BOOT_PIN = 9;
+int redPin = 4;
+int greenPin = 3;
+int yellowPin = 2;
+const int STATUS_PIN = 8;
+const int BUTTON_PIN = 9;
 
 const int PWM_FREQ = 5000;
 const int PWM_RESOLUTION = 8;
@@ -95,6 +99,8 @@ String cfgMqttPass = "";
 String cfgMqttClient = "AI-Light";
 String cfgMqttTopic = "opencode/status";
 String cfgMqttStatus = "openCodeLight/status";
+String cfgOtaUrl = "";
+bool otaInProgress = false;
 
 
 // =====================================================
@@ -111,6 +117,10 @@ void loadConfig() {
   cfgMqttClient = preferences.getString("mqtt_client", "AI-Light");
   cfgMqttTopic = preferences.getString("mqtt_topic", "opencode/status");
   cfgMqttStatus = preferences.getString("mqtt_status", "openCodeLight/status");
+  redPin = preferences.getUInt("pin_red", 4);
+  greenPin = preferences.getUInt("pin_green", 3);
+  yellowPin = preferences.getUInt("pin_yellow", 2);
+  cfgOtaUrl = preferences.getString("ota_url", "");
 }
 
 bool isConfigComplete() {
@@ -119,6 +129,7 @@ bool isConfigComplete() {
 
 String getConfigJson() {
   JsonDocument doc;
+  doc["fw_version"] = FW_VERSION;
   doc["wifi_ssid"] = cfgWifiSsid;
   doc["mqtt_broker"] = cfgMqttBroker;
   doc["mqtt_port"] = cfgMqttPort;
@@ -127,6 +138,10 @@ String getConfigJson() {
   doc["mqtt_topic"] = cfgMqttTopic;
   doc["mqtt_status"] = cfgMqttStatus;
   doc["comm_mode"] = useMQTT ? 1 : 0;
+  doc["pin_red"] = redPin;
+  doc["pin_green"] = greenPin;
+  doc["pin_yellow"] = yellowPin;
+  doc["ota_url"] = cfgOtaUrl;
   String out;
   serializeJson(doc, out);
   return out;
@@ -159,6 +174,14 @@ void saveConfigFromJson(const String& json) {
     preferences.putString("mqtt_topic", doc["mqtt_topic"].as<String>());
   if (doc.containsKey("mqtt_status"))
     preferences.putString("mqtt_status", doc["mqtt_status"].as<String>());
+  if (doc.containsKey("pin_red"))
+    preferences.putUInt("pin_red", doc["pin_red"].as<uint8_t>());
+  if (doc.containsKey("pin_green"))
+    preferences.putUInt("pin_green", doc["pin_green"].as<uint8_t>());
+  if (doc.containsKey("pin_yellow"))
+    preferences.putUInt("pin_yellow", doc["pin_yellow"].as<uint8_t>());
+  if (doc.containsKey("ota_url"))
+    preferences.putString("ota_url", doc["ota_url"].as<String>());
 
   Serial.println("Config saved. Restarting...");
   delay(500);
@@ -177,15 +200,15 @@ void writeLed(int pin, int value) {
 }
 
 void allOff() {
-  writeLed(RED_PIN, 0);
-  writeLed(YELLOW_PIN, 0);
-  writeLed(GREEN_PIN, 0);
+  writeLed(redPin, 0);
+  writeLed(yellowPin, 0);
+  writeLed(greenPin, 0);
 }
 
 void setOnly(int red, int yellow, int green) {
-  writeLed(RED_PIN, constrain(red, 0, RED_MAX));
-  writeLed(YELLOW_PIN, constrain(yellow, 0, YELLOW_MAX));
-  writeLed(GREEN_PIN, constrain(green, 0, GREEN_MAX));
+  writeLed(redPin, constrain(red, 0, RED_MAX));
+  writeLed(yellowPin, constrain(yellow, 0, YELLOW_MAX));
+  writeLed(greenPin, constrain(green, 0, GREEN_MAX));
 }
 
 int triWave(unsigned long t, unsigned long period, int maxValue) {
@@ -413,7 +436,7 @@ bool bootWasPressed = false;
 bool switchTriggered = false;
 
 void checkBootButton() {
-  bool pressed = (digitalRead(BTN_BOOT_PIN) == LOW);
+  bool pressed = (digitalRead(BUTTON_PIN) == LOW);
   if (pressed && !bootWasPressed) {
     bootPressStart = millis();
     bootWasPressed = true;
@@ -450,12 +473,12 @@ bool statusLedState = false;
 void updateStatusLed() {
   bool connected = (useMQTT && wifiConnected) ? (WiFi.status() == WL_CONNECTED) : bleDeviceConnected;
   if (connected) {
-    digitalWrite(WIFI_LED_PIN, LOW);
+    digitalWrite(STATUS_PIN, LOW);
     return;
   }
   if (millis() - lastStatusLedToggle >= 500) {
     statusLedState = !statusLedState;
-    digitalWrite(WIFI_LED_PIN, statusLedState ? LOW : HIGH);
+    digitalWrite(STATUS_PIN, statusLedState ? LOW : HIGH);
     lastStatusLedToggle = millis();
   }
 }
@@ -471,7 +494,14 @@ class ServerCallbacks : public BLEServerCallbacks {
 };
 
 class ModeCharCallbacks : public BLECharacteristicCallbacks {
-  void onWrite(BLECharacteristic* c) { setMode(c->getValue()); }
+  void onWrite(BLECharacteristic* c) {
+    String val = c->getValue();
+    if (val == "ota") {
+      performOTA();
+      return;
+    }
+    setMode(val);
+  }
   void onRead(BLECharacteristic* c) { c->setValue(currentMode.c_str()); }
 };
 
@@ -529,6 +559,10 @@ void mqttCallback(char* topic, byte* payload, unsigned int length) {
     else if (c == "reasoning") setMode("thinking");
     else if (c == "using_tool") setMode("ai");
     else if (c == "error") setMode("error");
+    else if (c == "ota") {
+      performOTA();
+      return;
+    }
     else setMode(c);
   }
 }
@@ -546,7 +580,7 @@ bool connectWiFi() {
     }
     if (WiFi.status() == WL_CONNECTED) {
       Serial.printf("\nWiFi OK. IP: %s\n", WiFi.localIP().toString().c_str());
-      digitalWrite(WIFI_LED_PIN, LOW);
+      digitalWrite(STATUS_PIN, LOW);
       return true;
     }
     Serial.println("\nFailed, retrying..."); delay(2000);
@@ -581,6 +615,114 @@ void checkMQTTConnection() {
 }
 
 
+// =====================================================
+// OTA 固件更新
+// =====================================================
+
+void notifyOtaStatus(const String& status) {
+  if (pModeCharacteristic && bleDeviceConnected) {
+    pModeCharacteristic->setValue(status.c_str());
+    pModeCharacteristic->notify();
+  }
+  if (useMQTT && mqttClient.connected()) {
+    mqttClient.publish(cfgMqttStatus.c_str(), status.c_str(), true);
+  }
+}
+
+void performOTA() {
+  Serial.println("=== OTA Start ===");
+
+  if (otaInProgress) {
+    Serial.println("OTA already in progress");
+    return;
+  }
+
+  if (cfgOtaUrl.length() == 0) {
+    Serial.println("OTA URL not configured");
+    notifyOtaStatus("ota:no_url");
+    return;
+  }
+
+  if (WiFi.status() != WL_CONNECTED) {
+    Serial.println("WiFi not connected");
+    notifyOtaStatus("ota:no_wifi");
+    return;
+  }
+
+  otaInProgress = true;
+  notifyOtaStatus("ota:downloading");
+
+  HTTPClient http;
+  http.begin(cfgOtaUrl);
+  http.setTimeout(30000);
+
+  int httpCode = http.GET();
+  Serial.printf("HTTP GET: %d\n", httpCode);
+
+  if (httpCode != 200) {
+    Serial.printf("HTTP error: %d\n", httpCode);
+    notifyOtaStatus("ota:error");
+    http.end();
+    otaInProgress = false;
+    return;
+  }
+
+  int contentLength = http.getSize();
+  Serial.printf("Firmware size: %d bytes\n", contentLength);
+
+  if (contentLength <= 0) {
+    Serial.println("Invalid content length");
+    notifyOtaStatus("ota:error");
+    http.end();
+    otaInProgress = false;
+    return;
+  }
+
+  if (!Update.begin(contentLength)) {
+    Serial.println("Update.begin failed");
+    notifyOtaStatus("ota:error");
+    http.end();
+    otaInProgress = false;
+    return;
+  }
+
+  WiFiClient* stream = http.getStreamPtr();
+  uint8_t buf[1024];
+  int written = 0;
+  unsigned long lastProgress = millis();
+
+  while (http.connected() && written < contentLength) {
+    size_t size = stream->available();
+    if (size) {
+      int bytesRead = stream->readBytes(buf, min(size, sizeof(buf)));
+      size_t bytesWritten = Update.write(buf, bytesRead);
+      written += bytesWritten;
+
+      if (millis() - lastProgress >= 1000) {
+        Serial.printf("Progress: %d/%d (%.1f%%)\n", written, contentLength, (float)written / contentLength * 100);
+        lastProgress = millis();
+      }
+    }
+    delay(1);
+  }
+
+  Serial.printf("Written: %d/%d\n", written, contentLength);
+
+  if (Update.end(true)) {
+    Serial.println("OTA success!");
+    notifyOtaStatus("ota:success");
+    delay(1000);
+    ESP.restart();
+  } else {
+    Serial.printf("OTA error: %s\n", Update.errorString());
+    notifyOtaStatus("ota:error");
+  }
+
+  http.end();
+  otaInProgress = false;
+}
+
+
 // =====================================================
 // 初始化
 // =====================================================
@@ -589,23 +731,24 @@ void setup() {
   Serial.begin(115200);
   delay(500);
 
-  ledcAttach(RED_PIN, PWM_FREQ, PWM_RESOLUTION);
-  ledcAttach(YELLOW_PIN, PWM_FREQ, PWM_RESOLUTION);
-  ledcAttach(GREEN_PIN, PWM_FREQ, PWM_RESOLUTION);
-  pinMode(WIFI_LED_PIN, OUTPUT);
-  pinMode(BTN_BOOT_PIN, INPUT_PULLUP);
-  digitalWrite(WIFI_LED_PIN, HIGH);
-  allOff();
-
   preferences.begin("ai-light", false);
   useMQTT = (preferences.getUInt("comm_mode", 1) != 0);
   loadConfig();
 
+  ledcAttach(redPin, PWM_FREQ, PWM_RESOLUTION);
+  ledcAttach(yellowPin, PWM_FREQ, PWM_RESOLUTION);
+  ledcAttach(greenPin, PWM_FREQ, PWM_RESOLUTION);
+  pinMode(STATUS_PIN, OUTPUT);
+  pinMode(BUTTON_PIN, INPUT_PULLUP);
+  digitalWrite(STATUS_PIN, HIGH);
+  allOff();
+
   Serial.println();
   Serial.println("=== AI-Light ===");
   Serial.printf("Mode: %s\n", useMQTT ? "MQTT" : "BLE-only");
   Serial.printf("WiFi: %s\n", cfgWifiSsid.length() > 0 ? cfgWifiSsid.c_str() : "(not set)");
   Serial.printf("MQTT: %s\n", cfgMqttBroker.length() > 0 ? cfgMqttBroker.c_str() : "(not set)");
+  Serial.printf("Pins: R=%d G=%d Y=%d\n", redPin, greenPin, yellowPin);
 
   currentMode = "init";
   modeStart = millis();