package connect import ( "FireLeave_tool/logger" "encoding/json" "fmt" "os" "path/filepath" "strconv" "strings" ) // Area 检测区域结构体 type Area struct { ID int `json:"id"` Pts []Point `json:"pts"` } // Point 坐标点结构体 type Point struct { X float64 `json:"x"` Y float64 `json:"y"` } // 新的配置结构体 // ServiceConfig 服务配置结构体 type ServiceConfig struct { HostUUID string `json:"host_uuid"` DeviceUUID string `json:"device_uuid"` Address string `json:"address"` TaskID string `json:"task_id"` CameraRTSP string `json:"camera_rtsp"` CameraIP string `json:"camera_ip"` Confidence int `json:"confidence"` DetectArea DetectAreaConfig `json:"detect_area"` Param ParamConfig `json:"param"` } // DetectAreaConfig 检测区域配置 type DetectAreaConfig struct { FireLeave []Area `json:"fire_leave"` FireCheck []Area `json:"fire_check"` } // ParamConfig 参数配置 type ParamConfig struct { FireCheck FireCheckParam `json:"fire_check"` FireLeave FireLeaveParam `json:"fire_leave"` } // FireCheckParam 火检参数 type FireCheckParam struct { Confidence string `json:"confidence"` DetectionTime string `json:"detection_time"` } // FireLeaveParam 离岗参数 type FireLeaveParam struct { PersonCount string `json:"person_count"` Confidence string `json:"confidence"` DetectionTime string `json:"detection_time"` TemperatureThreshold string `json:"temperature_threshold"` AlarmTime string `json:"alarm_time"` } var ( devicesBaseDir = "/data/devices" deviceDataList []DeviceData 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 func LoadServiceConfig() ([]DeviceData, error) { if _, err := os.Stat(serviceConfigFile); os.IsNotExist(err) { logger.Logger.Printf("Service config file does not exist: %s", serviceConfigFile) return nil, fmt.Errorf("service config file does not exist") } data, err := os.ReadFile(serviceConfigFile) if err != nil { logger.Logger.Printf("Failed to read service config file: %v", err) return nil, err } // Check if file content is empty if len(data) == 0 { logger.Logger.Printf("Service config file is empty") return nil, fmt.Errorf("service config file is empty") } // 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 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) 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 var deviceDataList []DeviceData for _, config := range serviceConfigs { // Create DeviceData from ServiceConfig deviceData := DeviceData{ DeviceUUID: config.DeviceUUID, TaskID: config.TaskID, CameraRTSP: config.CameraRTSP, CameraIP: config.CameraIP, Confidence: config.Confidence, HostUUID: config.HostUUID, Address: config.Address, UploadURL: fixedUploadURL, // Set new fields for new configuration format FireLeaveDetectArea: config.DetectArea.FireLeave, FireCheckDetectArea: config.DetectArea.FireCheck, FireLeaveParam: config.Param.FireLeave, FireCheckParam: config.Param.FireCheck, } // Parse and set temperature threshold from FireLeaveParam if tempThresholdStr := config.Param.FireLeave.TemperatureThreshold; tempThresholdStr != "" { var tempThreshold float64 if _, err := fmt.Sscanf(tempThresholdStr, "%f", &tempThreshold); err == nil && tempThreshold > 0 { deviceData.TemperatureThreshold = tempThreshold logger.Logger.Printf("Set temperature threshold from config: %.1f°C (DeviceUUID=%s)", tempThreshold, config.DeviceUUID) } } NormalizeDeviceRuntimeFields(&deviceData) // Filter out configurations with empty DeviceUUID if deviceData.DeviceUUID != "" { deviceDataList = append(deviceDataList, deviceData) } } if len(deviceDataList) == 0 { logger.Logger.Printf("No valid device configurations found in service config file") return nil, fmt.Errorf("no valid device configurations") } logger.Logger.Printf("Loaded %d device configurations from service config file", len(deviceDataList)) 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 } } } 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 { return "" } // Only take the first detection area area := areas[0] var points []string for _, point := range area.Pts { // Directly round, remove decimals x := int(point.X) y := int(point.Y) points = append(points, fmt.Sprintf("%d,%d", x, y)) } return strings.Join(points, ",") } // ProcessDeviceDataForInference processes device data, converts to format required by inference program func ProcessDeviceDataForInference(data DeviceData) DeviceData { // Create a copy, only process fields that need conversion processed := data NormalizeDeviceRuntimeFields(&processed) // For backward compatibility, process original DetectArea if FireLeaveDetectArea is empty if len(data.FireLeaveDetectArea) > 0 { processed.DetectAreaStr = ProcessDetectAreaForInference(data.FireLeaveDetectArea) } else { processed.DetectAreaStr = ProcessDetectAreaForInference(data.DetectArea) } return processed } // ProcessFireLeaveDataForInference processes device data for fire_leave inference func ProcessFireLeaveDataForInference(data DeviceData) DeviceData { processed := data NormalizeDeviceRuntimeFields(&processed) if len(data.FireLeaveDetectArea) > 0 { processed.DetectAreaStr = ProcessDetectAreaForInference(data.FireLeaveDetectArea) } return processed } // ProcessFireCheckDataForInference processes device data for fire_check inference func ProcessFireCheckDataForInference(data DeviceData) DeviceData { processed := data NormalizeDeviceRuntimeFields(&processed) if len(data.FireCheckDetectArea) > 0 { processed.DetectAreaStr = ProcessDetectAreaForInference(data.FireCheckDetectArea) } return processed } func InitializeDevicesFromServiceConfig() ([]DeviceData, error) { deviceDataMutex.Lock() defer deviceDataMutex.Unlock() logger.Logger.Printf("Initializing device data from service configuration...") // Ensure device directory exists if err := os.MkdirAll(devicesBaseDir, 0755); err != nil { logger.Logger.Printf("Failed to create device directory: %v", err) return nil, err } logger.Logger.Printf("Device directory confirmed: %s", devicesBaseDir) // Load from service configuration configs, err := LoadServiceConfig() if err != nil { logger.Logger.Printf("Failed to load from service configuration: %v", err) return nil, err } logger.Logger.Printf("Loaded %d devices from service configuration", len(configs)) deviceDataList = []DeviceData{} for i, config := range configs { processedData := ProcessDeviceDataForInference(config) deviceDataList = append(deviceDataList, processedData) if err := saveDeviceToFile(processedData); err != nil { logger.Logger.Printf("Failed to save the device file [%s]: %v", processedData.DeviceUUID, err) continue } logger.Logger.Printf("Processed device [%d]: UUID=%s", i, processedData.DeviceUUID) } logger.Logger.Printf("Device data initialization completed, %d devices", len(deviceDataList)) return deviceDataList, nil } func saveDeviceToFile(deviceData DeviceData) error { filePath := filepath.Join(devicesBaseDir, fmt.Sprintf("device_%s.json", deviceData.DeviceUUID)) cleanedData := cleanDeviceData(deviceData) data, err := json.MarshalIndent(cleanedData, "", " ") if err != nil { logger.Logger.Printf("Device data serialization failed: %v, Device data: %+v", err, cleanedData) return fmt.Errorf("Device data serialization failed: %v", err) } if err := os.WriteFile(filePath, data, 0644); err != nil { return fmt.Errorf("Failed to write device file: %v", err) } logger.Logger.Printf("Device data saved successfully: %s", filePath) return nil } func cleanDeviceData(data DeviceData) DeviceData { cleaned := data cleaned.DeviceUUID = cleanString(data.DeviceUUID) cleaned.TaskID = cleanString(data.TaskID) cleaned.CameraRTSP = cleanString(data.CameraRTSP) cleaned.CameraIP = cleanString(data.CameraIP) cleaned.UploadURL = cleanString(data.UploadURL) cleaned.DetectAreaStr = cleanString(data.DetectAreaStr) if cleaned.Confidence < 0 || cleaned.Confidence > 100 { cleaned.Confidence = 70 } if cleaned.CameraChannel < 0 { cleaned.CameraChannel = 0 } if cleaned.PersonCount < 0 { cleaned.PersonCount = 0 } if cleaned.Temperature < 0 { cleaned.Temperature = 0 } // 只有当温度阈值真正为0时才设置默认值45.0°C,避免覆盖用户配置的值 if cleaned.TemperatureThreshold == 0 { cleaned.TemperatureThreshold = 45.0 logger.Logger.Printf("Set default temperature threshold: 45.0°C (DeviceUUID=%s)", cleaned.DeviceUUID) } return cleaned } func cleanString(s string) string { return strings.Map(func(r rune) rune { if r == 0xFFFD { // Unicode replacement character return -1 } if r < 32 && r != 9 && r != 10 && r != 13 { // Keep tab, newline, carriage return return -1 } return r }, s) } func GetDeviceDataList() []DeviceData { deviceDataMutex.Lock() defer deviceDataMutex.Unlock() if len(deviceDataList) == 0 { logger.Logger.Printf("No device data in memory, loading from device files...") if err := loadDevicesFromFiles(); err != nil { logger.Logger.Printf("Failed to load from device files: %v", err) return []DeviceData{} } } result := make([]DeviceData, len(deviceDataList)) copy(result, deviceDataList) return result } func loadDevicesFromFiles() error { // Clear existing data deviceDataList = []DeviceData{} files, err := filepath.Glob(filepath.Join(devicesBaseDir, "device_*.json")) if err != nil { return fmt.Errorf("Failed to find device files: %v", err) } logger.Logger.Printf("Found %d device files", len(files)) for _, file := range files { data, err := os.ReadFile(file) if err != nil { logger.Logger.Printf("Failed to read device file %s: %v", file, err) continue } var deviceData DeviceData if err := json.Unmarshal(data, &deviceData); err != nil { logger.Logger.Printf("Failed to parse device file %s: %v", file, err) continue } deviceDataList = append(deviceDataList, deviceData) logger.Logger.Printf("Loaded device from file: UUID=%s", deviceData.DeviceUUID) } logger.Logger.Printf("Loaded %d devices from device files", len(deviceDataList)) return nil } func UpdateDeviceData(data DeviceData) error { deviceDataMutex.Lock() defer deviceDataMutex.Unlock() //logger.Logger.Printf("Updating device data [%s]", data.DeviceUUID) // Clean input data cleanedData := cleanDeviceData(data) found := false for i, existing := range deviceDataList { if existing.DeviceUUID == cleanedData.DeviceUUID { deviceDataList[i] = cleanedData found = true //logger.Logger.Printf("Updated existing device: DeviceUUID=%s", cleanedData.DeviceUUID) break } } if !found { logger.Logger.Printf("Adding new device to list: DeviceUUID=%s", cleanedData.DeviceUUID) deviceDataList = append(deviceDataList, cleanedData) } if err := saveDeviceToFile(cleanedData); err != nil { logger.Logger.Printf("Failed to save device file: %v", err) return err } logger.Logger.Printf("Device data update completed: DeviceUUID=%s", cleanedData.DeviceUUID) return nil } // SaveServiceConfig saves multiple device configurations to service config file func SaveServiceConfig(deviceDataList []DeviceData) error { 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 { logger.Logger.Printf("Failed to serialize service configuration: %v", 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 { logger.Logger.Printf("Failed to write service config file: %v", err) return err } logger.Logger.Printf("Service configuration saved to: %s, %d devices", serviceConfigFile, len(deviceDataList)) return nil } // SaveSingleServiceConfig saves single device configuration (compatible with existing calls) func SaveSingleServiceConfig(deviceData DeviceData) error { // First read existing configuration existingConfigs, err := LoadServiceConfig() if err != nil { // If reading fails, create new configuration list existingConfigs = []DeviceData{deviceData} } else { // Find if configuration with same DeviceUUID already exists found := false for i, existing := range existingConfigs { if existing.DeviceUUID == deviceData.DeviceUUID { existingConfigs[i] = deviceData found = true break } } // If not exists, add to list if !found { existingConfigs = append(existingConfigs, deviceData) } } return SaveServiceConfig(existingConfigs) }