|
@@ -6,10 +6,17 @@
|
|
|
|------|-----|
|
|
|------|-----|
|
|
|
| 设备名称 | `AI-Light` |
|
|
| 设备名称 | `AI-Light` |
|
|
|
| Service UUID | `b8b7e001-7a6b-4f4f-9a8b-11c0ffee0001` |
|
|
| 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` | 绿灯 | 绿灯常亮 |
|
|
| `green` | 绿灯 | 绿灯常亮 |
|
|
|
| `idle` | 空闲(自动转为 traffic) | 同 traffic |
|
|
| `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 客户端示例
|
|
## Go 客户端示例
|
|
|
|
|
|
|
@@ -57,6 +116,7 @@ import (
|
|
|
var (
|
|
var (
|
|
|
serviceUUID = bluetooth.NewUUID([16]byte{0xb8, 0xb7, 0xe0, 0x01, 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})
|
|
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() {
|
|
func main() {
|
|
@@ -176,8 +236,9 @@ import (
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
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 设置灯效模式
|
|
// SetMode 设置灯效模式
|
|
@@ -232,6 +293,55 @@ func SetMode(mode string) error {
|
|
|
|
|
|
|
|
return nil
|
|
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")
|
|
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"
|
|
|
|
|
+ }`)
|
|
|
}
|
|
}
|
|
|
```
|
|
```
|
|
|
|
|
|