main.go 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793
  1. package main
  2. import (
  3. "context"
  4. "flag"
  5. "fmt"
  6. "io"
  7. "os"
  8. "os/exec"
  9. "os/signal"
  10. "path/filepath"
  11. "sort"
  12. "strconv"
  13. "strings"
  14. "sync"
  15. "syscall"
  16. "time"
  17. "ai-status-light/internal/api"
  18. "ai-status-light/internal/database"
  19. "ai-status-light/internal/discovery"
  20. "ai-status-light/internal/event"
  21. "ai-status-light/internal/logger"
  22. "ai-status-light/internal/monitor"
  23. mqttcli "ai-status-light/internal/mqtt"
  24. )
  25. const defaultDBPath = "./data/config.db"
  26. var Version = "dev"
  27. func main() {
  28. if len(os.Args) < 2 {
  29. printUsage()
  30. return
  31. }
  32. switch os.Args[1] {
  33. case "monitor":
  34. runMonitor(os.Args[2:])
  35. case "config":
  36. runConfig(os.Args[2:])
  37. case "serve":
  38. runServe(os.Args[2:])
  39. case "version":
  40. fmt.Printf("opencode-monitor %s\n", Version)
  41. default:
  42. printUsage()
  43. }
  44. }
  45. func printUsage() {
  46. fmt.Printf("opencode-monitor %s\n\n", Version)
  47. fmt.Println("用法: opencode-monitor <命令> [选项]")
  48. fmt.Println("")
  49. fmt.Println("命令:")
  50. fmt.Println(" monitor 启动监控")
  51. fmt.Println(" config 管理 MQTT 和 BLE 配置")
  52. fmt.Println(" serve 启动 API 服务")
  53. fmt.Println(" version 显示版本信息")
  54. fmt.Println("")
  55. fmt.Println("运行 'opencode-monitor <命令> -h' 查看命令帮助")
  56. }
  57. func runServe(args []string) {
  58. fs := flag.NewFlagSet("serve", flag.ExitOnError)
  59. addr := fs.String("addr", ":8080", "监听地址")
  60. dbPath := fs.String("db", defaultDBPath, "数据库路径")
  61. logFile := fs.String("log-file", "./logs", "日志文件路径(默认 ./logs/monitor.log)")
  62. logLevel := fs.String("log-level", "info", "日志级别 (debug/info/warn/error)")
  63. tls := fs.Bool("tls", false, "启用 HTTPS (使用自签名证书)")
  64. tlsCert := fs.String("tls-cert", "./data/tls/cert.pem", "TLS 证书文件路径")
  65. tlsKey := fs.String("tls-key", "./data/tls/key.pem", "TLS 私钥文件路径")
  66. fs.Parse(args)
  67. logger.SetLevel(logger.ParseLevel(*logLevel))
  68. if err := logger.InitFileLog(*logFile); err != nil {
  69. fmt.Printf("初始化日志文件失败: %v\n", err)
  70. return
  71. }
  72. defer logger.Close()
  73. db, err := database.New(*dbPath)
  74. if err != nil {
  75. logger.Error("打开数据库失败: %v", err)
  76. fmt.Printf("打开数据库失败: %v\n", err)
  77. return
  78. }
  79. defer db.Close()
  80. logger.Info("数据库已连接: %s", *dbPath)
  81. server := api.New(db, *addr)
  82. if *tls {
  83. if err := api.EnsureSelfSignedCert(*tlsCert, *tlsKey); err != nil {
  84. logger.Error("生成自签名证书失败: %v", err)
  85. fmt.Printf("生成自签名证书失败: %v\n", err)
  86. return
  87. }
  88. server.EnableTLS(*tlsCert, *tlsKey)
  89. logger.Info("HTTPS 已启用")
  90. fmt.Println("HTTPS 已启用 (自签名证书)")
  91. } else {
  92. fmt.Printf("API 服务已启动: %s\n", *addr)
  93. }
  94. fmt.Println("接口文档:")
  95. fmt.Println(" GET /api/health - 健康检查")
  96. fmt.Println(" GET /api/mqtt - 获取所有 MQTT 配置")
  97. fmt.Println(" POST /api/mqtt - 创建 MQTT 配置")
  98. fmt.Println(" GET /api/mqtt/:id - 获取单个 MQTT 配置")
  99. fmt.Println(" PUT /api/mqtt/:id - 更新 MQTT 配置")
  100. fmt.Println(" DELETE /api/mqtt/:id - 删除 MQTT 配置")
  101. fmt.Println(" GET /api/ble - 获取所有 BLE 配置")
  102. fmt.Println(" POST /api/ble - 创建 BLE 配置")
  103. fmt.Println(" GET /api/ble/:id - 获取单个 BLE 配置")
  104. fmt.Println(" PUT /api/ble/:id - 更新 BLE 配置")
  105. fmt.Println(" DELETE /api/ble/:id - 删除 BLE 配置")
  106. if err := server.Start(); err != nil {
  107. logger.Error("服务启动失败: %v", err)
  108. fmt.Printf("服务启动失败: %v\n", err)
  109. }
  110. }
  111. func runMonitor(args []string) {
  112. fs := flag.NewFlagSet("monitor", flag.ExitOnError)
  113. host := fs.String("host", "127.0.0.1", "主机地址")
  114. portsFlag := fs.String("ports", "", "端口列表,逗号分隔 (如: 4096,4097,4098)")
  115. scanFlag := fs.String("scan", "", "扫描端口范围 (如: 4096-4100)")
  116. intervalFlag := fs.Int("interval", 1, "动态扫描间隔(秒), 默认1")
  117. dbPath := fs.String("db", defaultDBPath, "数据库路径")
  118. apiAddr := fs.String("api-addr", "", "API 服务地址 (如: :8080)")
  119. tls := fs.Bool("tls", false, "启用 HTTPS (使用自签名证书)")
  120. tlsCert := fs.String("tls-cert", "./data/tls/cert.pem", "TLS 证书文件路径")
  121. tlsKey := fs.String("tls-key", "./data/tls/key.pem", "TLS 私钥文件路径")
  122. logFile := fs.String("log-file", "./logs", "日志文件路径(默认 ./logs/monitor.log)")
  123. logLevel := fs.String("log-level", "info", "日志级别 (debug/info/warn/error)")
  124. fs.Parse(args)
  125. logger.SetLevel(logger.ParseLevel(*logLevel))
  126. if err := logger.InitFileLog(*logFile); err != nil {
  127. fmt.Printf("初始化日志文件失败: %v\n", err)
  128. return
  129. }
  130. defer logger.Close()
  131. var scanRange *[2]int
  132. if *scanFlag != "" {
  133. parts := strings.Split(*scanFlag, "-")
  134. if len(parts) == 2 {
  135. start, err1 := strconv.Atoi(parts[0])
  136. end, err2 := strconv.Atoi(parts[1])
  137. if err1 == nil && err2 == nil {
  138. scanRange = &[2]int{start, end}
  139. }
  140. }
  141. }
  142. ctx, cancel := context.WithCancel(context.Background())
  143. defer cancel()
  144. sigChan := make(chan os.Signal, 1)
  145. signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
  146. db, err := database.New(*dbPath)
  147. if err != nil {
  148. logger.Error("打开数据库失败: %v", err)
  149. fmt.Printf("打开数据库失败: %v\n", err)
  150. return
  151. }
  152. defer db.Close()
  153. logger.Info("数据库已连接: %s", *dbPath)
  154. var mqttClient *mqttcli.Client
  155. cfg, err := db.GetMQTTConfig()
  156. if err != nil {
  157. logger.Error("读取 MQTT 配置失败: %v", err)
  158. fmt.Printf("读取 MQTT 配置失败: %v\n", err)
  159. } else if cfg != nil {
  160. mqttClient = mqttcli.NewFromConfig(cfg)
  161. if err := mqttClient.Connect(); err != nil {
  162. logger.Error("MQTT 连接失败: %v", err)
  163. fmt.Printf("MQTT 连接失败: %v\n", err)
  164. mqttClient = nil
  165. } else {
  166. defer mqttClient.Disconnect()
  167. logger.Info("MQTT 已连接: %s (主题: %s)", cfg.Broker, cfg.Topic)
  168. fmt.Printf("MQTT 已连接: %s (主题: %s)\n", cfg.Broker, cfg.Topic)
  169. }
  170. } else {
  171. logger.Info("未配置 MQTT,跳过 MQTT 连接")
  172. }
  173. var bleStdin io.WriteCloser
  174. bleCfg, err := db.GetBLEConfig()
  175. if err != nil {
  176. logger.Error("读取 BLE 配置失败: %v", err)
  177. fmt.Printf("读取 BLE 配置失败: %v\n", err)
  178. } else if bleCfg != nil {
  179. bleStdin = startBLERelay(bleCfg, ctx)
  180. } else {
  181. logger.Info("未配置 BLE,跳过 BLE 中继")
  182. }
  183. var apiServer *api.Server
  184. if *apiAddr != "" {
  185. apiServer = api.New(db, *apiAddr)
  186. if *tls {
  187. if err := api.EnsureSelfSignedCert(*tlsCert, *tlsKey); err != nil {
  188. logger.Error("生成自签名证书失败: %v", err)
  189. fmt.Printf("生成自签名证书失败: %v\n", err)
  190. return
  191. }
  192. apiServer.EnableTLS(*tlsCert, *tlsKey)
  193. }
  194. go func() {
  195. scheme := "http"
  196. if *tls {
  197. scheme = "https"
  198. }
  199. logger.Info("API 服务已启动: %s://%s", scheme, *apiAddr)
  200. fmt.Printf("API 服务已启动: %s://%s\n", scheme, *apiAddr)
  201. if err := apiServer.Start(); err != nil {
  202. logger.Error("API 服务失败: %v", err)
  203. fmt.Printf("API 服务失败: %v\n", err)
  204. }
  205. }()
  206. }
  207. callback := createCallback(mqttClient, apiServer, bleStdin)
  208. if *portsFlag != "" {
  209. runFixedMode(ctx, *host, *portsFlag, callback, sigChan)
  210. cancel()
  211. return
  212. }
  213. if scanRange != nil {
  214. fmt.Printf("扫描端口范围 %d-%d...\n", scanRange[0], scanRange[1])
  215. } else {
  216. fmt.Println("查找 OpenCode 实例...")
  217. }
  218. runDynamicMode(ctx, *host, scanRange, *intervalFlag, callback, sigChan)
  219. cancel()
  220. }
  221. func runConfig(args []string) {
  222. if len(args) < 1 {
  223. printConfigUsage()
  224. return
  225. }
  226. dbPath := defaultDBPath
  227. logFile := "./logs"
  228. logLevel := "info"
  229. for i, arg := range args {
  230. if arg == "--db" && i+1 < len(args) {
  231. dbPath = args[i+1]
  232. }
  233. if arg == "--log-file" && i+1 < len(args) {
  234. logFile = args[i+1]
  235. }
  236. if arg == "--log-level" && i+1 < len(args) {
  237. logLevel = args[i+1]
  238. }
  239. }
  240. logger.SetLevel(logger.ParseLevel(logLevel))
  241. if err := logger.InitFileLog(logFile); err != nil {
  242. fmt.Printf("初始化日志文件失败: %v\n", err)
  243. return
  244. }
  245. defer logger.Close()
  246. // 过滤全局选项,避免传递给子命令的 FlagSet
  247. var filtered []string
  248. for i := 0; i < len(args); i++ {
  249. switch args[i] {
  250. case "--db", "--log-file", "--log-level":
  251. i++ // 跳过值
  252. default:
  253. filtered = append(filtered, args[i])
  254. }
  255. }
  256. args = filtered
  257. db, err := database.New(dbPath)
  258. if err != nil {
  259. logger.Error("打开数据库失败: %v", err)
  260. fmt.Printf("打开数据库失败: %v\n", err)
  261. return
  262. }
  263. defer db.Close()
  264. logger.Info("数据库已连接: %s", dbPath)
  265. switch args[0] {
  266. case "list":
  267. configs, err := db.ListMQTTConfigs()
  268. if err != nil {
  269. logger.Error("查询配置失败: %v", err)
  270. fmt.Printf("查询失败: %v\n", err)
  271. return
  272. }
  273. if len(configs) == 0 {
  274. fmt.Println("未配置 MQTT")
  275. return
  276. }
  277. logger.Info("查询到 %d 条 MQTT 配置", len(configs))
  278. for _, cfg := range configs {
  279. status := "禁用"
  280. if cfg.Enabled {
  281. status = "启用"
  282. }
  283. auth := ""
  284. if cfg.Username != "" {
  285. auth = fmt.Sprintf(" [认证: %s]", cfg.Username)
  286. }
  287. fmt.Printf("[%d] %s | %s | %s%s | %s\n", cfg.ID, cfg.Broker, cfg.ClientID, cfg.Topic, auth, status)
  288. }
  289. case "set":
  290. fs := flag.NewFlagSet("config set", flag.ExitOnError)
  291. broker := fs.String("broker", "", "MQTT Broker 地址 (如: tcp://127.0.0.1:1883)")
  292. clientID := fs.String("client-id", "opencode-monitor", "MQTT 客户端 ID")
  293. username := fs.String("username", "", "MQTT 用户名")
  294. password := fs.String("password", "", "MQTT 密码")
  295. topic := fs.String("topic", "opencode/status", "MQTT 主题")
  296. enabled := fs.Bool("enabled", true, "是否启用")
  297. fs.Parse(args[1:])
  298. if *broker == "" {
  299. fmt.Println("必须指定 --broker")
  300. return
  301. }
  302. cfg := &database.MQTTConfig{
  303. Broker: *broker,
  304. ClientID: *clientID,
  305. Username: *username,
  306. Password: *password,
  307. Topic: *topic,
  308. Enabled: *enabled,
  309. }
  310. if err := db.SaveMQTTConfig(cfg); err != nil {
  311. logger.Error("保存 MQTT 配置失败: %v", err)
  312. fmt.Printf("保存失败: %v\n", err)
  313. return
  314. }
  315. logger.Info("MQTT 配置已保存: %s (主题: %s)", cfg.Broker, cfg.Topic)
  316. fmt.Println("配置已保存")
  317. case "delete":
  318. if len(args) < 2 {
  319. fmt.Println("必须指定配置 ID")
  320. return
  321. }
  322. id, err := strconv.Atoi(args[1])
  323. if err != nil {
  324. logger.Warn("无效的配置 ID: %s", args[1])
  325. fmt.Println("无效的 ID")
  326. return
  327. }
  328. if err := db.DeleteMQTTConfig(id); err != nil {
  329. logger.Error("删除配置失败: id=%d, %v", id, err)
  330. fmt.Printf("删除失败: %v\n", err)
  331. return
  332. }
  333. logger.Info("MQTT 配置已删除: id=%d", id)
  334. fmt.Println("配置已删除")
  335. case "ble":
  336. runBleConfig(db, args[1:])
  337. default:
  338. printConfigUsage()
  339. }
  340. }
  341. func printConfigUsage() {
  342. fmt.Println("用法: opencode-monitor config <子命令> [选项]")
  343. fmt.Println("")
  344. fmt.Println("子命令:")
  345. fmt.Println(" list 列出所有 MQTT 配置")
  346. fmt.Println(" set 设置 MQTT 配置")
  347. fmt.Println(" delete <id> 删除 MQTT 配置")
  348. fmt.Println(" ble list 列出所有 BLE 配置")
  349. fmt.Println(" ble set 设置 BLE 配置")
  350. fmt.Println(" ble delete <id> 删除 BLE 配置")
  351. fmt.Println("")
  352. fmt.Println("MQTT 选项:")
  353. fmt.Println(" --broker MQTT Broker 地址")
  354. fmt.Println(" --client-id MQTT 客户端 ID")
  355. fmt.Println(" --username MQTT 用户名")
  356. fmt.Println(" --password MQTT 密码")
  357. fmt.Println(" --topic MQTT 主题")
  358. fmt.Println(" --enabled 是否启用 (true/false)")
  359. fmt.Println("")
  360. fmt.Println("BLE 选项:")
  361. fmt.Println(" --device 蓝牙设备名称 (必填)")
  362. fmt.Println(" --service-uuid BLE 服务 UUID (必填)")
  363. fmt.Println(" --char-uuid BLE 特征 UUID (必填)")
  364. fmt.Println(" --enabled 是否启用 (true/false)")
  365. fmt.Println("")
  366. fmt.Println("全局选项:")
  367. fmt.Println(" --db 数据库路径")
  368. fmt.Println(" --log-file 日志文件路径(默认 ./logs/monitor.log)")
  369. fmt.Println(" --log-level 日志级别 (debug/info/warn/error)")
  370. }
  371. func startBLERelay(bleCfg *database.BLEConfig, ctx context.Context) io.WriteCloser {
  372. if len(embeddedBLERelay) == 0 {
  373. logger.Warn("BLE 中继未嵌入,请使用 make build-with-ble 构建")
  374. fmt.Println("警告: BLE 中继未嵌入,已跳过。请使用 make build-with-ble 构建")
  375. return nil
  376. }
  377. // 释放嵌入的 exe 到临时目录
  378. tmpDir := filepath.Join(os.TempDir(), "ai-status-light")
  379. if err := os.MkdirAll(tmpDir, 0755); err != nil {
  380. logger.Error("创建临时目录失败: %v", err)
  381. return nil
  382. }
  383. var tmpExe string
  384. if strings.Contains(strings.ToLower(os.Getenv("OS")), "windows") {
  385. tmpExe = filepath.Join(tmpDir, "ble_relay.exe")
  386. } else {
  387. tmpExe = filepath.Join(tmpDir, "ble_relay")
  388. }
  389. if err := os.WriteFile(tmpExe, embeddedBLERelay, 0755); err != nil {
  390. logger.Error("释放 BLE 中继失败: %v", err)
  391. return nil
  392. }
  393. logger.Debug("BLE 中继已释放到: %s", tmpExe)
  394. args := []string{
  395. "--device", bleCfg.DeviceName,
  396. "--service-uuid", bleCfg.ServiceUUID,
  397. "--char-uuid", bleCfg.CharUUID,
  398. }
  399. cmd := exec.CommandContext(ctx, tmpExe, args...)
  400. cmd.Stdout = os.Stdout
  401. cmd.Stderr = os.Stderr
  402. stdin, err := cmd.StdinPipe()
  403. if err != nil {
  404. logger.Error("创建 BLE 中继 stdin 管道失败: %v", err)
  405. return nil
  406. }
  407. if err := cmd.Start(); err != nil {
  408. logger.Error("启动 BLE 中继失败: %v", err)
  409. return nil
  410. }
  411. logger.Info("BLE 中继已启动: %s (PID: %d)", bleCfg.DeviceName, cmd.Process.Pid)
  412. fmt.Printf("BLE 中继已启动: %s (PID: %d)\n", bleCfg.DeviceName, cmd.Process.Pid)
  413. go func() {
  414. if err := cmd.Wait(); err != nil {
  415. logger.Error("BLE 中继退出: %v", err)
  416. }
  417. }()
  418. return stdin
  419. }
  420. func runBleConfig(db *database.DB, args []string) {
  421. if len(args) < 1 {
  422. printBleConfigUsage()
  423. return
  424. }
  425. switch args[0] {
  426. case "list":
  427. configs, err := db.ListBLEConfigs()
  428. if err != nil {
  429. logger.Error("查询 BLE 配置失败: %v", err)
  430. fmt.Printf("查询失败: %v\n", err)
  431. return
  432. }
  433. if len(configs) == 0 {
  434. fmt.Println("未配置 BLE")
  435. return
  436. }
  437. logger.Info("查询到 %d 条 BLE 配置", len(configs))
  438. for _, cfg := range configs {
  439. status := "禁用"
  440. if cfg.Enabled {
  441. status = "启用"
  442. }
  443. fmt.Printf("[%d] %s | %s | %s | %s\n", cfg.ID, cfg.DeviceName, cfg.ServiceUUID, cfg.CharUUID, status)
  444. }
  445. case "set":
  446. fs := flag.NewFlagSet("config ble set", flag.ExitOnError)
  447. deviceName := fs.String("device", "", "蓝牙设备名称 (必填)")
  448. serviceUUID := fs.String("service-uuid", "", "BLE 服务 UUID (必填)")
  449. charUUID := fs.String("char-uuid", "", "BLE 特征 UUID (必填)")
  450. enabled := fs.Bool("enabled", true, "是否启用")
  451. fs.Parse(args[1:])
  452. if *deviceName == "" || *serviceUUID == "" || *charUUID == "" {
  453. fmt.Println("错误: --device, --service-uuid, --char-uuid 为必填参数")
  454. fs.Usage()
  455. return
  456. }
  457. cfg := &database.BLEConfig{
  458. DeviceName: *deviceName,
  459. ServiceUUID: *serviceUUID,
  460. CharUUID: *charUUID,
  461. Enabled: *enabled,
  462. }
  463. if err := db.SaveBLEConfig(cfg); err != nil {
  464. logger.Error("保存 BLE 配置失败: %v", err)
  465. fmt.Printf("保存失败: %v\n", err)
  466. return
  467. }
  468. logger.Info("BLE 配置已保存: %s", cfg.DeviceName)
  469. fmt.Println("配置已保存")
  470. case "delete":
  471. if len(args) < 2 {
  472. fmt.Println("必须指定配置 ID")
  473. return
  474. }
  475. id, err := strconv.Atoi(args[1])
  476. if err != nil {
  477. logger.Warn("无效的配置 ID: %s", args[1])
  478. fmt.Println("无效的 ID")
  479. return
  480. }
  481. if err := db.DeleteBLEConfig(id); err != nil {
  482. logger.Error("删除 BLE 配置失败: id=%d, %v", id, err)
  483. fmt.Printf("删除失败: %v\n", err)
  484. return
  485. }
  486. logger.Info("BLE 配置已删除: id=%d", id)
  487. fmt.Println("配置已删除")
  488. default:
  489. printBleConfigUsage()
  490. }
  491. }
  492. func printBleConfigUsage() {
  493. fmt.Println("用法: opencode-monitor config ble <子命令> [选项]")
  494. fmt.Println("")
  495. fmt.Println("子命令:")
  496. fmt.Println(" list 列出所有 BLE 配置")
  497. fmt.Println(" set 设置 BLE 配置")
  498. fmt.Println(" delete <id> 删除 BLE 配置")
  499. fmt.Println("")
  500. fmt.Println("选项:")
  501. fmt.Println(" --device 蓝牙设备名称 (必填)")
  502. fmt.Println(" --service-uuid BLE 服务 UUID (必填)")
  503. fmt.Println(" --char-uuid BLE 特征 UUID (必填)")
  504. fmt.Println(" --enabled 是否启用 (true/false)")
  505. }
  506. func createCallback(mqttClient *mqttcli.Client, apiServer *api.Server, bleStdin io.Writer) monitor.EventCallback {
  507. lastStatus := make(map[int]string)
  508. var mu sync.Mutex
  509. publish := func(port int, status string, code string) {
  510. if mqttClient != nil {
  511. payload := map[string]interface{}{
  512. "port": port,
  513. "status": status,
  514. "code": code,
  515. "timestamp": time.Now().Format(time.RFC3339),
  516. }
  517. if err := mqttClient.PublishRaw(mqttClient.GetTopic(), payload); err != nil {
  518. logger.Error("MQTT 发送失败: %v", err)
  519. fmt.Printf("MQTT 发送失败: %v\n", err)
  520. }
  521. }
  522. if apiServer != nil {
  523. apiServer.BroadcastStatus(port, status, code)
  524. }
  525. if bleStdin != nil {
  526. msg := fmt.Sprintf(`{"port":%d,"code":"%s"}`+"\n", port, code)
  527. if _, err := bleStdin.Write([]byte(msg)); err != nil {
  528. logger.Error("BLE 发送失败: %v", err)
  529. }
  530. }
  531. }
  532. return func(port int, evt *event.SSEEvent) {
  533. msg := event.FormatEvent(port, evt)
  534. if msg != "" {
  535. fmt.Println(msg)
  536. }
  537. if mqttClient == nil && apiServer == nil && bleStdin == nil {
  538. return
  539. }
  540. var status, code string
  541. switch evt.Type {
  542. case "session.status":
  543. if s, ok := evt.Properties["status"].(map[string]interface{}); ok {
  544. if t, ok := s["type"].(string); ok {
  545. code = t
  546. }
  547. status = event.ParseStatus(s)
  548. }
  549. case "session.idle":
  550. status = "空闲"
  551. code = "idle"
  552. case "message.part.updated":
  553. if part, ok := evt.Properties["part"].(map[string]interface{}); ok {
  554. switch part["type"].(string) {
  555. case "tool":
  556. if st, ok := part["state"].(map[string]interface{}); ok {
  557. if s, ok := st["status"].(string); ok {
  558. code = s
  559. }
  560. status = event.ParseToolState(st)
  561. }
  562. case "reasoning":
  563. status = "思考中"
  564. code = "reasoning"
  565. default:
  566. status = "使用工具中"
  567. code = "using_tool"
  568. }
  569. }
  570. case "permission.updated":
  571. code = "permission"
  572. if title, ok := evt.Properties["title"].(string); ok && title != "" {
  573. status = "等待权限: " + title
  574. } else {
  575. status = "等待权限"
  576. }
  577. case "session.error":
  578. status = "错误"
  579. code = "error"
  580. }
  581. if status != "" {
  582. mu.Lock()
  583. prev := lastStatus[port]
  584. if status == "空闲" && prev != "" && prev != "空闲" {
  585. publish(port, "会话完成", "session_completed")
  586. }
  587. lastStatus[port] = status
  588. mu.Unlock()
  589. publish(port, status, code)
  590. }
  591. }
  592. }
  593. func runFixedMode(ctx context.Context, host string, portsFlag string, callback monitor.EventCallback, sigChan chan os.Signal) {
  594. var ports []int
  595. for _, p := range strings.Split(portsFlag, ",") {
  596. p = strings.TrimSpace(p)
  597. if port, err := strconv.Atoi(p); err == nil {
  598. ports = append(ports, port)
  599. }
  600. }
  601. if len(ports) == 0 {
  602. fmt.Println("未指定端口")
  603. return
  604. }
  605. logger.Info("固定模式启动,监控端口: %v", ports)
  606. fmt.Printf("监控端口: %v\n", ports)
  607. fmt.Println("Ctrl+C 停止")
  608. fmt.Println(strings.Repeat("-", 40))
  609. var wg sync.WaitGroup
  610. for _, port := range ports {
  611. wg.Add(1)
  612. go func(p int) {
  613. defer wg.Done()
  614. logger.Info("开始监控端口: %d", p)
  615. m := monitor.New(host, p, callback)
  616. m.Run(ctx)
  617. logger.Info("端口 %d 监控已停止", p)
  618. }(port)
  619. }
  620. <-sigChan
  621. logger.Info("收到停止信号,正在退出")
  622. fmt.Println("\n已停止")
  623. wg.Wait()
  624. logger.Info("所有监控协程已退出")
  625. }
  626. func runDynamicMode(ctx context.Context, host string, scanRange *[2]int, interval int, callback monitor.EventCallback, sigChan chan os.Signal) {
  627. scanner := discovery.NewScanner(host, scanRange)
  628. monitoredPorts := make(map[int]bool)
  629. runningMonitors := make(map[int]context.CancelFunc)
  630. var mu sync.Mutex
  631. startMonitor := func(port int) {
  632. mu.Lock()
  633. if _, exists := runningMonitors[port]; exists {
  634. mu.Unlock()
  635. return
  636. }
  637. monitorCtx, monitorCancel := context.WithCancel(ctx)
  638. runningMonitors[port] = monitorCancel
  639. mu.Unlock()
  640. go func() {
  641. logger.Info("开始监控端口: %d", port)
  642. fmt.Printf("开始监控端口: %d\n", port)
  643. m := monitor.New(host, port, callback)
  644. m.Run(monitorCtx)
  645. // 连接断开后清理记录,允许重新连接
  646. mu.Lock()
  647. delete(runningMonitors, port)
  648. delete(monitoredPorts, port)
  649. mu.Unlock()
  650. logger.Info("端口 %d 监控已停止,等待重新连接", port)
  651. fmt.Printf("端口 %d 监控已停止,等待重新连接\n", port)
  652. }()
  653. }
  654. initial := scanner.Discover()
  655. if len(initial) == 0 {
  656. logger.Info("未找到运行中的 OpenCode 实例,等待自动检测")
  657. fmt.Println("未找到运行中的 OpenCode 实例")
  658. fmt.Println("请先执行: opencode serve --port 4096")
  659. fmt.Println("启动后会自动检测,等待中...")
  660. }
  661. for _, port := range initial {
  662. monitoredPorts[port] = true
  663. startMonitor(port)
  664. }
  665. if len(monitoredPorts) > 0 {
  666. ports := make([]int, 0, len(monitoredPorts))
  667. for p := range monitoredPorts {
  668. ports = append(ports, p)
  669. }
  670. sort.Ints(ports)
  671. logger.Info("找到 %d 个实例: %v", len(monitoredPorts), ports)
  672. fmt.Printf("找到 %d 个实例: %v\n", len(monitoredPorts), ports)
  673. }
  674. logger.Info("动态模式启动,每 %d 秒扫描新实例", interval)
  675. fmt.Printf("每 %d 秒扫描新实例,Ctrl+C 停止\n", interval)
  676. fmt.Println(strings.Repeat("-", 40))
  677. scanTicker := time.NewTicker(time.Duration(interval) * time.Second)
  678. defer scanTicker.Stop()
  679. go func() {
  680. for {
  681. select {
  682. case <-ctx.Done():
  683. return
  684. case <-scanTicker.C:
  685. newPorts := scanner.Discover()
  686. if len(newPorts) > 0 {
  687. logger.Debug("扫描到 %d 个端口: %v", len(newPorts), newPorts)
  688. }
  689. for _, port := range newPorts {
  690. mu.Lock()
  691. alreadyMonitored := monitoredPorts[port]
  692. if !alreadyMonitored {
  693. monitoredPorts[port] = true
  694. }
  695. mu.Unlock()
  696. if !alreadyMonitored {
  697. logger.Info("发现新实例端口: %d,开始监控", port)
  698. startMonitor(port)
  699. }
  700. }
  701. }
  702. }
  703. }()
  704. <-sigChan
  705. logger.Info("收到停止信号,正在退出")
  706. fmt.Println("\n已停止")
  707. }