moki 1 週間 前
コミット
dc3f5c226a
1 ファイル変更128 行追加8 行削除
  1. 128 8
      docs/BLE_API.md

+ 128 - 8
docs/BLE_API.md

@@ -6,10 +6,17 @@
 |------|-----|
 | 设备名称 | `AI-Light` |
 | Service UUID | `b8b7e001-7a6b-4f4f-9a8b-11c0ffee0001` |
-| Characteristic UUID | `b8b7e002-7a6b-4f4f-9a8b-11c0ffee0001` |
-| Characteristic 属性 | READ / WRITE / NOTIFY |
 
-## 支持的模式
+### BLE 特征
+
+| 特征 | UUID | 属性 | 说明 |
+|------|------|------|------|
+| Mode | `b8b7e002-7a6b-4f4f-9a8b-11c0ffee0001` | READ / WRITE / NOTIFY | 灯效模式控制 |
+| Config | `b8b7e003-7a6b-4f4f-9a8b-11c0ffee0001` | READ / WRITE | WiFi/MQTT 配置 |
+
+## Mode 特征 - 灯效控制
+
+### 支持的模式
 
 | 模式 | 说明 | 灯效 |
 |------|------|------|
@@ -27,10 +34,62 @@
 | `green` | 绿灯 | 绿灯常亮 |
 | `idle` | 空闲(自动转为 traffic) | 同 traffic |
 
-## 超时规则
+### 行为说明
+
+- **重复模式忽略**:如果推送的模式与当前模式相同,设备会自动忽略,不做任何操作
+- **超时规则**:普通模式运行 **5分钟** → 自动进入 `traffic`;`traffic` 运行 **10分钟** → 自动进入 `off`
+- **通知**:模式变化时会通过 NOTIFY 特征推送当前模式
+
+## Config 特征 - WiFi/MQTT 配置
+
+### 读取配置
+
+读取 Config 特征返回当前配置 JSON(不含密码):
+
+```json
+{
+  "wifi_ssid": "MyNetwork",
+  "mqtt_broker": "192.168.1.100",
+  "mqtt_port": 1883,
+  "mqtt_user": "user",
+  "mqtt_client": "AI-Light",
+  "mqtt_topic": "opencode/status",
+  "mqtt_status": "openCodeLight/status",
+  "comm_mode": 1
+}
+```
+
+### 写入配置
+
+向 Config 特征写入 JSON 配置,设备自动保存到 NVS 并重启:
+
+```json
+{
+  "wifi_ssid": "MyNetwork",
+  "wifi_pass": "password123",
+  "mqtt_broker": "192.168.1.100",
+  "mqtt_port": 1883,
+  "mqtt_user": "user",
+  "mqtt_pass": "pass",
+  "mqtt_client": "AI-Light",
+  "mqtt_topic": "opencode/status",
+  "mqtt_status": "openCodeLight/status"
+}
+```
+
+| 字段 | 类型 | 必填 | 默认值 | 说明 |
+|------|------|------|--------|------|
+| wifi_ssid | string | 否 | - | WiFi SSID |
+| wifi_pass | string | 否 | - | WiFi 密码 |
+| mqtt_broker | string | 否 | - | MQTT Broker 地址 |
+| mqtt_port | number | 否 | 1883 | MQTT 端口 |
+| mqtt_user | string | 否 | - | MQTT 用户名 |
+| mqtt_pass | string | 否 | - | MQTT 密码 |
+| mqtt_client | string | 否 | AI-Light | MQTT Client ID |
+| mqtt_topic | string | 否 | opencode/status | 订阅主题 |
+| mqtt_status | string | 否 | openCodeLight/status | 状态发布主题 |
 
-- 普通模式运行 **5分钟** → 自动进入 `traffic`
-- `traffic` 运行 **10分钟** → 自动进入 `off`
+**注意**:所有字段都是可选的,只更新提供的字段,未提供的字段保持原值。
 
 ## Go 客户端示例
 
@@ -57,6 +116,7 @@ import (
 var (
 	serviceUUID  = bluetooth.NewUUID([16]byte{0xb8, 0xb7, 0xe0, 0x01, 0x7a, 0x6b, 0x4f, 0x4f, 0x9a, 0x8b, 0x11, 0xc0, 0xff, 0xee, 0x00, 0x01})
 	modeCharUUID = bluetooth.NewUUID([16]byte{0xb8, 0xb7, 0xe0, 0x02, 0x7a, 0x6b, 0x4f, 0x4f, 0x9a, 0x8b, 0x11, 0xc0, 0xff, 0xee, 0x00, 0x01})
+	configCharUUID = bluetooth.NewUUID([16]byte{0xb8, 0xb7, 0xe0, 0x03, 0x7a, 0x6b, 0x4f, 0x4f, 0x9a, 0x8b, 0x11, 0xc0, 0xff, 0xee, 0x00, 0x01})
 )
 
 func main() {
@@ -176,8 +236,9 @@ import (
 )
 
 var (
-	serviceUUID  = bluetooth.NewUUID([16]byte{0xb8, 0xb7, 0xe0, 0x01, 0x7a, 0x6b, 0x4f, 0x4f, 0x9a, 0x8b, 0x11, 0xc0, 0xff, 0xee, 0x00, 0x01})
-	modeCharUUID = bluetooth.NewUUID([16]byte{0xb8, 0xb7, 0xe0, 0x02, 0x7a, 0x6b, 0x4f, 0x4f, 0x9a, 0x8b, 0x11, 0xc0, 0xff, 0xee, 0x00, 0x01})
+	serviceUUID    = bluetooth.NewUUID([16]byte{0xb8, 0xb7, 0xe0, 0x01, 0x7a, 0x6b, 0x4f, 0x4f, 0x9a, 0x8b, 0x11, 0xc0, 0xff, 0xee, 0x00, 0x01})
+	modeCharUUID   = bluetooth.NewUUID([16]byte{0xb8, 0xb7, 0xe0, 0x02, 0x7a, 0x6b, 0x4f, 0x4f, 0x9a, 0x8b, 0x11, 0xc0, 0xff, 0xee, 0x00, 0x01})
+	configCharUUID = bluetooth.NewUUID([16]byte{0xb8, 0xb7, 0xe0, 0x03, 0x7a, 0x6b, 0x4f, 0x4f, 0x9a, 0x8b, 0x11, 0xc0, 0xff, 0xee, 0x00, 0x01})
 )
 
 // SetMode 设置灯效模式
@@ -232,6 +293,55 @@ func SetMode(mode string) error {
 
 	return nil
 }
+
+// SetConfig 写入 WiFi/MQTT 配置
+func SetConfig(configJSON string) error {
+	adapter := bluetooth.DefaultAdapter
+	if err := adapter.Enable(); err != nil {
+		return fmt.Errorf("enable adapter: %w", err)
+	}
+
+	var device bluetooth.Device
+	found := make(chan bool, 1)
+
+	go func() {
+		adapter.Scan(func(adapter *bluetooth.Adapter, scanResult bluetooth.ScanResult) {
+			if scanResult.LocalName() == "AI-Light" {
+				adapter.StopScan()
+				var err error
+				device, err = adapter.Connect(scanResult.Address, bluetooth.ConnectionParams{})
+				found <- err == nil
+			}
+		})
+	}()
+
+	select {
+	case ok := <-found:
+		if !ok {
+			return fmt.Errorf("connect failed")
+		}
+	case <-time.After(10 * time.Second):
+		return fmt.Errorf("scan timeout")
+	}
+	defer device.Disconnect()
+
+	services, err := device.DiscoverServices([]bluetooth.UUID{serviceUUID})
+	if err != nil || len(services) == 0 {
+		return fmt.Errorf("service not found")
+	}
+
+	characteristics, err := services[0].DiscoverCharacteristics([]bluetooth.UUID{configCharUUID})
+	if err != nil || len(characteristics) == 0 {
+		return fmt.Errorf("config characteristic not found")
+	}
+
+	_, err = characteristics[0].WriteString(configJSON)
+	if err != nil {
+		return fmt.Errorf("write failed: %w", err)
+	}
+
+	return nil
+}
 ```
 
 ### 调用示例
@@ -258,6 +368,16 @@ func main() {
 	
 	// 关闭
 	ailight.SetMode("off")
+	
+	// 配置 WiFi/MQTT
+	ailight.SetConfig(`{
+		"wifi_ssid": "MyNetwork",
+		"wifi_pass": "password123",
+		"mqtt_broker": "192.168.1.100",
+		"mqtt_port": 1883,
+		"mqtt_client": "AI-Light",
+		"mqtt_topic": "opencode/status"
+	}`)
 }
 ```