package simulation import ( "encoding/binary" "log/slog" "math" "time" "joylink.club/rtsssimulation/repository/model/proto" "fmt" "net/http" "strconv" "sync" "joylink.club/bj-rtsts-server/ats/verify/protos/state" "joylink.club/bj-rtsts-server/ats/verify/simulation/wayside/memory" "joylink.club/bj-rtsts-server/config" "joylink.club/bj-rtsts-server/dynamics" "joylink.club/bj-rtsts-server/vobc" "joylink.club/bj-rtsts-server/dto" ) func init() { // vobc 发来的列车信息 vobc.RegisterTrainInfoHandler(func(info []byte) { memory.UdpUpdateTime.VobcTime = time.Now().UnixMilli() for _, simulation := range GetSimulationArr() { simulation.Memory.Status.TrainStateMap.Range(func(_, value any) bool { train := value.(*state.TrainState) if !train.Show { // 下线列车 return false } // 拼接列车ID trainId, err := strconv.Atoi(train.Id) if err != nil { panic(dto.ErrorDto{Code: dto.ArgumentParseError, Message: err.Error()}) } trainInfo := decoderVobcTrainState(info) d := append(info, uint8(trainId)) // 发送给动力学 dynamics.SendDynamicsTrainMsg(d) // 存放至列车中 train.VobcState = trainInfo return true }) } }) dynamics.RegisterTrainInfoHandler(func(info *dynamics.TrainInfo) { memory.UdpUpdateTime.DynamicsTime = time.Now().UnixMilli() for _, simulation := range GetSimulationArr() { sta, ok := simulation.Memory.Status.TrainStateMap.Load(strconv.Itoa(int(info.Number))) if !ok { continue } trainState := sta.(*state.TrainState) // 给半实物仿真发送速度 vobc.SendTrainSpeedTask(convertVobc(info)) // 更新列车状态 memory.UpdateTrainState(simulation, convert(info, trainState, simulation)) } }) } // 仿真存储集合 var simulationMap sync.Map // 创建前检查 func IsExistSimulation() bool { i := 0 simulationMap.Range(func(_, _ any) bool { i++ return true }) return i > 0 } // 创建仿真对象 func CreateSimulation(projectId int32, mapIds []int32) string { simulationId := createSimulationId(projectId) _, e := simulationMap.Load(simulationId) if !e && IsExistSimulation() { panic(dto.ErrorDto{Code: dto.DataAlreadyExist, Message: "已有仿真在运行"}) } if !e { verifySimulation, err := memory.CreateSimulation(projectId, mapIds) if err != nil { panic(fmt.Sprintf("创建仿真失败:%s", err.Error())) } verifySimulation.SimulationId = simulationId //通知动力学 lineBaseInfo := buildLineBaseInfo(verifySimulation) httpCode, _, err := dynamics.SendSimulationStartReq(lineBaseInfo) if httpCode != http.StatusOK || err != nil { panic(dto.ErrorDto{Code: dto.DynamicsError, Message: fmt.Sprintf("动力学接口调用失败:[%d][%s]", httpCode, err)}) } simulationMap.Store(simulationId, verifySimulation) dynamicsRun(verifySimulation) } return simulationId } // 删除仿真对象 func DestroySimulation(simulationId string) { s, e := simulationMap.Load(simulationId) if !e { return } simulationInfo := s.(*memory.VerifySimulation) simulationMap.Delete(simulationId) // 停止ecs world simulationInfo.World.Close() //ecsSimulation.DestroySimulation(simulationInfo.WorldId) //移除道岔状态发送 dynamics.Stop() //通知动力学 httpCode, _, err := dynamics.SendSimulationEndReq() if httpCode != http.StatusOK { panic(dto.ErrorDto{Code: dto.DynamicsError, Message: fmt.Sprintf("动力学接口调用失败:[%d][%s]", httpCode, err)}) } } func createSimulationId(projectId int32) string { // 当前服务器IP + 端口 + 项目 return config.SimulationId_prefix + "_" + strconv.Itoa(config.Config.Server.Port) + "_" + strconv.Itoa(int(projectId)) } // 获取仿真列表 func ListAllSimulations() []*dto.SimulationInfoRspDto { var simArr []*dto.SimulationInfoRspDto simulationMap.Range(func(_, v any) bool { s := v.(*memory.VerifySimulation) simArr = append(simArr, &dto.SimulationInfoRspDto{ SimulationId: s.SimulationId, MapId: s.MapIds[0], MapIds: s.MapIds, ProjectId: s.ProjectId, }) return true }) return simArr } // 根据仿真id查找仿真实例 func FindSimulation(simulationId string) *memory.VerifySimulation { m, e := simulationMap.Load(simulationId) if e { return m.(*memory.VerifySimulation) } return nil } // 获取普通仿真数组 func GetSimulationArr() []*memory.VerifySimulation { var result []*memory.VerifySimulation simulationMap.Range(func(_, v any) bool { result = append(result, v.(*memory.VerifySimulation)) return true }) return result } func convert(info *dynamics.TrainInfo, sta *state.TrainState, simulation *memory.VerifySimulation) *state.TrainState { if sta.VobcState != nil && sta.VobcState.LifeSignal == int32(info.VobcLifeSignal) { sta.DynamicState.DiffTime = time.Now().UnixMilli() - sta.VobcState.UpdateTime } else { sta.DynamicState.DiffTime = 999 } slog.Debug("收到动力学原始消息", "Number", info.Number, "Link", info.Link, "LinkOffset", info.LinkOffset) id, port, offset, runDirection, pointTo, kilometer := memory.QueryDeviceByCalcLink(simulation.Repo, strconv.Itoa(int(info.Link)), int64(info.LinkOffset), info.Up) slog.Debug("处理动力学转换后的消息", "number", info.Number, "车头位置", id, "偏移", offset, "是否上行", runDirection, "是否ab", pointTo) sta.HeadDeviceId = simulation.GetComIdByUid(id) sta.DevicePort = port sta.HeadOffset = offset sta.PointTo = pointTo sta.TrainKilometer = kilometer sta.RunDirection = runDirection //判定车头方向 sta.HeadDirection = runDirection if sta.VobcState != nil { if sta.VobcState.DirectionForward { sta.HeadDirection = runDirection } else if sta.VobcState.DirectionBackward { sta.HeadDirection = !runDirection } } if info.Speed < 0 { sta.RunDirection = !sta.RunDirection } // 赋值动力学信息 sta.DynamicState.Heartbeat = int32(info.LifeSignal) sta.DynamicState.HeadLinkId = strconv.Itoa(int(info.Link)) sta.DynamicState.HeadLinkOffset = int64(info.LinkOffset) sta.DynamicState.Slope = int32(info.Slope) sta.DynamicState.Upslope = info.UpSlope sta.DynamicState.RunningUp = info.Up sta.DynamicState.RunningResistanceSum = float32(info.TotalResistance) / 1000 sta.DynamicState.AirResistance = float32(info.AirResistance) / 1000 sta.DynamicState.RampResistance = float32(info.SlopeResistance) / 1000 sta.DynamicState.CurveResistance = float32(info.CurveResistance) / 1000 sta.DynamicState.Speed = speedParse(info.Speed) sta.DynamicState.HeadSensorSpeed1 = speedParse(info.HeadSpeed1) sta.DynamicState.HeadSensorSpeed2 = speedParse(info.HeadSpeed2) sta.DynamicState.TailSensorSpeed1 = speedParse(info.TailSpeed1) sta.DynamicState.TailSensorSpeed2 = speedParse(info.TailSpeed2) sta.DynamicState.HeadRadarSpeed = speedParse(info.HeadRadarSpeed) sta.DynamicState.TailRadarSpeed = speedParse(info.TailRadarSpeed) sta.DynamicState.Acceleration = info.Acceleration return sta } // 转换成Vobc发送参数 func convertVobc(info *dynamics.TrainInfo) *vobc.SendTrainInfo { param := &vobc.SendTrainInfo{ Speed: uint16(math.Abs(float64(info.Speed * 36))), Upslope: info.UpSlope, Slope: uint16(info.Slope), TotalResistance: uint32(math.Abs(float64(info.TotalResistance / 10))), AirResistance: uint32(info.AirResistance / 10), SlopeResistance: uint32(math.Abs(float64(info.SlopeResistance / 10))), CurveResistance: uint32(info.CurveResistance / 10), } d := math.Abs(float64(info.Acceleration * 100)) if info.Acceleration > 0 { param.Acceleration = uint8(d) } else { param.Deceleration = uint8(d) } return param } func dynamicsRun(verifySimulation *memory.VerifySimulation) { _ = dynamics.Run(func() []*dynamics.TurnoutInfo { stateSlice := memory.GetAllTurnoutState(verifySimulation) var turnoutInfoSlice []*dynamics.TurnoutInfo for _, sta := range stateSlice { code64, err := strconv.ParseUint(sta.Id, 10, 16) if err != nil { slog.Error("id转uint16报错", err) } info := dynamics.TurnoutInfo{ Code: uint16(code64), NPosition: sta.Dw, RPosition: sta.Fw, } turnoutInfoSlice = append(turnoutInfoSlice, &info) } return turnoutInfoSlice }) } func buildLineBaseInfo(sim *memory.VerifySimulation) *dynamics.LineBaseInfo { info := &dynamics.LineBaseInfo{} for _, model := range sim.Repo.LinkList() { id, _ := strconv.Atoi(model.Id()) link := &dynamics.Link{ ID: int32(id), Len: int32(model.Length()), } info.LinkList = append(info.LinkList, link) if model.ARelation() != nil { turnoutId, _ := strconv.Atoi(sim.GetComIdByUid(model.ARelation().Device().Id())) link.ARelTurnoutId = int32(turnoutId) switch model.ARelation().Port() { case proto.Port_A: link.ARelTurnoutPoint = "A" case proto.Port_B: link.ARelTurnoutPoint = "B" case proto.Port_C: link.ARelTurnoutPoint = "C" } } if model.BRelation() != nil { turnoutId, _ := strconv.Atoi(sim.GetComIdByUid(model.BRelation().Device().Id())) link.BRelTurnoutId = int32(turnoutId) switch model.BRelation().Port() { case proto.Port_A: link.BRelTurnoutPoint = "A" case proto.Port_B: link.BRelTurnoutPoint = "B" case proto.Port_C: link.BRelTurnoutPoint = "C" } } } for _, model := range sim.Repo.SlopeList() { id, _ := strconv.Atoi(sim.GetComIdByUid(model.Id())) slope := &dynamics.Slope{ ID: int32(id), StartLinkOffset: int32(model.StartLinkPosition().Offset()), EndLinkOffset: int32(model.EndLinkPosition().Offset()), DegreeTrig: model.Degree(), } info.SlopeList = append(info.SlopeList, slope) startLinkId, _ := strconv.Atoi(model.StartLinkPosition().Link().Id()) slope.StartLinkId = int32(startLinkId) endLinkId, _ := strconv.Atoi(model.EndLinkPosition().Link().Id()) slope.EndLinkId = int32(endLinkId) } for _, model := range sim.Repo.SectionalCurvatureList() { id, _ := strconv.Atoi(sim.GetComIdByUid(model.Id())) curve := &dynamics.Curve{ ID: int32(id), StartLinkOffset: int32(model.StartLinkPosition().Offset()), EndLinkOffset: int32(model.EndLinkPosition().Offset()), Curvature: model.Radius(), } info.CurveList = append(info.CurveList, curve) startLinkId, _ := strconv.Atoi(model.StartLinkPosition().Link().Id()) curve.StartLinkId = int32(startLinkId) endLinkId, _ := strconv.Atoi(model.EndLinkPosition().Link().Id()) curve.EndLinkId = int32(endLinkId) } return info } // 解析VOBC列车信息 func decoderVobcTrainState(buf []byte) *state.TrainVobcState { trainVobcInfo := &state.TrainVobcState{} trainVobcInfo.LifeSignal = int32(binary.BigEndian.Uint16(buf[0:2])) b2 := buf[2] trainVobcInfo.Tc1Active = (b2 & 1) != 0 trainVobcInfo.Tc2Active = (b2 & (1 << 1)) != 0 trainVobcInfo.DirectionForward = (b2 & (1 << 2)) != 0 trainVobcInfo.DirectionBackward = (b2 & (1 << 3)) != 0 trainVobcInfo.TractionStatus = (b2 & (1 << 4)) != 0 trainVobcInfo.BrakingStatus = (b2 & (1 << 5)) != 0 trainVobcInfo.EmergencyBrakingStatus = (b2 & (1 << 6)) != 0 trainVobcInfo.TurnbackStatus = (b2 & 7) != 0 b3 := buf[3] trainVobcInfo.JumpStatus = (b3 & 1) != 0 trainVobcInfo.Ato = (b3 & (1 << 1)) != 0 trainVobcInfo.Fam = (b3 & (1 << 2)) != 0 trainVobcInfo.Cam = (b3 & (1 << 3)) != 0 trainVobcInfo.TractionSafetyCircuit = (b3 & (1 << 4)) != 0 trainVobcInfo.ParkingBrakeStatus = (b3 & (1 << 5)) != 0 trainVobcInfo.MaintainBrakeStatus = (b3 & (1 << 6)) != 0 trainVobcInfo.TractionForce = int64(binary.BigEndian.Uint16(buf[4:6])) trainVobcInfo.BrakeForce = int64(binary.BigEndian.Uint16(buf[6:8])) trainVobcInfo.TrainLoad = int64(binary.BigEndian.Uint16(buf[8:10])) b4 := buf[15] trainVobcInfo.LeftDoorOpenCommand = (b4 & 1) != 0 trainVobcInfo.RightDoorOpenCommand = (b4 & (1 << 1)) != 0 trainVobcInfo.LeftDoorCloseCommand = (b4 & (1 << 2)) != 0 trainVobcInfo.RightDoorCloseCommand = (b4 & (1 << 3)) != 0 trainVobcInfo.AllDoorClose = (b4 & (1 << 4)) != 0 trainVobcInfo.UpdateTime = time.Now().UnixMilli() return trainVobcInfo } // 发送给前端的速度格式化 func speedParse(speed float32) int32 { return int32(math.Abs(float64(speed * 3.6 * 100))) }