添加gpio控制/进程健康管理

This commit is contained in:
gqc
2025-12-03 14:12:38 +08:00
parent 7a9d103943
commit 9b02bd9779
6 changed files with 302 additions and 27 deletions
+120 -20
View File
@@ -23,6 +23,10 @@ import (
"time"
)
var (
inferenceLastHeartbeat sync.Map // key: deviceUUID, value: time.Time
)
type deviceAlarmState struct {
InCondition bool
StartTime int64
@@ -241,10 +245,18 @@ func processDevice(ctx context.Context, deviceData connect.DeviceData) {
logger.Logger.Printf("Start the inference program command: %v", inferenceCmd.Args)
if err := inferenceCmd.Start(); err != nil {
logger.Logger.Printf("The reasoning program failed to start (DeviceUUID=%s): %v", deviceData.DeviceUUID, err)
logger.Logger.Printf("推理程序启动失败 (DeviceUUID=%s): %v", deviceData.DeviceUUID, err)
return
}
// 关键:保存进程对象到全局 map
inferenceProcesses.Store(deviceData.DeviceUUID, inferenceCmd)
logger.Logger.Printf("推理程序已启动,PID=%d (DeviceUUID=%s)", inferenceCmd.Process.Pid, deviceData.DeviceUUID)
// 初始化心跳
UpdateInferenceHeartbeat(deviceData.DeviceUUID)
// 检查推理程序是否启动成功
time.Sleep(2 * time.Second)
if inferenceCmd.Process == nil {
@@ -550,6 +562,13 @@ register:
} else {
logger.Logger.Printf("Service registration successful: ServiceID=%s", serviceID)
}
// 新增:订阅本地告警 topic
if err := connect.SubscribeTopic("/dhlr/alert", connect.HandleBuzzerAlert); err != nil {
logger.Logger.Printf("订阅告警topic失败 (/dhlr/alert): %v", err)
} else {
logger.Logger.Printf("成功订阅本地告警topic: /dhlr/alert")
}
}
// GetAllDevices Get all devices (new method)
@@ -648,7 +667,7 @@ func triggerAlarmWithDelay(deviceData connect.DeviceData, alarmTime int64, coold
}
client.SendAsync(eventMsg)
logger.Logger.Printf("报警事件已上报 (DeviceUUID=%s)", deviceData.DeviceUUID)
logger.Logger.Printf("The alarm incident has been reported (DeviceUUID=%s)", deviceData.DeviceUUID)
// === 4. 上报数据快照 (data) ===
dataPayload := map[string]any{
@@ -679,30 +698,87 @@ func triggerAlarmWithDelay(deviceData connect.DeviceData, alarmTime int64, coold
// === 5. 清理本地文件 ===
os.Remove(mergedFile)
video_server.CleanupOldRawVideos(deviceData.DeviceUUID)
video_server.CleanupOldVideos(deviceData.DeviceUUID)
}
func waitAndRegister() error {
containerName := os.Getenv("CONTAINER_NAME")
if containerName == "" {
containerName = "fireleave-service-default"
func UpdateInferenceHeartbeat(deviceUUID string) {
inferenceLastHeartbeat.Store(deviceUUID, time.Now())
}
func ShouldRestartInference(deviceUUID string) bool {
if val, ok := inferenceLastHeartbeat.Load(deviceUUID); ok {
last := val.(time.Time)
return time.Since(last) > 90*time.Second
}
return true // 从未收到过,也要启动
}
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 {
// 拷贝一份指针,防止外部修改影响遍历
devCopy := device
if !fn(uuid, devCopy) {
return
}
}
}
// StopInferenceProcess 停止指定设备的推理进程(你现在 defer 中就是这么干的,抽出来统一管理)
func (dm *DeviceManager) StopInferenceProcess(deviceUUID string) {
// 这里你之前没有保存 cmd,我们需要一个地方存起来!
// 所以我们要加一个 map 保存每个设备的推理进程
if cmd, ok := inferenceProcesses.Load(deviceUUID); ok {
if process, ok2 := cmd.(*exec.Cmd); ok2 && process.Process != nil {
logger.Logger.Printf("正在停止推理进程 (DeviceUUID=%s, PID=%d)", deviceUUID, process.Process.Pid)
process.Process.Kill()
process.Wait() // 等待退出,防止僵尸进程
}
inferenceProcesses.Delete(deviceUUID)
}
}
// StartInferenceProcess 启动推理进程(把你 processDevice 里启动推理的部分抽出来)
func (dm *DeviceManager) StartInferenceProcess(deviceData *connect.DeviceData) {
dm.StopInferenceProcess(deviceData.DeviceUUID)
// 正确:只查询,不分配
port, err := connect.GlobalPortManager.GetPort(deviceData.DeviceUUID)
if err != nil {
logger.Logger.Printf("获取端口失败,无法重启推理程序 (DeviceUUID=%s): %v", deviceData.DeviceUUID, err)
return
}
// 等待 WS 连接成功
for i := 0; i < 30; i++ {
client := connect.GetWSClient()
if client != nil && client.IsConnected() {
// 连接成功 → 立即注册
serviceID, err := connect.RegisterService(containerName)
if err != nil {
return fmt.Errorf("服务注册失败: %v", err)
}
logger.Logger.Printf("服务注册成功!ServiceID=%s", serviceID)
return nil
}
time.Sleep(1 * time.Second)
detectAreaStr := connect.ProcessDetectAreaForInference(deviceData.DetectArea)
inferenceCmd := exec.Command(
"./yolov5",
"-s", deviceData.CameraRTSP,
"-m", "model",
"-c", deviceData.DeviceUUID,
"-p", fmt.Sprintf("%d", port), // 用老端口!
"-w", detectAreaStr,
"-r", fmt.Sprintf("%d", deviceData.Confidence),
)
inferenceCmd.Stdout = os.Stdout
inferenceCmd.Stderr = os.Stderr
logger.Logger.Printf("正在重启推理程序 (使用原端口 %d): %v", port, inferenceCmd.Args)
if err := inferenceCmd.Start(); err != nil {
logger.Logger.Printf("推理程序重启失败 (DeviceUUID=%s): %v", deviceData.DeviceUUID, err)
return
}
return fmt.Errorf("WebSocket 连接超时,无法注册服务")
inferenceProcesses.Store(deviceData.DeviceUUID, inferenceCmd)
logger.Logger.Printf("推理程序已成功重启,PID=%d (DeviceUUID=%s, Port=%d)",
inferenceCmd.Process.Pid, deviceData.DeviceUUID, port)
UpdateInferenceHeartbeat(deviceData.DeviceUUID)
}
var inferenceProcesses sync.Map
func main() {
// Define command line parameters (remove ws parameter)
var rtspURL string
@@ -767,6 +843,30 @@ func main() {
// 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 {
if ShouldRestartInference(uuid) {
logger.Logger.Printf("推理程序心跳超时(>90s),准备重启: DeviceUUID=%s", uuid)
GlobalDeviceManager.StopInferenceProcess(uuid)
time.Sleep(2 * time.Second)
GlobalDeviceManager.StartInferenceProcess(device)
UpdateInferenceHeartbeat(uuid) // 防止重复重启
}
return true
})
}
}()
go func() {
ticker := time.NewTicker(5 * time.Minute)
for range ticker.C {
for _, device := range GlobalDeviceManager.GetAllDevices() {
video_server.CleanupOldRawVideos(device.DeviceUUID)
}
}
}()
// Wait for termination signal
waitForShutdown(ctx, cancel, &wg)
}