列车pc仿真

This commit is contained in:
tiger_zhou 2024-04-02 18:20:10 +08:00
parent 089d62cfc2
commit 9b58348297
18 changed files with 944 additions and 206 deletions

View File

@ -354,7 +354,7 @@ func controlTrain(c *gin.Context) {
panic(sys_error.New("修改列车控制参数失败,请求参数异常", err)) panic(sys_error.New("修改列车控制参数失败,请求参数异常", err))
} }
simulation := checkDeviceDataAndReturn(req.SimulationId) simulation := checkDeviceDataAndReturn(req.SimulationId)
memory.ControlTrainUpdate(simulation, req) simulation.ControlTrainUpdate(req)
c.JSON(http.StatusOK, "ok") c.JSON(http.StatusOK, "ok")
} }

View File

@ -12,19 +12,6 @@ import (
// 综合后备盘IBP消息服务 // 综合后备盘IBP消息服务
func NewTrainControlMs(vs *memory.VerifySimulation, mapId int32) ms_api.MsgTask { 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 { return ms_api.NewScheduleTask(fmt.Sprintf("地图[%d]列车控制", mapId), func() error {
allTrainMap := &vs.Memory.Status.TrainStateMap allTrainMap := &vs.Memory.Status.TrainStateMap
allTrainMap.Range(func(key, value any) bool { allTrainMap.Range(func(key, value any) bool {

@ -1 +1 @@
Subproject commit 78f0ddfd2413bdac81d33ea2b199edaa4de9e6fb Subproject commit 5dd8f8963e28e79012656f95999016ad503ae43a

View File

@ -46,7 +46,7 @@ type btmCanetClient struct {
//最近一次车载ATP系统查询帧CRC16校验结果,true-校验通过 //最近一次车载ATP系统查询帧CRC16校验结果,true-校验通过
atpReqCrc16Check bool atpReqCrc16Check bool
//btm系统时间,每次接收到ATP查询请求帧时同步一次时间 //btm系统时间,每次接收到ATP查询请求帧时同步一次时间
btmTime btmClock btmTime BtmClock
//数据流水号 //数据流水号
dsn byte dsn byte
//重发的数据 //重发的数据
@ -57,6 +57,7 @@ type btmCanetClient struct {
func (s *btmCanetClient) GetState() state_proto.BTMState { func (s *btmCanetClient) GetState() state_proto.BTMState {
detector := s.baliseDetector detector := s.baliseDetector
var telegram string var telegram string
info := detector.eq[len(detector.eq)-1] info := detector.eq[len(detector.eq)-1]
if /*detector.aboveBalise &&*/ info != nil && len(info.telegram) != 0 { if /*detector.aboveBalise &&*/ info != nil && len(info.telegram) != 0 {
@ -69,17 +70,19 @@ func (s *btmCanetClient) GetState() state_proto.BTMState {
BaliseCount: uint32(detector.baliseCounter), BaliseCount: uint32(detector.baliseCounter),
MessageCounter: uint32(detector.messageCounter), MessageCounter: uint32(detector.messageCounter),
Telegram: telegram, Telegram: telegram,
Distance: info.Distance,
AboveBalise: detector.aboveBalise,
} }
} }
type btmClock struct { type BtmClock struct {
btmTk uint32 //与ATP系统同步的时间ms BtmTk uint32 //与ATP系统同步的时间ms
sysTk time.Time //本地系统时间 SysTk time.Time //本地系统时间
} }
// 获取以btmTk为基准的当前时间ms // 获取以btmTk为基准的当前时间ms
func (c *btmClock) tkNow() uint32 { func (c *BtmClock) TkNow() uint32 {
return c.btmTk + uint32(time.Now().UnixMilli()-c.sysTk.UnixMilli()) return c.BtmTk + uint32(time.Now().UnixMilli()-c.SysTk.UnixMilli())
} }
type BtmCanetClient interface { type BtmCanetClient interface {
@ -87,7 +90,7 @@ type BtmCanetClient interface {
Stop() Stop()
//HandleTrainHeadPositionInfo 处理收到列车位置信息 //HandleTrainHeadPositionInfo 处理收到列车位置信息
HandleTrainHeadPositionInfo(w ecs.World, h *TrainHeadPositionInfo) HandleTrainHeadPositionInfo(w ecs.World, h *TrainHeadPositionInfo)
//获取BTM显示状态 //获取BTM显示状态 + btm最新的状态
GetState() state_proto.BTMState GetState() state_proto.BTMState
} }
@ -156,7 +159,7 @@ func (s *btmCanetClient) handleCanetFrames(cfs []byte) {
dms := make([]*message.CanetFrame, 0, 16) //13个应答器报文数据帧+TimeA帧+TimeB帧+结束帧 dms := make([]*message.CanetFrame, 0, 16) //13个应答器报文数据帧+TimeA帧+TimeB帧+结束帧
for cfi := 0; cfi < cfSum; cfi++ { for cfi := 0; cfi < cfSum; cfi++ {
cfStart := cfi * 13 cfStart := cfi * 13
cf := message.NewCanetFrame(cfs[cfStart : cfStart+13]) cf := message.NewCanetFrame(cfs[cfStart:cfStart+13], false)
// //
switch cf.CanFrameType() { switch cf.CanFrameType() {
case message.CfReq: case message.CfReq:
@ -206,8 +209,8 @@ func (s *btmCanetClient) dealWithAptReq(f *message.CanetFrame) {
//处理查询请求 //处理查询请求
//slog.Debug(fmt.Sprintf("处理查询请求:%s", atpReq.String())) //slog.Debug(fmt.Sprintf("处理查询请求:%s", atpReq.String()))
// //
s.btmTime.btmTk = atpReq.Time s.btmTime.BtmTk = atpReq.Time
s.btmTime.sysTk = now s.btmTime.SysTk = now
s.atpReqSn = atpReq.FId.ID4 s.atpReqSn = atpReq.FId.ID4
s.atpReqCrc16Check = atpReq.Crc16CheckOk s.atpReqCrc16Check = atpReq.Crc16CheckOk
s.baliseDetector.powerAmplifierSwitch = atpReq.PowerAmplifierTurnOn s.baliseDetector.powerAmplifierSwitch = atpReq.PowerAmplifierTurnOn
@ -256,14 +259,14 @@ func (s *btmCanetClient) rspResendToAtp() {
// 当未收到应答器报文时响应:时间同步帧、状态应答帧 // 当未收到应答器报文时响应:时间同步帧、状态应答帧
func (s *btmCanetClient) rspToAtp(sb *BtmAntennaScanningBaliseInfo) { func (s *btmCanetClient) rspToAtp(sb *BtmAntennaScanningBaliseInfo) {
//BTM状态 //BTM状态
statusF := message.NewBtmStatusRspFrame(s.atpReqSn) statusF := message.NewBtmStatusRspFrame(s.atpReqSn, false)
btmStatus := s.createTrainBtmStatus() btmStatus := s.createTrainBtmStatus()
statusF.AntennaFault = btmStatus.AntennaFault statusF.AntennaFault = btmStatus.AntennaFault
statusF.BaliseCounter = byte(btmStatus.BaliseCounter) statusF.BaliseCounter = byte(btmStatus.BaliseCounter)
statusF.MessageCounter = byte(btmStatus.MessageCounter) statusF.MessageCounter = byte(btmStatus.MessageCounter)
statusF.PowerAmplifierOn = btmStatus.PowerAmplifierOn statusF.PowerAmplifierOn = btmStatus.PowerAmplifierOn
statusF.TkTimeA = s.btmTime.tkNow() statusF.TkTimeA = s.btmTime.TkNow()
statusF.PowerAmplifierFailure = btmStatus.PowerAmplifierFault statusF.PowerAmplifierFailure = btmStatus.PowerAmplifierFault
statusF.DetailedCode = 0 statusF.DetailedCode = 0
if btmStatus.AboveBalise { if btmStatus.AboveBalise {
@ -271,16 +274,17 @@ func (s *btmCanetClient) rspToAtp(sb *BtmAntennaScanningBaliseInfo) {
} }
statusF.AtpReqCrcCheckWrong = !s.atpReqCrc16Check statusF.AtpReqCrcCheckWrong = !s.atpReqCrc16Check
statusF.Dsn = s.dsn statusF.Dsn = s.dsn
s.dsnAdd1() s.dsnAdd1()
// //
//true-收到应答器报文 //true-收到应答器报文
isRcvTelegram := sb != nil && len(sb.telegram) > 0 isRcvTelegram := sb != nil && len(sb.telegram) > 0
if isRcvTelegram { //当收到应答器报文时响应:时间同步帧、状态应答帧、数据帧 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 { if statusDataCfOk {
timeSyncF := message.NewBtmTimeSyncCheckFrame(s.atpReqSn) timeSyncF := message.NewBtmTimeSyncCheckFrame(s.atpReqSn, false)
timeSyncF.T2 = s.btmTime.btmTk timeSyncF.T2 = s.btmTime.BtmTk
timeSyncF.T3 = s.btmTime.tkNow() timeSyncF.T3 = s.btmTime.TkNow()
s.sendCanetFrame(timeSyncF.Encode().Encode()) s.sendCanetFrame(timeSyncF.Encode().Encode())
// //
s.resendData = newResendData(statusDataCf) s.resendData = newResendData(statusDataCf)
@ -289,9 +293,9 @@ func (s *btmCanetClient) rspToAtp(sb *BtmAntennaScanningBaliseInfo) {
slog.Warn("BtmCanetClient应答帧、数据帧编码失败") slog.Warn("BtmCanetClient应答帧、数据帧编码失败")
} }
} else { //当未收到应答器报文时响应:时间同步帧、状态应答帧 } else { //当未收到应答器报文时响应:时间同步帧、状态应答帧
timeSyncF := message.NewBtmTimeSyncCheckFrame(s.atpReqSn) timeSyncF := message.NewBtmTimeSyncCheckFrame(s.atpReqSn, false)
timeSyncF.T2 = s.btmTime.btmTk timeSyncF.T2 = s.btmTime.BtmTk
timeSyncF.T3 = s.btmTime.tkNow() timeSyncF.T3 = s.btmTime.TkNow()
s.sendCanetFrame(timeSyncF.Encode().Encode()) s.sendCanetFrame(timeSyncF.Encode().Encode())
// //
statusCf := statusF.Encode().Encode() statusCf := statusF.Encode().Encode()

View File

@ -41,6 +41,7 @@ type BtmAntennaScanningBaliseInfo struct {
Time time.Time //应答器预计被BTM天线激活的时刻 Time time.Time //应答器预计被BTM天线激活的时刻
active bool //true-激活过,即列车扫过 active bool //true-激活过,即列车扫过
telegram []byte //应答器报文 telegram []byte //应答器报文
Distance int64 //BTM天线中心到应答器的距离mm
} }
// BaliseDetector 车载BTM天线应答器探测器 // BaliseDetector 车载BTM天线应答器探测器
@ -185,7 +186,7 @@ func (t *BaliseDetector) timeScanNearestBalise(curTime time.Time, wd *component.
s := float64(expectedBalise.Distance) / 1000 s := float64(expectedBalise.Distance) / 1000
st, ok := t.calculateBtmAntennaScanNextBaliseTime(curTime, curV, curAc, s) st, ok := t.calculateBtmAntennaScanNextBaliseTime(curTime, curV, curAc, s)
if ok { if ok {
return &BtmAntennaScanningBaliseInfo{BaliseId: expectedBalise.BaliseId, Time: st} return &BtmAntennaScanningBaliseInfo{BaliseId: expectedBalise.BaliseId, Time: st, Distance: expectedBalise.Distance}
} }
} }
return nil return nil

View File

@ -8,16 +8,19 @@ type BtmDataMessageFrame struct {
//帧ID //帧ID
FId CanFrameId FId CanFrameId
//8字节,应答器报文片段 //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{ 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 { func (f *BtmDataMessageFrame) Encode() *CanetFrame {
cf := &CanetFrame{} cf := &CanetFrame{}
cf.IsTrainPcSim = f.IsTrainPcSim
cf.CanId = f.FId cf.CanId = f.FId
cf.CanLen = 8 cf.CanLen = 8
cf.FF = true cf.FF = true
@ -59,16 +62,19 @@ type BtmDataMessageTimeAFrame struct {
//时间戳A //时间戳A
TimeA uint32 TimeA uint32
//CRC //CRC
Crc32A uint32 Crc32A uint32
IsTrainPcSim bool
} }
func NewBtmDataMessageTimeAFrame(sn byte) *BtmDataMessageTimeAFrame { func NewBtmDataMessageTimeAFrame(sn byte, isTrainPcSim bool) *BtmDataMessageTimeAFrame {
return &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 { func (f *BtmDataMessageTimeAFrame) Encode() *CanetFrame {
cf := &CanetFrame{} cf := &CanetFrame{}
cf.IsTrainPcSim = f.IsTrainPcSim
cf.CanId = f.FId cf.CanId = f.FId
cf.CanLen = 8 cf.CanLen = 8
cf.FF = true cf.FF = true
@ -161,16 +167,19 @@ type BtmDataMessageTimeBFrame struct {
//时间戳B //时间戳B
TimeB uint32 TimeB uint32
//CRC //CRC
Crc32B uint32 Crc32B uint32
IsTrainPcSim bool
} }
func NewBtmDataMessageTimeBFrame(sn byte) *BtmDataMessageTimeBFrame { func NewBtmDataMessageTimeBFrame(sn byte, isTrainPcSim bool) *BtmDataMessageTimeBFrame {
return &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 { func (f *BtmDataMessageTimeBFrame) Encode() *CanetFrame {
cf := &CanetFrame{} cf := &CanetFrame{}
cf.IsTrainPcSim = f.IsTrainPcSim
cf.CanId = f.FId cf.CanId = f.FId
cf.CanLen = 8 cf.CanLen = 8
cf.FF = true cf.FF = true
@ -263,16 +272,19 @@ type BtmDataMessageEndFrame struct {
TkB uint32 TkB uint32
//CRC32C校验包含对状态应答帧与数据帧的总校验总共为8×17-4=132字节4-即Crc32C //CRC32C校验包含对状态应答帧与数据帧的总校验总共为8×17-4=132字节4-即Crc32C
//1个状态应答帧 + 13个BtmDataMessageFrame + 1个BtmDataMessageTimeAFrame + 1个BtmDataMessageTimeBFrame + 1个BtmDataMessageEndFrame //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{ 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 { func (f *BtmDataMessageEndFrame) Encode() *CanetFrame {
cf := &CanetFrame{} cf := &CanetFrame{}
cf.IsTrainPcSim = f.IsTrainPcSim
cf.CanId = f.FId cf.CanId = f.FId
cf.CanLen = 8 cf.CanLen = 8
cf.FF = true cf.FF = true

View File

@ -8,16 +8,17 @@ import "log/slog"
// 共17帧17X12个字节每个帧12字节 // 共17帧17X12个字节每个帧12字节
// msg - 应答器报文 // msg - 应答器报文
// msgPackError - true BTM解包发生错误则数据帧及CRC32A/B全填“0xFF” // 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个字节 if len(msg) > 104 { //数据帧最多存储13*8个字节
return nil, false return nil, false
} }
//最近一次ATP查询请求序列号 //最近一次ATP查询请求序列号
sn := statusRsp.FId.ID4 sn := statusRsp.FId.ID4
//13个BtmDataMessageFrame [0x00,0x0c] //13个BtmDataMessageFrame [0x00,0x0c]
//数据
dms := make([]*BtmDataMessageFrame, 13) dms := make([]*BtmDataMessageFrame, 13)
for mr := 0x00; mr <= 0x0c; mr++ { 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 dms[mr].Message = make([]byte, 8) //8字节数组默认值0
// //
if !msgPackError { if !msgPackError {
@ -38,7 +39,7 @@ func CreateBtmRspFramesData(statusRsp *BtmStatusRspFrame, msg []byte, msgPackErr
} }
} }
// //
dtA := NewBtmDataMessageTimeAFrame(sn) dtA := NewBtmDataMessageTimeAFrame(sn, isTrainPcSim)
dtA.TimeA = msgTimeA dtA.TimeA = msgTimeA
if !msgPackError { if !msgPackError {
var crc32AData []byte var crc32AData []byte
@ -49,7 +50,7 @@ func CreateBtmRspFramesData(statusRsp *BtmStatusRspFrame, msg []byte, msgPackErr
dtA.Crc32A = 0xff_ff_ff_ff dtA.Crc32A = 0xff_ff_ff_ff
} }
// //
dtB := NewBtmDataMessageTimeBFrame(sn) dtB := NewBtmDataMessageTimeBFrame(sn, isTrainPcSim)
dtB.TimeB = msgTimeB dtB.TimeB = msgTimeB
if !msgPackError { if !msgPackError {
var crc32BData []byte var crc32BData []byte
@ -60,7 +61,7 @@ func CreateBtmRspFramesData(statusRsp *BtmStatusRspFrame, msg []byte, msgPackErr
dtB.Crc32B = 0xff_ff_ff_ff dtB.Crc32B = 0xff_ff_ff_ff
} }
// //
end := NewBtmDataMessageEndFrame(sn) end := NewBtmDataMessageEndFrame(sn, isTrainPcSim)
end.TkB = tkTimeB end.TkB = tkTimeB
// //
statusCf := statusRsp.Encode() statusCf := statusRsp.Encode()

View File

@ -26,12 +26,14 @@ type BtmStatusRspFrame struct {
//BTM detailed code //BTM detailed code
DetailedCode byte DetailedCode byte
//有具体报文内容时TK A时刻填充为CD信号上升沿时刻报文全零时TK A时刻填充为CD信号下降沿时刻。 //有具体报文内容时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{ 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 { func (f *BtmStatusRspFrame) Decode(cf *CanetFrame) bool {
@ -113,6 +115,7 @@ func (f *BtmStatusRspFrame) Decode(cf *CanetFrame) bool {
} }
func (f *BtmStatusRspFrame) Encode() *CanetFrame { func (f *BtmStatusRspFrame) Encode() *CanetFrame {
cf := &CanetFrame{} cf := &CanetFrame{}
cf.IsTrainPcSim = f.IsTrainPcSim
cf.CanId = f.FId cf.CanId = f.FId
cf.CanLen = 8 cf.CanLen = 8
cf.FF = true cf.FF = true

View File

@ -14,12 +14,14 @@ type BtmTimeSyncCheckFrame struct {
T2 uint32 T2 uint32
//T3时刻 //T3时刻
//BTM将在T3时刻发送回复帧给ATP并保持流水号与ATP查询帧一致。 //BTM将在T3时刻发送回复帧给ATP并保持流水号与ATP查询帧一致。
T3 uint32 T3 uint32
IsTrainPcSim bool
} }
func NewBtmTimeSyncCheckFrame(sn byte) *BtmTimeSyncCheckFrame { func NewBtmTimeSyncCheckFrame(sn byte, isTrainPcSim bool) *BtmTimeSyncCheckFrame {
return &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 { func (f *BtmTimeSyncCheckFrame) Encode() *CanetFrame {
cf := &CanetFrame{} cf := &CanetFrame{}
cf.IsTrainPcSim = f.IsTrainPcSim
cf.CanId = f.FId cf.CanId = f.FId
cf.CanLen = 8 cf.CanLen = 8
cf.FF = true cf.FF = true

View File

@ -8,30 +8,33 @@ import (
// CanetFrame USR-CANET200_V1.0.7 设备 以太网-CAN 透传协议帧(13字节) // CanetFrame USR-CANET200_V1.0.7 设备 以太网-CAN 透传协议帧(13字节)
type CanetFrame struct { type CanetFrame struct {
FF bool //true-1扩展帧 FF bool //true-1扩展帧
RTR bool //true-1远程帧 RTR bool //true-1远程帧
CanLen byte //CAN帧数据长度[0,8]CanData中有效数据字节数 CanLen byte //CAN帧数据长度[0,8]CanData中有效数据字节数
CanId CanFrameId //CAN帧ID CanId CanFrameId //CAN帧ID
CanData []byte //CAN帧数据8字节 CanData []byte //CAN帧数据8字节
IsTrainPcSim bool //是否列车pc仿真
} }
func NewCanetFrame(buf []byte) *CanetFrame { func NewCanetFrame(buf []byte, isTrainPcSim bool) *CanetFrame {
cf := &CanetFrame{} cf := &CanetFrame{IsTrainPcSim: isTrainPcSim}
cf.Decode(buf) cf.Decode(buf)
return cf return cf
} }
func (p *CanetFrame) Encode() []byte { func (p *CanetFrame) Encode() []byte {
buf := make([]byte, 0, 13) buf := make([]byte, 0)
//canet200帧信息 if !p.IsTrainPcSim {
b1 := byte(0x00) //canet200帧信息
if p.FF { b1 := byte(0x00)
b1 |= 0x80 //bit7 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 //CAN 帧ID
buf = append(buf, p.CanId.ID1) buf = append(buf, p.CanId.ID1)
buf = append(buf, p.CanId.ID2) buf = append(buf, p.CanId.ID2)
@ -44,13 +47,17 @@ func (p *CanetFrame) Encode() []byte {
buf = append(buf, p.CanData...) buf = append(buf, p.CanData...)
return buf return buf
} }
func (p *CanetFrame) Decode(buf []byte) { func (p *CanetFrame) Decode(buf []byte) {
if len(buf) != 13 { if len(buf) != 13 {
panic("len(buf)!=13") panic("len(buf)!=13")
} }
// //
p.FF = buf[0]&0x80 == 0x80 if !p.IsTrainPcSim {
p.RTR = buf[0]&0x40 == 0x40 p.FF = buf[0]&0x80 == 0x80
p.RTR = buf[0]&0x40 == 0x40
}
p.CanLen = buf[0] & 0x0f p.CanLen = buf[0] & 0x0f
//1 2 3 4 //1 2 3 4
p.CanId.ID1 = buf[1] p.CanId.ID1 = buf[1]
@ -62,8 +69,8 @@ func (p *CanetFrame) Decode(buf []byte) {
} }
func (p *CanetFrame) String() string { func (p *CanetFrame) String() string {
sb := strings.Builder{} 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, 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.CanId.ID1, p.CanId.ID2, p.CanId.ID3, p.CanId.ID4, p.IsTrainPcSim))
sb.WriteString("CanData = ") sb.WriteString("CanData = ")
for _, d := range p.CanData { for _, d := range p.CanData {
sb.WriteString(fmt.Sprintf(" 0x%0x ", d)) sb.WriteString(fmt.Sprintf(" 0x%0x ", d))

191
third_party/train_pc_sim/train_pc_sim.go vendored Normal file
View File

@ -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])
}
}

View File

@ -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
}

View File

@ -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数据报文
}

View File

@ -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
)

View File

@ -139,7 +139,7 @@ func TrainConnTypeUpdate(vs *VerifySimulation, ct *dto.TrainConnThirdDto) {
}) })
} }
train := data.(*state_proto.TrainState) train := data.(*state_proto.TrainState)
train.ConnState.Conn = conn //train.ConnState.Conn = conn
train.ConnState.ConnType = ct.ConnType train.ConnState.ConnType = ct.ConnType
} }

View File

@ -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
}
}

View File

@ -134,10 +134,6 @@ func CreateSimulation(projectId int32, mapIds []int32, runConfig *dto.ProjectRun
// return s.runState // return s.runState
// } // }
func (s *VerifySimulation) GetTrainPcSimConfig() config.VehiclePCSimConfig {
return s.runConfig.PcSimConfig
}
// 获取仿真世界信息 // 获取仿真世界信息
func (s *VerifySimulation) GetComIdByUid(uid string) uint32 { func (s *VerifySimulation) GetComIdByUid(uid string) uint32 {
es := s.UidMap es := s.UidMap
@ -391,11 +387,11 @@ func (s *VerifySimulation) HandleSemiPhysicalTrainControlMsg(b []byte) {
return true return true
} }
connState := train.ConnState 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 { 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)) d := append(b, uint8(trainId))
// 发送给动力学 // 发送给动力学
dynamics.Default().SendTrainControlMessage(d) dynamics.Default().SendTrainControlMessage(d)

View File

@ -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}
}