package main import ( "FireLeave_tool/connect" "FireLeave_tool/gpio_monitor" "FireLeave_tool/httpreader" "FireLeave_tool/logger" "FireLeave_tool/reporter" "FireLeave_tool/tcpreader" "FireLeave_tool/video_server" "context" "encoding/json" "flag" "fmt" "log" "math" "net" "net/http" "os" "os/exec" "os/signal" "path/filepath" "strconv" "sync" "syscall" "time" ) type deviceAlarmState struct { InCondition bool StartTime int64 AlarmPauseUntil int64 FireLeaveForbidUntil int64 FireLeaveDisabled bool FireCheckCandidate bool FireCheckFirstSeen time.Time FireCheckLastSeen time.Time FireCheckHasResult bool FireCheckLastNumber int FireCheckWaitingClear bool } var deviceStates sync.Map // map[string]*deviceAlarmState var mergingAlarms sync.Map var GlobalReporter = reporter.NewReporter() const defaultFireLeaveForbidDuration = 15 * time.Minute const fireCheckLastSeenGrace = time.Second const buildVersion = "2026-05-13-logger-init-fix" type DeviceManager struct { mu sync.Mutex devices map[string]*connect.DeviceData } func NewDeviceManager() *DeviceManager { return &DeviceManager{ devices: make(map[string]*connect.DeviceData), } } func getDeviceState(deviceUUID string) *deviceAlarmState { if s, ok := deviceStates.Load(deviceUUID); ok { return s.(*deviceAlarmState) } s := &deviceAlarmState{} deviceStates.Store(deviceUUID, s) return s } func applyFireLeaveForbidWindow(duration time.Duration) { if duration <= 0 { return } now := time.Now().Unix() until := now + int64(duration.Seconds()) applyFireLeaveForbidWindowUntil(now, until) } func applyFireLeaveForbidWindowUntil(startTime, endTime int64) { if endTime <= time.Now().Unix() { return } count := 0 GlobalDeviceManager.RangeDevices(func(uuid string, device *connect.DeviceData) bool { state := getDeviceState(uuid) state.FireLeaveForbidUntil = endTime state.InCondition = false count++ return true }) logger.Logger.Printf("fire_leave reporting paused: start=%d, end=%d, devices=%d", startTime, endTime, count) } func clearFireLeaveForbidWindow() { count := 0 GlobalDeviceManager.RangeDevices(func(uuid string, device *connect.DeviceData) bool { state := getDeviceState(uuid) state.FireLeaveForbidUntil = 0 state.InCondition = false count++ return true }) logger.Logger.Printf("fire_leave reporting pause cleared, devices=%d", count) } func persistAndApplyFireLeaveForbidWindow(minute string, duration time.Duration) { if duration <= 0 { if err := reporter.SaveForbidTimeWindow(minute, 0, 0); err != nil { logger.Logger.Printf("Failed to clear forbid time config: %v", err) } clearFireLeaveForbidWindow() return } startTime := time.Now().Unix() endTime := startTime + int64(duration.Seconds()) if err := reporter.SaveForbidTimeWindow(minute, startTime, endTime); err != nil { logger.Logger.Printf("Failed to save forbid time config: %v", err) } else { logger.Logger.Printf("Forbid time config saved successfully: Minute=%s, Start=%d, End=%d", minute, startTime, endTime) } applyFireLeaveForbidWindowUntil(startTime, endTime) } func restoreFireLeaveForbidWindow(deviceUUID string) { config, err := reporter.LoadForbidTimeConfig() if err != nil || config.EndTime <= time.Now().Unix() { return } state := getDeviceState(deviceUUID) state.FireLeaveForbidUntil = config.EndTime state.InCondition = false logger.Logger.Printf("Restored fire_leave forbid window: DeviceUUID=%s, Start=%d, End=%d", deviceUUID, config.StartTime, config.EndTime) } func (dm *DeviceManager) AddDevice(deviceData connect.DeviceData) { dm.mu.Lock() defer dm.mu.Unlock() dm.devices[deviceData.DeviceUUID] = &deviceData logger.Logger.Printf("add equipments: DeviceUUID=%s", deviceData.DeviceUUID) } func (dm *DeviceManager) StopDevice(deviceUUID string) { dm.mu.Lock() defer dm.mu.Unlock() if _, exists := dm.devices[deviceUUID]; exists { delete(dm.devices, deviceUUID) logger.Logger.Printf("stop equipments: DeviceUUID=%s", deviceUUID) } } func (dm *DeviceManager) GetDevice(deviceUUID string) (*connect.DeviceData, bool) { dm.mu.Lock() defer dm.mu.Unlock() device, exists := dm.devices[deviceUUID] return device, exists } var GlobalDeviceManager = NewDeviceManager() func waitForHTTPService(port int, timeout time.Duration) bool { deadline := time.Now().Add(timeout) address := fmt.Sprintf("127.0.0.1:%d", port) logger.Logger.Printf("Start waiting for the HTTP service to start (Port=%d, Timeout=%v)", port, timeout) attempt := 0 for time.Now().Before(deadline) { attempt++ conn, err := net.DialTimeout("tcp", address, 2*time.Second) if err != nil { if attempt == 1 || attempt%5 == 0 { logger.Logger.Printf("Wait for the TCP connection... (Port=%d, Number of attempts=%d, Error: %v)", port, attempt, err) } time.Sleep(1 * time.Second) continue } conn.Close() client := &http.Client{Timeout: 3 * time.Second} resp, err := client.Get(fmt.Sprintf("http://%s/health", address)) if err == nil { resp.Body.Close() if resp.StatusCode == http.StatusOK { logger.Logger.Printf("The HTTP service has started successfully (Port=%d, Number of attempts=%d)", port, attempt) return true } } if attempt == 1 || attempt%5 == 0 { logger.Logger.Printf("Wait for the HTTP service to be ready... (Port=%d, Total number of attempts=%d)", port, attempt) } time.Sleep(1 * time.Second) } logger.Logger.Printf("HTTP service startup timeout (Port=%d, Total number of attempts=%d)", port, attempt) return false } func waitForTCPService(port int, timeout time.Duration) bool { deadline := time.Now().Add(timeout) address := fmt.Sprintf("127.0.0.1:%d", port) logger.Logger.Printf("Start waiting for the TCP service to start (Port=%d, Timeout=%v)", port, timeout) attempt := 0 lastError := "" for time.Now().Before(deadline) { attempt++ conn, err := net.DialTimeout("tcp", address, 2*time.Second) if err == nil { conn.Close() logger.Logger.Printf("The TCP service has been successfully started (Port=%d, Number of attempts=%d)", port, attempt) return true } currentError := err.Error() if currentError != lastError { logger.Logger.Printf("Wait for TCP service... (Port=%d, Number of attempts=%d, Error: %v)", port, attempt, err) lastError = currentError } else if attempt%5 == 0 { logger.Logger.Printf("Wait for TCP service... (Port=%d, Number of attempts=%d, Error: %v)", port, attempt, err) } time.Sleep(2 * time.Second) } logger.Logger.Printf("TCP service startup timeout (Port=%d, Total number of attempts=%d, Final error: %s)", port, attempt, lastError) return false } func logProcessInfo(pid int, deviceUUID string) error { cmd := exec.Command("ps", "-o", "pid,ppid,user,stat,start_time,cmd", "-p", fmt.Sprintf("%d", pid)) output, err := cmd.Output() if err != nil { return fmt.Errorf("Failed to obtain process information: %v", err) } logger.Logger.Printf("Detailed information about the reasoning program process (DeviceUUID=%s, PID=%d):\n%s", deviceUUID, pid, string(output)) return nil } func getIntFromNumber(num json.Number, def int) int { if i, err := num.Int64(); err == nil { return int(i) } return def } func getFireCheckDetectionDuration(deviceData connect.DeviceData) time.Duration { detectionSec := 2 if deviceData.FireCheckParam.DetectionTime != "" { if sec, err := strconv.Atoi(deviceData.FireCheckParam.DetectionTime); err == nil && sec >= 0 { detectionSec = sec } } return time.Duration(detectionSec) * time.Second } func processDevice(ctx context.Context, deviceData connect.DeviceData) { logger.Logger.Printf("Start processing the equipment: DeviceUUID=%s", deviceData.DeviceUUID) debugDeviceData(deviceData) GlobalDeviceManager.AddDevice(deviceData) restoreFireLeaveForbidWindow(deviceData.DeviceUUID) defer GlobalDeviceManager.StopDevice(deviceData.DeviceUUID) deviceCtx, cancel := context.WithCancel(ctx) defer cancel() portFireLeave, err := connect.GlobalPortManager.AllocatePort(deviceData.DeviceUUID) if err != nil { logger.Logger.Printf(" DeviceUUID=%s Port allocation failed: %v", deviceData.DeviceUUID, err) return } defer connect.GlobalPortManager.ReleasePort(deviceData.DeviceUUID) portFireCheck, err := connect.GlobalPortManager.AllocatePort(fmt.Sprintf("%s_fire_check", deviceData.DeviceUUID)) if err != nil { logger.Logger.Printf(" DeviceUUID=%s Fire check port allocation failed: %v", deviceData.DeviceUUID, err) return } defer connect.GlobalPortManager.ReleasePort(fmt.Sprintf("%s_fire_check", deviceData.DeviceUUID)) logger.Logger.Printf("Port allocation successful: DeviceUUID=%s, FireLeavePort=%d, FireCheckPort=%d", deviceData.DeviceUUID, portFireLeave, portFireCheck) inferenceServerFireLeave := httpreader.NewInferenceServer(deviceData.DeviceUUID, portFireLeave) if err := inferenceServerFireLeave.Start(); err != nil { logger.Logger.Printf("Failed to start fire_leave inference server (DeviceUUID=%s): %v", deviceData.DeviceUUID, err) return } inferenceServerFireCheck := httpreader.NewInferenceServer(fmt.Sprintf("%s_fire_check", deviceData.DeviceUUID), portFireCheck) if err := inferenceServerFireCheck.Start(); err != nil { logger.Logger.Printf("Failed to start fire_check inference server (DeviceUUID=%s): %v", deviceData.DeviceUUID, err) return } defer inferenceServerFireLeave.StopWithContext(deviceCtx) defer inferenceServerFireCheck.StopWithContext(deviceCtx) if !waitForHTTPService(portFireLeave, 10*time.Second) { logger.Logger.Printf("Fire_leave inference server startup timeout (DeviceUUID=%s, Port=%d)", deviceData.DeviceUUID, portFireLeave) return } if !waitForHTTPService(portFireCheck, 10*time.Second) { logger.Logger.Printf("Fire_check inference server startup timeout (DeviceUUID=%s, Port=%d)", deviceData.DeviceUUID, portFireCheck) return } logger.Logger.Printf("Inference servers started successfully: DeviceUUID=%s, FireLeavePort=%d, FireCheckPort=%d", deviceData.DeviceUUID, portFireLeave, portFireCheck) fireLeaveDetectAreaStr := connect.ProcessDetectAreaForInference(deviceData.FireLeaveDetectArea) logger.Logger.Printf("The processed fire_leave detection area: %s", fireLeaveDetectAreaStr) fireCheckDetectAreaStr := connect.ProcessDetectAreaForInference(deviceData.FireCheckDetectArea) logger.Logger.Printf("The processed fire_check detection area: %s", fireCheckDetectAreaStr) inferenceCmdFireLeave := exec.Command( "./yolov5", "-s", deviceData.CameraRTSP, "-m", "model", "-c", deviceData.DeviceUUID, "-p", fmt.Sprintf("%d", portFireLeave), "-w", fireLeaveDetectAreaStr, "-r", fmt.Sprintf("%d", deviceData.Confidence), ) inferenceCmdFireLeave.Stdout = os.Stdout inferenceCmdFireLeave.Stderr = os.Stderr logger.Logger.Printf("Start fire_leave inference program command: %v", inferenceCmdFireLeave.Args) if err := inferenceCmdFireLeave.Start(); err != nil { logger.Logger.Printf("Fire_leave inference program startup failed (DeviceUUID=%s): %v", deviceData.DeviceUUID, err) return } // 婵犵數濮烽弫鎼佸磿閹寸姴绶ら柦妯侯棦濞差亝鏅滈柣鎰靛墮鎼村﹪姊虹粙璺ㄧ伇闁稿鍋ゅ畷鎴﹀Χ婢跺鍘撴繛鎾村嚬閸ㄥ崬鈻嶅▔涔猒leave闂傚倸鍊风粈渚€骞栭位鍥敃閿曗偓閻ょ偓绻濇繝鍌涘櫧闁活厽鐟╅弻鐔煎箲閹伴潧娈梺缁樻尰缁诲牓寮婚妶鍚ゅ湱鈧綆鍋呭鎺楁⒑缁嬫鍎愰柟鐟版喘楠炲啴鎸婃径鍡樼亖闂佸湱顭堢€涒晠寮抽悙鐑樷拻濞达綀妫勮闂佺粯顨嗗ú婊堝焵椤掍胶顣叉繝銏★耿閹儳鐣¢柇锔藉兊闁荤娀缂氬▍锝嗩殽閸ヮ剚鈷戦柛鎾瑰皺閸樻盯鏌涢悩宕囧閻庨潧鍚嬮幆鏃堝Ω閿旀儳骞楅梻濠庡亜濞诧箑煤閺嶃劎顩茬憸鐗堝笚閻?map connect.InferenceProcesses.Store(deviceData.DeviceUUID, inferenceCmdFireLeave) logger.Logger.Printf("Fire_leave inference program started, PID=%d (DeviceUUID=%s)", inferenceCmdFireLeave.Process.Pid, deviceData.DeviceUUID) fireCheckConfidence := connect.GetFireCheckConfidence(deviceData) inferenceCmdFireCheck := exec.Command( "./yolov5", "-s", deviceData.CameraRTSP, "-m", "fire_check", "-c", fmt.Sprintf("%s_fire_check", deviceData.DeviceUUID), "-p", fmt.Sprintf("%d", portFireCheck), "-w", fireCheckDetectAreaStr, "-r", fmt.Sprintf("%d", fireCheckConfidence), ) inferenceCmdFireCheck.Stdout = os.Stdout inferenceCmdFireCheck.Stderr = os.Stderr logger.Logger.Printf("Start fire_check inference program command: %v", inferenceCmdFireCheck.Args) if err := inferenceCmdFireCheck.Start(); err != nil { logger.Logger.Printf("Fire_check inference program startup failed (DeviceUUID=%s): %v", deviceData.DeviceUUID, err) return } // 婵犵數濮烽弫鎼佸磿閹寸姴绶ら柦妯侯棦濞差亝鏅滈柣鎰靛墮鎼村﹪姊虹粙璺ㄧ伇闁稿鍋ゅ畷鎴﹀Χ婢跺鍘撴繛鎾村嚬閸ㄥ崬鈻嶅▔涔猒check闂傚倸鍊风粈渚€骞栭位鍥敃閿曗偓閻ょ偓绻濇繝鍌涘櫧闁活厽鐟╅弻鐔煎箲閹伴潧娈梺缁樻尰缁诲牓寮婚妶鍚ゅ湱鈧綆鍋呭鎺楁⒑缁嬫鍎愰柟鐟版喘楠炲啴鎸婃径鍡樼亖闂佸湱顭堢€涒晠寮抽悙鐑樷拻濞达綀妫勮闂佺粯顨嗗ú婊堝焵椤掍胶顣叉繝銏★耿閹儳鐣¢柇锔藉兊闁荤娀缂氬▍锝嗩殽閸ヮ剚鈷戦柛鎾瑰皺閸樻盯鏌涢悩宕囧閻庨潧鍚嬮幆鏃堝Ω閿旀儳骞楅梻濠庡亜濞诧箑煤閺嶃劎顩茬憸鐗堝笚閻?map connect.InferenceProcesses.Store(fmt.Sprintf("%s_fire_check", deviceData.DeviceUUID), inferenceCmdFireCheck) logger.Logger.Printf("Fire_check inference program started, PID=%d (DeviceUUID=%s)", inferenceCmdFireCheck.Process.Pid, deviceData.DeviceUUID) connect.UpdateInferenceHeartbeat(deviceData.DeviceUUID) connect.UpdateInferenceHeartbeat(fmt.Sprintf("%s_fire_check", deviceData.DeviceUUID)) time.Sleep(2 * time.Second) if inferenceCmdFireLeave.Process == nil { logger.Logger.Printf("Fire_leave inference program process not created (DeviceUUID=%s)", deviceData.DeviceUUID) return } if err := inferenceCmdFireLeave.Process.Signal(syscall.Signal(0)); err != nil { logger.Logger.Printf("Fire_leave inference program exited (DeviceUUID=%s): %v", deviceData.DeviceUUID, err) return } if inferenceCmdFireCheck.Process == nil { logger.Logger.Printf("Fire_check inference program process not created (DeviceUUID=%s)", deviceData.DeviceUUID) return } if err := inferenceCmdFireCheck.Process.Signal(syscall.Signal(0)); err != nil { logger.Logger.Printf("Fire_check inference program exited (DeviceUUID=%s): %v", deviceData.DeviceUUID, err) return } logger.Logger.Printf("Both inference programs started successfully: DeviceUUID=%s", deviceData.DeviceUUID) if err := logProcessInfo(inferenceCmdFireLeave.Process.Pid, deviceData.DeviceUUID); err != nil { logger.Logger.Printf("Failed to record fire_leave process information: %v", err) } if err := logProcessInfo(inferenceCmdFireCheck.Process.Pid, fmt.Sprintf("%s_fire_check", deviceData.DeviceUUID)); err != nil { logger.Logger.Printf("Failed to record fire_check process information: %v", err) } fireLeaveActive := true fireCheckActive := true temperatureActive := false stopFireLeaveOnly := func(reason string) { if !fireLeaveActive { return } logger.Logger.Printf("Stop fire_leave only: %s (DeviceUUID=%s)", reason, deviceData.DeviceUUID) if inferenceCmdFireLeave.Process != nil { if err := inferenceCmdFireLeave.Process.Kill(); err != nil { logger.Logger.Printf("Fire_leave inference program termination failed (DeviceUUID=%s): %v", deviceData.DeviceUUID, err) } else { logger.Logger.Printf("Fire_leave inference program stopped (DeviceUUID=%s)", deviceData.DeviceUUID) } _ = inferenceCmdFireLeave.Wait() } connect.InferenceProcesses.Delete(deviceData.DeviceUUID) inferenceServerFireLeave.StopWithContext(context.Background()) connect.GlobalPortManager.ReleasePort(deviceData.DeviceUUID) state := getDeviceState(deviceData.DeviceUUID) state.FireLeaveDisabled = true state.InCondition = false fireLeaveActive = false } defer func() { logger.Logger.Printf("Start to stop inference programs (DeviceUUID=%s)", deviceData.DeviceUUID) if fireLeaveActive && inferenceCmdFireLeave.Process != nil { if err := inferenceCmdFireLeave.Process.Kill(); err != nil { logger.Logger.Printf("Fire_leave inference program termination failed (DeviceUUID=%s): %v", deviceData.DeviceUUID, err) } else { logger.Logger.Printf("Fire_leave inference program stopped (DeviceUUID=%s)", deviceData.DeviceUUID) } } if fireCheckActive && inferenceCmdFireCheck.Process != nil { if err := inferenceCmdFireCheck.Process.Kill(); err != nil { logger.Logger.Printf("Fire_check inference program termination failed (DeviceUUID=%s): %v", deviceData.DeviceUUID, err) } else { logger.Logger.Printf("Fire_check inference program stopped (DeviceUUID=%s)", deviceData.DeviceUUID) } } }() tempPort := portFireLeave + 1000 tempCmdArgs := []string{ "-s", deviceData.CameraRTSP, "-t", fmt.Sprintf("%d", tempPort), } if deviceData.CameraChannel > 0 { tempCmdArgs = append(tempCmdArgs, "-c", fmt.Sprintf("%d", deviceData.CameraChannel)) } tempCmd := exec.Command("./release/run_thermometry.sh", tempCmdArgs...) tempCmd.Stdout = os.Stdout tempCmd.Stderr = os.Stderr logger.Logger.Printf("Start the temperature program command: %v", tempCmd.Args) var tempClient *tcpreader.TCPClient if err := tempCmd.Start(); err != nil { logger.Logger.Printf("The temperature program failed to start (DeviceUUID=%s): %v", deviceData.DeviceUUID, err) stopFireLeaveOnly("temperature program failed to start") } else { time.Sleep(3 * time.Second) if tempCmd.Process == nil || tempCmd.Process.Pid == 0 { logger.Logger.Printf("The temperature program was not started correctly, and the process PID is 0 (DeviceUUID=%s)", deviceData.DeviceUUID) stopFireLeaveOnly("temperature program pid is empty") } else if err := tempCmd.Process.Signal(syscall.Signal(0)); err != nil { logger.Logger.Printf("The temperature program process check failed (DeviceUUID=%s): %v", deviceData.DeviceUUID, err) stopFireLeaveOnly("temperature program process check failed") } else { logger.Logger.Printf("The temperature program has been successfully started, PID=%d (DeviceUUID=%s)", tempCmd.Process.Pid, deviceData.DeviceUUID) logger.Logger.Printf("Wait for the temperature program to be initialized... (DeviceUUID=%s)", deviceData.DeviceUUID) time.Sleep(2 * time.Second) logger.Logger.Printf("Wait for the temperature program TCP server to start... (DeviceUUID=%s, Port=%d)", deviceData.DeviceUUID, tempPort) if !waitForTCPService(tempPort, 20*time.Second) { logger.Logger.Printf("The TCP server of the temperature program has timed out during startup (DeviceUUID=%s, Port=%d)", deviceData.DeviceUUID, tempPort) if tempCmd.Process != nil { if err := tempCmd.Process.Signal(syscall.Signal(0)); err != nil { logger.Logger.Printf("The temperature program has abnormally exited (DeviceUUID=%s): %v", deviceData.DeviceUUID, err) } else { logger.Logger.Printf("The temperature program is still running but the TCP service has not started. There might be a configuration issue(DeviceUUID=%s)", deviceData.DeviceUUID) } _ = tempCmd.Process.Kill() _ = tempCmd.Wait() } stopFireLeaveOnly("temperature TCP server startup timeout") } else { tempClient, err = tcpreader.NewTCPClient(deviceData.DeviceUUID, tempPort) if err != nil { logger.Logger.Printf("Failed to create a TCP client (DeviceUUID=%s): %v", deviceData.DeviceUUID, err) if tempCmd.Process != nil { _ = tempCmd.Process.Kill() _ = tempCmd.Wait() } stopFireLeaveOnly("temperature TCP client creation failed") } else { defer tempClient.Close() temperatureActive = true getDeviceState(deviceData.DeviceUUID).FireLeaveDisabled = false logger.Logger.Printf("The TCP client has been successfully created (DeviceUUID=%s, Port=%d)", deviceData.DeviceUUID, tempPort) } } } } if err := os.MkdirAll("/data/upload", 0755); err != nil { logger.Logger.Printf("Failed to create the upload directory: %v", err) return } logger.Logger.Printf("The directory upload has been successfully created: /data/upload") logger.Logger.Printf("Start monitoring the equipment (DeviceUUID=%s)", deviceData.DeviceUUID) //var lastPersonCount = -1 //var lastTemperature float64 = -1 monitorTicker := time.NewTicker(2 * time.Second) defer monitorTicker.Stop() fireLeaveDataChan := inferenceServerFireLeave.GetDataChan() if !fireLeaveActive { fireLeaveDataChan = nil } fireCheckDataChan := inferenceServerFireCheck.GetDataChan() for { select { case <-deviceCtx.Done(): logger.Logger.Printf("Equipment processing has been cancelled: DeviceUUID=%s", deviceData.DeviceUUID) if fireLeaveActive && inferenceCmdFireLeave.Process != nil { inferenceCmdFireLeave.Process.Kill() } if fireCheckActive && inferenceCmdFireCheck.Process != nil { inferenceCmdFireCheck.Process.Kill() } if tempCmd.Process != nil { tempCmd.Process.Kill() } if tempClient != nil { tempClient.Close() } return case inferenceResult, ok := <-fireLeaveDataChan: if !ok { logger.Logger.Printf("The fire_leave inference data channel is closed: DeviceUUID=%s", deviceData.DeviceUUID) return } currentPersonCount := 0 for _, param := range inferenceResult.Params { if param.ClassIdx == 1 { currentPersonCount = param.Number break } } if currentPersonCount != deviceData.PersonCount { deviceData.PersonCount = currentPersonCount connect.UpdateDeviceData(deviceData) logger.Logger.Printf("Personnel quantity update: %d (DeviceUUID=%s)", currentPersonCount, deviceData.DeviceUUID) } case fireCheckResult, ok := <-fireCheckDataChan: if !ok { logger.Logger.Printf("The fire_check inference data channel is closed: DeviceUUID=%s", deviceData.DeviceUUID) return } fireDetected := false fireNumber := 0 for _, param := range fireCheckResult.Params { if param.Number > 0 && (param.Name == "fire" || param.ClassIdx == 0) { fireDetected = true fireNumber = param.Number break } } state := getDeviceState(deviceData.DeviceUUID) state.FireCheckHasResult = true state.FireCheckLastNumber = fireNumber if fireDetected { seenAt := time.Now() state.FireCheckLastSeen = seenAt if state.FireCheckWaitingClear { continue } if !state.FireCheckCandidate { state.FireCheckCandidate = true state.FireCheckFirstSeen = seenAt logger.Logger.Printf("Fire target detected, start timing: required=%v, number=%d, DeviceUUID=%s", getFireCheckDetectionDuration(deviceData), fireNumber, deviceData.DeviceUUID) } elapsed := seenAt.Sub(state.FireCheckFirstSeen) required := getFireCheckDetectionDuration(deviceData) if elapsed >= required { alarmTime := seenAt.Unix() logger.Logger.Printf("Fire detection candidate confirmed by inference data, triggering alarm: duration=%v, DeviceUUID=%s", elapsed, deviceData.DeviceUUID) state.FireCheckCandidate = false state.FireCheckWaitingClear = true state.FireCheckFirstSeen = time.Time{} go GlobalReporter.TriggerFireCheckAlarm(deviceData, alarmTime) } } else if state.FireCheckWaitingClear { state.FireCheckWaitingClear = false state.FireCheckCandidate = false state.FireCheckFirstSeen = time.Time{} state.FireCheckLastSeen = time.Time{} logger.Logger.Printf("Fire target cleared, ready for next fire_check timing: DeviceUUID=%s", deviceData.DeviceUUID) } case <-monitorTicker.C: now := time.Now().Unix() // === 闂傚倸鍊搁崐椋庣矆娓氣偓瀹曘儳鈧綆鍠栫壕鍧楁煙閹増顥夐幖鏉戯躬閺屻倝鎳濋幍顔肩墯婵炲瓨绮岀紞濠囧蓟濞戙垹唯妞ゆ棁宕甸弳妤佺箾鐎涙鐭婄紓宥咃躬瀵鎮㈤悡搴g暰閻熸粌绉瑰铏綇閵婏絼绨婚梺闈涚墕閹冲繘宕甸崶顒佺厵妞ゆ梹顑欏鎰版煙瀹勭増鍤囬柟顔惧厴瀵爼宕归褎鍨舵穱濠囨倷椤忓嫧鍋撹閳ь剟娼ч惌鍌氱暦濠婂牊鍋勯柣鎾虫捣閻e搫顪冮妶鍛闁绘绮撳顐﹀幢濞戞瑧鍘遍柣蹇曞仜婢т粙鎯屾繝鍕=鐎广儱鎳庨顓熸叏婵犲懏顏犵紒杈ㄥ笒椤粓宕卞▎蹇撔梻鍌欒兌绾爼寮插鍫熷亱闁绘顕ч弰銉╂煃?=== state := getDeviceState(deviceData.DeviceUUID) if state.FireCheckWaitingClear && !state.FireCheckLastSeen.IsZero() && time.Since(state.FireCheckLastSeen) > fireCheckLastSeenGrace { lastSeenAge := time.Since(state.FireCheckLastSeen) state.FireCheckWaitingClear = false state.FireCheckCandidate = false state.FireCheckFirstSeen = time.Time{} state.FireCheckLastSeen = time.Time{} logger.Logger.Printf("Fire target lost, management rearmed fire_check: lastSeenAge=%v, DeviceUUID=%s", lastSeenAge.Round(time.Millisecond), deviceData.DeviceUUID) } if state.FireCheckCandidate { checkAt := time.Now() lastSeenAge := checkAt.Sub(state.FireCheckLastSeen) elapsed := checkAt.Sub(state.FireCheckFirstSeen) required := getFireCheckDetectionDuration(deviceData) if lastSeenAge > fireCheckLastSeenGrace { logger.Logger.Printf("Fire detection candidate expired: lastSeenAge=%v, DeviceUUID=%s", lastSeenAge, deviceData.DeviceUUID) state.FireCheckCandidate = false state.FireCheckFirstSeen = time.Time{} state.FireCheckLastSeen = time.Time{} } else { logger.Logger.Printf("Fire target timing: elapsed=%v, required=%v, DeviceUUID=%s", elapsed.Round(time.Millisecond), required, deviceData.DeviceUUID) } } // === 闂傚倸鍊峰ù鍥х暦閸偅鍙忛柡澶嬪殮濞差亜鐓涢柛婊€鐒﹂弲顏堟偡濠婂嫬鐏村┑锛勬暬楠炲洭寮剁捄銊モ偓鐐烘偡濠婂喚鍎旈柛鈹惧亾濡炪倖甯婄粈浣该归鈧弻娑㈠煘閹冣拤闂佺懓鍢查幊妯虹暦濠婂牆绠甸柟鐑樻尰濮e矂姊婚崒姘偓鐑芥倿閿旈敮鍋撶粭娑樻噽閻瑩鏌熸潏楣冩闁稿顑夐弻鐔兼倷椤掑倻鐛梺鎸庣箓濡稓寮ч埀顒€鈹戦鏂や緵闁告挻鐩、娆撳川鐎涙ǚ鎷洪梺鍛婄箓鐎氼喛鍊撮梻浣芥〃缁€渚€骞楀鍛灊?== if !fireLeaveActive || !temperatureActive || tempClient == nil { if state.InCondition { state.InCondition = false } continue } tempResult, err := tempClient.ReadTemperatureResult() if err == nil { if math.Abs(tempResult.Tmp-deviceData.Temperature) >= 0.5 { oldTemp := deviceData.Temperature deviceData.Temperature = tempResult.Tmp connect.UpdateDeviceData(deviceData) GlobalDeviceManager.AddDevice(deviceData) logger.Logger.Printf("Temperature update: %.1fC -> %.1fC (DeviceUUID=%s)", oldTemp, tempResult.Tmp, deviceData.DeviceUUID) } } detectionSec := getIntFromNumber(deviceData.DetectionTime, 20) alarmCooldown := getIntFromNumber(deviceData.AlarmTime, 300) // === 闂傚倸鍊搁崐鎼佸磹閹间礁纾归柟闂寸閻ゎ喗銇勯幇鈺佺労闁搞倖娲熼弻銈囩矙鐠恒劋绮甸梺鍛婄懃缁绘﹢寮诲☉銏╂晝闁绘ɑ褰冩慨鏇㈡⒑閸濆嫭濯奸柛鎾村哺楠炲牓濡搁妷顔藉缓闂佺硶鍓濋〃鍛不濞差亝鈷戦悹鎭掑妼閺嬪倿鏌涙惔銊ゆ喚婵犫偓?+ 濠电姷鏁告慨鐑藉极閹间礁纾婚柣鎰惈缁犱即鏌熺紒銏犳灈闁汇倗鍋撶换娑㈠箣閻愯尙鍔伴梺绋款儐閹瑰洭骞冮悜钘夌妞ゆ洖妫涚粈濠勭磽閸屾瑧璐伴柛鐘愁殜閹兘鍩℃担鐑樻濠殿喗銇涢崑鎾绘煙瀹曞洤鈻堟い銏☆殕閹峰懘宕烽鐔滄棃姊婚崒娆掑厡闁稿海鏁诲畷婊冣攽閸喐娈伴梺鍦劋椤ㄥ懐绮婚悩鍏呯箚妞ゆ牗鐟х粔鍧楁煥濠靛棭妲搁柣鎰攻閵囧嫰骞掗崱妞惧缂?=== noPerson := deviceData.PersonCount == 0 var tempStableHigh bool threshold := deviceData.TemperatureThreshold if deviceData.Temperature > threshold+0.5 { tempStableHigh = true } else if deviceData.Temperature < threshold-0.5 { tempStableHigh = false } else { tempStableHigh = deviceData.Temperature > threshold } conditionMet := noPerson && tempStableHigh if now < state.FireLeaveForbidUntil { if state.InCondition { state.InCondition = false } logger.Logger.Printf("fire_leave report paused: %ds remaining (DeviceUUID=%s)", state.FireLeaveForbidUntil-now, deviceData.DeviceUUID) continue } if now < state.AlarmPauseUntil { if state.InCondition { logger.Logger.Printf("Alarm in cooldown: %ds remaining (DeviceUUID=%s)", state.AlarmPauseUntil-now, deviceData.DeviceUUID) state.InCondition = false } continue } if conditionMet { if !state.InCondition { state.StartTime = now state.InCondition = true logger.Logger.Printf("Alarm condition met, start timing (%ds required): PersonCount=%d, Temp=%.1fC > %.1fC (DeviceUUID=%s)", detectionSec, deviceData.PersonCount, deviceData.Temperature, threshold, deviceData.DeviceUUID) } else if now-state.StartTime >= int64(detectionSec) { currentTime := time.Now() logger.Logger.Printf("[%d] ALARM TRIGGERED (after %ds): PersonCount=%d, Temp=%.1fC > %.1fC (DeviceUUID=%s)", currentTime.Unix(), detectionSec, deviceData.PersonCount, deviceData.Temperature, threshold, deviceData.DeviceUUID) go func() { alarmStartTime := time.Now() logger.Logger.Printf("[%d] Start async alarm handling (DeviceUUID=%s, alarmTime=%d, cooldown=%d)", alarmStartTime.Unix(), deviceData.DeviceUUID, state.StartTime, alarmCooldown) defer func() { if err := recover(); err != nil { logger.Logger.Printf("[%d] Alarm handling panic: %v (DeviceUUID=%s)", time.Now().Unix(), err, deviceData.DeviceUUID) } }() // 闂傚倸鍊峰ù鍥х暦閸偅鍙忛柟缁㈠櫘閺佸嫰鏌涘☉娆愮稇闁汇値鍠栭湁闁稿繐鍚嬬紞鎴︽煛鐎b晝绐旈柡灞炬礋瀹曠厧鈹戦幇顓夛妇绱撴担鍝勵€岄柛锝忕秮瀵寮撮悢椋庣獮濠碘槅鍨抽崢褏鏁妷鈺傗拺闁告稑锕ラ悡銉╂煙閸愭煡鍙勯柍銉閹瑰嫰濡搁敃鈧壕顖炴⒑閸涘﹦绠撻悗姘舵敱鐎靛ジ鍩€椤掑嫭鈷掑ù锝囩摂閸ゅ啴鏌涢悩宕囧⒌闁轰礁鍟撮、鏃堝礋椤撶喐顔曟繝鐢靛仜濡瑩骞愭繝姘惞鐎广儱顦伴悡蹇擃熆閼哥數娲存俊缁㈠枟閵? GlobalReporter.TriggerAlarmWithDelay(deviceData, state.StartTime, alarmCooldown) logger.Logger.Printf("[%d] Alarm handling completed (DeviceUUID=%s, elapsed=%.3fs)", time.Now().Unix(), deviceData.DeviceUUID, time.Since(alarmStartTime).Seconds()) }() state.AlarmPauseUntil = now + int64(alarmCooldown) state.InCondition = false logger.Logger.Printf("[%d] Alarm cooldown set to %d seconds (DeviceUUID=%s)", time.Now().Unix(), alarmCooldown, deviceData.DeviceUUID) deviceDataCopy := deviceData deviceDataCopy.AlarmPauseUntil = state.AlarmPauseUntil logger.Logger.Printf("[%d] Update device data (DeviceUUID=%s)", time.Now().Unix(), deviceData.DeviceUUID) connect.UpdateDeviceData(deviceDataCopy) logger.Logger.Printf("[%d] Device data update completed (DeviceUUID=%s)", time.Now().Unix(), deviceData.DeviceUUID) } } else { if state.InCondition { logger.Logger.Printf("Alarm condition interrupted: lasted %ds < %ds (DeviceUUID=%s)", now-state.StartTime, detectionSec, deviceData.DeviceUUID) state.InCondition = false } } } } } func waitForShutdown(ctx context.Context, cancel context.CancelFunc, wg *sync.WaitGroup) { stop := make(chan os.Signal, 1) signal.Notify(stop, os.Interrupt, syscall.SIGTERM) select { case sig := <-stop: logger.Logger.Printf("Received stop signal: %v, start cleaning...", sig) // 缂傚倸鍊搁崐鎼佸磹閻戣姤鍊块柨鏇炲€搁拑鐔兼煏婵炵偓娅撻柡浣稿閺屾稑鈽夐崡鐐茬闂佸搫妫庨崐婵嬪蓟濞戙垹鐒洪柛鎰典簴濡插牓姊哄ú璇插箺妞ゃ劌锕濠氬灳閹颁礁鎮戦梺鍛婂姂閸斿矂鈥栫€n喗鐓曢柣鏃堟敱閸熺偟绱掔紒妯兼创妤犵偛顑夐幃娆撳级閹寸媭鏆¢梻鍌欑劍鐎笛兠洪埡鍐ㄧ筏閻犳亽鍔岄崹婵囥亜閹惧崬鐏╃€瑰憡绻堥弻鈩冨緞鐎n亞浠稿銈冨劜缁诲牆顫忓ú顏勭闁绘劖褰冩慨锕€顪冮妶鍐ㄥ姕闁圭澧介崚鎺楀醇閻旈绐為梺褰掑亰閸樻悂骞忛崫鍕垫富闁靛牆妫楁慨褏绱掗弻銉х暫闁诡垰鍟撮獮瀣晜閻e苯骞楅梻浣烘嚀閻忔繄鈧凹鍠氬褔鍩€椤掆偓铻栭柣姗€娼ф禒婊呯磼婢跺﹦绉虹€规洘妞介崺鈧い鎺嶉檷娴滄粓鏌熼悜妯虹仴妞ゅ繆鏅犻幃妤€顫濋澶嬓﹂梺瀹狀潐閸ㄨ泛顕ラ崟顓濇勃闁规儳鎳愬绲﹐utine闂傚倸鍊搁崐鐑芥嚄閸洍鈧箓宕奸妷顔芥櫈闂佺硶鍓濋悷銉╁垂濠靛牃鍋撻獮鍨姎妞わ缚绮欏? cancel() done := make(chan struct{}) go func() { wg.Wait() close(done) }() select { case <-done: logger.Logger.Printf("All goroutines exited normally") case <-time.After(15 * time.Second): logger.Logger.Printf("Waiting time exceeded, force exit") connect.InferenceProcesses.Range(func(key, value interface{}) bool { if cmd, ok := value.(*exec.Cmd); ok && cmd.Process != nil { logger.Logger.Printf("Force kill process: %v", key) cmd.Process.Kill() } return true }) } case <-ctx.Done(): logger.Logger.Printf("The context has been cancelled.") } logger.Logger.Printf("The program exits successfully.") os.Exit(0) } func initWebSocketAsync(ctx context.Context, wg *sync.WaitGroup) { logger.Logger.Printf("Start the initialization of asynchronous WebSocket...") if err := connect.InitWSChannel(); err != nil { logger.Logger.Printf("Failed to initialize WebSocket channel: %v", err) return } logger.Logger.Printf("Waiting for WebSocket connection to be established...") containerName := "fireleave-container" for i := 0; i < 30; i++ { select { case <-ctx.Done(): logger.Logger.Printf("Context cancelled, aborting WebSocket registration") return case <-time.After(1 * time.Second): client := connect.GetWSClient() if client != nil && client.IsConnected() { goto register } } } logger.Logger.Printf("WebSocket connection timeout after 30 seconds") return register: serviceID, err := connect.RegisterService(containerName) if err != nil { logger.Logger.Printf("Service registration failed: %v", err) } else { logger.Logger.Printf("Service registration successful: ServiceID=%s", serviceID) } wsClient := connect.GetWSClient() GlobalReporter.SetWSClient(wsClient) logger.Logger.Printf("Updated GlobalReporter with WebSocket client: %v", wsClient != nil) // 闂傚倸鍊搁崐椋庣矆娓氣偓楠炴牠顢曢敃鈧壕褰掓煟閻旂厧浜伴柣鏂挎閹便劌顪冪拠韫闁荤姳璀﹂崹鍫曞蓟濞戙垹绠涢梻鍫熺⊕閻忓秹姊洪崫鍕闁告挻宀搁獮鍫ュΩ閵夊海鍠撻幏鐘诲箵閹烘繃娴囬梻鍌欑閹诧繝鎳濇ィ鍐ㄧ9闁割煈鍣鏍р攽閻樺疇澹樼紒鈧崘顔界厪濠电姴绻掗悾杈ㄧ箾閸涱偄鈧洟鍩為幋锔藉亹妞ゆ劦婢€婢规洖鈹戦悙瀛樼稇闁告艾顑呴…鍥р枎閹惧磭顔夐梺鍓插亝濞叉﹢鎮″☉姗嗙唵閻犺桨璀﹂崕蹇斻亜閺囩喓鐭掗柡灞剧洴閹垽宕崟顏咁潟闂備浇顕栭崰鏍礊婵犲洤鏄ラ柍?topic if err := connect.SubscribeTopic("/dhlr/alert", connect.HandleBuzzerAlert); err != nil { logger.Logger.Printf("Subscribe alert topic failed (/dhlr/alert): %v", err) } else { logger.Logger.Printf("Subscribed local alert topic: /dhlr/alert") } // 闂傚倸鍊搁崐椋庣矆娓氣偓楠炴牠顢曢敃鈧壕褰掓煟閻旂厧浜伴柣鏂挎閹便劌顪冪拠韫闁荤姳璀﹂崹鍫曞蓟濞戙垹绠涢梻鍫熺⊕閻忓秹姊洪崫鍕闁告挻宀搁獮鍫ュΩ閵夊海鍠撻幏鐘诲箵閹烘繃娴囬梻鍌欑閹诧繝鎳濇ィ鍐ㄧ9闁割煈鍣鏍р攽閻樺疇澹樼紒鈧崘顔界厪濠电姴绻掗悾杈ㄧ箾閸涘洤鍠氬〒濠氭煏閸繂鈧懓鈻嶉弮鍌滅<闁绘ê鍟块悘鎾煥濠靛牆浠卞┑顔瑰亾闂侀潧鐗嗛幏瀣焽婵犲洦鈷戦柡鍌樺劜濞呭懘鏌涢悩瀹犲閸楄鲸銇勯弽顐沪闁?topic if err := connect.SubscribeTopic("/dhlr/forbid_time", handleForbidTimeMessage); err != nil { logger.Logger.Printf("Subscribe forbid time topic failed (/dhlr/forbid_time): %v", err) } else { logger.Logger.Printf("Subscribed forbid time topic: /dhlr/forbid_time") } } func (dm *DeviceManager) GetAllDevices() map[string]*connect.DeviceData { dm.mu.Lock() defer dm.mu.Unlock() result := make(map[string]*connect.DeviceData) for k, v := range dm.devices { result[k] = v } return result } func (dm *DeviceManager) RangeDevices(fn func(uuid string, device *connect.DeviceData) bool) { dm.mu.Lock() defer dm.mu.Unlock() for uuid, device := range dm.devices { if !fn(uuid, device) { return } } } func main() { fmt.Printf("FireLeave Tool build: %s\n", buildVersion) var rtspURL string var deviceUUID string flag.StringVar(&rtspURL, "rtsp", "", "RTSP stream URL for testing") flag.StringVar(&deviceUUID, "uuid", "", "Device UUID for testing") flag.Parse() if err := logger.InitLogger(); err != nil { log.Fatalf("Logger initialization failed: %v", err) } defer logger.CloseLogger() if err := logger.InitReporterLogger(); err != nil { logger.Logger.Printf("Reporter logger initialization failed: %v", err) } if err := logger.InitGPILogger(); err != nil { logger.Logger.Printf("GPIO logger initialization failed: %v", err) } go func() { for { logger.CleanupOldLogs() time.Sleep(24 * time.Hour) } }() logger.Logger.Printf("=== Starting FireLeave Tool ===") ctx, cancel := context.WithCancel(context.Background()) defer cancel() var wg sync.WaitGroup logger.Logger.Printf("Step 1: Initialize device data from service config") deviceDataList, err := connect.InitializeDevicesFromServiceConfig() if err != nil { logger.Logger.Printf("Device data initialization failed: %v", err) logger.Logger.Printf("Will try to use existing device files...") deviceDataList = connect.GetDeviceDataList() } logger.Logger.Printf("Step 2: Starting %d devices", len(deviceDataList)) for i, deviceData := range deviceDataList { logger.Logger.Printf("Starting device[%d]: UUID=%s", i, deviceData.DeviceUUID) wg.Add(1) go func(deviceData connect.DeviceData, index int) { defer wg.Done() defer GlobalDeviceManager.StopDevice(deviceData.DeviceUUID) logger.Logger.Printf("Device processing goroutine started: UUID=%s, Index=%d", deviceData.DeviceUUID, index) processDevice(ctx, deviceData) }(deviceData, i) } logger.Logger.Printf("Step 3: All devices started") logger.Logger.Printf("Step 4: Asynchronously initialize WebSocket connection") go initWebSocketAsync(ctx, &wg) logger.Logger.Printf("Step 5: Start GPIO button monitoring") go func() { monitor := gpio_monitor.NewGPIOButtonMonitor(gpio_monitor.ButtonGPIO) monitor.SetOnPress(func() { minute := reporter.GetForbidTimeConfig() duration := reporter.ParseForbidDuration(minute, defaultFireLeaveForbidDuration) persistAndApplyFireLeaveForbidWindow(minute, duration) }) if err := monitor.Start(ctx); err != nil { logger.Logger.Printf("Failed to start GPIO button monitor: %v", err) } else { logger.Logger.Printf("GPIO button monitor started successfully") } }() logger.Logger.Printf("=== Main program startup completed ===") logger.Logger.Printf("System status: Running devices=%d", len(deviceDataList)) // Add device status monitoring go monitorDeviceStatus(ctx, deviceDataList) go func() { ticker := time.NewTicker(30 * time.Second) defer ticker.Stop() for range ticker.C { GlobalDeviceManager.RangeDevices(func(uuid string, device *connect.DeviceData) bool { state := getDeviceState(uuid) if !state.FireLeaveDisabled && connect.ShouldRestartInference(uuid) { logger.Logger.Printf("fire_leave inference heartbeat timeout (>90s), restarting: DeviceUUID=%s", uuid) if err := connect.RestartInferenceProcess(device, uuid); err != nil { logger.Logger.Printf("fire_leave inference restart failed: DeviceUUID=%s, err=%v", uuid, err) } } fireCheckUUID := fmt.Sprintf("%s_fire_check", uuid) if connect.ShouldRestartInference(fireCheckUUID) { logger.Logger.Printf("fire_check inference heartbeat timeout (>90s), restarting: DeviceUUID=%s", uuid) if err := connect.RestartInferenceProcess(device, fireCheckUUID); err != nil { logger.Logger.Printf("fire_check inference restart failed: DeviceUUID=%s, err=%v", uuid, err) } } return true }) } }() go func() { ticker := time.NewTicker(5 * time.Minute) for range ticker.C { for _, device := range GlobalDeviceManager.GetAllDevices() { video_server.CleanupOldRawVideos(device.DeviceUUID) } } }() waitForShutdown(ctx, cancel, &wg) } func printGlobalDeviceStatus() { now := time.Now().Unix() activeDevices := GlobalDeviceManager.GetAllDevices() if len(activeDevices) == 0 { logger.Logger.Printf("=== Global Device Status monitoring === No running devices for the time being") return } logger.Logger.Printf("=== Global Device Status monitoring === Currently running %d", len(activeDevices)) i := 1 for uuid, data := range activeDevices { state := getDeviceState(uuid) cooldownLeft := int64(0) if now < state.AlarmPauseUntil { cooldownLeft = state.AlarmPauseUntil - now } personValue := "null" tempValue := "null" thresholdValue := "null" if !state.FireLeaveDisabled { personValue = fmt.Sprintf("%d", data.PersonCount) tempValue = fmt.Sprintf("%.1fC", data.Temperature) thresholdValue = fmt.Sprintf("%.1fC", data.TemperatureThreshold) } fireValue := "null" if state.FireCheckHasResult { fireValue = fmt.Sprintf("%d", state.FireCheckLastNumber) } fireCandidate := "false" fireLastSeenAge := "null" if state.FireCheckCandidate { fireCandidate = "true" fireLastSeenAge = time.Since(state.FireCheckLastSeen).Round(time.Millisecond).String() } logger.Logger.Printf(" [%d] DeviceUUID=%s | fire:%s | fire_candidate:%s | fire_last_seen_age:%s | fire_leave_enabled:%t | person:%s | temp:%s | threshold value:%s | cooling time:%ds", i, uuid, fireValue, fireCandidate, fireLastSeenAge, !state.FireLeaveDisabled, personValue, tempValue, thresholdValue, cooldownLeft, ) i++ } } func monitorDeviceStatus(ctx context.Context, initialDevices []connect.DeviceData) { ticker := time.NewTicker(30 * time.Second) defer ticker.Stop() logger.Logger.Printf("Start global device status monitoring (print once every 30 seconds)") for { select { case <-ticker.C: printGlobalDeviceStatus() if files, err := filepath.Glob("/data/devices/device_*.json"); err == nil { logger.Logger.Printf("Device file status: A total of %d device files", len(files)) for i, file := range files { if i < 3 { logger.Logger.Printf(" device file[%d]: %s", i, filepath.Base(file)) } } } logger.Logger.Printf("Operating equipment %d ", len(GlobalDeviceManager.GetAllDevices())) case <-ctx.Done(): logger.Logger.Printf("Global device status monitoring has stopped") return } } } func debugDeviceData(deviceData connect.DeviceData) { logger.Logger.Printf("Debug device data: UUID=%s", deviceData.DeviceUUID) logger.Logger.Printf(" TaskID: %q", deviceData.TaskID) logger.Logger.Printf(" CameraRTSP: %q", deviceData.CameraRTSP) logger.Logger.Printf(" CameraIP: %q", deviceData.CameraIP) logger.Logger.Printf(" UploadURL: %q", deviceData.UploadURL) logger.Logger.Printf(" DetectAreaStr: %q", deviceData.DetectAreaStr) logger.Logger.Printf(" PersonCount: %d", deviceData.PersonCount) logger.Logger.Printf(" Temperature: %f", deviceData.Temperature) } func handleForbidTimeMessage(message []byte) { logger.Logger.Printf("Received forbid time message: %s", string(message)) var msg struct { UUID string `json:"uuid"` Topic string `json:"topic"` QoS int `json:"qos"` Content json.RawMessage `json:"content"` Timeout int `json:"timeout"` } if err := json.Unmarshal(message, &msg); err != nil { logger.Logger.Printf("Failed to parse forbid time message: %v", err) return } var content struct { Minute string `json:"minute"` FireLeaveTimeout string `json:"fire_leave_timeout,omitempty"` FireCheckTimeout string `json:"fire_check_timeout,omitempty"` } if err := json.Unmarshal(msg.Content, &content); err != nil { logger.Logger.Printf("Failed to parse content: %v", err) return } if content.Minute != "" { logger.Logger.Printf("Forbid time config received: Minute=%s", content.Minute) persistAndApplyFireLeaveForbidWindow(content.Minute, reporter.ParseForbidDuration(content.Minute, 0)) } if content.FireLeaveTimeout != "" || content.FireCheckTimeout != "" { fireLeaveTimeout, err1 := time.ParseDuration(content.FireLeaveTimeout) fireCheckTimeout, err2 := time.ParseDuration(content.FireCheckTimeout) if err1 != nil { fireLeaveTimeout = 30 * time.Minute logger.Logger.Printf("Invalid fire_leave_timeout, using default: 30m0s") } if err2 != nil { fireCheckTimeout = 30 * time.Minute logger.Logger.Printf("Invalid fire_check_timeout, using default: 30m0s") } logger.Logger.Printf("Event cycle timeout config received: FireLeaveTimeout=%v, FireCheckTimeout=%v", fireLeaveTimeout, fireCheckTimeout) if err := reporter.SaveEventCycleTimeoutConfig(fireLeaveTimeout, fireCheckTimeout); err != nil { logger.Logger.Printf("Failed to save event cycle timeout config: %v", err) } else { logger.Logger.Printf("Event cycle timeout config saved successfully: FireLeaveTimeout=%v, FireCheckTimeout=%v", fireLeaveTimeout, fireCheckTimeout) } } }