main.go 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609
  1. package main
  2. import (
  3. "context"
  4. "flag"
  5. "fmt"
  6. "io"
  7. "os"
  8. "os/exec"
  9. "path/filepath"
  10. "strconv"
  11. "strings"
  12. "time"
  13. "ai-status-light/internal/api"
  14. "ai-status-light/internal/database"
  15. "ai-status-light/internal/logger"
  16. mqttcli "ai-status-light/internal/mqtt"
  17. )
  18. const defaultDBPath = "./data/config.db"
  19. var Version = "dev"
  20. func main() {
  21. if len(os.Args) < 2 {
  22. printUsage()
  23. return
  24. }
  25. switch os.Args[1] {
  26. case "serve":
  27. runServe(os.Args[2:])
  28. case "config":
  29. runConfig(os.Args[2:])
  30. case "version":
  31. fmt.Printf("ai-status-light %s\n", Version)
  32. default:
  33. printUsage()
  34. }
  35. }
  36. func printUsage() {
  37. fmt.Printf("ai-status-light %s\n\n", Version)
  38. fmt.Println("用法: ai-status-light <命令> [选项]")
  39. fmt.Println("")
  40. fmt.Println("命令:")
  41. fmt.Println(" serve 启动 API 服务(接收 OpenCode 插件事件)")
  42. fmt.Println(" config 管理 MQTT 和 BLE 配置")
  43. fmt.Println(" version 显示版本信息")
  44. fmt.Println("")
  45. fmt.Println("运行 'ai-status-light <命令> -h' 查看命令帮助")
  46. }
  47. func runServe(args []string) {
  48. fs := flag.NewFlagSet("serve", flag.ExitOnError)
  49. addr := fs.String("addr", ":8080", "监听地址")
  50. dbPath := fs.String("db", defaultDBPath, "数据库路径")
  51. logFile := fs.String("log-file", "./logs", "日志文件路径(默认 ./logs/monitor.log)")
  52. logLevel := fs.String("log-level", "info", "日志级别 (debug/info/warn/error)")
  53. tls := fs.Bool("tls", false, "启用 HTTPS (使用自签名证书)")
  54. tlsCert := fs.String("tls-cert", "./data/tls/cert.pem", "TLS 证书文件路径")
  55. tlsKey := fs.String("tls-key", "./data/tls/key.pem", "TLS 私钥文件路径")
  56. fs.Parse(args)
  57. logger.SetLevel(logger.ParseLevel(*logLevel))
  58. if err := logger.InitFileLog(*logFile); err != nil {
  59. fmt.Printf("初始化日志文件失败: %v\n", err)
  60. return
  61. }
  62. defer logger.Close()
  63. ctx, cancel := context.WithCancel(context.Background())
  64. defer cancel()
  65. db, err := database.New(*dbPath)
  66. if err != nil {
  67. logger.Error("打开数据库失败: %v", err)
  68. fmt.Printf("打开数据库失败: %v\n", err)
  69. return
  70. }
  71. defer db.Close()
  72. logger.Info("数据库已连接: %s", *dbPath)
  73. var mqttClient *mqttcli.Client
  74. cfg, err := db.GetMQTTConfig()
  75. if err != nil {
  76. logger.Error("读取 MQTT 配置失败: %v", err)
  77. fmt.Printf("读取 MQTT 配置失败: %v\n", err)
  78. } else if cfg != nil {
  79. mqttClient = mqttcli.NewFromConfig(cfg)
  80. if err := mqttClient.Connect(); err != nil {
  81. logger.Error("MQTT 连接失败: %v", err)
  82. fmt.Printf("MQTT 连接失败: %v\n", err)
  83. mqttClient = nil
  84. } else {
  85. defer mqttClient.Disconnect()
  86. logger.Info("MQTT 已连接: %s (主题: %s)", cfg.Broker, cfg.Topic)
  87. fmt.Printf("MQTT 已连接: %s (主题: %s)\n", cfg.Broker, cfg.Topic)
  88. }
  89. } else {
  90. logger.Info("未配置 MQTT,跳过 MQTT 连接")
  91. }
  92. var bleStdin io.WriteCloser
  93. bleCfg, err := db.GetBLEConfig()
  94. if err != nil {
  95. logger.Error("读取 BLE 配置失败: %v", err)
  96. fmt.Printf("读取 BLE 配置失败: %v\n", err)
  97. } else if bleCfg != nil {
  98. bleStdin = startBLERelay(bleCfg, ctx)
  99. } else {
  100. logger.Info("未配置 BLE,跳过 BLE 中继")
  101. }
  102. server := api.New(db, *addr)
  103. server.SetMQTTClient(mqttClient)
  104. server.SetBLEStdin(bleStdin)
  105. // 启动定时清理任务(每10分钟清理超过24小时的历史记录)
  106. go func() {
  107. ticker := time.NewTicker(10 * time.Minute)
  108. defer ticker.Stop()
  109. for {
  110. select {
  111. case <-ctx.Done():
  112. return
  113. case <-ticker.C:
  114. if err := db.CleanOldStatusRecords(24); err != nil {
  115. logger.Error("清理历史记录失败: %v", err)
  116. }
  117. }
  118. }
  119. }()
  120. logger.Info("定时清理任务已启动(每10分钟清理超过24小时的历史记录)")
  121. if *tls {
  122. if err := api.EnsureSelfSignedCert(*tlsCert, *tlsKey); err != nil {
  123. logger.Error("生成自签名证书失败: %v", err)
  124. fmt.Printf("生成自签名证书失败: %v\n", err)
  125. return
  126. }
  127. server.EnableTLS(*tlsCert, *tlsKey)
  128. logger.Info("HTTPS 已启用")
  129. fmt.Println("HTTPS 已启用 (自签名证书)")
  130. }
  131. fmt.Printf("API 服务已启动: %s\n", *addr)
  132. fmt.Println("Ctrl+C 停止")
  133. if err := server.Start(); err != nil {
  134. logger.Error("服务启动失败: %v", err)
  135. fmt.Printf("服务启动失败: %v\n", err)
  136. }
  137. }
  138. func runConfig(args []string) {
  139. if len(args) < 1 {
  140. printConfigUsage()
  141. return
  142. }
  143. dbPath := defaultDBPath
  144. logFile := "./logs"
  145. logLevel := "info"
  146. for i, arg := range args {
  147. if arg == "--db" && i+1 < len(args) {
  148. dbPath = args[i+1]
  149. }
  150. if arg == "--log-file" && i+1 < len(args) {
  151. logFile = args[i+1]
  152. }
  153. if arg == "--log-level" && i+1 < len(args) {
  154. logLevel = args[i+1]
  155. }
  156. }
  157. logger.SetLevel(logger.ParseLevel(logLevel))
  158. if err := logger.InitFileLog(logFile); err != nil {
  159. fmt.Printf("初始化日志文件失败: %v\n", err)
  160. return
  161. }
  162. defer logger.Close()
  163. // 过滤全局选项,避免传递给子命令的 FlagSet
  164. var filtered []string
  165. for i := 0; i < len(args); i++ {
  166. switch args[i] {
  167. case "--db", "--log-file", "--log-level":
  168. i++ // 跳过值
  169. default:
  170. filtered = append(filtered, args[i])
  171. }
  172. }
  173. args = filtered
  174. db, err := database.New(dbPath)
  175. if err != nil {
  176. logger.Error("打开数据库失败: %v", err)
  177. fmt.Printf("打开数据库失败: %v\n", err)
  178. return
  179. }
  180. defer db.Close()
  181. logger.Info("数据库已连接: %s", dbPath)
  182. switch args[0] {
  183. case "list":
  184. configs, err := db.ListMQTTConfigs()
  185. if err != nil {
  186. logger.Error("查询配置失败: %v", err)
  187. fmt.Printf("查询失败: %v\n", err)
  188. return
  189. }
  190. if len(configs) == 0 {
  191. fmt.Println("未配置 MQTT")
  192. return
  193. }
  194. logger.Info("查询到 %d 条 MQTT 配置", len(configs))
  195. for _, cfg := range configs {
  196. status := "禁用"
  197. if cfg.Enabled {
  198. status = "启用"
  199. }
  200. auth := ""
  201. if cfg.Username != "" {
  202. auth = fmt.Sprintf(" [认证: %s]", cfg.Username)
  203. }
  204. fmt.Printf("[%d] %s | %s | %s%s | %s\n", cfg.ID, cfg.Broker, cfg.ClientID, cfg.Topic, auth, status)
  205. }
  206. case "set":
  207. fs := flag.NewFlagSet("config set", flag.ExitOnError)
  208. broker := fs.String("broker", "", "MQTT Broker 地址 (如: tcp://127.0.0.1:1883)")
  209. clientID := fs.String("client-id", "opencode-monitor", "MQTT 客户端 ID")
  210. username := fs.String("username", "", "MQTT 用户名")
  211. password := fs.String("password", "", "MQTT 密码")
  212. topic := fs.String("topic", "opencode/status", "MQTT 主题")
  213. enabled := fs.Bool("enabled", true, "是否启用")
  214. fs.Parse(args[1:])
  215. if *broker == "" {
  216. fmt.Println("必须指定 --broker")
  217. return
  218. }
  219. cfg := &database.MQTTConfig{
  220. Broker: *broker,
  221. ClientID: *clientID,
  222. Username: *username,
  223. Password: *password,
  224. Topic: *topic,
  225. Enabled: *enabled,
  226. }
  227. if err := db.SaveMQTTConfig(cfg); err != nil {
  228. logger.Error("保存 MQTT 配置失败: %v", err)
  229. fmt.Printf("保存失败: %v\n", err)
  230. return
  231. }
  232. logger.Info("MQTT 配置已保存: %s (主题: %s)", cfg.Broker, cfg.Topic)
  233. fmt.Println("配置已保存")
  234. case "delete":
  235. if len(args) < 2 {
  236. fmt.Println("必须指定配置 ID")
  237. return
  238. }
  239. id, err := strconv.Atoi(args[1])
  240. if err != nil {
  241. logger.Warn("无效的配置 ID: %s", args[1])
  242. fmt.Println("无效的 ID")
  243. return
  244. }
  245. if err := db.DeleteMQTTConfig(id); err != nil {
  246. logger.Error("删除配置失败: id=%d, %v", id, err)
  247. fmt.Printf("删除失败: %v\n", err)
  248. return
  249. }
  250. logger.Info("MQTT 配置已删除: id=%d", id)
  251. fmt.Println("配置已删除")
  252. case "ble":
  253. runBleConfig(db, args[1:])
  254. case "device":
  255. runDeviceConfig(db, args[1:])
  256. default:
  257. printConfigUsage()
  258. }
  259. }
  260. func printConfigUsage() {
  261. fmt.Println("用法: ai-status-light config <子命令> [选项]")
  262. fmt.Println("")
  263. fmt.Println("子命令:")
  264. fmt.Println(" list 列出所有 MQTT 配置")
  265. fmt.Println(" set 设置 MQTT 配置")
  266. fmt.Println(" delete <id> 删除 MQTT 配置")
  267. fmt.Println(" ble list 列出所有 BLE 配置")
  268. fmt.Println(" ble set 设置 BLE 配置")
  269. fmt.Println(" ble delete <id> 删除 BLE 配置")
  270. fmt.Println("")
  271. fmt.Println("MQTT 选项:")
  272. fmt.Println(" --broker MQTT Broker 地址")
  273. fmt.Println(" --client-id MQTT 客户端 ID")
  274. fmt.Println(" --username MQTT 用户名")
  275. fmt.Println(" --password MQTT 密码")
  276. fmt.Println(" --topic MQTT 主题")
  277. fmt.Println(" --enabled 是否启用 (true/false)")
  278. fmt.Println("")
  279. fmt.Println("BLE 选项:")
  280. fmt.Println(" --device 蓝牙设备名称 (必填)")
  281. fmt.Println(" --service-uuid BLE 服务 UUID (必填)")
  282. fmt.Println(" --mode-char-uuid BLE 模式特征 UUID (必填)")
  283. fmt.Println(" --config-char-uuid BLE 配置特征 UUID (必填)")
  284. fmt.Println(" --enabled 是否启用 (true/false)")
  285. fmt.Println("")
  286. fmt.Println("全局选项:")
  287. fmt.Println(" --db 数据库路径")
  288. fmt.Println(" --log-file 日志文件路径(默认 ./logs/monitor.log)")
  289. fmt.Println(" --log-level 日志级别 (debug/info/warn/error)")
  290. }
  291. func startBLERelay(bleCfg *database.BLEConfig, ctx context.Context) io.WriteCloser {
  292. if len(embeddedBLERelay) == 0 {
  293. logger.Warn("BLE 中继未嵌入,请使用 make build-with-ble 构建")
  294. fmt.Println("警告: BLE 中继未嵌入,已跳过。请使用 make build-with-ble 构建")
  295. return nil
  296. }
  297. // 释放嵌入的 exe 到临时目录
  298. tmpDir := filepath.Join(os.TempDir(), "ai-status-light")
  299. if err := os.MkdirAll(tmpDir, 0755); err != nil {
  300. logger.Error("创建临时目录失败: %v", err)
  301. return nil
  302. }
  303. var tmpExe string
  304. if strings.Contains(strings.ToLower(os.Getenv("OS")), "windows") {
  305. tmpExe = filepath.Join(tmpDir, "ble_relay.exe")
  306. } else {
  307. tmpExe = filepath.Join(tmpDir, "ble_relay")
  308. }
  309. if err := os.WriteFile(tmpExe, embeddedBLERelay, 0755); err != nil {
  310. logger.Error("释放 BLE 中继失败: %v", err)
  311. return nil
  312. }
  313. logger.Debug("BLE 中继已释放到: %s", tmpExe)
  314. args := []string{
  315. "--device", bleCfg.DeviceName,
  316. "--service-uuid", bleCfg.ServiceUUID,
  317. "--mode-char-uuid", bleCfg.ModeCharUUID,
  318. "--config-char-uuid", bleCfg.ConfigCharUUID,
  319. }
  320. cmd := exec.CommandContext(ctx, tmpExe, args...)
  321. cmd.Stdout = os.Stdout
  322. cmd.Stderr = os.Stderr
  323. stdin, err := cmd.StdinPipe()
  324. if err != nil {
  325. logger.Error("创建 BLE 中继 stdin 管道失败: %v", err)
  326. return nil
  327. }
  328. if err := cmd.Start(); err != nil {
  329. logger.Error("启动 BLE 中继失败: %v", err)
  330. return nil
  331. }
  332. logger.Info("BLE 中继已启动: %s (PID: %d)", bleCfg.DeviceName, cmd.Process.Pid)
  333. fmt.Printf("BLE 中继已启动: %s (PID: %d)\n", bleCfg.DeviceName, cmd.Process.Pid)
  334. go func() {
  335. if err := cmd.Wait(); err != nil {
  336. logger.Error("BLE 中继退出: %v", err)
  337. }
  338. }()
  339. return stdin
  340. }
  341. func runBleConfig(db *database.DB, args []string) {
  342. if len(args) < 1 {
  343. printBleConfigUsage()
  344. return
  345. }
  346. switch args[0] {
  347. case "list":
  348. configs, err := db.ListBLEConfigs()
  349. if err != nil {
  350. logger.Error("查询 BLE 配置失败: %v", err)
  351. fmt.Printf("查询失败: %v\n", err)
  352. return
  353. }
  354. if len(configs) == 0 {
  355. fmt.Println("未配置 BLE")
  356. return
  357. }
  358. logger.Info("查询到 %d 条 BLE 配置", len(configs))
  359. for _, cfg := range configs {
  360. status := "禁用"
  361. if cfg.Enabled {
  362. status = "启用"
  363. }
  364. fmt.Printf("[%d] %s | %s | %s | %s | %s\n", cfg.ID, cfg.DeviceName, cfg.ServiceUUID, cfg.ModeCharUUID, cfg.ConfigCharUUID, status)
  365. }
  366. case "set":
  367. fs := flag.NewFlagSet("config ble set", flag.ExitOnError)
  368. deviceName := fs.String("device", "", "蓝牙设备名称 (必填)")
  369. serviceUUID := fs.String("service-uuid", "", "BLE 服务 UUID (必填)")
  370. modeCharUUID := fs.String("mode-char-uuid", "", "BLE 模式特征 UUID (必填)")
  371. configCharUUID := fs.String("config-char-uuid", "", "BLE 配置特征 UUID (必填)")
  372. enabled := fs.Bool("enabled", true, "是否启用")
  373. fs.Parse(args[1:])
  374. if *deviceName == "" || *serviceUUID == "" || *modeCharUUID == "" || *configCharUUID == "" {
  375. fmt.Println("错误: --device, --service-uuid, --mode-char-uuid, --config-char-uuid 为必填参数")
  376. fs.Usage()
  377. return
  378. }
  379. cfg := &database.BLEConfig{
  380. DeviceName: *deviceName,
  381. ServiceUUID: *serviceUUID,
  382. ModeCharUUID: *modeCharUUID,
  383. ConfigCharUUID: *configCharUUID,
  384. Enabled: *enabled,
  385. }
  386. if err := db.SaveBLEConfig(cfg); err != nil {
  387. logger.Error("保存 BLE 配置失败: %v", err)
  388. fmt.Printf("保存失败: %v\n", err)
  389. return
  390. }
  391. logger.Info("BLE 配置已保存: %s", cfg.DeviceName)
  392. fmt.Println("配置已保存")
  393. case "delete":
  394. if len(args) < 2 {
  395. fmt.Println("必须指定配置 ID")
  396. return
  397. }
  398. id, err := strconv.Atoi(args[1])
  399. if err != nil {
  400. logger.Warn("无效的配置 ID: %s", args[1])
  401. fmt.Println("无效的 ID")
  402. return
  403. }
  404. if err := db.DeleteBLEConfig(id); err != nil {
  405. logger.Error("删除 BLE 配置失败: id=%d, %v", id, err)
  406. fmt.Printf("删除失败: %v\n", err)
  407. return
  408. }
  409. logger.Info("BLE 配置已删除: id=%d", id)
  410. fmt.Println("配置已删除")
  411. default:
  412. printBleConfigUsage()
  413. }
  414. }
  415. func printBleConfigUsage() {
  416. fmt.Println("用法: ai-status-light config ble <子命令> [选项]")
  417. fmt.Println("")
  418. fmt.Println("子命令:")
  419. fmt.Println(" list 列出所有 BLE 配置")
  420. fmt.Println(" set 设置 BLE 配置")
  421. fmt.Println(" delete <id> 删除 BLE 配置")
  422. fmt.Println("")
  423. fmt.Println("选项:")
  424. fmt.Println(" --device 蓝牙设备名称 (必填)")
  425. fmt.Println(" --service-uuid BLE 服务 UUID (必填)")
  426. fmt.Println(" --mode-char-uuid BLE 模式特征 UUID (必填)")
  427. fmt.Println(" --config-char-uuid BLE 配置特征 UUID (必填)")
  428. fmt.Println(" --enabled 是否启用 (true/false)")
  429. }
  430. func runDeviceConfig(db *database.DB, args []string) {
  431. if len(args) < 1 {
  432. printDeviceConfigUsage()
  433. return
  434. }
  435. switch args[0] {
  436. case "list":
  437. configs, err := db.ListDeviceConfigs()
  438. if err != nil {
  439. logger.Error("查询设备配置失败: %v", err)
  440. fmt.Printf("查询失败: %v\n", err)
  441. return
  442. }
  443. if len(configs) == 0 {
  444. fmt.Println("未配置设备")
  445. return
  446. }
  447. logger.Info("查询到 %d 条设备配置", len(configs))
  448. for _, cfg := range configs {
  449. status := "禁用"
  450. if cfg.Enabled {
  451. status = "启用"
  452. }
  453. fmt.Printf("[%d] %s | %s | %s\n", cfg.ID, cfg.DeviceName, cfg.ConfigTopic, status)
  454. }
  455. case "set":
  456. fs := flag.NewFlagSet("config device set", flag.ExitOnError)
  457. deviceName := fs.String("device", "", "设备名称 (必填)")
  458. configTopic := fs.String("topic", "agent/status/config", "配置 topic")
  459. wifiSSID := fs.String("wifi-ssid", "", "WiFi SSID")
  460. wifiPass := fs.String("wifi-pass", "", "WiFi 密码")
  461. mqttBroker := fs.String("mqtt-broker", "", "MQTT Broker 地址")
  462. mqttPort := fs.Int("mqtt-port", 1883, "MQTT 端口")
  463. mqttUser := fs.String("mqtt-user", "", "MQTT 用户名")
  464. mqttPass := fs.String("mqtt-pass", "", "MQTT 密码")
  465. mqttClient := fs.String("mqtt-client", "", "MQTT 客户端 ID")
  466. mqttTopic := fs.String("mqtt-topic", "", "MQTT 状态 topic")
  467. mqttStatus := fs.String("mqtt-status", "", "MQTT 状态上报 topic")
  468. pinRed := fs.Int("pin-red", 4, "红灯引脚")
  469. pinGreen := fs.Int("pin-green", 3, "绿灯引脚")
  470. pinYellow := fs.Int("pin-yellow", 2, "黄灯引脚")
  471. enabled := fs.Bool("enabled", true, "是否启用")
  472. fs.Parse(args[1:])
  473. if *deviceName == "" {
  474. fmt.Println("错误: --device 为必填参数")
  475. fs.Usage()
  476. return
  477. }
  478. cfg := &database.DeviceConfig{
  479. DeviceName: *deviceName,
  480. ConfigTopic: *configTopic,
  481. WifiSSID: *wifiSSID,
  482. WifiPass: *wifiPass,
  483. MqttBroker: *mqttBroker,
  484. MqttPort: *mqttPort,
  485. MqttUser: *mqttUser,
  486. MqttPass: *mqttPass,
  487. MqttClient: *mqttClient,
  488. MqttTopic: *mqttTopic,
  489. MqttStatus: *mqttStatus,
  490. PinRed: *pinRed,
  491. PinGreen: *pinGreen,
  492. PinYellow: *pinYellow,
  493. Enabled: *enabled,
  494. }
  495. if err := db.SaveDeviceConfig(cfg); err != nil {
  496. logger.Error("保存设备配置失败: %v", err)
  497. fmt.Printf("保存失败: %v\n", err)
  498. return
  499. }
  500. logger.Info("设备配置已保存: %s", cfg.DeviceName)
  501. fmt.Println("配置已保存")
  502. case "delete":
  503. if len(args) < 2 {
  504. fmt.Println("必须指定配置 ID")
  505. return
  506. }
  507. id, err := strconv.Atoi(args[1])
  508. if err != nil {
  509. logger.Warn("无效的配置 ID: %s", args[1])
  510. fmt.Println("无效的 ID")
  511. return
  512. }
  513. if err := db.DeleteDeviceConfig(id); err != nil {
  514. logger.Error("删除设备配置失败: id=%d, %v", id, err)
  515. fmt.Printf("删除失败: %v\n", err)
  516. return
  517. }
  518. logger.Info("设备配置已删除: id=%d", id)
  519. fmt.Println("配置已删除")
  520. default:
  521. printDeviceConfigUsage()
  522. }
  523. }
  524. func printDeviceConfigUsage() {
  525. fmt.Println("用法: ai-status-light config device <子命令> [选项]")
  526. fmt.Println("")
  527. fmt.Println("子命令:")
  528. fmt.Println(" list 列出所有设备配置")
  529. fmt.Println(" set 设置设备配置")
  530. fmt.Println(" delete <id> 删除设备配置")
  531. fmt.Println("")
  532. fmt.Println("选项:")
  533. fmt.Println(" --device 设备名称 (必填)")
  534. fmt.Println(" --topic 配置 topic (默认: agent/status/config)")
  535. fmt.Println(" --wifi-ssid WiFi SSID")
  536. fmt.Println(" --wifi-pass WiFi 密码")
  537. fmt.Println(" --mqtt-broker MQTT Broker 地址")
  538. fmt.Println(" --mqtt-port MQTT 端口 (默认: 1883)")
  539. fmt.Println(" --mqtt-user MQTT 用户名")
  540. fmt.Println(" --mqtt-pass MQTT 密码")
  541. fmt.Println(" --mqtt-client MQTT 客户端 ID")
  542. fmt.Println(" --mqtt-topic MQTT 状态 topic")
  543. fmt.Println(" --mqtt-status MQTT 状态上报 topic")
  544. fmt.Println(" --pin-red 红灯引脚 (默认: 4)")
  545. fmt.Println(" --pin-green 绿灯引脚 (默认: 3)")
  546. fmt.Println(" --pin-yellow 黄灯引脚 (默认: 2)")
  547. fmt.Println(" --enabled 是否启用 (true/false)")
  548. }