feat: update inference reporting and ffmpeg video merge
This commit is contained in:
@@ -270,6 +270,15 @@ func NormalizeDeviceRuntimeFields(deviceData *DeviceData) {
|
||||
}
|
||||
}
|
||||
|
||||
func GetFireCheckConfidence(deviceData DeviceData) int {
|
||||
if confidenceText := strings.TrimSpace(deviceData.FireCheckParam.Confidence); confidenceText != "" {
|
||||
if confidence, err := strconv.Atoi(confidenceText); err == nil && confidence > 0 {
|
||||
return confidence
|
||||
}
|
||||
}
|
||||
return deviceData.Confidence
|
||||
}
|
||||
|
||||
// ProcessDetectAreaForInference processes detection area data, converts to format required by inference program
|
||||
func ProcessDetectAreaForInference(areas []Area) string {
|
||||
if len(areas) == 0 {
|
||||
|
||||
+72
-98
@@ -12,126 +12,100 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
InferenceProcesses sync.Map // key: deviceUUID string, value: *exec.Cmd
|
||||
|
||||
// 记录最后一次收到心跳的时间
|
||||
inferenceLastHeartbeat sync.Map // key: deviceUUID string, value: time.Time
|
||||
InferenceProcesses sync.Map // key: process UUID, value: *exec.Cmd
|
||||
inferenceLastHeartbeat sync.Map // key: process UUID, value: time.Time
|
||||
)
|
||||
|
||||
// UpdateInferenceHeartbeat 每收到一帧 /video/post 就调用这个
|
||||
func UpdateInferenceHeartbeat(deviceUUID string) {
|
||||
inferenceLastHeartbeat.Store(deviceUUID, time.Now())
|
||||
// 输出心跳日志
|
||||
if strings.Contains(deviceUUID, "_fire_check") {
|
||||
logger.Logger.Printf("推理心跳收到 | 时间: %s | UUID: %s | 类型: fire_check", time.Now().Format("2006/01/02 15:04:05.000"), deviceUUID)
|
||||
// UpdateInferenceHeartbeat records the latest heartbeat time for one inference process.
|
||||
func UpdateInferenceHeartbeat(processUUID string) {
|
||||
inferenceLastHeartbeat.Store(processUUID, time.Now())
|
||||
if strings.Contains(processUUID, "_fire_check") {
|
||||
logger.Logger.Printf("推理心跳收到 | 时间: %s | UUID: %s | 类型: fire_check", time.Now().Format("2006/01/02 15:04:05.000"), processUUID)
|
||||
} else {
|
||||
logger.Logger.Printf("推理心跳收到 | 时间: %s | UUID: %s | 类型: fire_leave", time.Now().Format("2006/01/02 15:04:05.000"), deviceUUID)
|
||||
logger.Logger.Printf("推理心跳收到 | 时间: %s | UUID: %s | 类型: fire_leave", time.Now().Format("2006/01/02 15:04:05.000"), processUUID)
|
||||
}
|
||||
}
|
||||
|
||||
// ShouldRestartInference 判断是否需要重启推理程序
|
||||
func ShouldRestartInference(deviceUUID string) bool {
|
||||
val, ok := inferenceLastHeartbeat.Load(deviceUUID)
|
||||
// ShouldRestartInference reports whether one inference process should be restarted.
|
||||
func ShouldRestartInference(processUUID string) bool {
|
||||
val, ok := inferenceLastHeartbeat.Load(processUUID)
|
||||
if !ok {
|
||||
return true // 从未收到过心跳,肯定要启动
|
||||
return true
|
||||
}
|
||||
last := val.(time.Time)
|
||||
return time.Since(last) > 85*time.Second
|
||||
}
|
||||
|
||||
// StopInferenceProcess 停止推理进程(供 main 和 DeviceManager 调用)
|
||||
func StopInferenceProcess(deviceUUID string) {
|
||||
if raw, ok := InferenceProcesses.Load(deviceUUID); ok {
|
||||
// StopInferenceProcess stops one inference process.
|
||||
func StopInferenceProcess(processUUID string) {
|
||||
if raw, ok := InferenceProcesses.Load(processUUID); ok {
|
||||
if cmd, ok := raw.(*exec.Cmd); ok && cmd.Process != nil {
|
||||
logger.Logger.Printf("正在停止推理进程 (DeviceUUID=%s, PID=%d)", deviceUUID, cmd.Process.Pid)
|
||||
cmd.Process.Kill()
|
||||
cmd.Wait()
|
||||
logger.Logger.Printf("正在停止推理进程 (ProcessUUID=%s, PID=%d)", processUUID, cmd.Process.Pid)
|
||||
_ = cmd.Process.Kill()
|
||||
_ = cmd.Wait()
|
||||
}
|
||||
InferenceProcesses.Delete(deviceUUID)
|
||||
InferenceProcesses.Delete(processUUID)
|
||||
}
|
||||
}
|
||||
|
||||
// StartInferenceProcess 重启推理程序(保持原端口!)
|
||||
// StartInferenceProcess keeps the old "restart both" behavior for compatibility.
|
||||
func StartInferenceProcess(deviceData *DeviceData) {
|
||||
|
||||
// 重启fire_leave推理程序
|
||||
StopInferenceProcess(deviceData.DeviceUUID)
|
||||
|
||||
portFireLeave, err := GlobalPortManager.GetPort(deviceData.DeviceUUID)
|
||||
if err != nil {
|
||||
logger.Logger.Printf("获取fire_leave端口失败,无法重启推理程序 (DeviceUUID=%s): %v", deviceData.DeviceUUID, err)
|
||||
return
|
||||
}
|
||||
|
||||
// 重启fire_check推理程序
|
||||
StopInferenceProcess(fmt.Sprintf("%s_fire_check", deviceData.DeviceUUID))
|
||||
|
||||
portFireCheck, err := GlobalPortManager.GetPort(fmt.Sprintf("%s_fire_check", deviceData.DeviceUUID))
|
||||
if err != nil {
|
||||
logger.Logger.Printf("获取fire_check端口失败,无法重启推理程序 (DeviceUUID=%s): %v", deviceData.DeviceUUID, err)
|
||||
return
|
||||
}
|
||||
|
||||
// 为fire_leave推理程序获取检测区域
|
||||
fireLeaveDetectAreaStr := ProcessDetectAreaForInference(deviceData.FireLeaveDetectArea)
|
||||
|
||||
// 启动fire_leave推理程序
|
||||
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("正在重启fire_leave推理程序 (使用原端口 %d): %v", portFireLeave, inferenceCmdFireLeave.Args)
|
||||
|
||||
if err := inferenceCmdFireLeave.Start(); err != nil {
|
||||
if err := RestartInferenceProcess(deviceData, deviceData.DeviceUUID); err != nil {
|
||||
logger.Logger.Printf("fire_leave推理程序重启失败 (DeviceUUID=%s): %v", deviceData.DeviceUUID, err)
|
||||
return
|
||||
}
|
||||
|
||||
// 保存fire_leave进程对象
|
||||
InferenceProcesses.Store(deviceData.DeviceUUID, inferenceCmdFireLeave)
|
||||
logger.Logger.Printf("fire_leave推理程序已成功重启,PID=%d (DeviceUUID=%s, Port=%d)",
|
||||
inferenceCmdFireLeave.Process.Pid, deviceData.DeviceUUID, portFireLeave)
|
||||
|
||||
// 为fire_check推理程序获取检测区域
|
||||
fireCheckDetectAreaStr := ProcessDetectAreaForInference(deviceData.FireCheckDetectArea)
|
||||
|
||||
// 启动fire_check推理程序
|
||||
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", deviceData.Confidence),
|
||||
"-R",
|
||||
)
|
||||
|
||||
inferenceCmdFireCheck.Stdout = os.Stdout
|
||||
inferenceCmdFireCheck.Stderr = os.Stderr
|
||||
|
||||
logger.Logger.Printf("正在重启fire_check推理程序 (使用原端口 %d): %v", portFireCheck, inferenceCmdFireCheck.Args)
|
||||
|
||||
if err := inferenceCmdFireCheck.Start(); err != nil {
|
||||
if err := RestartInferenceProcess(deviceData, fmt.Sprintf("%s_fire_check", deviceData.DeviceUUID)); err != nil {
|
||||
logger.Logger.Printf("fire_check推理程序重启失败 (DeviceUUID=%s): %v", deviceData.DeviceUUID, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// RestartInferenceProcess restarts only the requested inference process UUID.
|
||||
func RestartInferenceProcess(deviceData *DeviceData, processUUID string) error {
|
||||
if deviceData == nil {
|
||||
return fmt.Errorf("device data is nil")
|
||||
}
|
||||
|
||||
// 保存fire_check进程对象
|
||||
InferenceProcesses.Store(fmt.Sprintf("%s_fire_check", deviceData.DeviceUUID), inferenceCmdFireCheck)
|
||||
logger.Logger.Printf("fire_check推理程序已成功重启,PID=%d (DeviceUUID=%s, Port=%d)",
|
||||
inferenceCmdFireCheck.Process.Pid, deviceData.DeviceUUID, portFireCheck)
|
||||
port, err := GlobalPortManager.GetPort(processUUID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get port failed: %w", err)
|
||||
}
|
||||
|
||||
// 重启后立即刷新一次心跳,防止 30s 检测又触发
|
||||
UpdateInferenceHeartbeat(deviceData.DeviceUUID)
|
||||
UpdateInferenceHeartbeat(fmt.Sprintf("%s_fire_check", deviceData.DeviceUUID))
|
||||
modelName := "model"
|
||||
detectAreaStr := ProcessDetectAreaForInference(deviceData.FireLeaveDetectArea)
|
||||
args := []string{
|
||||
"-s", deviceData.CameraRTSP,
|
||||
"-m", modelName,
|
||||
"-c", processUUID,
|
||||
"-p", fmt.Sprintf("%d", port),
|
||||
"-w", detectAreaStr,
|
||||
"-r", fmt.Sprintf("%d", deviceData.Confidence),
|
||||
}
|
||||
|
||||
if strings.HasSuffix(processUUID, "_fire_check") {
|
||||
modelName = "fire_check"
|
||||
detectAreaStr = ProcessDetectAreaForInference(deviceData.FireCheckDetectArea)
|
||||
confidence := GetFireCheckConfidence(*deviceData)
|
||||
args = []string{
|
||||
"-s", deviceData.CameraRTSP,
|
||||
"-m", modelName,
|
||||
"-c", processUUID,
|
||||
"-p", fmt.Sprintf("%d", port),
|
||||
"-w", detectAreaStr,
|
||||
"-r", fmt.Sprintf("%d", confidence),
|
||||
}
|
||||
}
|
||||
|
||||
StopInferenceProcess(processUUID)
|
||||
cmd := exec.Command("./yolov5", args...)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
|
||||
logger.Logger.Printf("正在重启推理程序 (ProcessUUID=%s, Port=%d): %v", processUUID, port, cmd.Args)
|
||||
if err := cmd.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
InferenceProcesses.Store(processUUID, cmd)
|
||||
UpdateInferenceHeartbeat(processUUID)
|
||||
logger.Logger.Printf("推理程序已成功重启,PID=%d (ProcessUUID=%s, Port=%d)", cmd.Process.Pid, processUUID, port)
|
||||
return nil
|
||||
}
|
||||
|
||||
+54
-14
@@ -132,6 +132,7 @@ type WSClient struct {
|
||||
subscribers map[string]func([]byte)
|
||||
closeChan chan struct{}
|
||||
reconnectFlag bool
|
||||
reconnecting bool
|
||||
}
|
||||
|
||||
// NewWSClient creates WebSocket client
|
||||
@@ -185,7 +186,6 @@ func InitWSChannel() error {
|
||||
}
|
||||
|
||||
isConnecting = true
|
||||
defer func() { isConnecting = false }()
|
||||
|
||||
logger.Logger.Printf("Starting WebSocket connection initialization: %s", wsURL)
|
||||
|
||||
@@ -194,15 +194,21 @@ func InitWSChannel() error {
|
||||
client, err := NewWSClient(wsURL)
|
||||
if err != nil {
|
||||
logger.Logger.Printf("Failed to create WebSocket client: %v", err)
|
||||
wsMutex.Lock()
|
||||
isConnecting = false
|
||||
wsMutex.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
wsMutex.Lock()
|
||||
defer wsMutex.Unlock()
|
||||
if ConfigUpdateChan == nil {
|
||||
ConfigUpdateChan = make(chan DeviceData, 100)
|
||||
}
|
||||
|
||||
// Note: No longer calling LoadDeviceData() here to avoid duplicate loading
|
||||
wsClient = client
|
||||
isConnecting = false
|
||||
logger.Logger.Printf("WebSocket connection initialized successfully (asynchronous)")
|
||||
}()
|
||||
|
||||
@@ -228,7 +234,8 @@ func GetServiceID() string {
|
||||
|
||||
// RegisterService service registration - modified: now requires providing own server_id
|
||||
func RegisterService(containerName string) (string, error) {
|
||||
if wsClient == nil {
|
||||
client := GetWSClient()
|
||||
if client == nil {
|
||||
return "", fmt.Errorf("WebSocket client not initialized")
|
||||
}
|
||||
|
||||
@@ -243,7 +250,7 @@ func RegisterService(containerName string) (string, error) {
|
||||
},
|
||||
}
|
||||
logger.Logger.Printf("Starting service registration: ContainerName=%s, ServiceID=%s", containerName, serviceID)
|
||||
response, err := wsClient.sendRequest("register", params, true)
|
||||
response, err := client.sendRequest("register", params, true)
|
||||
if err != nil {
|
||||
logger.Logger.Printf("Service registration failed: %v", err)
|
||||
return "", err
|
||||
@@ -258,21 +265,24 @@ func RegisterService(containerName string) (string, error) {
|
||||
return "", fmt.Errorf("Service registration failed: Server returned abnormal response")
|
||||
}
|
||||
|
||||
wsClient.serviceID = serviceID
|
||||
client.mutex.Lock()
|
||||
client.serviceID = serviceID
|
||||
client.mutex.Unlock()
|
||||
logger.Logger.Printf("Service registration successful: ServiceID=%s", serviceID)
|
||||
return serviceID, nil
|
||||
}
|
||||
|
||||
// SubscribeTopic topic subscription
|
||||
func SubscribeTopic(topic string, callback func([]byte)) error {
|
||||
if wsClient == nil {
|
||||
client := GetWSClient()
|
||||
if client == nil {
|
||||
return fmt.Errorf("WebSocket client not initialized")
|
||||
}
|
||||
params := SubscribeParams{
|
||||
Topic: topic,
|
||||
}
|
||||
logger.Logger.Printf("Starting topic subscription: Topic=%s", topic)
|
||||
response, err := wsClient.sendRequest("subscribe", params, true)
|
||||
response, err := client.sendRequest("subscribe", params, true)
|
||||
if err != nil {
|
||||
logger.Logger.Printf("Topic subscription failed: %v", err)
|
||||
return err
|
||||
@@ -281,9 +291,9 @@ func SubscribeTopic(topic string, callback func([]byte)) error {
|
||||
logger.Logger.Printf("Topic subscription error: Code=%d, Message=%s", response.Error.Code, response.Error.Message)
|
||||
return fmt.Errorf("Topic subscription error: %s", response.Error.Message)
|
||||
}
|
||||
wsClient.mutex.Lock()
|
||||
wsClient.subscribers[topic] = callback
|
||||
wsClient.mutex.Unlock()
|
||||
client.mutex.Lock()
|
||||
client.subscribers[topic] = callback
|
||||
client.mutex.Unlock()
|
||||
logger.Logger.Printf("Topic subscription successful: Topic=%s", topic)
|
||||
return nil
|
||||
}
|
||||
@@ -344,7 +354,7 @@ func (c *WSClient) sendBinaryFrame(frameType byte, payload []byte) error {
|
||||
if err != nil {
|
||||
logger.Logger.Printf("WebSocket message sending failed: %v", err)
|
||||
c.connected = false
|
||||
go c.reconnect()
|
||||
c.startReconnectLocked()
|
||||
return fmt.Errorf("Message sending failed: %v", err)
|
||||
}
|
||||
return nil
|
||||
@@ -431,7 +441,7 @@ func (c *WSClient) readLoop() {
|
||||
c.mutex.Lock()
|
||||
c.connected = false
|
||||
c.mutex.Unlock()
|
||||
go c.reconnect() // Trigger reconnection
|
||||
c.startReconnect() // Trigger reconnection
|
||||
return
|
||||
}
|
||||
|
||||
@@ -569,7 +579,7 @@ func (c *WSClient) heartbeatLoop() {
|
||||
c.mutex.Lock()
|
||||
c.connected = false
|
||||
c.mutex.Unlock()
|
||||
go c.reconnect()
|
||||
c.startReconnect()
|
||||
} else {
|
||||
logger.Logger.Printf("Ping sent successfully")
|
||||
}
|
||||
@@ -596,7 +606,13 @@ func (c *WSClient) reRegisterAndResubscribe() {
|
||||
logger.Logger.Printf("Re-registration successful: ServiceID=%s", serviceID)
|
||||
|
||||
// 3. 重新订阅
|
||||
c.mutex.Lock()
|
||||
subscribers := make(map[string]func([]byte), len(c.subscribers))
|
||||
for topic, callback := range c.subscribers {
|
||||
subscribers[topic] = callback
|
||||
}
|
||||
c.mutex.Unlock()
|
||||
for topic, callback := range subscribers {
|
||||
if err := SubscribeTopic(topic, callback); err != nil {
|
||||
logger.Logger.Printf("Failed to re-subscribe topic %s: %v", topic, err)
|
||||
} else {
|
||||
@@ -605,9 +621,32 @@ func (c *WSClient) reRegisterAndResubscribe() {
|
||||
}
|
||||
}
|
||||
|
||||
func (c *WSClient) startReconnect() {
|
||||
c.mutex.Lock()
|
||||
defer c.mutex.Unlock()
|
||||
c.startReconnectLocked()
|
||||
}
|
||||
|
||||
func (c *WSClient) startReconnectLocked() {
|
||||
if !c.reconnectFlag || c.reconnecting {
|
||||
return
|
||||
}
|
||||
c.reconnecting = true
|
||||
go c.reconnect()
|
||||
}
|
||||
|
||||
// connect/wschannel.go → reconnect complete fix
|
||||
func (c *WSClient) reconnect() {
|
||||
if !c.reconnectFlag {
|
||||
defer func() {
|
||||
c.mutex.Lock()
|
||||
c.reconnecting = false
|
||||
c.mutex.Unlock()
|
||||
}()
|
||||
|
||||
c.mutex.Lock()
|
||||
reconnectFlag := c.reconnectFlag
|
||||
c.mutex.Unlock()
|
||||
if !reconnectFlag {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -636,12 +675,13 @@ func (c *WSClient) reconnect() {
|
||||
|
||||
// 重启读写循环
|
||||
go c.readLoop()
|
||||
go c.heartbeatLoop()
|
||||
return
|
||||
}
|
||||
|
||||
logger.Logger.Printf("Reconnection failed after 12 attempts")
|
||||
c.mutex.Lock()
|
||||
c.reconnectFlag = false
|
||||
c.mutex.Unlock()
|
||||
}
|
||||
func (d *DeviceData) ToInferenceParams(port int) InferenceParams {
|
||||
return InferenceParams{
|
||||
|
||||
Reference in New Issue
Block a user