update current project state

This commit is contained in:
gqc
2026-07-03 20:55:15 +08:00
parent 6056b1fdc5
commit 13c4d03055
13 changed files with 1084 additions and 185 deletions
+149 -4
View File
@@ -6,6 +6,7 @@ import (
"fmt" "fmt"
"os" "os"
"path/filepath" "path/filepath"
"strconv"
"strings" "strings"
) )
@@ -69,6 +70,8 @@ var (
serviceConfigFile = "/usr/local/etc/service.conf" serviceConfigFile = "/usr/local/etc/service.conf"
) )
const fixedUploadURL = "http://172.17.0.1/api/v1/upload/video"
// LoadServiceConfig loads multiple device data from service config file // LoadServiceConfig loads multiple device data from service config file
func LoadServiceConfig() ([]DeviceData, error) { func LoadServiceConfig() ([]DeviceData, error) {
if _, err := os.Stat(serviceConfigFile); os.IsNotExist(err) { if _, err := os.Stat(serviceConfigFile); os.IsNotExist(err) {
@@ -88,16 +91,24 @@ func LoadServiceConfig() ([]DeviceData, error) {
return nil, fmt.Errorf("service config file is empty") return nil, fmt.Errorf("service config file is empty")
} }
// Parse as ServiceConfig array // Parse as the canonical ServiceConfig array. Fall back to the previous
// DeviceData array cache shape so older local service.conf files still boot.
var serviceConfigs []ServiceConfig var serviceConfigs []ServiceConfig
if err := json.Unmarshal(data, &serviceConfigs); err != nil { if err := json.Unmarshal(data, &serviceConfigs); err != nil {
var legacyDeviceData []DeviceData
if legacyErr := json.Unmarshal(data, &legacyDeviceData); legacyErr != nil {
logger.Logger.Printf("Failed to parse service config file: %v", err) logger.Logger.Printf("Failed to parse service config file: %v", err)
return nil, err return nil, err
} }
for i := range legacyDeviceData {
NormalizeDeviceRuntimeFields(&legacyDeviceData[i])
}
logger.Logger.Printf("Loaded %d legacy device configurations from service config file", len(legacyDeviceData))
return legacyDeviceData, nil
}
// Convert ServiceConfig to DeviceData // Convert ServiceConfig to DeviceData
var deviceDataList []DeviceData var deviceDataList []DeviceData
const FixedUploadURL = "http://192.168.80.168/api/v1/upload/video"
for _, config := range serviceConfigs { for _, config := range serviceConfigs {
// Create DeviceData from ServiceConfig // Create DeviceData from ServiceConfig
@@ -109,7 +120,7 @@ func LoadServiceConfig() ([]DeviceData, error) {
Confidence: config.Confidence, Confidence: config.Confidence,
HostUUID: config.HostUUID, HostUUID: config.HostUUID,
Address: config.Address, Address: config.Address,
UploadURL: FixedUploadURL, UploadURL: fixedUploadURL,
// Set new fields for new configuration format // Set new fields for new configuration format
FireLeaveDetectArea: config.DetectArea.FireLeave, FireLeaveDetectArea: config.DetectArea.FireLeave,
FireCheckDetectArea: config.DetectArea.FireCheck, FireCheckDetectArea: config.DetectArea.FireCheck,
@@ -125,6 +136,7 @@ func LoadServiceConfig() ([]DeviceData, error) {
logger.Logger.Printf("Set temperature threshold from config: %.1f°C (DeviceUUID=%s)", tempThreshold, config.DeviceUUID) logger.Logger.Printf("Set temperature threshold from config: %.1f°C (DeviceUUID=%s)", tempThreshold, config.DeviceUUID)
} }
} }
NormalizeDeviceRuntimeFields(&deviceData)
// Filter out configurations with empty DeviceUUID // Filter out configurations with empty DeviceUUID
if deviceData.DeviceUUID != "" { if deviceData.DeviceUUID != "" {
@@ -141,6 +153,123 @@ func LoadServiceConfig() ([]DeviceData, error) {
return deviceDataList, nil return deviceDataList, nil
} }
func DecodeDeviceDataPayload(data []byte) (DeviceData, error) {
var deviceData DeviceData
if err := json.Unmarshal(data, &deviceData); err == nil && deviceData.DeviceUUID != "" {
NormalizeDeviceRuntimeFields(&deviceData)
if deviceData.UploadURL == "" {
deviceData.UploadURL = fixedUploadURL
}
return deviceData, nil
}
var serviceConfig ServiceConfig
if err := json.Unmarshal(data, &serviceConfig); err != nil {
return DeviceData{}, err
}
deviceData = ServiceConfigToDeviceData(serviceConfig, fixedUploadURL)
if deviceData.DeviceUUID == "" {
return DeviceData{}, fmt.Errorf("device_uuid is empty")
}
return deviceData, nil
}
func ServiceConfigToDeviceData(config ServiceConfig, uploadURL string) DeviceData {
deviceData := DeviceData{
DeviceUUID: config.DeviceUUID,
TaskID: config.TaskID,
CameraRTSP: config.CameraRTSP,
CameraIP: config.CameraIP,
Confidence: config.Confidence,
HostUUID: config.HostUUID,
Address: config.Address,
UploadURL: uploadURL,
FireLeaveDetectArea: config.DetectArea.FireLeave,
FireCheckDetectArea: config.DetectArea.FireCheck,
FireLeaveParam: config.Param.FireLeave,
FireCheckParam: config.Param.FireCheck,
}
NormalizeDeviceRuntimeFields(&deviceData)
return deviceData
}
func DeviceDataToServiceConfig(deviceData DeviceData) ServiceConfig {
config := ServiceConfig{
HostUUID: deviceData.HostUUID,
DeviceUUID: deviceData.DeviceUUID,
Address: deviceData.Address,
TaskID: deviceData.TaskID,
CameraRTSP: deviceData.CameraRTSP,
CameraIP: deviceData.CameraIP,
Confidence: deviceData.Confidence,
DetectArea: DetectAreaConfig{
FireLeave: deviceData.FireLeaveDetectArea,
FireCheck: deviceData.FireCheckDetectArea,
},
Param: ParamConfig{
FireLeave: deviceData.FireLeaveParam,
FireCheck: deviceData.FireCheckParam,
},
}
if len(config.DetectArea.FireLeave) == 0 {
config.DetectArea.FireLeave = deviceData.DetectArea
}
if config.Param.FireLeave.DetectionTime == "" && deviceData.DetectionTime != "" {
config.Param.FireLeave.DetectionTime = deviceData.DetectionTime.String()
}
if config.Param.FireLeave.AlarmTime == "" && deviceData.AlarmTime != "" {
config.Param.FireLeave.AlarmTime = deviceData.AlarmTime.String()
}
if config.Param.FireLeave.TemperatureThreshold == "" && deviceData.TemperatureThreshold > 0 {
config.Param.FireLeave.TemperatureThreshold = strconv.FormatFloat(deviceData.TemperatureThreshold, 'f', -1, 64)
}
if config.Param.FireLeave.Confidence == "" && deviceData.Confidence > 0 {
config.Param.FireLeave.Confidence = strconv.Itoa(deviceData.Confidence)
}
if config.Param.FireCheck.Confidence == "" && deviceData.Confidence > 0 {
config.Param.FireCheck.Confidence = strconv.Itoa(deviceData.Confidence)
}
return config
}
func NormalizeDeviceRuntimeFields(deviceData *DeviceData) {
if deviceData == nil {
return
}
if len(deviceData.FireLeaveDetectArea) == 0 && len(deviceData.DetectArea) > 0 {
deviceData.FireLeaveDetectArea = deviceData.DetectArea
}
if len(deviceData.DetectArea) == 0 && len(deviceData.FireLeaveDetectArea) > 0 {
deviceData.DetectArea = deviceData.FireLeaveDetectArea
}
if deviceData.FireLeaveParam.PersonCount != "" {
if count, err := strconv.Atoi(deviceData.FireLeaveParam.PersonCount); err == nil && count >= 0 {
deviceData.PersonCount = count
}
}
if deviceData.FireLeaveParam.DetectionTime != "" {
deviceData.DetectionTime = json.Number(deviceData.FireLeaveParam.DetectionTime)
}
if deviceData.FireLeaveParam.AlarmTime != "" {
deviceData.AlarmTime = json.Number(deviceData.FireLeaveParam.AlarmTime)
}
if deviceData.FireLeaveParam.TemperatureThreshold != "" {
if threshold, err := strconv.ParseFloat(deviceData.FireLeaveParam.TemperatureThreshold, 64); err == nil && threshold > 0 {
deviceData.TemperatureThreshold = threshold
logger.Logger.Printf("Set temperature threshold from config: %.1f掳C (DeviceUUID=%s)", threshold, deviceData.DeviceUUID)
}
}
if deviceData.FireLeaveParam.Confidence != "" {
if confidence, err := strconv.Atoi(deviceData.FireLeaveParam.Confidence); err == nil && confidence > 0 {
deviceData.Confidence = confidence
}
}
}
// ProcessDetectAreaForInference processes detection area data, converts to format required by inference program // ProcessDetectAreaForInference processes detection area data, converts to format required by inference program
func ProcessDetectAreaForInference(areas []Area) string { func ProcessDetectAreaForInference(areas []Area) string {
if len(areas) == 0 { if len(areas) == 0 {
@@ -165,6 +294,7 @@ func ProcessDetectAreaForInference(areas []Area) string {
func ProcessDeviceDataForInference(data DeviceData) DeviceData { func ProcessDeviceDataForInference(data DeviceData) DeviceData {
// Create a copy, only process fields that need conversion // Create a copy, only process fields that need conversion
processed := data processed := data
NormalizeDeviceRuntimeFields(&processed)
// For backward compatibility, process original DetectArea if FireLeaveDetectArea is empty // For backward compatibility, process original DetectArea if FireLeaveDetectArea is empty
if len(data.FireLeaveDetectArea) > 0 { if len(data.FireLeaveDetectArea) > 0 {
@@ -179,6 +309,7 @@ func ProcessDeviceDataForInference(data DeviceData) DeviceData {
// ProcessFireLeaveDataForInference processes device data for fire_leave inference // ProcessFireLeaveDataForInference processes device data for fire_leave inference
func ProcessFireLeaveDataForInference(data DeviceData) DeviceData { func ProcessFireLeaveDataForInference(data DeviceData) DeviceData {
processed := data processed := data
NormalizeDeviceRuntimeFields(&processed)
if len(data.FireLeaveDetectArea) > 0 { if len(data.FireLeaveDetectArea) > 0 {
processed.DetectAreaStr = ProcessDetectAreaForInference(data.FireLeaveDetectArea) processed.DetectAreaStr = ProcessDetectAreaForInference(data.FireLeaveDetectArea)
} }
@@ -188,6 +319,7 @@ func ProcessFireLeaveDataForInference(data DeviceData) DeviceData {
// ProcessFireCheckDataForInference processes device data for fire_check inference // ProcessFireCheckDataForInference processes device data for fire_check inference
func ProcessFireCheckDataForInference(data DeviceData) DeviceData { func ProcessFireCheckDataForInference(data DeviceData) DeviceData {
processed := data processed := data
NormalizeDeviceRuntimeFields(&processed)
if len(data.FireCheckDetectArea) > 0 { if len(data.FireCheckDetectArea) > 0 {
processed.DetectAreaStr = ProcessDetectAreaForInference(data.FireCheckDetectArea) processed.DetectAreaStr = ProcessDetectAreaForInference(data.FireCheckDetectArea)
} }
@@ -380,12 +512,25 @@ func UpdateDeviceData(data DeviceData) error {
// SaveServiceConfig saves multiple device configurations to service config file // SaveServiceConfig saves multiple device configurations to service config file
func SaveServiceConfig(deviceDataList []DeviceData) error { func SaveServiceConfig(deviceDataList []DeviceData) error {
configData, err := json.MarshalIndent(deviceDataList, "", " ") serviceConfigs := make([]ServiceConfig, 0, len(deviceDataList))
for _, deviceData := range deviceDataList {
if deviceData.DeviceUUID == "" {
continue
}
serviceConfigs = append(serviceConfigs, DeviceDataToServiceConfig(deviceData))
}
configData, err := json.MarshalIndent(serviceConfigs, "", " ")
if err != nil { if err != nil {
logger.Logger.Printf("Failed to serialize service configuration: %v", err) logger.Logger.Printf("Failed to serialize service configuration: %v", err)
return err return err
} }
if err := os.MkdirAll(filepath.Dir(serviceConfigFile), 0755); err != nil {
logger.Logger.Printf("Failed to create service config directory: %v", err)
return err
}
if err := os.WriteFile(serviceConfigFile, configData, 0644); err != nil { if err := os.WriteFile(serviceConfigFile, configData, 0644); err != nil {
logger.Logger.Printf("Failed to write service config file: %v", err) logger.Logger.Printf("Failed to write service config file: %v", err)
return err return err
+4 -5
View File
@@ -9,7 +9,6 @@ import (
"time" "time"
"FireLeave_tool/logger" "FireLeave_tool/logger"
"FireLeave_tool/portmanager"
) )
var ( var (
@@ -24,9 +23,9 @@ func UpdateInferenceHeartbeat(deviceUUID string) {
inferenceLastHeartbeat.Store(deviceUUID, time.Now()) inferenceLastHeartbeat.Store(deviceUUID, time.Now())
// 输出心跳日志 // 输出心跳日志
if strings.Contains(deviceUUID, "_fire_check") { 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) logger.Logger.Printf("推理心跳收到 | 时间: %s | UUID: %s | 类型: fire_check", time.Now().Format("2006/01/02 15:04:05.000"), deviceUUID)
} else { } 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"), deviceUUID)
} }
} }
@@ -58,7 +57,7 @@ func StartInferenceProcess(deviceData *DeviceData) {
// 重启fire_leave推理程序 // 重启fire_leave推理程序
StopInferenceProcess(deviceData.DeviceUUID) StopInferenceProcess(deviceData.DeviceUUID)
portFireLeave, err := portmanager.GlobalPortManager.GetPort(deviceData.DeviceUUID) portFireLeave, err := GlobalPortManager.GetPort(deviceData.DeviceUUID)
if err != nil { if err != nil {
logger.Logger.Printf("获取fire_leave端口失败,无法重启推理程序 (DeviceUUID=%s): %v", deviceData.DeviceUUID, err) logger.Logger.Printf("获取fire_leave端口失败,无法重启推理程序 (DeviceUUID=%s): %v", deviceData.DeviceUUID, err)
return return
@@ -67,7 +66,7 @@ func StartInferenceProcess(deviceData *DeviceData) {
// 重启fire_check推理程序 // 重启fire_check推理程序
StopInferenceProcess(fmt.Sprintf("%s_fire_check", deviceData.DeviceUUID)) StopInferenceProcess(fmt.Sprintf("%s_fire_check", deviceData.DeviceUUID))
portFireCheck, err := portmanager.GlobalPortManager.GetPort(fmt.Sprintf("%s_fire_check", deviceData.DeviceUUID)) portFireCheck, err := GlobalPortManager.GetPort(fmt.Sprintf("%s_fire_check", deviceData.DeviceUUID))
if err != nil { if err != nil {
logger.Logger.Printf("获取fire_check端口失败,无法重启推理程序 (DeviceUUID=%s): %v", deviceData.DeviceUUID, err) logger.Logger.Printf("获取fire_check端口失败,无法重启推理程序 (DeviceUUID=%s): %v", deviceData.DeviceUUID, err)
return return
+376 -70
View File
@@ -28,6 +28,12 @@ type PublishParams struct {
Content json.RawMessage `json:"content,omitempty"` Content json.RawMessage `json:"content,omitempty"`
} }
const (
wsFrameRequest byte = 0x01
wsFrameReply byte = 0x02
wsFrameCast byte = 0x03
)
type InferenceHeartbeat struct { type InferenceHeartbeat struct {
DeviceUUID string `json:"device_uuid"` DeviceUUID string `json:"device_uuid"`
PersonCount int `json:"person_count"` PersonCount int `json:"person_count"`
@@ -47,8 +53,6 @@ func (c *WSClient) IsConnected() bool {
return c.connected return c.connected
} }
// DeviceData struct // DeviceData struct
type DeviceData struct { type DeviceData struct {
DeviceUUID string `json:"device_uuid"` DeviceUUID string `json:"device_uuid"`
@@ -100,6 +104,12 @@ type WSError struct {
Message string `json:"message"` Message string `json:"message"`
} }
type ServiceReply struct {
PacketID uint32
Result []byte
Error *WSError
}
type RegisterParams struct { type RegisterParams struct {
ContainerName string `json:"container_name,omitempty"` ContainerName string `json:"container_name,omitempty"`
ServiceID string `json:"service_id,omitempty"` ServiceID string `json:"service_id,omitempty"`
@@ -117,8 +127,8 @@ type WSClient struct {
serverID string serverID string
connected bool connected bool
mutex sync.Mutex mutex sync.Mutex
messageID int64 messageID uint32
callbacks map[int64]chan *WSMessage callbacks map[uint32]chan *ServiceReply
subscribers map[string]func([]byte) subscribers map[string]func([]byte)
closeChan chan struct{} closeChan chan struct{}
reconnectFlag bool reconnectFlag bool
@@ -136,7 +146,7 @@ func NewWSClient(url string) (*WSClient, error) {
url: url, url: url,
connected: true, connected: true,
messageID: 1, messageID: 1,
callbacks: make(map[int64]chan *WSMessage), callbacks: make(map[uint32]chan *ServiceReply),
subscribers: make(map[string]func([]byte)), subscribers: make(map[string]func([]byte)),
closeChan: make(chan struct{}), closeChan: make(chan struct{}),
reconnectFlag: true, reconnectFlag: true,
@@ -243,15 +253,8 @@ func RegisterService(containerName string) (string, error) {
return "", fmt.Errorf("Service registration error: %s", response.Error.Message) return "", fmt.Errorf("Service registration error: %s", response.Error.Message)
} }
// Parse response if string(response.Result) != "ok" {
var resultStr string logger.Logger.Printf("Service registration failed: Expected result 'ok', got '%s'", string(response.Result))
if err := json.Unmarshal(response.Result, &resultStr); err != nil {
logger.Logger.Printf("Failed to parse service registration response: %v, Raw response: %s", err, string(response.Result))
return "", fmt.Errorf("Failed to parse service registration response: %v", err)
}
if resultStr != "ok" {
logger.Logger.Printf("Service registration failed: Expected result 'ok', got '%s'", resultStr)
return "", fmt.Errorf("Service registration failed: Server returned abnormal response") return "", fmt.Errorf("Service registration failed: Server returned abnormal response")
} }
@@ -286,17 +289,58 @@ func SubscribeTopic(topic string, callback func([]byte)) error {
} }
func (c *WSClient) SendRawMessage(message interface{}) error { func (c *WSClient) SendRawMessage(message interface{}) error {
messageData, err := json.Marshal(message)
if err != nil {
logger.Logger.Printf("[WebSocket Send Full JSON] marshal failed: %v", err)
} else {
logger.Logger.Printf("[WebSocket Send Full JSON] %s", string(messageData))
}
if metric, ok := extractMetricData(message); ok {
return c.sendMetricData(metric.routeKey, metric.metric)
}
return fmt.Errorf("unsupported websocket message type for protobuf protocol")
}
type metricDataMessage struct {
routeKey []byte
metric []byte
}
func extractMetricData(message interface{}) (metricDataMessage, bool) {
msg, ok := message.(map[string]interface{})
if !ok || msg["method"] != "metric_data" {
return metricDataMessage{}, false
}
params, ok := msg["params"].(map[string]interface{})
if !ok {
return metricDataMessage{}, false
}
routeKey, ok := params["route_key"].(string)
if !ok || routeKey == "" {
return metricDataMessage{}, false
}
metric, ok := params["metric"].(string)
if !ok {
return metricDataMessage{}, false
}
return metricDataMessage{
routeKey: []byte(routeKey),
metric: []byte(metric),
}, true
}
func (c *WSClient) sendBinaryFrame(frameType byte, payload []byte) error {
c.mutex.Lock() c.mutex.Lock()
defer c.mutex.Unlock() defer c.mutex.Unlock()
if !c.connected { if !c.connected {
return fmt.Errorf("WebSocket connection disconnected") return fmt.Errorf("WebSocket connection disconnected")
} }
messageData, err := json.Marshal(message) frame := make([]byte, 1+len(payload))
if err != nil { frame[0] = frameType
return fmt.Errorf("Message serialization failed: %v", err) copy(frame[1:], payload)
} logger.Logger.Printf("Sending WebSocket binary frame: type=0x%02x, payload=%d bytes", frameType, len(payload))
logger.Logger.Printf("Sending WebSocket message: %s", string(messageData)) err := c.Conn.WriteMessage(websocket.BinaryMessage, frame)
err = c.Conn.WriteMessage(websocket.TextMessage, messageData)
if err != nil { if err != nil {
logger.Logger.Printf("WebSocket message sending failed: %v", err) logger.Logger.Printf("WebSocket message sending failed: %v", err)
c.connected = false c.connected = false
@@ -306,27 +350,38 @@ func (c *WSClient) SendRawMessage(message interface{}) error {
return nil return nil
} }
func (c *WSClient) sendMetricData(routeKey, metric []byte) error {
payload := encodeServiceCastMetricData(routeKey, metric)
return c.sendBinaryFrame(wsFrameCast, payload)
}
// sendRequest sends request // sendRequest sends request
func (c *WSClient) sendRequest(method string, params interface{}, waitResponse bool) (*WSMessage, error) { func (c *WSClient) sendRequest(method string, params interface{}, waitResponse bool) (*ServiceReply, error) {
c.mutex.Lock() c.mutex.Lock()
id := c.messageID id := c.messageID
c.messageID++ c.messageID++
c.mutex.Unlock() c.mutex.Unlock()
message := WSMessage{ var payload []byte
ID: id, switch method {
Method: method, case "register":
registerParams, ok := params.(RegisterParams)
if !ok {
return nil, fmt.Errorf("invalid register params")
} }
if params != nil { payload = encodeServiceRequestRegister(id, registerParams.ServiceID)
paramsData, err := json.Marshal(params) case "subscribe":
if err != nil { subscribeParams, ok := params.(SubscribeParams)
return nil, fmt.Errorf("Parameter serialization failed: %v", err) if !ok {
return nil, fmt.Errorf("invalid subscribe params")
} }
message.Params = paramsData payload = encodeServiceRequestSubscribe(id, subscribeParams.Topic)
default:
return nil, fmt.Errorf("unsupported request method: %s", method)
} }
if waitResponse { if waitResponse {
callback := make(chan *WSMessage, 1) callback := make(chan *ServiceReply, 1)
c.mutex.Lock() c.mutex.Lock()
c.callbacks[id] = callback c.callbacks[id] = callback
c.mutex.Unlock() c.mutex.Unlock()
@@ -338,7 +393,7 @@ func (c *WSClient) sendRequest(method string, params interface{}, waitResponse b
logger.Logger.Printf("Cleaning up callback channel: ID=%d", id) logger.Logger.Printf("Cleaning up callback channel: ID=%d", id)
}() }()
if err := c.SendRawMessage(message); err != nil { if err := c.sendBinaryFrame(wsFrameRequest, payload); err != nil {
return nil, err return nil, err
} }
@@ -355,7 +410,7 @@ func (c *WSClient) sendRequest(method string, params interface{}, waitResponse b
return nil, fmt.Errorf("Connection closed") return nil, fmt.Errorf("Connection closed")
} }
} }
return nil, c.SendRawMessage(message) return nil, c.sendBinaryFrame(wsFrameRequest, payload)
} }
// readLoop message reading loop // readLoop message reading loop
@@ -370,7 +425,7 @@ func (c *WSClient) readLoop() {
}() }()
for { for {
_, messageData, err := c.Conn.ReadMessage() messageType, messageData, err := c.Conn.ReadMessage()
if err != nil { if err != nil {
logger.Logger.Printf("WebSocket message reading failed: %v", err) logger.Logger.Printf("WebSocket message reading failed: %v", err)
c.mutex.Lock() c.mutex.Lock()
@@ -380,34 +435,51 @@ func (c *WSClient) readLoop() {
return return
} }
if messageType != websocket.BinaryMessage {
logger.Logger.Printf("[WebSocket Received Raw] non-binary type=%d size=%d payload=%s", messageType, len(messageData), string(messageData))
continue
}
if len(messageData) == 0 {
logger.Logger.Printf("Ignoring empty WebSocket binary frame")
continue
}
if string(messageData) == "pong" { if string(messageData) == "pong" {
logger.Logger.Printf("Received pong response") logger.Logger.Printf("Received pong response")
continue continue
} }
logger.Logger.Printf("Received WebSocket message: %s", string(messageData)) frameType := messageData[0]
payload := messageData[1:]
logger.Logger.Printf("Received WebSocket binary frame: type=0x%02x, payload=%d bytes", frameType, len(payload))
var message WSMessage switch frameType {
if err := json.Unmarshal(messageData, &message); err != nil { case wsFrameReply:
logger.Logger.Printf("WebSocket message parsing failed: %v", err) reply, err := decodeServiceReply(payload)
if err != nil {
logger.Logger.Printf("WebSocket reply parsing failed: %v", err)
continue continue
} }
logServiceReply(reply)
if message.ID > 0 {
c.mutex.Lock() c.mutex.Lock()
if callback, exists := c.callbacks[message.ID]; exists { if callback, exists := c.callbacks[reply.PacketID]; exists {
select { select {
case callback <- &message: case callback <- reply:
logger.Logger.Printf("Successfully sent message to callback channel: ID=%d", message.ID) logger.Logger.Printf("Successfully sent reply to callback channel: ID=%d", reply.PacketID)
default: default:
logger.Logger.Printf("Callback channel full, discarding message: ID=%d", message.ID) logger.Logger.Printf("Callback channel full, discarding reply: ID=%d", reply.PacketID)
} }
} else {
logger.Logger.Printf("[WebSocket Received Reply] no waiting callback for packet_id=%d", reply.PacketID)
} }
c.mutex.Unlock() c.mutex.Unlock()
case wsFrameCast:
if err := c.handleServiceCast(payload); err != nil {
logger.Logger.Printf("WebSocket cast handling failed: %v", err)
} }
default:
if message.Method == "publish" { logger.Logger.Printf("Ignoring unknown WebSocket frame type: 0x%02x", frameType)
c.handlePublishMessage(message) continue
} }
} }
} }
@@ -433,40 +505,47 @@ func (c *WSClient) handlePublishMessage(message WSMessage) {
} }
if publishParams.Data != nil { if publishParams.Data != nil {
var deviceData DeviceData logger.Logger.Printf("Ignoring publish.data device configuration payload; service.conf is managed by startup config only")
if err := json.Unmarshal(publishParams.Data, &deviceData); err != nil { }
logger.Logger.Printf("Failed to parse DeviceData: %v", err)
return
} }
// Check if DeviceUUID is valid func (c *WSClient) handleServiceCast(payload []byte) error {
if deviceData.DeviceUUID == "" { topic, content, ok, err := decodeServiceCastTopicEvent(payload)
logger.Logger.Printf("Invalid DeviceData: DeviceUUID is empty, ignoring") if err != nil {
return return err
}
if !ok {
logger.Logger.Printf("Ignoring unsupported ServiceCast body")
return nil
} }
logger.Logger.Printf("Received valid DeviceData: DeviceUUID=%s", deviceData.DeviceUUID) logger.Logger.Printf("WebSocket received topic_event: Topic=%s | Content=%s", topic, string(content))
c.mutex.Lock()
// 1. Save original configuration to service config file (using SaveSingleServiceConfig) callback, exists := c.subscribers[topic]
if err := SaveSingleServiceConfig(deviceData); err != nil { c.mutex.Unlock()
logger.Logger.Printf("Failed to save service configuration: %v", err) if !exists || callback == nil {
logger.Logger.Printf("No subscriber for topic_event: Topic=%s", topic)
return nil
} }
// 2. Process data and save to device data file wrapped, err := wrapTopicEvent(topic, content)
processedData := ProcessDeviceDataForInference(deviceData) if err != nil {
if err := UpdateDeviceData(processedData); err != nil { return err
logger.Logger.Printf("Failed to update DeviceData: %v", err) }
return callback(wrapped)
return nil
} }
select { func wrapTopicEvent(topic string, content []byte) ([]byte, error) {
case ConfigUpdateChan <- processedData: msg := map[string]interface{}{
logger.Logger.Printf("New DeviceData sent to config update channel: DeviceUUID=%s", processedData.DeviceUUID) "topic": topic,
default:
logger.Logger.Printf("Config update channel full, discarding message")
} }
if json.Valid(content) {
msg["content"] = json.RawMessage(content)
} else {
msg["content"] = string(content)
} }
return json.Marshal(msg)
} }
// heartbeatLoop heartbeat loop - fixed version // heartbeatLoop heartbeat loop - fixed version
@@ -575,11 +654,238 @@ func (d *DeviceData) ToInferenceParams(port int) InferenceParams {
} }
} }
func appendVarint(dst []byte, v uint64) []byte {
for v >= 0x80 {
dst = append(dst, byte(v)|0x80)
v >>= 7
}
return append(dst, byte(v))
}
func appendBytesField(dst []byte, fieldNumber int, value []byte) []byte {
dst = appendVarint(dst, uint64(fieldNumber<<3|2))
dst = appendVarint(dst, uint64(len(value)))
return append(dst, value...)
}
func appendVarintField(dst []byte, fieldNumber int, value uint64) []byte {
dst = appendVarint(dst, uint64(fieldNumber<<3))
return appendVarint(dst, value)
}
func encodeServiceRequestRegister(packetID uint32, serviceID string) []byte {
register := appendBytesField(nil, 1, []byte(serviceID))
payload := appendVarintField(nil, 1, uint64(packetID))
payload = appendBytesField(payload, 10, register)
return payload
}
func encodeServiceRequestSubscribe(packetID uint32, topic string) []byte {
subscribe := appendBytesField(nil, 1, []byte(topic))
payload := appendVarintField(nil, 1, uint64(packetID))
payload = appendBytesField(payload, 11, subscribe)
return payload
}
func encodeServiceCastMetricData(routeKey, metric []byte) []byte {
metricData := appendBytesField(nil, 1, routeKey)
metricData = appendBytesField(metricData, 2, metric)
return appendBytesField(nil, 11, metricData)
}
func consumeVarint(data []byte, pos *int) (uint64, error) {
var value uint64
var shift uint
for *pos < len(data) {
b := data[*pos]
*pos += 1
value |= uint64(b&0x7f) << shift
if b < 0x80 {
return value, nil
}
shift += 7
if shift >= 64 {
return 0, fmt.Errorf("protobuf varint overflow")
}
}
return 0, fmt.Errorf("unexpected end of protobuf varint")
}
func consumeBytes(data []byte, pos *int) ([]byte, error) {
length, err := consumeVarint(data, pos)
if err != nil {
return nil, err
}
if length > uint64(len(data)-*pos) {
return nil, fmt.Errorf("protobuf bytes field length out of range")
}
value := data[*pos : *pos+int(length)]
*pos += int(length)
return value, nil
}
func skipField(data []byte, pos *int, wireType int) error {
switch wireType {
case 0:
_, err := consumeVarint(data, pos)
return err
case 2:
_, err := consumeBytes(data, pos)
return err
default:
return fmt.Errorf("unsupported protobuf wire type: %d", wireType)
}
}
func decodeServiceReply(payload []byte) (*ServiceReply, error) {
reply := &ServiceReply{}
for pos := 0; pos < len(payload); {
key, err := consumeVarint(payload, &pos)
if err != nil {
return nil, err
}
fieldNumber := int(key >> 3)
wireType := int(key & 0x7)
switch fieldNumber {
case 1:
if wireType != 0 {
return nil, fmt.Errorf("invalid packet_id wire type: %d", wireType)
}
packetID, err := consumeVarint(payload, &pos)
if err != nil {
return nil, err
}
reply.PacketID = uint32(packetID)
case 10:
if wireType != 2 {
return nil, fmt.Errorf("invalid result wire type: %d", wireType)
}
result, err := consumeBytes(payload, &pos)
if err != nil {
return nil, err
}
reply.Result = result
case 11:
if wireType != 2 {
return nil, fmt.Errorf("invalid error wire type: %d", wireType)
}
errorPayload, err := consumeBytes(payload, &pos)
if err != nil {
return nil, err
}
reply.Error = decodeServiceReplyError(errorPayload)
default:
if err := skipField(payload, &pos, wireType); err != nil {
return nil, err
}
}
}
return reply, nil
}
func logServiceReply(reply *ServiceReply) {
if reply == nil {
logger.Logger.Printf("[WebSocket Received Reply] <nil>")
return
}
errorJSON := "null"
if reply.Error != nil {
if data, err := json.Marshal(reply.Error); err == nil {
errorJSON = string(data)
} else {
errorJSON = fmt.Sprintf(`{"marshal_error":%q}`, err.Error())
}
}
logger.Logger.Printf("[WebSocket Received Reply] packet_id=%d result=%s error=%s", reply.PacketID, string(reply.Result), errorJSON)
}
func decodeServiceReplyError(payload []byte) *WSError {
wsErr := &WSError{}
for pos := 0; pos < len(payload); {
key, err := consumeVarint(payload, &pos)
if err != nil {
return wsErr
}
fieldNumber := int(key >> 3)
wireType := int(key & 0x7)
switch fieldNumber {
case 1:
code, err := consumeVarint(payload, &pos)
if err != nil {
return wsErr
}
wsErr.Code = int(int32(code))
case 2:
msg, err := consumeBytes(payload, &pos)
if err != nil {
return wsErr
}
wsErr.Message = string(msg)
default:
_ = skipField(payload, &pos, wireType)
}
}
return wsErr
}
func decodeServiceCastTopicEvent(payload []byte) (string, []byte, bool, error) {
for pos := 0; pos < len(payload); {
key, err := consumeVarint(payload, &pos)
if err != nil {
return "", nil, false, err
}
fieldNumber := int(key >> 3)
wireType := int(key & 0x7)
if fieldNumber != 10 {
if err := skipField(payload, &pos, wireType); err != nil {
return "", nil, false, err
}
continue
}
if wireType != 2 {
return "", nil, false, fmt.Errorf("invalid topic_event wire type: %d", wireType)
}
topicPayload, err := consumeBytes(payload, &pos)
if err != nil {
return "", nil, false, err
}
topic, content, err := decodeTopicEvent(topicPayload)
return topic, content, true, err
}
return "", nil, false, nil
}
func decodeTopicEvent(payload []byte) (string, []byte, error) {
var topic string
var content []byte
for pos := 0; pos < len(payload); {
key, err := consumeVarint(payload, &pos)
if err != nil {
return "", nil, err
}
fieldNumber := int(key >> 3)
wireType := int(key & 0x7)
switch fieldNumber {
case 1:
value, err := consumeBytes(payload, &pos)
if err != nil {
return "", nil, err
}
topic = string(value)
case 2:
value, err := consumeBytes(payload, &pos)
if err != nil {
return "", nil, err
}
content = value
default:
if err := skipField(payload, &pos, wireType); err != nil {
return "", nil, err
}
}
}
return topic, content, nil
}
func (c *WSClient) SendAsync(message interface{}) { func (c *WSClient) SendAsync(message interface{}) {
go func() { go func() {
+180
View File
@@ -0,0 +1,180 @@
# Current Change Summary
Date: 2026-05-13
## 1. Configuration Ownership
The startup device configuration is owned by:
```text
/usr/local/etc/service.conf
```
This file is the Docker startup configuration source. The service reads it at startup and uses it to initialize devices, inference processes, detection areas, and runtime parameters.
WebSocket messages must not update this file.
## 2. service.conf Schema
`service.conf` is written as an array of `ServiceConfig` objects:
```json
[
{
"host_uuid": "",
"device_uuid": "",
"address": "",
"task_id": "",
"camera_rtsp": "",
"camera_ip": "",
"confidence": 70,
"detect_area": {
"fire_leave": [],
"fire_check": []
},
"param": {
"fire_check": {
"confidence": "70",
"detection_time": "2"
},
"fire_leave": {
"person_count": "0",
"confidence": "70",
"detection_time": "20",
"temperature_threshold": "45",
"alarm_time": "300"
}
}
}
]
```
The service still tolerates the previous `DeviceData[]` cache shape when reading `service.conf`, but writes back only the canonical `ServiceConfig[]` shape.
## 3. service.conf Write Boundary
The remaining write functions are:
- `connect.SaveServiceConfig()`
- `connect.SaveSingleServiceConfig()`
They write to:
```text
/usr/local/etc/service.conf
```
The old WebSocket path that wrote `publish.params.data` into `service.conf` is now disabled. If a WebSocket publish message contains `data`, the service logs and ignores it.
## 4. Runtime Device State
Runtime state is stored under:
```text
/data/devices/device_{device_uuid}.json
```
This includes operational values such as current temperature, current person count, and alarm cooldown state.
This is separate from `service.conf`.
## 5. WebSocket Topic Responsibilities
The service currently subscribes to:
```text
/dhlr/alert
/dhlr/forbid_time
```
### /dhlr/alert
This topic controls the local sound/light alarm.
Handled by:
```text
connect.HandleBuzzerAlert()
```
It reads:
- `type`
- `buzzer_duration`
It triggers GPIO109. It does not write `service.conf`.
### /dhlr/forbid_time
This topic controls the `fire_leave` reporting pause window.
It reads:
- `minute`
It does not write `service.conf`.
It writes the pause configuration to:
```text
/data/cache/forbid_time_config.json
```
## 6. forbid_time Persistence
`forbid_time_config.json` now stores:
```json
{
"minute": "15",
"start_time": 1770000000,
"end_time": 1770000900
}
```
Meaning:
- `minute`: configured pause duration
- `start_time`: Unix timestamp when the current pause window started
- `end_time`: Unix timestamp when the current pause window ends
If Docker restarts while a pause window is still active, startup restores the pause window from `end_time`.
If `end_time` is expired, no pause is restored.
If `minute` is `0`, the pause window is cleared and `end_time` is written as `0`.
## 7. fire_leave Pause Behavior
During an active pause window:
- person detection continues
- temperature reading continues
- `fire_leave` reporting is skipped
- `fire_check` reporting is not affected
Default pause duration is:
```text
15 minutes
```
## 8. Port Manager Fix
Inference startup and heartbeat restart now use the same port manager:
```text
connect.GlobalPortManager
```
This avoids restart failures caused by using a separate `portmanager.GlobalPortManager` state.
## 9. Known Remaining Risks
1. The protobuf WebSocket protocol migration is not implemented because this repository does not contain `service.proto` or generated Go protobuf types.
2. The `reporter` package still contains old event-cycle functions. GPIO no longer calls them, but the old reporter upload paths still reference event-cycle behavior. This should be cleaned after confirming no external dependency remains.
3. `fire_check` heartbeat restart currently calls a shared restart function that restarts both `fire_leave` and `fire_check`. This is functional but broader than necessary.
4. The local environment currently does not expose `go` or `gofmt`, so compilation and formatting have not been executed in this workspace.
BIN
View File
Binary file not shown.
+9 -19
View File
@@ -8,7 +8,6 @@ import (
"time" "time"
"FireLeave_tool/logger" "FireLeave_tool/logger"
"FireLeave_tool/reporter"
) )
const ( const (
@@ -27,6 +26,7 @@ type GPIOButtonMonitor struct {
lastAlertTime time.Time lastAlertTime time.Time
cooldownTime time.Duration cooldownTime time.Duration
cooldownActive bool cooldownActive bool
onPress func()
} }
// NewGPIOButtonMonitor 创建GPIO按钮监视器 // NewGPIOButtonMonitor 创建GPIO按钮监视器
@@ -42,6 +42,10 @@ func NewGPIOButtonMonitor(gpioPin int) *GPIOButtonMonitor {
} }
} }
func (m *GPIOButtonMonitor) SetOnPress(handler func()) {
m.onPress = handler
}
// InitGPIO 初始化GPIO // InitGPIO 初始化GPIO
func (m *GPIOButtonMonitor) InitGPIO() error { func (m *GPIOButtonMonitor) InitGPIO() error {
gpioPath := fmt.Sprintf("/sys/class/gpio/gpio%d", m.gpioPin) gpioPath := fmt.Sprintf("/sys/class/gpio/gpio%d", m.gpioPin)
@@ -182,28 +186,14 @@ func (m *GPIOButtonMonitor) Start(ctx context.Context) error {
if time.Since(m.lastAlertTime) >= m.cooldownTime { if time.Since(m.lastAlertTime) >= m.cooldownTime {
// 检测到稳定的高电平,切换事件周期 // 检测到稳定的高电平,切换事件周期
if log := logger.GlobalLoggerManager.GetLogger("gpio"); log != nil { if log := logger.GlobalLoggerManager.GetLogger("gpio"); log != nil {
log.Printf("GPIO %d detected stable high level after debounce, switching event cycle...", m.gpioPin) log.Printf("GPIO %d detected stable high level after debounce, triggering fire_leave pause...", m.gpioPin)
} else if log := logger.GlobalLoggerManager.GetLogger("reporter"); log != nil { } else if log := logger.GlobalLoggerManager.GetLogger("reporter"); log != nil {
log.Printf("GPIO %d detected stable high level after debounce, switching event cycle...", m.gpioPin) log.Printf("GPIO %d detected stable high level after debounce, triggering fire_leave pause...", m.gpioPin)
} }
// 切换事件周期 // 切换事件周期
var targetCycle reporter.EventCycle if m.onPress != nil {
switch reporter.GetCurrentEventCycle() { m.onPress()
case reporter.EventCycleFireLeave:
targetCycle = reporter.EventCycleFireCheck
case reporter.EventCycleFireCheck:
targetCycle = reporter.EventCycleFireLeave
default:
targetCycle = reporter.EventCycleFireCheck
}
if err := reporter.SwitchEventCycle(targetCycle); err != nil {
if log := logger.GlobalLoggerManager.GetLogger("gpio"); log != nil {
log.Printf("Failed to switch event cycle: %v", err)
} else if log := logger.GlobalLoggerManager.GetLogger("reporter"); log != nil {
log.Printf("Failed to switch event cycle: %v", err)
}
} }
// 更新lastAlertTime,开始冷却期 // 更新lastAlertTime,开始冷却期
+1 -1
View File
@@ -116,7 +116,7 @@ func (is *InferenceServer) handleVideoPost(w http.ResponseWriter, r *http.Reques
return return
} }
now := time.Now().Format("2006/01/02 15:04:05.000") now := time.Now().Format("2006/01/02 15:04:05.000")
logger.Logger.Printf("推理心跳收到 | 时间: %s | UUID: %s | Serial: %s | 人数量: %d", logger.Logger.Printf("推理心跳收到 | 时间: %s | UUID: %s | Serial: %s | 人数量: %d",
now, is.deviceUUID, result.Serial, getPersonCount(result)) now, is.deviceUUID, result.Serial, getPersonCount(result))
// 这一行就是你缺失的救命稻草! // 这一行就是你缺失的救命稻草!
+1 -1
View File
@@ -9,7 +9,7 @@ import (
) )
// Logger 全局日志实例 // Logger 全局日志实例
var Logger *log.Logger var Logger = log.New(os.Stdout, "", log.LstdFlags)
var logFile *os.File var logFile *os.File
var logFileName string var logFileName string
var mu sync.Mutex var mu sync.Mutex
+87 -9
View File
@@ -30,6 +30,7 @@ type deviceAlarmState struct {
InCondition bool InCondition bool
StartTime int64 StartTime int64
AlarmPauseUntil int64 AlarmPauseUntil int64
FireLeaveForbidUntil int64
// fire_check火焰检测状态 // fire_check火焰检测状态
FireCheckInCondition bool FireCheckInCondition bool
FireCheckStartTime int64 FireCheckStartTime int64
@@ -42,6 +43,9 @@ var mergingAlarms sync.Map
// 全局上报管理器 // 全局上报管理器
var GlobalReporter = reporter.NewReporter() var GlobalReporter = reporter.NewReporter()
const defaultFireLeaveForbidDuration = 15 * time.Minute
const buildVersion = "2026-05-13-logger-init-fix"
// DeviceManager 设备管理器 // DeviceManager 设备管理器
type DeviceManager struct { type DeviceManager struct {
mu sync.Mutex mu sync.Mutex
@@ -63,6 +67,71 @@ func getDeviceState(deviceUUID string) *deviceAlarmState {
return 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)
}
// AddDevice 添加设备 // AddDevice 添加设备
func (dm *DeviceManager) AddDevice(deviceData connect.DeviceData) { func (dm *DeviceManager) AddDevice(deviceData connect.DeviceData) {
dm.mu.Lock() dm.mu.Lock()
@@ -196,6 +265,7 @@ func processDevice(ctx context.Context, deviceData connect.DeviceData) {
// 首先将设备添加到设备管理器 // 首先将设备添加到设备管理器
GlobalDeviceManager.AddDevice(deviceData) GlobalDeviceManager.AddDevice(deviceData)
restoreFireLeaveForbidWindow(deviceData.DeviceUUID)
defer GlobalDeviceManager.StopDevice(deviceData.DeviceUUID) defer GlobalDeviceManager.StopDevice(deviceData.DeviceUUID)
// 创建带取消的子上下文 // 创建带取消的子上下文
@@ -588,6 +658,15 @@ func processDevice(ctx context.Context, deviceData connect.DeviceData) {
conditionMet := noPerson && tempStableHigh 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
}
// 1. 冷却期检查 // 1. 冷却期检查
if now < state.AlarmPauseUntil { if now < state.AlarmPauseUntil {
if state.InCondition { if state.InCondition {
@@ -775,6 +854,7 @@ func (dm *DeviceManager) RangeDevices(fn func(uuid string, device *connect.Devic
} }
} }
func main() { func main() {
fmt.Printf("FireLeave Tool build: %s\n", buildVersion)
var rtspURL string var rtspURL string
var deviceUUID string var deviceUUID string
@@ -842,6 +922,11 @@ func main() {
go func() { go func() {
// 创建GPIO按钮监视器 // 创建GPIO按钮监视器
monitor := gpio_monitor.NewGPIOButtonMonitor(gpio_monitor.ButtonGPIO) monitor := gpio_monitor.NewGPIOButtonMonitor(gpio_monitor.ButtonGPIO)
monitor.SetOnPress(func() {
minute := reporter.GetForbidTimeConfig()
duration := reporter.ParseForbidDuration(minute, defaultFireLeaveForbidDuration)
persistAndApplyFireLeaveForbidWindow(minute, duration)
})
// 启动GPIO监控 // 启动GPIO监控
if err := monitor.Start(ctx); err != nil { if err := monitor.Start(ctx); err != nil {
logger.Logger.Printf("Failed to start GPIO button monitor: %v", err) logger.Logger.Printf("Failed to start GPIO button monitor: %v", err)
@@ -908,8 +993,7 @@ func printGlobalDeviceStatus() {
i := 1 i := 1
for uuid, data := range activeDevices { for uuid, data := range activeDevices {
stateInf, _ := deviceStates.Load(uuid) state := getDeviceState(uuid)
state := stateInf.(*deviceAlarmState)
cooldownLeft := int64(0) cooldownLeft := int64(0)
if now < state.AlarmPauseUntil { if now < state.AlarmPauseUntil {
cooldownLeft = state.AlarmPauseUntil - now cooldownLeft = state.AlarmPauseUntil - now
@@ -998,13 +1082,7 @@ func handleForbidTimeMessage(message []byte) {
// 处理禁止时间配置 // 处理禁止时间配置
if content.Minute != "" { if content.Minute != "" {
logger.Logger.Printf("Forbid time config received: Minute=%s", content.Minute) logger.Logger.Printf("Forbid time config received: Minute=%s", content.Minute)
persistAndApplyFireLeaveForbidWindow(content.Minute, reporter.ParseForbidDuration(content.Minute, 0))
// 保存到缓存文件
if err := reporter.SaveForbidTimeConfig(content.Minute); err != nil {
logger.Logger.Printf("Failed to save forbid time config: %v", err)
} else {
logger.Logger.Printf("Forbid time config saved successfully: Minute=%s", content.Minute)
}
} }
// 处理事件周期时限配置 // 处理事件周期时限配置
+93 -27
View File
@@ -165,12 +165,23 @@ var (
ForbidTimeFile = "/data/cache/forbid_time_config.json" ForbidTimeFile = "/data/cache/forbid_time_config.json"
) )
type ForbidTimeConfig struct {
Minute string `json:"minute"`
Minutr string `json:"minutr,omitempty"`
StartTime int64 `json:"start_time"`
EndTime int64 `json:"end_time"`
}
// SaveForbidTimeConfig 保存禁止时间配置 // SaveForbidTimeConfig 保存禁止时间配置
func SaveForbidTimeConfig(minutr string) error { func SaveForbidTimeConfig(minute string) error {
config := struct { return SaveForbidTimeWindow(minute, 0, 0)
Minutr string `json:"minutr"` }
}{
Minutr: minutr, func SaveForbidTimeWindow(minute string, startTime, endTime int64) error {
config := ForbidTimeConfig{
Minute: minute,
StartTime: startTime,
EndTime: endTime,
} }
data, err := json.Marshal(config) data, err := json.Marshal(config)
@@ -186,6 +197,22 @@ func SaveForbidTimeConfig(minutr string) error {
return os.WriteFile(ForbidTimeFile, data, 0644) return os.WriteFile(ForbidTimeFile, data, 0644)
} }
func LoadForbidTimeConfig() (ForbidTimeConfig, error) {
data, err := os.ReadFile(ForbidTimeFile)
if err != nil {
return ForbidTimeConfig{}, err
}
var config ForbidTimeConfig
if err := json.Unmarshal(data, &config); err != nil {
return ForbidTimeConfig{}, err
}
if config.Minute == "" {
config.Minute = config.Minutr
}
return config, nil
}
// SaveEventCycleTimeoutConfig 保存事件周期时限配置 // SaveEventCycleTimeoutConfig 保存事件周期时限配置
func SaveEventCycleTimeoutConfig(fireLeaveTimeout, fireCheckTimeout time.Duration) error { func SaveEventCycleTimeoutConfig(fireLeaveTimeout, fireCheckTimeout time.Duration) error {
config := CycleTimeoutConfig{ config := CycleTimeoutConfig{
@@ -208,25 +235,36 @@ func SaveEventCycleTimeoutConfig(fireLeaveTimeout, fireCheckTimeout time.Duratio
// GetForbidTimeConfig 获取禁止时间配置 // GetForbidTimeConfig 获取禁止时间配置
func GetForbidTimeConfig() string { func GetForbidTimeConfig() string {
// 读取缓存文件 config, err := LoadForbidTimeConfig()
data, err := os.ReadFile(ForbidTimeFile)
if err != nil { if err != nil {
// 缓存中没有值时,默认返回0 // 缓存中没有值时,默认返回0
return "0" return "0"
} }
var config struct { if config.Minute != "" {
Minutr string `json:"minutr"` return config.Minute
} }
if err := json.Unmarshal(data, &config); err != nil {
// 解析失败时,默认返回0
return "0"
}
return config.Minutr return config.Minutr
} }
func GetForbidDuration(defaultDuration time.Duration) time.Duration {
return ParseForbidDuration(GetForbidTimeConfig(), defaultDuration)
}
func ParseForbidDuration(value string, defaultDuration time.Duration) time.Duration {
if value == "" || value == "0" {
return defaultDuration
}
if duration, err := time.ParseDuration(value); err == nil && duration > 0 {
return duration
}
var minutes int
if _, err := fmt.Sscanf(value, "%d", &minutes); err == nil && minutes > 0 {
return time.Duration(minutes) * time.Minute
}
return defaultDuration
}
// ShouldReportBothEvents 判断是否应该同时上报两种事件 // ShouldReportBothEvents 判断是否应该同时上报两种事件
func ShouldReportBothEvents() bool { func ShouldReportBothEvents() bool {
// 注意:此逻辑已被修改,现在只在特定情况下使用 // 注意:此逻辑已被修改,现在只在特定情况下使用
@@ -246,6 +284,29 @@ func jsonString(v interface{}) string {
return string(b) return string(b)
} }
func logJSON(logger *log.Logger, label string, v interface{}) {
if logger == nil {
return
}
b, err := json.Marshal(v)
if err != nil {
logger.Printf("%s JSON编码失败: %v", label, err)
return
}
logger.Printf("%s %s", label, string(b))
}
func cleanupMergedFileAfterUpload(logger *log.Logger, mergedFile, deviceUUID string, uploadFailed bool) {
if uploadFailed {
logger.Printf("Nginx上传失败,保留临时文件用于排查: %s (DeviceUUID=%s)", mergedFile, deviceUUID)
return
}
logger.Printf("清理临时文件: %s (DeviceUUID=%s)", mergedFile, deviceUUID)
if err := os.Remove(mergedFile); err != nil {
logger.Printf("清理临时文件失败: %v (DeviceUUID=%s, 文件=%s)", err, deviceUUID, mergedFile)
}
}
// UploadVideoAndReport 上传视频并上报事件 // UploadVideoAndReport 上传视频并上报事件
func (r *Reporter) UploadVideoAndReport(deviceData connect.DeviceData, alarmTime int64, mergedFile string) error { func (r *Reporter) UploadVideoAndReport(deviceData connect.DeviceData, alarmTime int64, mergedFile string) error {
// 异步执行视频上传和上报,避免阻塞主线程 // 异步执行视频上传和上报,避免阻塞主线程
@@ -321,7 +382,9 @@ func (r *Reporter) UploadVideoAndReport(deviceData connect.DeviceData, alarmTime
logger.Printf("开始上传视频到Nginx (DeviceUUID=%s, 文件=%s, URL=%s)", deviceData.DeviceUUID, mergedFile, deviceData.UploadURL) logger.Printf("开始上传视频到Nginx (DeviceUUID=%s, 文件=%s, URL=%s)", deviceData.DeviceUUID, mergedFile, deviceData.UploadURL)
videoURL, err := video_server.UploadToNginx(mergedFile, nginxParams, deviceData.UploadURL) videoURL, err := video_server.UploadToNginx(mergedFile, nginxParams, deviceData.UploadURL)
videoURLStr := "" videoURLStr := ""
uploadFailed := false
if err != nil { if err != nil {
uploadFailed = true
logger.Printf("Nginx上传失败: %v (DeviceUUID=%s)", err, deviceData.DeviceUUID) logger.Printf("Nginx上传失败: %v (DeviceUUID=%s)", err, deviceData.DeviceUUID)
} else { } else {
videoURLStr = videoURL videoURLStr = videoURL
@@ -336,14 +399,14 @@ func (r *Reporter) UploadVideoAndReport(deviceData connect.DeviceData, alarmTime
r.wsClient = connect.GetWSClient() r.wsClient = connect.GetWSClient()
if r.wsClient == nil { if r.wsClient == nil {
logger.Printf("WebSocket客户端仍然为nil,无法上报事件 (DeviceUUID=%s)", deviceData.DeviceUUID) logger.Printf("WebSocket客户端仍然为nil,无法上报事件 (DeviceUUID=%s)", deviceData.DeviceUUID)
os.Remove(mergedFile) cleanupMergedFileAfterUpload(logger, mergedFile, deviceData.DeviceUUID, uploadFailed)
return return
} }
} }
if !r.wsClient.IsConnected() { if !r.wsClient.IsConnected() {
logger.Printf("WS未就绪,延迟上报 (DeviceUUID=%s)", deviceData.DeviceUUID) logger.Printf("WS未就绪,延迟上报 (DeviceUUID=%s)", deviceData.DeviceUUID)
os.Remove(mergedFile) cleanupMergedFileAfterUpload(logger, mergedFile, deviceData.DeviceUUID, uploadFailed)
return return
} }
logger.Printf("WebSocket客户端连接状态正常 (DeviceUUID=%s)", deviceData.DeviceUUID) logger.Printf("WebSocket客户端连接状态正常 (DeviceUUID=%s)", deviceData.DeviceUUID)
@@ -365,8 +428,7 @@ func (r *Reporter) UploadVideoAndReport(deviceData connect.DeviceData, alarmTime
} }
// 清理临时文件 // 清理临时文件
logger.Printf("清理临时文件: %s (DeviceUUID=%s)", mergedFile, deviceData.DeviceUUID) cleanupMergedFileAfterUpload(logger, mergedFile, deviceData.DeviceUUID, uploadFailed)
os.Remove(mergedFile)
logger.Printf("视频上传和事件上报完成 (DeviceUUID=%s)", deviceData.DeviceUUID) logger.Printf("视频上传和事件上报完成 (DeviceUUID=%s)", deviceData.DeviceUUID)
}() }()
@@ -405,7 +467,9 @@ func (r *Reporter) UploadVideoAndReportFireLeave(deviceData connect.DeviceData,
logger.Printf("开始上传视频到Nginx (DeviceUUID=%s, 文件=%s, URL=%s)", deviceData.DeviceUUID, mergedFile, deviceData.UploadURL) logger.Printf("开始上传视频到Nginx (DeviceUUID=%s, 文件=%s, URL=%s)", deviceData.DeviceUUID, mergedFile, deviceData.UploadURL)
videoURL, err := video_server.UploadToNginx(mergedFile, nginxParams, deviceData.UploadURL) videoURL, err := video_server.UploadToNginx(mergedFile, nginxParams, deviceData.UploadURL)
videoURLStr := "" videoURLStr := ""
uploadFailed := false
if err != nil { if err != nil {
uploadFailed = true
logger.Printf("Nginx上传失败: %v (DeviceUUID=%s)", err, deviceData.DeviceUUID) logger.Printf("Nginx上传失败: %v (DeviceUUID=%s)", err, deviceData.DeviceUUID)
} else { } else {
videoURLStr = videoURL videoURLStr = videoURL
@@ -420,14 +484,14 @@ func (r *Reporter) UploadVideoAndReportFireLeave(deviceData connect.DeviceData,
r.wsClient = connect.GetWSClient() r.wsClient = connect.GetWSClient()
if r.wsClient == nil { if r.wsClient == nil {
logger.Printf("WebSocket客户端仍然为nil,无法上报事件 (DeviceUUID=%s)", deviceData.DeviceUUID) logger.Printf("WebSocket客户端仍然为nil,无法上报事件 (DeviceUUID=%s)", deviceData.DeviceUUID)
os.Remove(mergedFile) cleanupMergedFileAfterUpload(logger, mergedFile, deviceData.DeviceUUID, uploadFailed)
return return
} }
} }
if !r.wsClient.IsConnected() { if !r.wsClient.IsConnected() {
logger.Printf("WS未就绪,延迟上报 (DeviceUUID=%s)", deviceData.DeviceUUID) logger.Printf("WS未就绪,延迟上报 (DeviceUUID=%s)", deviceData.DeviceUUID)
os.Remove(mergedFile) cleanupMergedFileAfterUpload(logger, mergedFile, deviceData.DeviceUUID, uploadFailed)
return return
} }
logger.Printf("WebSocket客户端连接状态正常 (DeviceUUID=%s)", deviceData.DeviceUUID) logger.Printf("WebSocket客户端连接状态正常 (DeviceUUID=%s)", deviceData.DeviceUUID)
@@ -449,8 +513,7 @@ func (r *Reporter) UploadVideoAndReportFireLeave(deviceData connect.DeviceData,
} }
// 清理临时文件 // 清理临时文件
logger.Printf("清理临时文件: %s (DeviceUUID=%s)", mergedFile, deviceData.DeviceUUID) cleanupMergedFileAfterUpload(logger, mergedFile, deviceData.DeviceUUID, uploadFailed)
os.Remove(mergedFile)
logger.Printf("温度报警视频上传和事件上报完成 (DeviceUUID=%s)", deviceData.DeviceUUID) logger.Printf("温度报警视频上传和事件上报完成 (DeviceUUID=%s)", deviceData.DeviceUUID)
}() }()
@@ -489,7 +552,9 @@ func (r *Reporter) UploadVideoAndReportFireCheck(deviceData connect.DeviceData,
logger.Printf("开始上传视频到Nginx (DeviceUUID=%s, 文件=%s, URL=%s)", deviceData.DeviceUUID, mergedFile, deviceData.UploadURL) logger.Printf("开始上传视频到Nginx (DeviceUUID=%s, 文件=%s, URL=%s)", deviceData.DeviceUUID, mergedFile, deviceData.UploadURL)
videoURL, err := video_server.UploadToNginx(mergedFile, nginxParams, deviceData.UploadURL) videoURL, err := video_server.UploadToNginx(mergedFile, nginxParams, deviceData.UploadURL)
videoURLStr := "" videoURLStr := ""
uploadFailed := false
if err != nil { if err != nil {
uploadFailed = true
logger.Printf("Nginx上传失败: %v (DeviceUUID=%s)", err, deviceData.DeviceUUID) logger.Printf("Nginx上传失败: %v (DeviceUUID=%s)", err, deviceData.DeviceUUID)
} else { } else {
videoURLStr = videoURL videoURLStr = videoURL
@@ -504,14 +569,14 @@ func (r *Reporter) UploadVideoAndReportFireCheck(deviceData connect.DeviceData,
r.wsClient = connect.GetWSClient() r.wsClient = connect.GetWSClient()
if r.wsClient == nil { if r.wsClient == nil {
logger.Printf("WebSocket客户端仍然为nil,无法上报事件 (DeviceUUID=%s)", deviceData.DeviceUUID) logger.Printf("WebSocket客户端仍然为nil,无法上报事件 (DeviceUUID=%s)", deviceData.DeviceUUID)
os.Remove(mergedFile) cleanupMergedFileAfterUpload(logger, mergedFile, deviceData.DeviceUUID, uploadFailed)
return return
} }
} }
if !r.wsClient.IsConnected() { if !r.wsClient.IsConnected() {
logger.Printf("WS未就绪,延迟上报 (DeviceUUID=%s)", deviceData.DeviceUUID) logger.Printf("WS未就绪,延迟上报 (DeviceUUID=%s)", deviceData.DeviceUUID)
os.Remove(mergedFile) cleanupMergedFileAfterUpload(logger, mergedFile, deviceData.DeviceUUID, uploadFailed)
return return
} }
logger.Printf("WebSocket客户端连接状态正常 (DeviceUUID=%s)", deviceData.DeviceUUID) logger.Printf("WebSocket客户端连接状态正常 (DeviceUUID=%s)", deviceData.DeviceUUID)
@@ -533,8 +598,7 @@ func (r *Reporter) UploadVideoAndReportFireCheck(deviceData connect.DeviceData,
} }
// 清理临时文件 // 清理临时文件
logger.Printf("清理临时文件: %s (DeviceUUID=%s)", mergedFile, deviceData.DeviceUUID) cleanupMergedFileAfterUpload(logger, mergedFile, deviceData.DeviceUUID, uploadFailed)
os.Remove(mergedFile)
logger.Printf("火焰检测报警视频上传和事件上报完成 (DeviceUUID=%s)", deviceData.DeviceUUID) logger.Printf("火焰检测报警视频上传和事件上报完成 (DeviceUUID=%s)", deviceData.DeviceUUID)
}() }()
@@ -729,6 +793,8 @@ func (r *Reporter) GenerateNginxParams(deviceData connect.DeviceData, alarmTime
params["temperature"] = deviceData.Temperature params["temperature"] = deviceData.Temperature
} }
logJSON(r.getLogger(), fmt.Sprintf("[Nginx Upload Params JSON] DeviceUUID=%s Level=%s", deviceData.DeviceUUID, level), params)
return params return params
} }
+133
View File
@@ -0,0 +1,133 @@
#!/bin/sh
set -eu
SYSTEM_DIR="${1:-/data/logs/system}"
REPORTER_DIR="${2:-/data/logs/reporter}"
LINES="${LINES:-40}"
DEVICE_UUID="${DEVICE_UUID:-}"
print_usage() {
cat <<'USAGE'
用法:
sh scripts/check_logs.sh [系统日志目录] [上报日志目录]
示例:
sh scripts/check_logs.sh
sh scripts/check_logs.sh /data/logs/system /data/logs/reporter
LINES=80 sh scripts/check_logs.sh
DEVICE_UUID=your-device-uuid sh scripts/check_logs.sh
环境变量:
LINES 每个分组显示最近多少行,默认 40
DEVICE_UUID 可选,按设备 UUID 过滤
USAGE
}
case "${1:-}" in
-h|--help)
print_usage
exit 0
;;
esac
has_logs() {
dir="$1"
[ -d "$dir" ] || return 1
find "$dir" -maxdepth 1 -type f -name '*.log' 2>/dev/null | grep -q .
}
grep_logs() {
pattern="$1"
shift
for dir in "$@"; do
has_logs "$dir" || continue
if [ -n "$DEVICE_UUID" ]; then
grep -hE "$pattern" "$dir"/*.log 2>/dev/null | grep -F "$DEVICE_UUID" || true
else
grep -hE "$pattern" "$dir"/*.log 2>/dev/null || true
fi
done
}
count_logs() {
pattern="$1"
shift
grep_logs "$pattern" "$@" | wc -l | tr -d ' '
}
section() {
title="$1"
pattern="$2"
shift 2
count="$(count_logs "$pattern" "$@")"
printf '\n=== %s%s 条)===\n' "$title" "$count"
if [ "$count" = "0" ]; then
echo "没有匹配日志。"
return
fi
grep_logs "$pattern" "$@" | tail -n "$LINES"
}
summary_item() {
label="$1"
pattern="$2"
shift 2
printf '%-34s %s\n' "$label:" "$(count_logs "$pattern" "$@")"
}
echo "FireLeave 日志快速检查"
echo "系统日志目录 : $SYSTEM_DIR"
echo "上报日志目录 : $REPORTER_DIR"
echo "每组显示行数 : $LINES"
if [ -n "$DEVICE_UUID" ]; then
echo "设备过滤 : $DEVICE_UUID"
fi
if ! has_logs "$SYSTEM_DIR" && ! has_logs "$REPORTER_DIR"; then
echo "指定目录下没有找到 *.log 日志文件。"
exit 1
fi
printf '\n=== 汇总 ===\n'
summary_item "程序启动次数" "Starting FireLeave Tool" "$SYSTEM_DIR"
summary_item "WS 注册成功" "Service registration successful" "$SYSTEM_DIR"
summary_item "Topic 订阅成功" "Topic subscription successful" "$SYSTEM_DIR"
summary_item "收到 topic_event" "WebSocket received topic_event" "$SYSTEM_DIR"
summary_item "收到声光报警命令" "Received platform alert push|Platform alert command" "$SYSTEM_DIR"
summary_item "收到 forbid_time" "Received forbid time message|Forbid time config received" "$SYSTEM_DIR"
summary_item "fire_leave 暂停命中" "fire_leave reporting paused|fire_leave report paused" "$SYSTEM_DIR"
summary_item "火焰报警触发" "Fire detected|FIRE DETECTED ALARM TRIGGERED|Fire detected continuously" "$SYSTEM_DIR"
summary_item "离人报警触发" "ALARM TRIGGERED|Alarm condition met" "$SYSTEM_DIR"
summary_item "WS/上报失败" "WebSocket.*failed|Response timeout|Asynchronous reporting failed|unsupported websocket message type|reply parsing failed|cast handling failed|WebSocket消息发送失败|响应超时|上报失败" "$SYSTEM_DIR" "$REPORTER_DIR"
summary_item "异常/错误" "panic|fatal|killed|segfault|failed|error|Error|timeout|abnormally|not connected|失败|错误|异常|超时|未连接|无法上报" "$SYSTEM_DIR" "$REPORTER_DIR"
section "启动和设备初始化" "Starting FireLeave Tool|Initialize device data|Loaded .*device|Starting device|Device processing goroutine started|Main program startup completed|System status|初始化设备|设备数据初始化|启动设备" "$SYSTEM_DIR"
section "推理服务启动" "Port allocation successful|Fire_leave inference server|Fire_check inference server|inference program started|Both inference programs started successfully|Start fire_leave inference program command|Start fire_check inference program command|端口分配成功|推理程序启动|推理服务" "$SYSTEM_DIR"
section "推理心跳和重启" "推理心跳收到|Inference data processed|fire_leave.*心跳|fire_check.*心跳|heartbeat.*timeout|准备重启|StartInferenceProcess|推理程序心跳超时" "$SYSTEM_DIR"
section "WebSocket 注册和订阅" "Starting WebSocket connection initialization|WebSocket connection initialized successfully|Starting service registration|Service registration successful|Starting topic subscription|Topic subscription successful|/dhlr/alert|/dhlr/forbid_time|WebSocket连接|服务注册|订阅" "$SYSTEM_DIR"
section "WebSocket 二进制协议" "Sending WebSocket binary frame|Received WebSocket binary frame|Received response|Response timeout|WebSocket reply parsing failed|WebSocket cast handling failed|Ignoring non-binary|Ignoring unknown|发送WebSocket二进制帧|收到WebSocket二进制帧|响应超时" "$SYSTEM_DIR"
section "WebSocket 重连" "WebSocket message reading failed|Failed to send ping|Reconnection attempt|WebSocket reconnection successful|Re-registration successful|Re-subscribed|Reconnection failed|read loop exited|重连|重新注册|重新订阅|读取失败" "$SYSTEM_DIR"
section "下行 topic_event 分发" "WebSocket received topic_event|No subscriber for topic_event|Received forbid time message|Received platform alert push|Forbid time config received|Platform alert command|收到.*topic_event|没有订阅者" "$SYSTEM_DIR"
section "声光报警 alert" "Received platform alert push|Outer parsing failed|Second parsing failed|Not a sound/light alert command|Platform alert command|Buzzer starting|GPIO109|sound/light alarm|声光报警|蜂鸣器|平台告警" "$SYSTEM_DIR"
section "forbid_time 和 fire_leave 暂停" "Received forbid time message|Forbid time config received|fire_leave reporting paused|fire_leave report paused|fire_leave reporting pause cleared|Forbid time config saved successfully|Restored fire_leave forbid window|Invalid fire_leave_timeout|Event cycle timeout config|禁止时间|暂停|forbid_time" "$SYSTEM_DIR"
section "fire_leave 离人报警链路" "Personnel quantity update|Temperature update|Alarm condition met|ALARM TRIGGERED|fire_leave report paused|报警事件已上报|数据快照已上报|level.*fire_leave|Level=fire_leave|人员数量更新|温度更新|离人|温度报警" "$SYSTEM_DIR" "$REPORTER_DIR"
section "fire_check 火焰报警链路" "Received fire_check inference data|Fire detected|Fire detection started|Fire detected continuously|FIRE DETECTED ALARM TRIGGERED|火焰检测|报警事件已上报|数据快照已上报|level.*fire_check|Level=fire_check" "$SYSTEM_DIR" "$REPORTER_DIR"
section "视频合并上传和事件上报" "开始合并视频|Merging .* videos|Video merged and moved successfully|开始上传视频|UploadToNginx|Nginx|upload failed|报警事件已上报|数据快照已上报|Asynchronous reporting successful|Asynchronous reporting failed|Sending WebSocket binary frame: type=0x03|视频上传|事件上报|数据快照" "$SYSTEM_DIR" "$REPORTER_DIR"
section "需要重点排查的问题" "panic|fatal|killed|segfault|WebSocket.*failed|Response timeout|Asynchronous reporting failed|unsupported websocket message type|reply parsing failed|cast handling failed|not connected|无法上报|失败|错误|异常|timeout|abnormally|超时|未连接" "$SYSTEM_DIR" "$REPORTER_DIR"
printf '\n检查完成。\n'
+4 -2
View File
@@ -17,6 +17,7 @@ import (
// UploadToNginx 上传视频到 Nginx 服务器,返回 URL // UploadToNginx 上传视频到 Nginx 服务器,返回 URL
func UploadToNginx(filePath string, eventParams map[string]any, uploadURL string) (string, error) { func UploadToNginx(filePath string, eventParams map[string]any, uploadURL string) (string, error) {
logger.Logger.Printf("开始上传视频到 Nginx: %s → %s", filePath, uploadURL) logger.Logger.Printf("开始上传视频到 Nginx: %s → %s", filePath, uploadURL)
fileName := filepath.Base(filePath)
// 打开文件 // 打开文件
logger.Logger.Printf("打开文件: %s", filePath) logger.Logger.Printf("打开文件: %s", filePath)
@@ -35,8 +36,8 @@ func UploadToNginx(filePath string, eventParams map[string]any, uploadURL string
writer := multipart.NewWriter(body) writer := multipart.NewWriter(body)
// 创建表单文件字段 // 创建表单文件字段
logger.Logger.Printf("创建表单文件字段: %s", filepath.Base(filePath)) logger.Logger.Printf("创建表单文件字段: %s", fileName)
part, err := writer.CreateFormFile("file", filepath.Base(filePath)) part, err := writer.CreateFormFile("file", fileName)
if err != nil { if err != nil {
logger.Logger.Printf("创建表单文件字段失败: %v", err) logger.Logger.Printf("创建表单文件字段失败: %v", err)
return "", fmt.Errorf("create form file failed: %w", err) return "", fmt.Errorf("create form file failed: %w", err)
@@ -60,6 +61,7 @@ func UploadToNginx(filePath string, eventParams map[string]any, uploadURL string
} }
logger.Logger.Printf("[Upload Event parameters]eventParams: %v", eventParams) logger.Logger.Printf("[Upload Event parameters]eventParams: %v", eventParams)
logger.Logger.Printf("[Upload Event parameters]eventJSON: %s", string(eventJSON)) logger.Logger.Printf("[Upload Event parameters]eventJSON: %s", string(eventJSON))
logger.Logger.Printf("[Nginx Multipart Detail] URL=%s file_field=file event_field=event filename=%s event_json=%s", uploadURL, fileName, string(eventJSON))
// 写入事件字段 // 写入事件字段
logger.Logger.Printf("写入事件字段到表单") logger.Logger.Printf("写入事件字段到表单")