diff --git a/api/simulation.go b/api/simulation.go index 161d5d9..ce74a41 100644 --- a/api/simulation.go +++ b/api/simulation.go @@ -354,7 +354,7 @@ func controlTrain(c *gin.Context) { panic(sys_error.New("修改列车控制参数失败,请求参数异常", err)) } simulation := checkDeviceDataAndReturn(req.SimulationId) - memory.ControlTrainUpdate(simulation, req) + simulation.ControlTrainUpdate(req) c.JSON(http.StatusOK, "ok") } diff --git a/message_server/train_control_ms.go b/message_server/train_control_ms.go index ccdf16c..08e556d 100644 --- a/message_server/train_control_ms.go +++ b/message_server/train_control_ms.go @@ -12,19 +12,6 @@ import ( // 综合后备盘IBP消息服务 func NewTrainControlMs(vs *memory.VerifySimulation, mapId int32) ms_api.MsgTask { - /* var findMapId int32 = 0 - for _, d := range vs.MapIds { - mapData := memory.QueryGiType(d) - if mapData == data_proto.PictureType_TrainControlCab { - findMapId = d - break - } - } - - if findMapId == 0 { - slog.Error("未找到对应的列城控制图形") - return nil - }*/ return ms_api.NewScheduleTask(fmt.Sprintf("地图[%d]列车控制", mapId), func() error { allTrainMap := &vs.Memory.Status.TrainStateMap allTrainMap.Range(func(key, value any) bool { diff --git a/third_party/can_btm/balise_btm.go b/third_party/can_btm/balise_btm.go index c532d5f..9cdc5de 100644 --- a/third_party/can_btm/balise_btm.go +++ b/third_party/can_btm/balise_btm.go @@ -46,7 +46,7 @@ type btmCanetClient struct { //最近一次车载ATP系统查询帧CRC16校验结果,true-校验通过 atpReqCrc16Check bool //btm系统时间,每次接收到ATP查询请求帧时同步一次时间 - btmTime btmClock + btmTime BtmClock //数据流水号 dsn byte //重发的数据 @@ -57,6 +57,7 @@ type btmCanetClient struct { func (s *btmCanetClient) GetState() state_proto.BTMState { detector := s.baliseDetector + var telegram string info := detector.eq[len(detector.eq)-1] if /*detector.aboveBalise &&*/ info != nil && len(info.telegram) != 0 { @@ -69,17 +70,19 @@ func (s *btmCanetClient) GetState() state_proto.BTMState { BaliseCount: uint32(detector.baliseCounter), MessageCounter: uint32(detector.messageCounter), Telegram: telegram, + Distance: info.Distance, + AboveBalise: detector.aboveBalise, } } -type btmClock struct { - btmTk uint32 //与ATP系统同步的时间ms - sysTk time.Time //本地系统时间 +type BtmClock struct { + BtmTk uint32 //与ATP系统同步的时间ms + SysTk time.Time //本地系统时间 } // 获取以btmTk为基准的当前时间ms -func (c *btmClock) tkNow() uint32 { - return c.btmTk + uint32(time.Now().UnixMilli()-c.sysTk.UnixMilli()) +func (c *BtmClock) TkNow() uint32 { + return c.BtmTk + uint32(time.Now().UnixMilli()-c.SysTk.UnixMilli()) } type BtmCanetClient interface { @@ -87,7 +90,7 @@ type BtmCanetClient interface { Stop() //HandleTrainHeadPositionInfo 处理收到列车位置信息 HandleTrainHeadPositionInfo(w ecs.World, h *TrainHeadPositionInfo) - //获取BTM显示状态 + //获取BTM显示状态 + btm最新的状态 GetState() state_proto.BTMState } @@ -156,7 +159,7 @@ func (s *btmCanetClient) handleCanetFrames(cfs []byte) { dms := make([]*message.CanetFrame, 0, 16) //13个应答器报文数据帧+TimeA帧+TimeB帧+结束帧 for cfi := 0; cfi < cfSum; cfi++ { cfStart := cfi * 13 - cf := message.NewCanetFrame(cfs[cfStart : cfStart+13]) + cf := message.NewCanetFrame(cfs[cfStart:cfStart+13], false) // switch cf.CanFrameType() { case message.CfReq: @@ -206,8 +209,8 @@ func (s *btmCanetClient) dealWithAptReq(f *message.CanetFrame) { //处理查询请求 //slog.Debug(fmt.Sprintf("处理查询请求:%s", atpReq.String())) // - s.btmTime.btmTk = atpReq.Time - s.btmTime.sysTk = now + s.btmTime.BtmTk = atpReq.Time + s.btmTime.SysTk = now s.atpReqSn = atpReq.FId.ID4 s.atpReqCrc16Check = atpReq.Crc16CheckOk s.baliseDetector.powerAmplifierSwitch = atpReq.PowerAmplifierTurnOn @@ -256,14 +259,14 @@ func (s *btmCanetClient) rspResendToAtp() { // 当未收到应答器报文时响应:时间同步帧、状态应答帧 func (s *btmCanetClient) rspToAtp(sb *BtmAntennaScanningBaliseInfo) { //BTM状态 - statusF := message.NewBtmStatusRspFrame(s.atpReqSn) + statusF := message.NewBtmStatusRspFrame(s.atpReqSn, false) btmStatus := s.createTrainBtmStatus() statusF.AntennaFault = btmStatus.AntennaFault statusF.BaliseCounter = byte(btmStatus.BaliseCounter) statusF.MessageCounter = byte(btmStatus.MessageCounter) statusF.PowerAmplifierOn = btmStatus.PowerAmplifierOn - statusF.TkTimeA = s.btmTime.tkNow() + statusF.TkTimeA = s.btmTime.TkNow() statusF.PowerAmplifierFailure = btmStatus.PowerAmplifierFault statusF.DetailedCode = 0 if btmStatus.AboveBalise { @@ -271,16 +274,17 @@ func (s *btmCanetClient) rspToAtp(sb *BtmAntennaScanningBaliseInfo) { } statusF.AtpReqCrcCheckWrong = !s.atpReqCrc16Check statusF.Dsn = s.dsn + s.dsnAdd1() // //true-收到应答器报文 isRcvTelegram := sb != nil && len(sb.telegram) > 0 if isRcvTelegram { //当收到应答器报文时响应:时间同步帧、状态应答帧、数据帧 - statusDataCf, statusDataCfOk := message.CreateBtmRspFramesData(statusF, sb.telegram, false, s.btmTime.tkNow(), s.btmTime.tkNow(), s.btmTime.tkNow()) + statusDataCf, statusDataCfOk := message.CreateBtmRspFramesData(statusF, sb.telegram, false, s.btmTime.TkNow(), s.btmTime.TkNow(), s.btmTime.TkNow(), false) if statusDataCfOk { - timeSyncF := message.NewBtmTimeSyncCheckFrame(s.atpReqSn) - timeSyncF.T2 = s.btmTime.btmTk - timeSyncF.T3 = s.btmTime.tkNow() + timeSyncF := message.NewBtmTimeSyncCheckFrame(s.atpReqSn, false) + timeSyncF.T2 = s.btmTime.BtmTk + timeSyncF.T3 = s.btmTime.TkNow() s.sendCanetFrame(timeSyncF.Encode().Encode()) // s.resendData = newResendData(statusDataCf) @@ -289,9 +293,9 @@ func (s *btmCanetClient) rspToAtp(sb *BtmAntennaScanningBaliseInfo) { slog.Warn("BtmCanetClient应答帧、数据帧编码失败") } } else { //当未收到应答器报文时响应:时间同步帧、状态应答帧 - timeSyncF := message.NewBtmTimeSyncCheckFrame(s.atpReqSn) - timeSyncF.T2 = s.btmTime.btmTk - timeSyncF.T3 = s.btmTime.tkNow() + timeSyncF := message.NewBtmTimeSyncCheckFrame(s.atpReqSn, false) + timeSyncF.T2 = s.btmTime.BtmTk + timeSyncF.T3 = s.btmTime.TkNow() s.sendCanetFrame(timeSyncF.Encode().Encode()) // statusCf := statusF.Encode().Encode() diff --git a/third_party/can_btm/balise_detection.go b/third_party/can_btm/balise_detection.go index cdf7b01..c0c9f75 100644 --- a/third_party/can_btm/balise_detection.go +++ b/third_party/can_btm/balise_detection.go @@ -41,6 +41,7 @@ type BtmAntennaScanningBaliseInfo struct { Time time.Time //应答器预计被BTM天线激活的时刻 active bool //true-激活过,即列车扫过 telegram []byte //应答器报文 + Distance int64 //BTM天线中心到应答器的距离,mm } // BaliseDetector 车载BTM天线,应答器探测器 @@ -185,7 +186,7 @@ func (t *BaliseDetector) timeScanNearestBalise(curTime time.Time, wd *component. s := float64(expectedBalise.Distance) / 1000 st, ok := t.calculateBtmAntennaScanNextBaliseTime(curTime, curV, curAc, s) if ok { - return &BtmAntennaScanningBaliseInfo{BaliseId: expectedBalise.BaliseId, Time: st} + return &BtmAntennaScanningBaliseInfo{BaliseId: expectedBalise.BaliseId, Time: st, Distance: expectedBalise.Distance} } } return nil diff --git a/third_party/message/can_btm_data.go b/third_party/message/can_btm_data.go index f9dcd85..5feff58 100644 --- a/third_party/message/can_btm_data.go +++ b/third_party/message/can_btm_data.go @@ -8,16 +8,19 @@ type BtmDataMessageFrame struct { //帧ID FId CanFrameId //8字节,应答器报文片段 - Message []byte + Message []byte + IsTrainPcSim bool } -func NewBtmDataMessageFrame(sn byte, offset byte) *BtmDataMessageFrame { +func NewBtmDataMessageFrame(sn byte, offset byte, isTrainPcSim bool) *BtmDataMessageFrame { return &BtmDataMessageFrame{ - FId: *NewCanFrameId(CAN_ADDR_RSP_ATP, CAN_ADDR_RSP_BTM, 0x80+offset, sn), + FId: *NewCanFrameId(CAN_ADDR_RSP_ATP, CAN_ADDR_RSP_BTM, 0x80+offset, sn), + IsTrainPcSim: isTrainPcSim, } } func (f *BtmDataMessageFrame) Encode() *CanetFrame { cf := &CanetFrame{} + cf.IsTrainPcSim = f.IsTrainPcSim cf.CanId = f.FId cf.CanLen = 8 cf.FF = true @@ -59,16 +62,19 @@ type BtmDataMessageTimeAFrame struct { //时间戳A TimeA uint32 //CRC - Crc32A uint32 + Crc32A uint32 + IsTrainPcSim bool } -func NewBtmDataMessageTimeAFrame(sn byte) *BtmDataMessageTimeAFrame { +func NewBtmDataMessageTimeAFrame(sn byte, isTrainPcSim bool) *BtmDataMessageTimeAFrame { return &BtmDataMessageTimeAFrame{ - FId: *NewCanFrameId(CAN_ADDR_RSP_ATP, CAN_ADDR_RSP_BTM, 0x80+0x0d, sn), + FId: *NewCanFrameId(CAN_ADDR_RSP_ATP, CAN_ADDR_RSP_BTM, 0x80+0x0d, sn), + IsTrainPcSim: isTrainPcSim, } } func (f *BtmDataMessageTimeAFrame) Encode() *CanetFrame { cf := &CanetFrame{} + cf.IsTrainPcSim = f.IsTrainPcSim cf.CanId = f.FId cf.CanLen = 8 cf.FF = true @@ -161,16 +167,19 @@ type BtmDataMessageTimeBFrame struct { //时间戳B TimeB uint32 //CRC - Crc32B uint32 + Crc32B uint32 + IsTrainPcSim bool } -func NewBtmDataMessageTimeBFrame(sn byte) *BtmDataMessageTimeBFrame { +func NewBtmDataMessageTimeBFrame(sn byte, isTrainPcSim bool) *BtmDataMessageTimeBFrame { return &BtmDataMessageTimeBFrame{ - FId: *NewCanFrameId(CAN_ADDR_RSP_ATP, CAN_ADDR_RSP_BTM, 0x80+0x0e, sn), + FId: *NewCanFrameId(CAN_ADDR_RSP_ATP, CAN_ADDR_RSP_BTM, 0x80+0x0e, sn), + IsTrainPcSim: isTrainPcSim, } } func (f *BtmDataMessageTimeBFrame) Encode() *CanetFrame { cf := &CanetFrame{} + cf.IsTrainPcSim = f.IsTrainPcSim cf.CanId = f.FId cf.CanLen = 8 cf.FF = true @@ -263,16 +272,19 @@ type BtmDataMessageEndFrame struct { TkB uint32 //CRC32C校验包含对状态应答帧与数据帧的总校验,总共为8×17-4=132字节,4-即Crc32C //1个状态应答帧 + 13个BtmDataMessageFrame + 1个BtmDataMessageTimeAFrame + 1个BtmDataMessageTimeBFrame + 1个BtmDataMessageEndFrame - Crc32C uint32 + Crc32C uint32 + IsTrainPcSim bool } -func NewBtmDataMessageEndFrame(sn byte) *BtmDataMessageEndFrame { +func NewBtmDataMessageEndFrame(sn byte, isTrainPcSim bool) *BtmDataMessageEndFrame { return &BtmDataMessageEndFrame{ - FId: *NewCanFrameId(CAN_ADDR_RSP_ATP, CAN_ADDR_RSP_BTM, 0x80+0x7f, sn), + FId: *NewCanFrameId(CAN_ADDR_RSP_ATP, CAN_ADDR_RSP_BTM, 0x80+0x7f, sn), + IsTrainPcSim: isTrainPcSim, } } func (f *BtmDataMessageEndFrame) Encode() *CanetFrame { cf := &CanetFrame{} + cf.IsTrainPcSim = f.IsTrainPcSim cf.CanId = f.FId cf.CanLen = 8 cf.FF = true diff --git a/third_party/message/can_btm_rsp.go b/third_party/message/can_btm_rsp.go index bff40ff..c3b28ae 100644 --- a/third_party/message/can_btm_rsp.go +++ b/third_party/message/can_btm_rsp.go @@ -8,16 +8,17 @@ import "log/slog" // 共17帧,17X12个字节,每个帧12字节 // msg - 应答器报文 // msgPackError - true BTM解包发生错误,则数据帧及CRC32A/B全填“0xFF” -func CreateBtmRspFramesData(statusRsp *BtmStatusRspFrame, msg []byte, msgPackError bool, msgTimeA uint32, msgTimeB uint32, tkTimeB uint32) ([]byte, bool) { +func CreateBtmRspFramesData(statusRsp *BtmStatusRspFrame, msg []byte, msgPackError bool, msgTimeA uint32, msgTimeB uint32, tkTimeB uint32, isTrainPcSim bool) ([]byte, bool) { if len(msg) > 104 { //数据帧最多存储13*8个字节 return nil, false } //最近一次ATP查询请求序列号 sn := statusRsp.FId.ID4 //13个BtmDataMessageFrame [0x00,0x0c] + //数据 dms := make([]*BtmDataMessageFrame, 13) for mr := 0x00; mr <= 0x0c; mr++ { - dms[mr] = NewBtmDataMessageFrame(sn, byte(mr)) + dms[mr] = NewBtmDataMessageFrame(sn, byte(mr), isTrainPcSim) dms[mr].Message = make([]byte, 8) //8字节数组,默认值0 // if !msgPackError { @@ -38,7 +39,7 @@ func CreateBtmRspFramesData(statusRsp *BtmStatusRspFrame, msg []byte, msgPackErr } } // - dtA := NewBtmDataMessageTimeAFrame(sn) + dtA := NewBtmDataMessageTimeAFrame(sn, isTrainPcSim) dtA.TimeA = msgTimeA if !msgPackError { var crc32AData []byte @@ -49,7 +50,7 @@ func CreateBtmRspFramesData(statusRsp *BtmStatusRspFrame, msg []byte, msgPackErr dtA.Crc32A = 0xff_ff_ff_ff } // - dtB := NewBtmDataMessageTimeBFrame(sn) + dtB := NewBtmDataMessageTimeBFrame(sn, isTrainPcSim) dtB.TimeB = msgTimeB if !msgPackError { var crc32BData []byte @@ -60,7 +61,7 @@ func CreateBtmRspFramesData(statusRsp *BtmStatusRspFrame, msg []byte, msgPackErr dtB.Crc32B = 0xff_ff_ff_ff } // - end := NewBtmDataMessageEndFrame(sn) + end := NewBtmDataMessageEndFrame(sn, isTrainPcSim) end.TkB = tkTimeB // statusCf := statusRsp.Encode() diff --git a/third_party/message/can_btm_status_rsp.go b/third_party/message/can_btm_status_rsp.go index 053554f..3661f47 100644 --- a/third_party/message/can_btm_status_rsp.go +++ b/third_party/message/can_btm_status_rsp.go @@ -26,12 +26,14 @@ type BtmStatusRspFrame struct { //BTM detailed code DetailedCode byte //有具体报文内容时TK A时刻填充为CD信号上升沿时刻,报文全零时TK A时刻填充为CD信号下降沿时刻。 - TkTimeA uint32 + TkTimeA uint32 + IsTrainPcSim bool } -func NewBtmStatusRspFrame(sn byte) *BtmStatusRspFrame { +func NewBtmStatusRspFrame(sn byte, isTrainPcSim bool) *BtmStatusRspFrame { return &BtmStatusRspFrame{ - FId: *NewCanFrameId(CAN_ADDR_RSP_ATP, CAN_ADDR_RSP_BTM, CAN_FRAME_STATUS_RSP, sn), + FId: *NewCanFrameId(CAN_ADDR_RSP_ATP, CAN_ADDR_RSP_BTM, CAN_FRAME_STATUS_RSP, sn), + IsTrainPcSim: isTrainPcSim, } } func (f *BtmStatusRspFrame) Decode(cf *CanetFrame) bool { @@ -113,6 +115,7 @@ func (f *BtmStatusRspFrame) Decode(cf *CanetFrame) bool { } func (f *BtmStatusRspFrame) Encode() *CanetFrame { cf := &CanetFrame{} + cf.IsTrainPcSim = f.IsTrainPcSim cf.CanId = f.FId cf.CanLen = 8 cf.FF = true diff --git a/third_party/message/can_btm_time_sync_rsp.go b/third_party/message/can_btm_time_sync_rsp.go index 6cb40fd..20fbbfe 100644 --- a/third_party/message/can_btm_time_sync_rsp.go +++ b/third_party/message/can_btm_time_sync_rsp.go @@ -14,12 +14,14 @@ type BtmTimeSyncCheckFrame struct { T2 uint32 //T3时刻 //BTM将在T3时刻发送回复帧给ATP,并保持流水号与ATP查询帧一致。 - T3 uint32 + T3 uint32 + IsTrainPcSim bool } -func NewBtmTimeSyncCheckFrame(sn byte) *BtmTimeSyncCheckFrame { +func NewBtmTimeSyncCheckFrame(sn byte, isTrainPcSim bool) *BtmTimeSyncCheckFrame { return &BtmTimeSyncCheckFrame{ - FId: *NewCanFrameId(CAN_ADDR_RSP_ATP, CAN_ADDR_RSP_BTM, CAN_FRAME_TIME_SYNC_RSP, sn), + FId: *NewCanFrameId(CAN_ADDR_RSP_ATP, CAN_ADDR_RSP_BTM, CAN_FRAME_TIME_SYNC_RSP, sn), + IsTrainPcSim: isTrainPcSim, } } @@ -76,6 +78,7 @@ func (f *BtmTimeSyncCheckFrame) Decode(cf *CanetFrame) bool { } func (f *BtmTimeSyncCheckFrame) Encode() *CanetFrame { cf := &CanetFrame{} + cf.IsTrainPcSim = f.IsTrainPcSim cf.CanId = f.FId cf.CanLen = 8 cf.FF = true diff --git a/third_party/message/can_net.go b/third_party/message/can_net.go index 746ffc8..35b5dcb 100644 --- a/third_party/message/can_net.go +++ b/third_party/message/can_net.go @@ -8,30 +8,33 @@ import ( // CanetFrame USR-CANET200_V1.0.7 设备 以太网-CAN 透传协议帧(13字节) type CanetFrame struct { - FF bool //true-1扩展帧 - RTR bool //true-1远程帧 - CanLen byte //CAN帧数据长度[0,8],CanData中有效数据字节数 - CanId CanFrameId //CAN帧ID - CanData []byte //CAN帧数据,8字节 + FF bool //true-1扩展帧 + RTR bool //true-1远程帧 + CanLen byte //CAN帧数据长度[0,8],CanData中有效数据字节数 + CanId CanFrameId //CAN帧ID + CanData []byte //CAN帧数据,8字节 + IsTrainPcSim bool //是否列车pc仿真 } -func NewCanetFrame(buf []byte) *CanetFrame { - cf := &CanetFrame{} +func NewCanetFrame(buf []byte, isTrainPcSim bool) *CanetFrame { + cf := &CanetFrame{IsTrainPcSim: isTrainPcSim} cf.Decode(buf) return cf } func (p *CanetFrame) Encode() []byte { - buf := make([]byte, 0, 13) - //canet200帧信息 - b1 := byte(0x00) - if p.FF { - b1 |= 0x80 //bit7 + buf := make([]byte, 0) + if !p.IsTrainPcSim { + //canet200帧信息 + b1 := byte(0x00) + if p.FF { + b1 |= 0x80 //bit7 + } + if p.RTR { + b1 |= 0x40 //bit6 + } + b1 |= p.CanLen & 0x0f + buf = append(buf, b1) } - if p.RTR { - b1 |= 0x40 //bit6 - } - b1 |= p.CanLen & 0x0f - buf = append(buf, b1) //CAN 帧ID buf = append(buf, p.CanId.ID1) buf = append(buf, p.CanId.ID2) @@ -44,13 +47,17 @@ func (p *CanetFrame) Encode() []byte { buf = append(buf, p.CanData...) return buf } + func (p *CanetFrame) Decode(buf []byte) { if len(buf) != 13 { panic("len(buf)!=13") } // - p.FF = buf[0]&0x80 == 0x80 - p.RTR = buf[0]&0x40 == 0x40 + if !p.IsTrainPcSim { + p.FF = buf[0]&0x80 == 0x80 + p.RTR = buf[0]&0x40 == 0x40 + } + p.CanLen = buf[0] & 0x0f //1 2 3 4 p.CanId.ID1 = buf[1] @@ -62,8 +69,8 @@ func (p *CanetFrame) Decode(buf []byte) { } func (p *CanetFrame) String() string { sb := strings.Builder{} - sb.WriteString(fmt.Sprintf("CanetFrame FF = %t, RTR = %t, CanLen = %d, ID1 = 0x%0x, ID2 = 0x%0x, ID3 = 0x%0x, ID4 = 0x%0x,", p.FF, p.RTR, p.CanLen, - p.CanId.ID1, p.CanId.ID2, p.CanId.ID3, p.CanId.ID4)) + sb.WriteString(fmt.Sprintf("CanetFrame FF = %t, RTR = %t, CanLen = %d, ID1 = 0x%0x, ID2 = 0x%0x, ID3 = 0x%0x, ID4 = 0x%0x, isTrainPcSim=%v", p.FF, p.RTR, p.CanLen, + p.CanId.ID1, p.CanId.ID2, p.CanId.ID3, p.CanId.ID4, p.IsTrainPcSim)) sb.WriteString("CanData = ") for _, d := range p.CanData { sb.WriteString(fmt.Sprintf(" 0x%0x ", d)) diff --git a/third_party/train_pc_sim/train_pc_sim.go b/third_party/train_pc_sim/train_pc_sim.go new file mode 100644 index 0000000..f5b43b0 --- /dev/null +++ b/third_party/train_pc_sim/train_pc_sim.go @@ -0,0 +1,191 @@ +package train_pc_sim + +import ( + "context" + "fmt" + "joylink.club/bj-rtsts-server/config" + "joylink.club/bj-rtsts-server/dto/state_proto" + "joylink.club/bj-rtsts-server/third_party/tcp" + "joylink.club/bj-rtsts-server/ts/simulation/wayside/memory" + "joylink.club/ecs" + "log/slog" + "sync" + "time" +) + +type TrainPcSim interface { + Start(pcSimManage TrainPcSimManage) + Stop() + SendDriverActive(tc *state_proto.TrainConnState, vobc *state_proto.TrainVobcState) + SendHandleSwitch(oldTraction, oldBrakeForce int64, tc *state_proto.TrainConnState, vobc *state_proto.TrainVobcState) + SendBaliseData(msgType uint16, data []byte) + PublishTrainControlEvent(world ecs.World, events []TrainControlEvent) +} + +type TrainPcSimManage interface { + GetTrainPcSimConfig() config.VehiclePCSimConfig + //4.4.1. 车载输出数字量信息报文内容 + TrainPcSimDigitalOutInfoHandle(data []byte) + //4.4.2. 车载输出数字反馈量信息报文内容 + TrainPcSimDigitalReportHandle(data []byte) + //创建/删除列车 + TrainPcSimConnOrRemoveHandle(state byte) + //门模式 + TrainDoorModeHandle(state byte) + TrainPcSimMockInfo(data []byte) + + TrainBtmQuery(data []byte) +} + +var ( + initLock = &sync.Mutex{} + singleObj *trainPcSimService +) + +func Default() TrainPcSim { + defer initLock.Unlock() + initLock.Lock() + if singleObj == nil { + singleObj = &trainPcSimService{} + } + return singleObj +} + +type trainPcSimService struct { + pcSimClient *tcp.TcpClient + cancleContext context.CancelFunc + trainPcSimManage TrainPcSimManage +} + +func (pc *trainPcSimService) Start(pcSimManage TrainPcSimManage) { + config := pcSimManage.GetTrainPcSimConfig() + if config.PcSimIp == "" || !config.Open { + slog.Info("车载pc仿真配置未开启") + return + } + + client, err := tcp.StartTcpClient(fmt.Sprintf("%v:%v", config.PcSimIp, config.PcSimPort), pc.reivceData) + if err != nil { + slog.Error("连接车载pc平台失败", err) + return + } + pc.pcSimClient = client + ctx, ctxFun := context.WithCancel(context.Background()) + pc.cancleContext = ctxFun + pc.trainPcSimManage = pcSimManage + vs := pcSimManage.(*memory.VerifySimulation) + FireTrainControlEventType.Subscribe(vs.World, pc.trainControlEventHandle) + + go pc.sendTrainLocationAndSpeedTask(ctx) +} +func (pc *trainPcSimService) Stop() { + if pc.cancleContext != nil { + pc.cancleContext() + pc.cancleContext = nil + } + pc.pcSimClient.Close() +} + +// 依据文档80ms发送列车速度位置 +func (pc *trainPcSimService) sendTrainLocationAndSpeedTask(ctx context.Context) { + for { + select { + case <-ctx.Done(): + return + default: + } + /* trainStatus := acc.radarVobcManager.FindAccTrain() + if trainStatus != nil { + speedAcc := trainStatus.DynamicState.Acceleration + t := speedAcc / accSpeedUnit + acc.vobcClient.SendMsg(&message.Accelerometer{Acc: math.Float32bits(t)}) + }*/ + time.Sleep(time.Millisecond * 80) + } +} +func (pc *trainPcSimService) SendDriverActive(tc *state_proto.TrainConnState, vobc *state_proto.TrainVobcState) { + if tc.Conn && tc.ConnType == state_proto.TrainConnState_PC_SIM { + defulatBuf := make([]byte, 0) + msg := &TrainPcSimBaseMessage{Data: defulatBuf} + if vobc.Tc1Active || vobc.Tc2Active { + msg.Type = SENDER_TRAIN_TC_ACTIVE + } else if vobc.Tc1Active == false && vobc.Tc2Active == false { + msg.Type = SENDER_TRAIN_TC_NOT_ACTIVE + } + pc.pcSimClient.Send(msg.Encode()) + } +} +func (pc *trainPcSimService) SendHandleSwitch(oldTraction, oldBrakeForce int64, tc *state_proto.TrainConnState, vobc *state_proto.TrainVobcState) { + if tc.Conn && tc.ConnType == state_proto.TrainConnState_PC_SIM { + msg := &TrainPcSimBaseMessage{} + newTraction := vobc.TractionForce + newBrake := vobc.BrakeForce + if newTraction <= oldTraction && newTraction == 0 { + //手柄取消前进 + msg.Type = RECIVE_TRAIN_HAND_KEY_CANCLE_FORWARD + } else if newTraction > oldTraction { + //手柄前进 + msg.Type = SENDER_TRAIN_HAND_KEY_FORWARD + } else if newBrake >= oldBrakeForce && newBrake == 0 { + //手柄取消后退 + msg.Type = RECIVE_TRAIN_HAND_KEY_CACLE_BACKWARD + } else if newBrake < oldBrakeForce { + //手柄后退 + msg.Type = RECIVE_TRAIN_HAND_KEY_BACKWARD + } else { + slog.Error("") + return + } + pc.pcSimClient.Send(msg.Encode()) + } +} + +func (pc *trainPcSimService) SendBaliseData(msgType uint16, data []byte) { + msg := &TrainPcSimBaseMessage{} + msg.Type = msgType + msg.Data = data + pc.pcSimClient.Send(msg.Encode()) +} + +func (pc *trainPcSimService) trainControlEventHandle(w ecs.World, event TrainControlEvent) { + msg := &TrainPcSimBaseMessage{} + msg.Type = SENDER_TRAIN_OUTR_INFO + data := []byte{event.Command, event.Status} + msg.Data = data + pc.pcSimClient.Send(msg.Encode()) +} +func (pc *trainPcSimService) PublishTrainControlEvent(world ecs.World, events []TrainControlEvent) { + if len(events) <= 0 { + slog.Warn("") + return + } + for _, event := range events { + FireTrainControlEventType.Publish(world, &event) + } +} + +// 接受来自pc仿真的消息 +func (pc *trainPcSimService) reivceData(len int, data []byte) { + baseMsg := &TrainPcSimBaseMessage{} + err := baseMsg.Decode(data) + if err != nil { + slog.Error("车载pc仿真接受数据解析失败 ") + return + } + + switch baseMsg.Type { + case RECIVE_TRAIN_CREATE_REMOVE: + pc.trainPcSimManage.TrainPcSimConnOrRemoveHandle(data[0]) + case RECIVE_TRAIN_INTERFACE_CABINET_OUTR: + pc.trainPcSimManage.TrainPcSimDigitalOutInfoHandle(data) + case RECIVE_TRAIN_INTERFACE_CABINET_OUTR_BACK: + pc.trainPcSimManage.TrainPcSimDigitalReportHandle(data) + case RECIVE_TRAIN_QUERY_STATUS: + pc.trainPcSimManage.TrainBtmQuery(data) + case RECIVE_TRAIN_MOCK_DATA: + pc.trainPcSimManage.TrainPcSimMockInfo(data) + case RECIVE_TRAIN_DOOR_MODE: + pc.trainPcSimManage.TrainDoorModeHandle(data[0]) + } + +} diff --git a/third_party/train_pc_sim/train_pc_sim_message.go b/third_party/train_pc_sim/train_pc_sim_message.go new file mode 100644 index 0000000..a7a1a24 --- /dev/null +++ b/third_party/train_pc_sim/train_pc_sim_message.go @@ -0,0 +1,95 @@ +package train_pc_sim + +import ( + "bytes" + "encoding/binary" + "fmt" + "github.com/snksoft/crc" + "joylink.club/bj-rtsts-server/third_party/message" +) + +const PC_SIM_HEADER = 0xEB + +type TrainPcSimBaseMessage struct { + Type uint16 + DataLen byte + Data []byte + Crc uint16 // 校验码 +} + +// 编码 +func (tp *TrainPcSimBaseMessage) Encode() []byte { + pack := make([]byte, 0) + pack = append(pack, PC_SIM_HEADER) + pack = binary.BigEndian.AppendUint16(pack, tp.Type) + pack = append(pack, byte(len(tp.Data))) + dataBufs := bytes.NewBuffer(tp.Data) + binary.Write(dataBufs, binary.BigEndian, tp.Data) + data := dataBufs.Bytes() + pack = append(pack, data...) + pack = binary.BigEndian.AppendUint16(pack, uint16(crc.CalculateCRC(crc.CRC16, data))) + return pack +} + +// 解码 +func (tp *TrainPcSimBaseMessage) Decode(data []byte) error { + if len(data) < 3 { + return fmt.Errorf("") + } + buf := bytes.NewBuffer(data) + h, _ := buf.ReadByte() + if h != PC_SIM_HEADER { + return fmt.Errorf("") + } + var pcType uint16 + binary.Read(buf, binary.BigEndian, &pcType) + len, _ := buf.ReadByte() + if buf.Len() < int(len)+2 { + return fmt.Errorf("") + } + var dd = make([]byte, 0, len) + binary.Read(buf, binary.BigEndian, &dd) + var crcCode uint16 + binary.Read(buf, binary.BigEndian, &crcCode) + tp.Type = pcType + tp.DataLen = len + tp.Data = dd + tp.Crc = crcCode + return nil + //crc.CalculateCRC(crc.CRC16, data) +} + +func IsTrue(d bool) byte { + if d { + return 1 + } + return 0 +} +func IsTrueForByte(d byte) bool { + if d == 1 { + return true + } + return false +} + +// 列车pc仿真btm查询 +type TrainPcSimBtmQueryFrame struct { + message.CanetFrame //这里不是用 can 网络的前2帧 +} + +// func (pc *TrainPcSimBtmQueryFrame) GetCanFrameId() message.CanFrameId { +// return pc.CanId +// } +// +// func (pc *TrainPcSimBtmQueryFrame) GetCanFrameData() []byte { +// return pc.CanData +// } +func (q *TrainPcSimBtmQueryFrame) Decode(d []byte) error { + q.CanId.ID1 = d[0] + q.CanId.ID2 = d[1] + q.CanId.ID3 = d[2] + q.CanId.ID4 = d[3] >> 3 + // + q.CanData = d[4:] + return nil +} diff --git a/third_party/train_pc_sim/train_pc_sim_message_sender.go b/third_party/train_pc_sim/train_pc_sim_message_sender.go new file mode 100644 index 0000000..9537197 --- /dev/null +++ b/third_party/train_pc_sim/train_pc_sim_message_sender.go @@ -0,0 +1,103 @@ +package train_pc_sim + +import ( + "joylink.club/ecs" +) + +const ( + //钥匙开关状态 + KEY_STATE = iota + //手柄向前控制 + HANDLE_FORWORD + //手柄向后控制 + HANDLE_BACKWORD + //外部紧急制动反馈 + OUTER_EMERGENCY_BRAKE + //列车制动状态 + TRAIN_BRAKE_STATE + //开左门 + LEFT_OPEN_DOOR + //关右门 + CLOSE_RIGHT_DOOR + //关左门 + CLOSE_LEFT_DOOR + //开右门 + OPEN_RIGHT_DOOR + //折返按钮 + TURN_BACK + //强制门允许 + FORCE_DOOR_ALLOW + //模式降级按钮 + TRAIN_MODE_DOWN + + //确认按钮 + CONFIRM + //模式升级按钮 + TRAIN_MODE_UP + //牵引制动手柄零位 + HANDLE_TO_ZERO + //ATO发车按钮 + ATO_SEND_TRAIN + + //列车完整性 + TRAIN_INTEGRITY + //车载ATP/ATO旁路状态 + ATP_ATO_BYPASS_STATe + //车辆牵引已切除状态 + TRAIN_TRACTION_CUTED + //障碍物检测按钮 + OBSTACLE_CHECK + //驾驶室激活反馈按钮 + DRIVER_ACTIVE_REPORT + //制动重故障按钮 + BRAKE_HEAVY_FAULT + //左门状态按钮 + LEFT_DOOR_STATE + //右门状态按钮 + RIGHT_DOOR_STATE + //唤醒按钮 + WAKE_UP + //检修按钮 + OVERHAUL + //欠压按钮 + UNDERVOLTAGE + //休眠按钮 + SLEEP + _ + //紧急手柄拉下 + EMERGENT_HANDLE_DOWN + //车门锁闭状态 + DOOR_LOCK_STATE + //逃生门状态 + LIFE_DOOR + //车辆低压上电状态 + TRAIN_LOW_POWER + //ATP上电按钮 + ATP_POWER_ON + _ + //AA自动开关门 + DOOR_MODE_AA + + //AM自开人关 + DOOR_MODE_AM + //MM人开人关 + DOOR_MODE_MM +) + +type TrainControlEvent struct { + Command byte + Status byte +} + +var FireTrainControlEventType = ecs.NewEventType[TrainControlEvent]() + +// 应答器组内序号 +// 唯一标识组内一个应答器,目前固定为1 +const PC_SIM_BALISE_NUM uint32 = 1 + +// 4.3.3. 应答器信息报文内容 +type TrainPcSimBtm struct { + BaliseId uint32 //应答器组ID + BaliseLocation float32 //应答器与车头发车位置的距离,单位cm + BaliseData []byte //有数据的CAN数据报文 +} diff --git a/third_party/train_pc_sim/train_pc_sim_message_type.go b/third_party/train_pc_sim/train_pc_sim_message_type.go new file mode 100644 index 0000000..28c8912 --- /dev/null +++ b/third_party/train_pc_sim/train_pc_sim_message_type.go @@ -0,0 +1,49 @@ +package train_pc_sim + +const ( + //仿真系统车载pc仿真平台 + + //车辆输出数字量信息 + SENDER_TRAIN_OUTR_INFO = 0x00 + //速度位置信息 + sender_train_location_info = 0x01 + //驾驶室激活 + SENDER_TRAIN_TC_ACTIVE = 0x04 + //驾驶室未激活 + SENDER_TRAIN_TC_NOT_ACTIVE = 0x05 + + //手柄前进 + SENDER_TRAIN_HAND_KEY_FORWARD = 0x06 + + //车载pc仿真平台 仿真系统 + + //手柄取消前进(注1) + RECIVE_TRAIN_HAND_KEY_CANCLE_FORWARD = 0x07 + //手柄后退 + RECIVE_TRAIN_HAND_KEY_BACKWARD = 0x08 + //手柄取消后退(注1) + RECIVE_TRAIN_HAND_KEY_CACLE_BACKWARD = 0x09 + //有数据应答器 + RECIVE_TRAIN_BTM_HAS_DATA = 0x31 + + //无数据应答器 + RECIVE_TRAIN_BTM_NOT_DATA = 0x32 + + //清空所有预发应答器 + recive_train_btn_clear_all_pre_data = 0x33 + //创建/删除列车 + RECIVE_TRAIN_CREATE_REMOVE = 0x50 + + //门模式 + RECIVE_TRAIN_DOOR_MODE = 0x60 + + //车载接口柜输出数字量信息 + RECIVE_TRAIN_INTERFACE_CABINET_OUTR = 0x01 + //车载接口柜输出数字反馈量信息 + RECIVE_TRAIN_INTERFACE_CABINET_OUTR_BACK = 0x02 + //电流环模拟量 + RECIVE_TRAIN_MOCK_DATA = 0x03 + + //状态查询帧 + RECIVE_TRAIN_QUERY_STATUS = 0x04 +) diff --git a/ts/simulation/wayside/memory/wayside_memory_train.go b/ts/simulation/wayside/memory/wayside_memory_train.go index e0f822b..42c76c6 100644 --- a/ts/simulation/wayside/memory/wayside_memory_train.go +++ b/ts/simulation/wayside/memory/wayside_memory_train.go @@ -139,7 +139,7 @@ func TrainConnTypeUpdate(vs *VerifySimulation, ct *dto.TrainConnThirdDto) { }) } train := data.(*state_proto.TrainState) - train.ConnState.Conn = conn + //train.ConnState.Conn = conn train.ConnState.ConnType = ct.ConnType } diff --git a/ts/simulation/wayside/memory/wayside_memory_train_control.go b/ts/simulation/wayside/memory/wayside_memory_train_control.go deleted file mode 100644 index ca8400f..0000000 --- a/ts/simulation/wayside/memory/wayside_memory_train_control.go +++ /dev/null @@ -1,118 +0,0 @@ -package memory - -import ( - "fmt" - "joylink.club/bj-rtsts-server/dto/request_proto" - "joylink.club/bj-rtsts-server/dto/state_proto" - "joylink.club/bj-rtsts-server/sys_error" -) - -/*func createTrainControl(vs *VerifySimulation) *state_proto.TrainControlState { - var tccMapId int32 - for _, mapId := range vs.MapIds { - picProType := QueryGiType(mapId) - if picProType == data_proto.PictureType_TrainControlCab { - tccMapId = mapId - break - } - } - if tccMapId == 0 { - panic(sys_error.New("未找到列车控制相关图形数据")) - } - tcs := &state_proto.TrainControlState{ - ConnState: &state_proto.TrainConnState{Conn: false, ConnType: state_proto.TrainConnState_NONE}, - } - tcc := QueryUidStructure[*TccUidStructure](tccMapId) - for _, d := range tcc.ButtonIds { - tcs.Buttons = append(tcs.Buttons, &state_proto.TrainControlState_TccButton{Id: d.CommonId, Code: d.Code}) - } - for _, d := range tcc.Keys { - tcs.Keys = append(tcs.Keys, &state_proto.TrainControlState_TccKey{Id: d.CommonId, Code: d.Code}) - } - for _, d := range tcc.Handler { - tcs.Handlers = append(tcs.Handlers, &state_proto.TrainControlState_TccHander{Id: d.CommonId, Code: d.Code}) - } - return tcs -}*/ -// 列车控制修改参数 -func ControlTrainUpdate(vs *VerifySimulation, ct *request_proto.TrainControl) { - allTrainMap := &vs.Memory.Status.TrainStateMap - data, ok := allTrainMap.Load(ct.TrainId) - if !ok { - panic(sys_error.New(fmt.Sprintf("列车【%s】不存在", ct.TrainId))) - } - sta := data.(*state_proto.TrainState) - - if ct.ControlType == request_proto.TrainControl_EMERGENT_BUTTON { - trainControlEB(sta, ct.Button, ct.DeviceId) - } else if ct.ControlType == request_proto.TrainControl_DRIVER_KEY_SWITCH { - trainControlDriverKey(sta, ct.DriverKey, ct.DeviceId) - } else if ct.ControlType == request_proto.TrainControl_DIRECTION_KEY_SWITCH { - trainControlDirKey(sta, ct.DirKey, ct.DeviceId) - } else if ct.ControlType == request_proto.TrainControl_HANDLER { - trainControlHandle(sta, ct.Handler, ct.DeviceId) - } -} -func trainControlEB(trainState *state_proto.TrainState, request *request_proto.TrainControl_EmergentButton, deviceId uint32) { - trainState.VobcState.EmergencyBrakingStatus = request.Active - if trainState.Tcc.Ebutton == nil { - trainState.Tcc.Ebutton = &state_proto.TrainControlState_EmergentButton{Id: deviceId, Passed: request.Active} - } else { - trainState.Tcc.Ebutton.Passed = request.Active - } -} -func trainControlDirKey(trainState *state_proto.TrainState, request *request_proto.TrainControl_DirectionKeySwitch, deviceId uint32) { - trainState.VobcState.DirectionForward = false - trainState.VobcState.DirectionBackward = false - if request.Val == 1 { - trainState.VobcState.DirectionForward = true - } else if request.Val == 0 { - trainState.VobcState.DirectionBackward = false - } - if trainState.Tcc.DirKey == nil { - trainState.Tcc.DirKey = &state_proto.TrainControlState_DirectionKeySwitch{Id: deviceId, Val: request.Val} - } else { - trainState.Tcc.DirKey.Val = request.Val - } - -} - -func trainControlDriverKey(trainState *state_proto.TrainState, request *request_proto.TrainControl_DriverKeySwitch, deviceId uint32) { - if request.Dt == request_proto.DriverType_ONE_END { - trainState.VobcState.Tc1Active = request.Val - } else if request.Dt == request_proto.DriverType_TWO_END { - trainState.VobcState.Tc2Active = request.Val - } - var addNew = true - for _, k := range trainState.Tcc.DriverKey { - if k.Id == deviceId { - k.Dt = request.Dt - k.Id = deviceId - k.Val = request.Val - addNew = false - break - } - } - if addNew { - trainState.Tcc.DriverKey = append(trainState.Tcc.DriverKey, &state_proto.TrainControlState_DriverKeySwitch{Id: deviceId, Val: request.Val, Dt: request.Dt}) - } -} -func trainControlHandle(trainState *state_proto.TrainState, request *request_proto.TrainControl_PushHandler, deviceId uint32) { - trainState.VobcState.TractionStatus = false - trainState.VobcState.TractionForce = 0 - trainState.VobcState.BrakingStatus = false - trainState.VobcState.BrakeForce = 0 - if request.Val > 0 { - trainState.VobcState.TractionStatus = true - trainState.VobcState.TractionForce = int64(request.Val) - } else if request.Val < 0 { - trainState.VobcState.BrakingStatus = true - trainState.VobcState.BrakeForce = int64(request.Val) - } - if trainState.Tcc.DirKey == nil { - trainState.Tcc.PushHandler = &state_proto.TrainControlState_PushHandler{Id: deviceId, Val: request.Val} - } else { - trainState.Tcc.PushHandler.Val = request.Val - } - -} diff --git a/ts/simulation/wayside/memory/wayside_simulation.go b/ts/simulation/wayside/memory/wayside_simulation.go index 2489dd2..49915e6 100644 --- a/ts/simulation/wayside/memory/wayside_simulation.go +++ b/ts/simulation/wayside/memory/wayside_simulation.go @@ -134,10 +134,6 @@ func CreateSimulation(projectId int32, mapIds []int32, runConfig *dto.ProjectRun // return s.runState // } -func (s *VerifySimulation) GetTrainPcSimConfig() config.VehiclePCSimConfig { - return s.runConfig.PcSimConfig -} - // 获取仿真世界信息 func (s *VerifySimulation) GetComIdByUid(uid string) uint32 { es := s.UidMap @@ -391,11 +387,11 @@ func (s *VerifySimulation) HandleSemiPhysicalTrainControlMsg(b []byte) { return true } connState := train.ConnState + trainId, err := strconv.Atoi(train.Id) + if err != nil { + panic(dto.ErrorDto{Code: dto.ArgumentParseError, Message: err.Error()}) + } if connState.Conn == true && connState.ConnType == state_proto.TrainConnState_VOBC { - trainId, err := strconv.Atoi(train.Id) - if err != nil { - panic(dto.ErrorDto{Code: dto.ArgumentParseError, Message: err.Error()}) - } d := append(b, uint8(trainId)) // 发送给动力学 dynamics.Default().SendTrainControlMessage(d) diff --git a/ts/simulation/wayside/memory/wayside_simulation_train_pc.go b/ts/simulation/wayside/memory/wayside_simulation_train_pc.go new file mode 100644 index 0000000..e57d658 --- /dev/null +++ b/ts/simulation/wayside/memory/wayside_simulation_train_pc.go @@ -0,0 +1,404 @@ +package memory + +import ( + "bytes" + "encoding/binary" + "encoding/hex" + "fmt" + "joylink.club/bj-rtsts-server/config" + "joylink.club/bj-rtsts-server/const/balise_const" + "joylink.club/bj-rtsts-server/dto/request_proto" + "joylink.club/bj-rtsts-server/dto/state_proto" + "joylink.club/bj-rtsts-server/sys_error" + "joylink.club/bj-rtsts-server/third_party/can_btm" + "joylink.club/bj-rtsts-server/third_party/message" + train_pc_sim "joylink.club/bj-rtsts-server/third_party/train_pc_sim" + "log/slog" + "strings" + "time" +) + +func (s *VerifySimulation) GetTrainPcSimConfig() config.VehiclePCSimConfig { + return s.runConfig.PcSimConfig +} + +// 列车控制 +func (s *VerifySimulation) ControlTrainUpdate(ct *request_proto.TrainControl) { + allTrainMap := &s.Memory.Status.TrainStateMap + data, ok := allTrainMap.Load(ct.TrainId) + if !ok { + panic(sys_error.New(fmt.Sprintf("列车【%s】不存在", ct.TrainId))) + } + sta := data.(*state_proto.TrainState) + var tce []train_pc_sim.TrainControlEvent = nil + if ct.ControlType == request_proto.TrainControl_EMERGENT_BUTTON { + trainControlEB(sta, ct.Button, ct.DeviceId) + } else if ct.ControlType == request_proto.TrainControl_DRIVER_KEY_SWITCH { + tce = trainControlDriverKey(sta, ct.DriverKey, ct.DeviceId) + train_pc_sim.Default().SendDriverActive(sta.ConnState, sta.VobcState) + } else if ct.ControlType == request_proto.TrainControl_DIRECTION_KEY_SWITCH { + trainControlDirKey(sta, ct.DirKey, ct.DeviceId) + } else if ct.ControlType == request_proto.TrainControl_HANDLER { + oldTraction := sta.VobcState.TractionForce + oldBrakeForce := sta.VobcState.BrakeForce + tce = trainControlHandle(sta, ct.Handler, ct.DeviceId) + train_pc_sim.Default().SendHandleSwitch(oldTraction, oldBrakeForce, sta.ConnState, sta.VobcState) + } + + if !sta.ConnState.Conn && sta.ConnState.ConnType != state_proto.TrainConnState_PC_SIM { + slog.Error("当前列车未连接车载pc仿真,", sta.Id) + return + } + + train_pc_sim.Default().PublishTrainControlEvent(s.World, tce) + +} + +func trainControlEB(trainState *state_proto.TrainState, request *request_proto.TrainControl_EmergentButton, deviceId uint32) { + trainState.VobcState.EmergencyBrakingStatus = request.Active + if trainState.Tcc.Ebutton == nil { + trainState.Tcc.Ebutton = &state_proto.TrainControlState_EmergentButton{Id: deviceId, Passed: request.Active} + } else { + trainState.Tcc.Ebutton.Passed = request.Active + } +} + +// 列车方向 +func trainControlDirKey(trainState *state_proto.TrainState, request *request_proto.TrainControl_DirectionKeySwitch, deviceId uint32) { + trainState.VobcState.DirectionForward = false + trainState.VobcState.DirectionBackward = false + if request.Val == 1 { + trainState.VobcState.DirectionForward = true + } else if request.Val == 0 { + trainState.VobcState.DirectionBackward = false + } + if trainState.Tcc.DirKey == nil { + trainState.Tcc.DirKey = &state_proto.TrainControlState_DirectionKeySwitch{Id: deviceId, Val: request.Val} + } else { + trainState.Tcc.DirKey.Val = request.Val + } + +} + +// 列车驾驶端激活 +func trainControlDriverKey(trainState *state_proto.TrainState, request *request_proto.TrainControl_DriverKeySwitch, deviceId uint32) []train_pc_sim.TrainControlEvent { + if request.Dt == request_proto.DriverType_ONE_END { + trainState.VobcState.Tc1Active = request.Val + } else if request.Dt == request_proto.DriverType_TWO_END { + trainState.VobcState.Tc2Active = request.Val + } + var addNew = true + for _, k := range trainState.Tcc.DriverKey { + if k.Id == deviceId { + k.Id = deviceId + k.Val = request.Val + addNew = false + break + } + } + if addNew { + trainState.Tcc.DriverKey = append(trainState.Tcc.DriverKey, &state_proto.TrainControlState_DriverKeySwitch{Id: deviceId, Val: request.Val}) + } + return []train_pc_sim.TrainControlEvent{{Command: train_pc_sim.KEY_STATE, Status: train_pc_sim.IsTrue(request.Val)}} + +} + +// 列车牵引控制 +func trainControlHandle(trainState *state_proto.TrainState, request *request_proto.TrainControl_PushHandler, deviceId uint32) []train_pc_sim.TrainControlEvent { + trainState.VobcState.TractionStatus = false + trainState.VobcState.TractionForce = 0 + trainState.VobcState.BrakingStatus = false + trainState.VobcState.BrakeForce = 0 + tce := make([]train_pc_sim.TrainControlEvent, 0) + tce = append(tce, train_pc_sim.TrainControlEvent{Command: train_pc_sim.HANDLE_FORWORD, Status: 0}) + tce = append(tce, train_pc_sim.TrainControlEvent{Command: train_pc_sim.HANDLE_BACKWORD, Status: 0}) + tce = append(tce, train_pc_sim.TrainControlEvent{Command: train_pc_sim.HANDLE_TO_ZERO, Status: 0}) + if request.Val > 0 { + trainState.VobcState.TractionStatus = true + trainState.VobcState.TractionForce = int64(request.Val) + tce = append(tce, train_pc_sim.TrainControlEvent{Command: train_pc_sim.HANDLE_FORWORD, Status: 1}) + } else if request.Val < 0 { + trainState.VobcState.BrakingStatus = true + trainState.VobcState.BrakeForce = int64(request.Val) + tce = append(tce, train_pc_sim.TrainControlEvent{Command: train_pc_sim.HANDLE_BACKWORD, Status: 1}) + } else { + + tce = append(tce, train_pc_sim.TrainControlEvent{Command: train_pc_sim.HANDLE_TO_ZERO, Status: 1}) + } + if trainState.Tcc.PushHandler == nil { + trainState.Tcc.PushHandler = &state_proto.TrainControlState_PushHandler{Id: deviceId, Val: request.Val} + } else { + trainState.Tcc.PushHandler.Val = request.Val + } + return tce +} +func (s *VerifySimulation) findConnTrain(ct state_proto.TrainConnState_TrainConnType) *state_proto.TrainState { + var findTrain *state_proto.TrainState + s.Memory.Status.TrainStateMap.Range(func(k, v any) bool { + train := v.(*state_proto.TrainState) + connState := train.ConnState + if connState.ConnType == ct { + findTrain = train + return false + } + return true + }) + return findTrain +} + +// 4.4.1. 车载输出数字量信息报文内容 +func (s *VerifySimulation) TrainPcSimDigitalOutInfoHandle(data []byte) { + train := s.findConnTrain(state_proto.TrainConnState_PC_SIM) + if train == nil { + slog.Error("车载输出数字量,未找到连接车载pc仿真的列车") + return + } + if !train.ConnState.Conn { + slog.Error("车载输出数字量,,列车未连接车载pc仿真") + return + } + buf := bytes.NewBuffer(data) + vobc := train.VobcState + cutTraction, _ := buf.ReadByte() //切牵引 + trainDoorOutLed, _ := buf.ReadByte() //车门外指示灯 + stopBrakeAppend, _ := buf.ReadByte() //停放制动施加 + emergentBrake, _ := buf.ReadByte() //紧急制动 + leftOpenDoor, _ := buf.ReadByte() //开左门允许 + rightOpenDoor, _ := buf.ReadByte() //开右门允许 + closeRightDoor, _ := buf.ReadByte() //关右门 + doorAlwaysClosed, _ := buf.ReadByte() //车门保持关闭 + localAtpControl, _ := buf.ReadByte() //本端ATP控车 + atoMode, _ := buf.ReadByte() //ATO模式 + atoTractionCommandOut, _ := buf.ReadByte() //ATO牵引命令输出 + atoTractionCommand1, _ := buf.ReadByte() //ATO牵引指令1 + atoTractionCommand2, _ := buf.ReadByte() //ATO牵引指令2 + atoTractionCommand3, _ := buf.ReadByte() //ATO牵引指令3 + atoBrakeCommand, _ := buf.ReadByte() //ATO制动命令输出 + skipCommand, _ := buf.ReadByte() //跳跃指令 + direction1, _ := buf.ReadByte() //列车方向1 + direction2, _ := buf.ReadByte() //列车方向2 + atoLazyCommandOut, _ := buf.ReadByte() //ATO惰行命令输出 + sleepCommand, _ := buf.ReadByte() //休眠指令 + wakeUpCommand, _ := buf.ReadByte() //唤醒指令 + toPullTrainLed, _ := buf.ReadByte() //ATO发车指示灯 + arLightCommand, _ := buf.ReadByte() //AR灯命令 + atoAlwaysBrake, _ := buf.ReadByte() //ATO保持制动 + atoOpenLeftDoor, _ := buf.ReadByte() //ATO开左门 + atoOpenRightDoor, _ := buf.ReadByte() //ATO开右门 + atoCloseLeftDoor, _ := buf.ReadByte() //ATO关左门 + ariverActive, _ := buf.ReadByte() //驾驶室激活 + noSpeedSigle, _ := buf.ReadByte() //零速信号 + famMode, _ := buf.ReadByte() //FAM模式 + camMode, _ := buf.ReadByte() //CAM模式 + trainStartedLed, _ := buf.ReadByte() //列车启动指示灯 + mostUseBrake, _ := buf.ReadByte() //常用制动 + splittingOut, _ := buf.ReadByte() //过分相输出 + modeRelay, _ := buf.ReadByte() //模式继电器 + tractionEffective, _ := buf.ReadByte() //牵引有效 + brakeEffective, _ := buf.ReadByte() //制动有效 + lifeDoorUsed, _ := buf.ReadByte() //逃生门使能 + brakeQuarantine, _ := buf.ReadByte() //制动隔离 + stopNotAllBrake, _ := buf.ReadByte() //停放制动缓解 + + vobc.TractionSafetyCircuit = train_pc_sim.IsTrueForByte(cutTraction) + vobc.TrainDoorOutLed = train_pc_sim.IsTrueForByte(trainDoorOutLed) //? 说明暂无此属性 + vobc.ParkingBrakeStatus = train_pc_sim.IsTrueForByte(stopBrakeAppend) + vobc.EmergencyBrakingStatus = train_pc_sim.IsTrueForByte(emergentBrake) + vobc.LeftDoorOpenCommand = train_pc_sim.IsTrueForByte(leftOpenDoor) + vobc.RightDoorOpenCommand = train_pc_sim.IsTrueForByte(rightOpenDoor) + vobc.RightDoorCloseCommand = train_pc_sim.IsTrueForByte(closeRightDoor) + vobc.AllDoorClose = train_pc_sim.IsTrueForByte(doorAlwaysClosed) + vobc.LocalAtpControl = train_pc_sim.IsTrueForByte(localAtpControl) //? + vobc.Ato = train_pc_sim.IsTrueForByte(atoMode) + vobc.AtoTractionCommandOut = train_pc_sim.IsTrueForByte(atoTractionCommandOut) //? + + vobc.AtoTractionCommand1 = train_pc_sim.IsTrueForByte(atoTractionCommand1) //? + vobc.AtoTractionCommand2 = train_pc_sim.IsTrueForByte(atoTractionCommand2) //? + vobc.AtoTractionCommand3 = train_pc_sim.IsTrueForByte(atoTractionCommand3) //? + vobc.AtoBrakeCommand = train_pc_sim.IsTrueForByte(atoBrakeCommand) //? + vobc.JumpStatus = train_pc_sim.IsTrueForByte(skipCommand) + vobc.DirectionForward = train_pc_sim.IsTrueForByte(direction1) + vobc.DirectionBackward = train_pc_sim.IsTrueForByte(direction2) + + vobc.AtoLazyCommandOut = train_pc_sim.IsTrueForByte(atoLazyCommandOut) //? + vobc.SleepBtn = train_pc_sim.IsTrueForByte(sleepCommand) + vobc.WakeUpBtn = train_pc_sim.IsTrueForByte(wakeUpCommand) + vobc.AtoSendTrainBtn = train_pc_sim.IsTrueForByte(toPullTrainLed) + vobc.TurnbackStatus = train_pc_sim.IsTrueForByte(arLightCommand) //? + vobc.AtoAlwaysBrake = train_pc_sim.IsTrueForByte(atoAlwaysBrake) //? + vobc.AtoOpenLeftDoor = train_pc_sim.IsTrueForByte(atoOpenLeftDoor) //? + vobc.AtoOpenRightDoor = train_pc_sim.IsTrueForByte(atoOpenRightDoor) //? + vobc.AtoCloseLeftDoor = train_pc_sim.IsTrueForByte(atoCloseLeftDoor) //? + vobc.Tc1Active = train_pc_sim.IsTrueForByte(ariverActive) + vobc.NoSpeedSigle = train_pc_sim.IsTrueForByte(noSpeedSigle) //? + vobc.Fam = train_pc_sim.IsTrueForByte(famMode) + vobc.Cam = train_pc_sim.IsTrueForByte(camMode) + vobc.TrainStartedLed = train_pc_sim.IsTrueForByte(trainStartedLed) //? + vobc.MostUseBrake = train_pc_sim.IsTrueForByte(mostUseBrake) //? //常用制动 + vobc.SplittingOut = train_pc_sim.IsTrueForByte(splittingOut) //? //过分相输出 + vobc.ModeRelay = train_pc_sim.IsTrueForByte(modeRelay) //? //模式继电器 + vobc.TractionEffective = train_pc_sim.IsTrueForByte(tractionEffective) //? //牵引有效 + vobc.BrakeEffective = train_pc_sim.IsTrueForByte(brakeEffective) //? //制动有效 + vobc.LifeDoorState = train_pc_sim.IsTrueForByte(lifeDoorUsed) //逃生门使能 + vobc.BrakeQuarantine = train_pc_sim.IsTrueForByte(brakeQuarantine) //? //制动隔离 + vobc.StopNotAllBrake = train_pc_sim.IsTrueForByte(stopNotAllBrake) //? //停放制动缓解 +} + +// 4.4.2. 车载输出数字反馈量信息报文内容 +func (s *VerifySimulation) TrainPcSimDigitalReportHandle(data []byte) { + train := s.findConnTrain(state_proto.TrainConnState_PC_SIM) + if train == nil { + slog.Error("车载输出数字反馈量信息,未找到连接车载pc仿真的列车") + return + } + if !train.ConnState.Conn { + slog.Error("车载输出数字反馈量信息,列车未连接车载pc仿真") + return + } + vobc := train.VobcState + buf := bytes.NewBuffer(data) + localEndAct, _ := buf.ReadByte() + direction1, _ := buf.ReadByte() + direction2, _ := buf.ReadByte() + + vobc.Tc1Active = train_pc_sim.IsTrueForByte(localEndAct) //本端驾驶室激活(钥匙) + vobc.DirectionForward = train_pc_sim.IsTrueForByte(direction1) //方向手柄进位 + vobc.DirectionBackward = train_pc_sim.IsTrueForByte(direction2) //方向手柄退位 +} + +// 创建/删除列车 +func (s *VerifySimulation) TrainPcSimConnOrRemoveHandle(state byte) { + train := s.findConnTrain(state_proto.TrainConnState_PC_SIM) + if train == nil { + slog.Error("创建删除列车,未找到连接车载pc仿真的列车") + return + } + connState := train.ConnState + if state == 0x01 { + connState.Conn = true + } else { + connState.Conn = false + } +} + +// 门模式 +func (s *VerifySimulation) TrainDoorModeHandle(state byte) { + train := s.findConnTrain(state_proto.TrainConnState_PC_SIM) + if train == nil { + slog.Error("车载pc仿真门模式,未找到连接车载pc仿真的列车") + return + } + if !train.ConnState.Conn { + slog.Error("车载pc仿真门模式,列车未连接车载pc仿真") + return + } + switch state { + case 0x00: + //0x00表示自开自关(AA) + train.VobcState.DoorModeAA = true + case 0x01: + //0x01表示自开人关(AM) + train.VobcState.DoorModeAM = true + case 0x02: + //0x02表示人开人关(MM) + train.VobcState.DoorModeMM = true + } +} + +// 4.4.3. 车载输出模拟量信息报文内容(0x03) +func (s *VerifySimulation) TrainPcSimMockInfo(data []byte) { + train := s.findConnTrain(state_proto.TrainConnState_PC_SIM) + if train == nil { + slog.Error("车载输出模拟量,未找到连接车载pc仿真的列车") + return + } + if !train.ConnState.Conn { + slog.Error("车载输出模拟量,列车未连接车载pc仿真") + return + } + mockData := binary.BigEndian.Uint16(data) + train.VobcState.MockInfo = uint32(mockData) +} + +// 4.4.4. 车载输出BTM查询同步帧报文内容(0x04) +func (s *VerifySimulation) TrainBtmQuery(data []byte) { + if len(data) < 12 { + slog.Error("列车btm查询报文长度错误:", len(data)) + return + } + train := s.findConnTrain(state_proto.TrainConnState_PC_SIM) + if train == nil { + slog.Error("车载输出btm查询,未找到连接车载pc仿真的列车") + return + } + if !train.ConnState.Conn { + slog.Error("车载输出btm查询,列车未连接车载pc仿真") + return + } + + trainAtm := message.NewCanetFrame(data, true) + atpReq := &message.AtpRequestFrame{} + if !atpReq.Decode(trainAtm) { + slog.Warn("列车pc驾驶模拟-CanetFrame解码成AtpRequestFrame失败", "CanetFrame", trainAtm.String()) + return + } + cl := clock(atpReq) + btmRepFrame := createBtmStatus(trainAtm.CanId.ID4, train.BtmState, atpReq, cl) + if atpReq.ResendRequest == 2 { + //重新发送 + if len(train.BtmState.BaliseTelegramForPcSimResend) > 0 { + dd, _ := hex.DecodeString(train.BtmState.BaliseTelegramForPcSimResend) + train_pc_sim.Default().SendBaliseData(train_pc_sim.RECIVE_TRAIN_BTM_HAS_DATA, dd) + } + } else { + timeSyncF := message.NewBtmTimeSyncCheckFrame(trainAtm.CanId.ID4, true) + timeSyncF.T2 = cl.BtmTk + timeSyncF.T3 = cl.TkNow() + telCount := strings.Count(train.BtmState.Telegram, "00") + if telCount >= balise_const.UserTelegramByteLen { + //无数据 + queryData := make([]byte, 0) + queryData = append(queryData, btmRepFrame.Encode().Encode()...) + queryData = append(queryData, timeSyncF.Encode().Encode()...) + train_pc_sim.Default().SendBaliseData(train_pc_sim.RECIVE_TRAIN_BTM_NOT_DATA, queryData) + } else { + //有数据 + aliseData, _ := hex.DecodeString(train.BtmState.Telegram) + statusDataCf, statusDataCfOk := message.CreateBtmRspFramesData(btmRepFrame, aliseData, false, cl.TkNow(), cl.TkNow(), cl.TkNow(), true) + if statusDataCfOk { + queryData := make([]byte, 0) + queryData = append(queryData, btmRepFrame.Encode().Encode()...) + queryData = append(queryData, timeSyncF.Encode().Encode()...) + queryData = append(queryData, statusDataCf...) //数据帧包含结束帧 + train.BtmState.BaliseTelegramForPcSimResend = fmt.Sprintf("%X", statusDataCf) + train_pc_sim.Default().SendBaliseData(train_pc_sim.RECIVE_TRAIN_BTM_HAS_DATA, queryData) + } else { + slog.Error("列车pc仿真 BtmCanetClient应答帧、数据帧编码失败") + } + } + } +} + +func createBtmStatus(canIdSn byte, btmState *state_proto.BTMState, atpReq *message.AtpRequestFrame, cl can_btm.BtmClock) *message.BtmStatusRspFrame { + statusF := message.NewBtmStatusRspFrame(canIdSn, true) + //btmStatus := aa(train, atpReq) + + statusF.PowerAmplifierOn = true + statusF.PowerAmplifierFailure = false + statusF.AtpReqCrcCheckWrong = !atpReq.Crc16CheckOk + statusF.AntennaFault = false + statusF.BaliseCounter = byte(btmState.BaliseCount) + statusF.MessageCounter = byte(btmState.MessageCounter) + statusF.TkTimeA = cl.TkNow() + statusF.DetailedCode = 0 + if btmState.AboveBalise { + statusF.DetailedCode = 0x07 + } + statusF.Dsn = byte(btmState.DataSerialNumber) + return statusF +} + +func clock(atpReq *message.AtpRequestFrame) can_btm.BtmClock { + now := time.Now() + return can_btm.BtmClock{BtmTk: atpReq.Time, SysTk: now} +}