事件触发类型改为按照坐标触发

This commit is contained in:
MATRIX\29620 2025-01-22 17:59:21 +08:00
parent 130b8db17a
commit b91e3f5bfb
2 changed files with 328 additions and 95 deletions

2
.idea/.name generated
View File

@ -1 +1 @@
SimTruckService.go
simulation_service.go

View File

@ -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 (
// Route1G7干线的触发点
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)