//go:build !omit_maixcam package channels import ( "context" "encoding/json" "fmt" "net" "sync" "github.com/YspCoder/clawgo/pkg/bus" "github.com/YspCoder/clawgo/pkg/config" "github.com/YspCoder/clawgo/pkg/logger" ) type MaixCamChannel struct { *BaseChannel config config.MaixCamConfig listener net.Listener clients map[net.Conn]bool clientsMux sync.RWMutex } const maixcamCompiled = true type MaixCamMessage struct { Type string `json:"type"` Tips string `json:"tips"` Timestamp float64 `json:"timestamp"` Data map[string]interface{} `json:"data"` } func NewMaixCamChannel(cfg config.MaixCamConfig, bus *bus.MessageBus) (*MaixCamChannel, error) { base := NewBaseChannel("maixcam", cfg, bus, cfg.AllowFrom) return &MaixCamChannel{ BaseChannel: base, config: cfg, clients: make(map[net.Conn]bool), }, nil } func (c *MaixCamChannel) Start(ctx context.Context) error { logger.InfoC("maixcam", logger.C0079) addr := fmt.Sprintf("%s:%d", c.config.Host, c.config.Port) listener, err := net.Listen("tcp", addr) if err != nil { return fmt.Errorf("failed to listen on %s: %w", addr, err) } c.listener = listener c.setRunning(true) logger.InfoCF("maixcam", logger.C0080, map[string]interface{}{ "host": c.config.Host, "port": c.config.Port, }) go c.acceptConnections(ctx) return nil } func (c *MaixCamChannel) acceptConnections(ctx context.Context) { logger.DebugC("maixcam", logger.C0081) for { select { case <-ctx.Done(): logger.InfoC("maixcam", logger.C0082) return default: conn, err := c.listener.Accept() if err != nil { if c.IsRunning() { logger.ErrorCF("maixcam", logger.C0083, map[string]interface{}{ logger.FieldError: err.Error(), }) } return } logger.InfoCF("maixcam", logger.C0084, map[string]interface{}{ "remote_addr": conn.RemoteAddr().String(), }) c.clientsMux.Lock() c.clients[conn] = true c.clientsMux.Unlock() go c.handleConnection(conn, ctx) } } } func (c *MaixCamChannel) handleConnection(conn net.Conn, ctx context.Context) { logger.DebugC("maixcam", logger.C0085) defer func() { conn.Close() c.clientsMux.Lock() delete(c.clients, conn) c.clientsMux.Unlock() logger.DebugC("maixcam", logger.C0086) }() decoder := json.NewDecoder(conn) for { select { case <-ctx.Done(): return default: var msg MaixCamMessage if err := decoder.Decode(&msg); err != nil { if err.Error() != "EOF" { logger.ErrorCF("maixcam", logger.C0087, map[string]interface{}{ logger.FieldError: err.Error(), }) } return } c.processMessage(msg, conn) } } } func (c *MaixCamChannel) processMessage(msg MaixCamMessage, conn net.Conn) { switch msg.Type { case "person_detected": c.handlePersonDetection(msg) case "heartbeat": logger.DebugC("maixcam", logger.C0088) case "status": c.handleStatusUpdate(msg) default: logger.WarnCF("maixcam", logger.C0089, map[string]interface{}{ "message_type": msg.Type, }) } } func (c *MaixCamChannel) handlePersonDetection(msg MaixCamMessage) { logger.InfoCF("maixcam", logger.C0090, map[string]interface{}{ logger.FieldSenderID: "maixcam", logger.FieldChatID: "default", "timestamp": msg.Timestamp, "data": msg.Data, }) senderID := "maixcam" chatID := "default" classInfo, ok := msg.Data["class_name"].(string) if !ok { classInfo = "person" } score, _ := msg.Data["score"].(float64) x, _ := msg.Data["x"].(float64) y, _ := msg.Data["y"].(float64) w, _ := msg.Data["w"].(float64) h, _ := msg.Data["h"].(float64) content := fmt.Sprintf("Person detected.\nClass: %s\nConfidence: %.2f%%\nPosition: (%.0f, %.0f)\nSize: %.0fx%.0f", classInfo, score*100, x, y, w, h) metadata := map[string]string{ "timestamp": fmt.Sprintf("%.0f", msg.Timestamp), "class_id": fmt.Sprintf("%.0f", msg.Data["class_id"]), "score": fmt.Sprintf("%.2f", score), "x": fmt.Sprintf("%.0f", x), "y": fmt.Sprintf("%.0f", y), "w": fmt.Sprintf("%.0f", w), "h": fmt.Sprintf("%.0f", h), } c.HandleMessage(senderID, chatID, content, []string{}, metadata) } func (c *MaixCamChannel) handleStatusUpdate(msg MaixCamMessage) { logger.InfoCF("maixcam", logger.C0091, map[string]interface{}{ "status": msg.Data, }) } func (c *MaixCamChannel) Stop(ctx context.Context) error { logger.InfoC("maixcam", logger.C0092) c.setRunning(false) if c.listener != nil { c.listener.Close() } c.clientsMux.Lock() defer c.clientsMux.Unlock() for conn := range c.clients { conn.Close() } c.clients = make(map[net.Conn]bool) logger.InfoC("maixcam", logger.C0093) return nil } func (c *MaixCamChannel) Send(ctx context.Context, msg bus.OutboundMessage) error { if !c.IsRunning() { return fmt.Errorf("maixcam channel not running") } c.clientsMux.RLock() defer c.clientsMux.RUnlock() if len(c.clients) == 0 { logger.WarnC("maixcam", logger.C0094) return fmt.Errorf("no connected MaixCam devices") } response := map[string]interface{}{ "type": "command", "timestamp": float64(0), "message": msg.Content, logger.FieldChatID: msg.ChatID, } data, err := json.Marshal(response) if err != nil { return fmt.Errorf("failed to marshal response: %w", err) } var sendErr error for conn := range c.clients { if _, err := conn.Write(data); err != nil { logger.ErrorCF("maixcam", logger.C0095, map[string]interface{}{ "client": conn.RemoteAddr().String(), logger.FieldError: err.Error(), }) sendErr = err } } return sendErr }