From b91e3f5bfb193b497b1ae425adaccfc9690a329a Mon Sep 17 00:00:00 2001 From: "MATRIX\\29620" <2962004889@qq.com> Date: Wed, 22 Jan 2025 17:59:21 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BA=8B=E4=BB=B6=E8=A7=A6=E5=8F=91=E7=B1=BB?= =?UTF-8?q?=E5=9E=8B=E6=94=B9=E4=B8=BA=E6=8C=89=E7=85=A7=E5=9D=90=E6=A0=87?= =?UTF-8?q?=E8=A7=A6=E5=8F=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .idea/.name | 2 +- .../tms/core/service/SimTruckService.go | 421 ++++++++++++++---- 2 files changed, 328 insertions(+), 95 deletions(-) diff --git a/.idea/.name b/.idea/.name index 17113c9..39470d7 100644 --- a/.idea/.name +++ b/.idea/.name @@ -1 +1 @@ -SimTruckService.go \ No newline at end of file +simulation_service.go \ No newline at end of file diff --git a/main/golang/antesv/tms/core/service/SimTruckService.go b/main/golang/antesv/tms/core/service/SimTruckService.go index 7ee667a..a42be9e 100644 --- a/main/golang/antesv/tms/core/service/SimTruckService.go +++ b/main/golang/antesv/tms/core/service/SimTruckService.go @@ -9,6 +9,7 @@ import ( "net/http" "os" "path/filepath" + "strconv" "strings" "sync" "sync/atomic" @@ -73,7 +74,6 @@ type SimulationParams struct { CoalPickupOrder string `json:"coal_pickup_order"` // 固定提煤单 } -// Truck 表示一辆卡车 type Truck struct { ID int VIN string @@ -95,6 +95,7 @@ type Truck struct { targetPointReachedCount int32 eventMutex sync.Mutex + triggeredEvents map[string]bool // 记录已触发的事件类型 eventsSent map[int]bool currentEventIndex int } @@ -124,6 +125,170 @@ var additionalPoints = []RoutePoint{ {Longitude: 90.625132, Latitude: 43.802499, Attribute: 4}, } +// EventTriggerPoint 定义触发点及其关联事件 +type EventTriggerPoint struct { + Longitude float64 + Latitude float64 + EventType string // 对应事件类型 "0", "1", "2" 等 + Tolerance float64 // 判定到达的容差范围 +} + +// 为每条路线定义触发点 +// 为每条路线定义触发点 +var ( + // Route1(G7干线)的触发点 + route1TriggerPoints = []EventTriggerPoint{ + { + Longitude: 89.172811, // 起点 - 创建运单 + Latitude: 44.859511, + EventType: "0", + Tolerance: 0.001, + }, + { + Longitude: 89.316852, // 进陆港前 - 司机接单 + Latitude: 44.762968, + EventType: "1", + Tolerance: 0.001, + }, + { + Longitude: 89.365738, // 进陆港 + Latitude: 44.768674, + EventType: "2", + Tolerance: 0.001, + }, + { + Longitude: 89.381814, // 领取货箱 + Latitude: 44.769508, + EventType: "3", + Tolerance: 0.001, + }, + { + Longitude: 89.413458, // 出陆港 + Latitude: 44.770878, + EventType: "4", + Tolerance: 0.001, + }, + { + Longitude: 89.552294, // 收费站 + Latitude: 44.762026, + EventType: "5", + Tolerance: 0.001, + }, + { + Longitude: 90.408540, // 开始排队 + Latitude: 43.868251, + EventType: "6", + Tolerance: 0.001, + }, + { + Longitude: 90.484399, // 装运 + Latitude: 43.842926, + EventType: "7", + Tolerance: 0.001, + }, + { + Longitude: 90.498991, // 收费站返程 + Latitude: 43.841625, + EventType: "8", + Tolerance: 0.001, + }, + { + Longitude: 90.515191, // 进陆港返程 + Latitude: 43.839318, + EventType: "9", + Tolerance: 0.001, + }, + { + Longitude: 90.525162, // 陆港卸货 + Latitude: 43.838095, + EventType: "10", + Tolerance: 0.001, + }, + { + Longitude: 90.535948, // 终点 - 运单结束 + Latitude: 43.835969, + EventType: "11", + Tolerance: 0.001, + }, + } + + // Route2(短倒/支线)的触发点 + route2TriggerPoints = []EventTriggerPoint{ + { + Longitude: 89.153942, // 起点 - 创建运单 + Latitude: 44.757513, + EventType: "0", + Tolerance: 0.001, + }, + { + Longitude: 89.174784, // 司机接单 + Latitude: 44.78754, + EventType: "1", + Tolerance: 0.001, + }, + { + Longitude: 89.196045, // 进陆港 + Latitude: 44.81793, + EventType: "2", + Tolerance: 0.001, + }, + { + Longitude: 89.214009, // 领取货箱 + Latitude: 44.838927, + EventType: "3", + Tolerance: 0.001, + }, + { + Longitude: 89.237319, // 出陆港 + Latitude: 44.854165, + EventType: "4", + Tolerance: 0.001, + }, + { + Longitude: 89.23939, // 收费站 + Latitude: 44.855511, + EventType: "5", + Tolerance: 0.001, + }, + { + Longitude: 90.600018, // 开始排队 + Latitude: 44.676285, + EventType: "6", + Tolerance: 0.001, + }, + { + Longitude: 90.616385, // 装运 + Latitude: 44.269544, + EventType: "7", + Tolerance: 0.001, + }, + { + Longitude: 90.617192, // 收费站返程 + Latitude: 44.075327, + EventType: "8", + Tolerance: 0.001, + }, + { + Longitude: 90.621784, // 进陆港返程 + Latitude: 44.052461, + EventType: "9", + Tolerance: 0.001, + }, + { + Longitude: 90.624094, // 陆港卸货 + Latitude: 44.040623, + EventType: "10", + Tolerance: 0.001, + }, + { + Longitude: 90.625132, // 终点 - 运单结束 + Latitude: 43.802499, + EventType: "11", + Tolerance: 0.001, + }, + } +) + // MQTT相关和全局变量 var ( mqttBroker = "tcp://123.6.102.119:1883" @@ -531,6 +696,18 @@ func sendEvent(mqttClient mqtt.Client, event map[string]interface{}) { }() } +// getTriggerPoints 根据路线名称获取对应的触发点 +func getTriggerPoints(routeName string) []EventTriggerPoint { + switch routeName { + case Route1Name: + return route1TriggerPoints + case Route2Name: + return route2TriggerPoints + default: + return nil + } +} + // sendInitialEvent 发送初始事件,根据 RouteName 选择事件类型 "0_route1" 或 "0_route2" func (t *Truck) sendInitialEvent(coalPickupOrder string) { var initialEvents []map[string]interface{} @@ -704,36 +881,25 @@ func (t *Truck) runSimulation(stopChan chan struct{}, mainOrderNumber string, co activeTrucksMutex.Unlock() }() - // 使用传入的主订单号作为 OrderID + // 设置基本信息 t.OrderID = mainOrderNumber - log.Printf("卡车 %d 订单号: %s", t.ID, t.OrderID) - - // 设置固定的提煤单 t.CoalPickupOrder = coalPickupOrder - log.Printf("卡车 %d 提煤单: %s", t.ID, t.CoalPickupOrder) - - // 生成运单 t.Waybill = generateWaybill(mainOrderNumber) - log.Printf("卡车 %d 生成运单: %s", t.ID, t.Waybill) - - // 生成固定的 VIN 码 t.VIN = generateFixedVIN() - log.Printf("卡车 %d 生成 VIN: %s", t.ID, t.VIN) - // 发送所有事件(包括创建运单事件和后续事件) - log.Printf("卡车 %d 开始发送所有事件", t.ID) - t.sendAllEvents() - - // 添加日志,确认即将发送异常状态事件 - log.Printf("卡车 %d 即将发送50条异常状态事件", t.ID) + // 初始化事件追踪 + t.eventMutex.Lock() + t.triggeredEvents = make(map[string]bool) + t.eventMutex.Unlock() // 发送50条异常状态事件 + log.Printf("卡车 %d 开始发送50条异常状态事件", t.ID) t.sendAbnormalEvents() // 设置初始事件索引 t.currentEventIndex = 0 - // 等待10秒后开始执行事件 + // 等待10秒后开始执行行驶模拟 log.Printf("卡车 %d 等待10秒后开始执行行驶模拟", t.ID) select { case <-time.After(10 * time.Second): @@ -741,7 +907,7 @@ func (t *Truck) runSimulation(stopChan chan struct{}, mainOrderNumber string, co return } - // 往返循环开始行驶模拟... + // 往返循环开始行驶模拟 for { select { case <-stopChan: @@ -767,6 +933,11 @@ func (t *Truck) runSimulation(stopChan chan struct{}, mainOrderNumber string, co reverseRoute := reverseRoute(t.Route) + // 重置事件触发状态用于返程 + t.eventMutex.Lock() + t.triggeredEvents = make(map[string]bool) + t.eventMutex.Unlock() + if !t.runRouteSegment(stopChan, reverseRoute) { return } @@ -778,69 +949,114 @@ func (t *Truck) runSimulation(stopChan chan struct{}, mainOrderNumber string, co } } -// sendAllEvents 按顺序发送所有事件 -func (t *Truck) sendAllEvents() { - // 事件类型按顺序存储在一个切片中 - eventTypes := []string{"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11"} +// checkAndTriggerEvent 检查并触发事件 +func (t *Truck) checkAndTriggerEvent(currentPoint RoutePoint) { + // 获取当前路线的触发点 + triggerPoints := getTriggerPoints(t.RouteName) + if triggerPoints == nil { + return + } - for _, eventType := range eventTypes { - // 获取当前事件类型的所有事件 - events := getRouteEvents(t.RouteName, eventType) - if len(events) == 0 { - log.Printf("卡车 %d 没有事件类型 %s 的事件定义", t.ID, eventType) + t.eventMutex.Lock() + defer t.eventMutex.Unlock() + + for _, triggerPoint := range triggerPoints { + // 如果已触发则跳过 + if t.triggeredEvents[triggerPoint.EventType] { continue } - // 遍历该类型的所有事件并发送 - for _, event := range events { - // 深拷贝事件 - eventCopy := cloneEvent(event) - if eventCopy == nil { + // 检查是否在触发点附近 + if math.Abs(currentPoint.Longitude-triggerPoint.Longitude) <= triggerPoint.Tolerance && + math.Abs(currentPoint.Latitude-triggerPoint.Latitude) <= triggerPoint.Tolerance { + + // 标记事件已触发 + t.triggeredEvents[triggerPoint.EventType] = true + + // 确保事件按顺序触发(除了事件0以外) + eventNum, _ := strconv.Atoi(triggerPoint.EventType) + if eventNum > 0 { + prevEvent := strconv.Itoa(eventNum - 1) + if !t.triggeredEvents[prevEvent] { + continue + } + } + + // 获取事件模板 + events := getRouteEvents(t.RouteName, triggerPoint.EventType) + if len(events) == 0 { + log.Printf("卡车 %d: 路线 %s 未找到事件类型 %s 的事件定义", + t.ID, t.RouteName, triggerPoint.EventType) continue } - // 更新事件的动态字段 - eventCopy["车牌"] = t.CarPlate - eventCopy["司机"] = t.DriverName - eventCopy["vin"] = t.VIN - eventCopy["订单"] = t.OrderID - eventCopy["运单"] = t.Waybill - eventCopy["time"] = time.Now().Format("2006-01-02 15:04:05") - - // 如果有货箱编号字段,设置它 - if _, exists := eventCopy["货箱编号"]; exists { - eventCopy["货箱编号"] = "TRA1205B" + // 克隆事件并填充信息 + event := cloneEvent(events[0]) + if event == nil { + continue } - // 如果有净重字段,设置它 - if _, exists := eventCopy["净重吨数"]; exists { - eventCopy["净重吨数"] = t.LoadWeight + // 更新事件基本信息 + event["车牌"] = t.CarPlate + event["司机"] = t.DriverName + event["vin"] = t.VIN + event["订单"] = t.OrderID + event["运单"] = t.Waybill + event["time"] = time.Now().Format("2006-01-02 15:04:05") + + // 更新位置信息 + if _, hasPos := event["pos"]; hasPos { + event["pos"] = []float64{currentPoint.Longitude, currentPoint.Latitude} } - if _, exists := eventCopy["提煤单"]; exists { - eventCopy["提煤单"] = t.CoalPickupOrder + // 根据路线类型设置运输方式 + if t.RouteName == Route1Name { + if _, hasType := event["线路类型"]; hasType { + event["线路类型"] = TransportModeTrunk // 干线 + } + } else if t.RouteName == Route2Name { + if _, hasType := event["线路类型"]; hasType { + event["线路类型"] = TransportModeShortHaul // 短倒 + } } - if pos, exists := eventCopy["pos"].([]interface{}); exists { - // 使用当前位置更新pos字段 - currentPoint := t.Route[0] // 使用路线的第一个点作为初始位置 - pos[0] = currentPoint.Longitude - pos[1] = currentPoint.Latitude - eventCopy["pos"] = pos + // 设置其他可选字段 + if _, exists := event["货箱编号"]; exists { + event["货箱编号"] = "TRA1205B" + } + if _, exists := event["净重吨数"]; exists { + event["净重吨数"] = fmt.Sprintf("%.2f", t.LoadWeight) + } + if _, exists := event["提煤单"]; exists { + event["提煤单"] = t.CoalPickupOrder } // 发送事件 - sendEvent(t.MQTTClient, eventCopy) + sendEvent(t.MQTTClient, event) - // 在事件之间添加延迟,让事件按序发生 - time.Sleep(5 * time.Second) + // 记录日志 + log.Printf("卡车 %d (%s) 在位置 [%.6f, %.6f] 触发事件类型 %s", + t.ID, t.RouteName, currentPoint.Longitude, currentPoint.Latitude, triggerPoint.EventType) + + // 特殊处理:如果是短倒路线且到达目标点,通知 G7 干线发车 + if t.RouteName == Route2Name && triggerPoint.EventType == "3" { + atomic.AddInt32(&t.targetPointReachedCount, 1) + if atomic.LoadInt32(&t.targetPointReachedCount) == 2 { + select { + case route1StartChan <- struct{}{}: + log.Printf("卡车 %d: 短倒路线第二次到达目标点,通知 G7 干线发车", t.ID) + default: + // 如果通道已满,避免阻塞 + } + } + } } } } -// runRouteSegment 执行路线段的模拟(已移除基于位置的事件触发逻辑) +// runRouteSegment 执行路线段的模拟 func (t *Truck) runRouteSegment(stopChan chan struct{}, routeSegment []RoutePoint) bool { - stepsBetweenPoints := 10 + stepsBetweenPoints := 5 for i := 0; i < len(routeSegment)-1; i++ { select { case <-stopChan: @@ -857,78 +1073,94 @@ func (t *Truck) runRouteSegment(stopChan chan struct{}, routeSegment []RoutePoin log.Printf("卡车 %d 模拟被停止", t.ID) return false default: - // 移除基于位置的事件触发逻辑 + // 检查和触发基于位置的事件 + t.checkAndTriggerEvent(pt) // 构建位置数据的 payload - speed := math.Round((20+80*rand.Float64())*10) / 10 // 定义 speed 变量 + speed := math.Round((20+80*rand.Float64())*10) / 10 vehicleType := "hydrogen" if t.RouteName == Route2Name { vehicleType = "electric" } + // 更新卡车状态 + t.Speed = speed + t.Mileage += speed * 0.0002 // 模拟里程增加 + t.Location = fmt.Sprintf("%.6f,%.6f", pt.Longitude, pt.Latitude) + + // 获取司机状态 + driverStatus := "正常" + driverStatusCategory := "run" + if status, found := getDriverStatusAtPoint(pt); found { + driverStatus = status + driverStatusCategory = mapDriverStatusToCategory(status) + } + payload := map[string]interface{}{ "alarm_level": 0, "charging_state": 0, "coal_pickup_order": t.CoalPickupOrder, - "cumulative_mileage": 15003.7, // 这里可以根据需求动态计算 + "cumulative_mileage": roundTo(t.Mileage, 1), "data_time": time.Now().Format("2006-01-02 15:04:05"), - "driver_status": "run", - "driver_status_original": "正常", + "driver_status": driverStatusCategory, + "driver_status_original": driverStatus, "general_alarm": 0, "highest_temperature": 43, "highest_voltage_battery": 3.65, - "latitude": pt.Latitude, // 动态设置纬度 - "longitude": pt.Longitude, // 动态设置经度 + "latitude": pt.Latitude, + "longitude": pt.Longitude, "lowest_temperature": 22, "lowest_voltage_battery": 3.29, "operation_mode": 0, - "order_id": t.OrderID, // 动态设置订单号 - "phonenumber": t.PhoneNumber, // 动态设置电话号码 + "order_id": t.OrderID, + "phonenumber": t.PhoneNumber, "soc": 82, - "speed": speed, // 使用 speed 变量 + "speed": speed, "subsystem_number": 1, "total_current": 13.4, "total_voltage": 351.2, - "vehicle_id": fmt.Sprintf("Truck-%d", t.ID), // 动态设置车辆ID + "vehicle_id": fmt.Sprintf("Truck-%d", t.ID), "vehicle_state": 0, "vehicle_type": vehicleType, - "vin": t.VIN, // 动态设置VIN - "waybill": t.Waybill, // 动态设置运单号 + "vin": t.VIN, + "waybill": t.Waybill, } - // 根据状态设置 driver_status 和 driver_status_original - // 由于已移除基于位置的事件触发,这里可以简化 - // 如果需要,可以根据其他条件调整 + // 根据车辆类型设置特定参数 + if t.RouteName == Route2Name { + // 电动车特定参数 + payload["soc"] = math.Max(20, 82-t.Mileage*0.01) // SOC随里程降低 + payload["total_current"] = 13.4 + rand.Float64()*2 - 1 // 随机波动的电流 + payload["total_voltage"] = 351.2 + rand.Float64()*10 - 5 // 随机波动的电压 + payload["highest_temperature"] = 43 + rand.Float64()*4 - 2 // 随机波动的温度 + payload["lowest_temperature"] = 22 + rand.Float64()*4 - 2 + } - // 创建一个仅包含值的数组,按照固定顺序 - //logKeysSorted("data", payload) + // 使用固定字段顺序构建数据数组 values := make([]interface{}, 0, len(dataFieldOrder)) for _, key := range dataFieldOrder { if val, exists := payload[key]; exists { values = append(values, val) } else { - // 如果某个字段缺失,根据需求填充默认值 values = append(values, nil) } } - // JSON 编码数组 + // JSON 编码 jsonData, err := json.Marshal(values) if err != nil { log.Printf("卡车 %d JSON 编码错误: %v", t.ID, err) continue } - // 发布到 MQTT 的 data 主题 + // 发布到 MQTT token := t.MQTTClient.Publish(mqttTopic, 0, false, jsonData) token.Wait() if token.Error() != nil { log.Printf("卡车 %d 发布 MQTT 数据消息失败: %v", t.ID, token.Error()) - } else { - log.Printf("卡车 %d (%s) 发布数据: %s", t.ID, t.RouteName, string(jsonData)) } - // 控制速度 + // 控制模拟速度 speedMultiplierMutex.RLock() currentMultiplier := speedMultiplier speedMultiplierMutex.RUnlock() @@ -1088,16 +1320,17 @@ func startRoute(stopChan chan struct{}, mqttClient mqtt.Client, route []RoutePoi MQTTClient: mqttClient, LoadWeight: loadWeight, RouteName: routeName, - CoalPickupOrder: coalPickupOrder, // 固定提煤单 - Waybill: "", // 将在 runSimulation 中生成 - DriverName: driverName, // 分配司机姓名 - PhoneNumber: "18940028888", // 分配固定电话号码 - CarPlate: "蒙K123ER", // 新增字段,固定或动态分配 - Mileage: 100.0, // 新增字段,示例值,实际应动态获取 - Speed: 60.0, // 新增字段,示例值,实际应动态获取 - Location: "北京", // 新增字段,示例值,实际应动态获取 - VehicleStatus: "ACC正常", // 新增字段,示例值,实际应动态获取 + CoalPickupOrder: coalPickupOrder, + Waybill: "", + DriverName: driverName, + PhoneNumber: "18940028888", + CarPlate: "蒙K123ER", + Mileage: 100.0, + Speed: 60.0, + Location: "北京", + VehicleStatus: "ACC正常", eventsSent: make(map[int]bool), + triggeredEvents: make(map[string]bool), // 初始化触发事件记录 } simulationWaitGroup.Add(1) go truck.runSimulation(stopChan, mainOrderNumber, coalPickupOrder)