This commit is contained in:
weizhihong 2023-11-27 17:00:40 +08:00
commit d3302b3437
6 changed files with 210 additions and 29 deletions

View File

@ -71,6 +71,7 @@ type ThridPartyConfig struct {
Interlocks []InterlockConfig `json:"interlock" description:"联锁配置"` Interlocks []InterlockConfig `json:"interlock" description:"联锁配置"`
RsspAxleCfgs []RsspAxleConfig `json:"rsspAxleCfgs" description:"所有联锁集中站计轴RSSP-I配置"` RsspAxleCfgs []RsspAxleConfig `json:"rsspAxleCfgs" description:"所有联锁集中站计轴RSSP-I配置"`
ElectricMachinery ElectricMachineryConfig `json:"electricMachinery" description:"电机配置"` ElectricMachinery ElectricMachineryConfig `json:"electricMachinery" description:"电机配置"`
BtmCanet BtmCanetConfig `json:"btmCanet" description:"BTM关联的网关设备CANET配置"`
} }
type DynamicsConfig struct { type DynamicsConfig struct {
Ip string `json:"ip" description:"IP配置"` Ip string `json:"ip" description:"IP配置"`
@ -99,6 +100,14 @@ type ElectricMachineryConfig struct {
Open bool `json:"open" description:"是否开启"` Open bool `json:"open" description:"是否开启"`
} }
// BtmCanetConfig BTM CANET网关设备配置
type BtmCanetConfig struct {
LocalUdpPort int `json:"localUdpPort" description:"本机监听接收UDP端口"`
RemoteIp string `json:"remoteIp" description:"CANET设备IP配置"`
RemoteUdpPort int `json:"remoteUdpPort" description:"CANET设备UDP端口"`
Open bool `json:"open" description:"是否开启"`
}
// RsspAxleConfig 计轴区段与联锁安全通信配置 // RsspAxleConfig 计轴区段与联锁安全通信配置
type RsspAxleConfig struct { type RsspAxleConfig struct {
Open bool `json:"open" description:"是否开启"` Open bool `json:"open" description:"是否开启"`

View File

@ -2,17 +2,29 @@ package can_btm
import ( import (
"fmt" "fmt"
"joylink.club/bj-rtsts-server/config"
"joylink.club/bj-rtsts-server/third_party/message" "joylink.club/bj-rtsts-server/third_party/message"
"joylink.club/bj-rtsts-server/third_party/udp" "joylink.club/bj-rtsts-server/third_party/udp"
"joylink.club/ecs" "joylink.club/bj-rtsts-server/ts/simulation/wayside/memory"
"joylink.club/rtsssimulation/fi" "joylink.club/rtsssimulation/fi"
"log/slog" "log/slog"
"sort" "sort"
"sync"
"time"
) )
//使用当有多个虚拟车时同一时刻只有一个虚拟车与CANET关联
// BtmCanetManager BTM CANET 管理器
type BtmCanetManager interface {
memory.VerifyEvn
//GetBtmCanetConfig 获取CANET配置信息
GetBtmCanetConfig() config.BtmCanetConfig
}
// btm与canet(网口-CAN口转换器) // btm与canet(网口-CAN口转换器)
type btmCanetClient struct { type btmCanetClient struct {
w ecs.World bcm BtmCanetManager
//udp server //udp server
udpServer udp.UdpServer udpServer udp.UdpServer
//udp client //udp client
@ -23,28 +35,61 @@ type btmCanetClient struct {
remoteUdpPort int remoteUdpPort int
//udp 远程ip //udp 远程ip
remoteIp string remoteIp string
//该CANET关联的唯一列车的id
trainId string
//车载ATP系统当前时间ms
atpSystemTime uint32
//最近一次车载ATP系统查询帧序号 //最近一次车载ATP系统查询帧序号
atpReqSn byte atpReqSn byte
//最近一次车载ATP系统查询帧CRC16校验结果 //最近一次车载ATP系统查询帧CRC16校验结果,true-校验通过
atpReqCrc16Check bool atpReqCrc16Check bool
//btm系统时间,每次接收到ATP查询请求帧时同步一次时间
btmTime btmClock
//数据流水号
dsn byte
//重发的数据
resendData *resendData
} }
type BtmCanetClient interface { type btmClock struct {
Start() btmTk uint32 //与ATP系统同步的时间ms
Stop() sysTk time.Time //本地系统时间
TrainId() string
} }
func NewBtmCanetClient(trainId string, remoteIp string, remoteUdpPort int, localUdpPort int) BtmCanetClient { // 获取以btmTk为基准的当前时间ms
return &btmCanetClient{trainId: trainId, remoteIp: remoteIp, remoteUdpPort: remoteUdpPort, localUdpPort: localUdpPort} func (c *btmClock) tkNow() uint32 {
return c.btmTk + uint32(time.Now().UnixMilli()-c.sysTk.UnixMilli())
} }
func (s *btmCanetClient) TrainId() string {
return s.trainId type BtmCanetClient interface {
Start(bcm BtmCanetManager)
Stop()
} }
func (s *btmCanetClient) Start() {
var (
btmClientLocker sync.Mutex
btmClient BtmCanetClient
)
func Default() BtmCanetClient {
btmClientLocker.Lock()
defer btmClientLocker.Unlock()
if btmClient == nil {
btmClient = &btmCanetClient{}
}
return btmClient
}
func (s *btmCanetClient) Start(bcm BtmCanetManager) {
s.bcm = bcm
cfg := s.bcm.GetBtmCanetConfig()
//测试用
cfg.Open = true
cfg.RemoteUdpPort = 5555
cfg.RemoteIp = "192.168.3.5"
cfg.LocalUdpPort = 6666
//
if !cfg.Open {
return
}
s.localUdpPort = cfg.LocalUdpPort
s.remoteIp = cfg.RemoteIp
s.remoteUdpPort = cfg.RemoteUdpPort
// //
s.udpServer = udp.NewServer(fmt.Sprintf(":%d", s.localUdpPort), s.handleCanetFrames) s.udpServer = udp.NewServer(fmt.Sprintf(":%d", s.localUdpPort), s.handleCanetFrames)
s.udpServer.Listen() s.udpServer.Listen()
@ -55,9 +100,11 @@ func (s *btmCanetClient) Start() {
func (s *btmCanetClient) Stop() { func (s *btmCanetClient) Stop() {
if s.udpServer != nil { if s.udpServer != nil {
s.udpServer.Close() s.udpServer.Close()
s.udpServer = nil
} }
if s.udpClient != nil { if s.udpClient != nil {
s.udpClient.Close() s.udpClient.Close()
s.udpClient = nil
} }
} }
func (s *btmCanetClient) handleCanetFrames(cfs []byte) { func (s *btmCanetClient) handleCanetFrames(cfs []byte) {
@ -114,34 +161,80 @@ func (s *btmCanetClient) dealWithAptReq(f *message.CanetFrame) {
return return
} }
//处理查询请求 //处理查询请求
slog.Debug(fmt.Sprintf("处理查询请求:%s", atpReq.String())) //slog.Debug(fmt.Sprintf("处理查询请求:%s", atpReq.String()))
// //
s.atpSystemTime = atpReq.Time s.btmTime.btmTk = atpReq.Time
s.btmTime.sysTk = time.Now()
s.atpReqSn = atpReq.FId.ID4 s.atpReqSn = atpReq.FId.ID4
s.atpReqCrc16Check = atpReq.Crc16CheckOk s.atpReqCrc16Check = atpReq.Crc16CheckOk
se := fi.TrainBalisePowerAmplifierSwitch(s.w, s.trainId, atpReq.PowerAmplifierTurnOn) se := fi.TrainBalisePowerAmplifierSwitch(s.bcm.EvnWorld(), atpReq.PowerAmplifierTurnOn)
if se != nil { if se != nil {
slog.Warn(fmt.Sprintf("列车[%s]车载BTM功率放大器开关控制异常[%s]", s.trainId, se.Error())) slog.Warn(fmt.Sprintf("列车车载BTM功率放大器开关控制异常[%s]", se.Error()))
} }
//ATP 是否要求BTM 重发上一应答器报文
isResendRequest := atpReq.ResendRequest == 2 //0b10
s.rspToAtp(isResendRequest)
} }
// BTM发送响应给ATP // BTM发送响应给ATP
// 当收到应答器报文时响应:时间同步帧、状态应答帧、数据帧 // 当收到应答器报文时响应:时间同步帧、状态应答帧、数据帧
// 当未收到应答器报文时响应:时间同步帧、状态应答帧 // 当未收到应答器报文时响应:时间同步帧、状态应答帧
func (s *btmCanetClient) rspToAtp() { func (s *btmCanetClient) rspToAtp(isResendRequest bool) {
timeSyncF := message.NewBtmTimeSyncCheckFrame(s.atpReqSn) //重发上一报文处理
timeSyncCf := timeSyncF.Encode().Encode() if isResendRequest && s.resendData != nil && s.resendData.canResend() {
s.resendData.countAdd1()
s.sendCanetFrame(s.resendData.data)
return
} else {
s.resendData = nil
}
//BTM状态
statusF := message.NewBtmStatusRspFrame(s.atpReqSn) statusF := message.NewBtmStatusRspFrame(s.atpReqSn)
btmStatus, btmStatusErr := fi.FindTrainBaliseBtmStatus(s.bcm.EvnWorld())
if btmStatusErr != nil {
slog.Debug(fmt.Sprintf("从仿真获取BTM状态失败%s", btmStatusErr.Error()))
return
}
statusF.AntennaFault = btmStatus.AntennaFault
statusF.BaliseCounter = byte(btmStatus.BaliseCounter)
statusF.MessageCounter = byte(btmStatus.MessageCounter)
statusF.PowerAmplifierOn = btmStatus.PowerAmplifierOn
statusF.TkTimeA = s.btmTime.tkNow()
statusF.PowerAmplifierFailure = btmStatus.PowerAmplifierFault
statusF.DetailedCode = 0
if btmStatus.AboveBalise {
statusF.DetailedCode = 0x07
}
statusF.AtpReqCrcCheckWrong = !s.atpReqCrc16Check
statusF.Dsn = s.dsn
s.dsnAdd1()
//
baliseTelegram, btSendErr := fi.BaliseTelegramForSend(s.bcm.EvnWorld())
if btSendErr != nil {
//slog.Debug(btSendErr.Error())
}
//true-收到应答器报文 //true-收到应答器报文
//todo isRcvTelegram := baliseTelegram != nil
isRcvTelegram := false
if isRcvTelegram { //当收到应答器报文时响应:时间同步帧、状态应答帧、数据帧 if isRcvTelegram { //当收到应答器报文时响应:时间同步帧、状态应答帧、数据帧
s.sendCanetFrame(timeSyncCf) statusDataCf, statusDataCfOk := message.CreateBtmRspFramesData(statusF, baliseTelegram.Telegram, false, s.btmTime.tkNow(), s.btmTime.tkNow(), s.btmTime.tkNow())
if statusDataCfOk {
timeSyncF := message.NewBtmTimeSyncCheckFrame(s.atpReqSn)
timeSyncF.T2 = s.btmTime.btmTk
timeSyncF.T3 = s.btmTime.tkNow()
s.sendCanetFrame(timeSyncF.Encode().Encode())
//
s.resendData = newResendData(statusDataCf)
s.sendCanetFrame(statusDataCf)
} else {
slog.Warn("BtmCanetClient应答帧、数据帧编码失败")
}
} else { //当未收到应答器报文时响应:时间同步帧、状态应答帧 } else { //当未收到应答器报文时响应:时间同步帧、状态应答帧
timeSyncF := message.NewBtmTimeSyncCheckFrame(s.atpReqSn)
timeSyncF.T2 = s.btmTime.btmTk
timeSyncF.T3 = s.btmTime.tkNow()
s.sendCanetFrame(timeSyncF.Encode().Encode())
//
statusCf := statusF.Encode().Encode() statusCf := statusF.Encode().Encode()
s.sendCanetFrame(timeSyncCf)
s.sendCanetFrame(statusCf) s.sendCanetFrame(statusCf)
} }
} }
@ -150,6 +243,32 @@ func (s *btmCanetClient) rspToAtp() {
func (s *btmCanetClient) sendCanetFrame(cf []byte) { func (s *btmCanetClient) sendCanetFrame(cf []byte) {
s.udpClient.Send(cf) s.udpClient.Send(cf)
} }
func (s *btmCanetClient) dsnAdd1() {
if s.dsn >= 255 {
s.dsn = 0
} else {
s.dsn++
}
}
// 准备重发的状态应答帧和报文数据帧
type resendData struct {
data []byte //重发的数据
count int //重发次数
}
func newResendData(data []byte) *resendData {
return &resendData{
data: data,
count: 0,
}
}
func (r *resendData) canResend() bool {
return r.count <= 3
}
func (r *resendData) countAdd1() {
r.count++
}
////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////

View File

@ -1,5 +1,10 @@
package message package message
import (
"fmt"
"strings"
)
// BtmStatusRspFrame BTM发往ATP的状态应答帧 // BtmStatusRspFrame BTM发往ATP的状态应答帧
type BtmStatusRspFrame struct { type BtmStatusRspFrame struct {
//帧ID //帧ID
@ -157,3 +162,18 @@ func (f *BtmStatusRspFrame) Encode() *CanetFrame {
cf.CanData = writer.(CanBusData).GetData() cf.CanData = writer.(CanBusData).GetData()
return cf return cf
} }
func (f *BtmStatusRspFrame) String() string {
sb := strings.Builder{}
sb.WriteString(fmt.Sprintf("BtmStatusRspFrame ID1 = 0x%0x, ID2 = 0x%0x, ID3 = 0x%0x, ID4 = 0x%0x,",
f.FId.ID1, f.FId.ID2, f.FId.ID3, f.FId.ID4))
sb.WriteString(fmt.Sprintf("Dsn = %d", f.Dsn))
sb.WriteString(fmt.Sprintf(",BaliseCounter = %d", f.BaliseCounter))
sb.WriteString(fmt.Sprintf(",MessageCounter = %d", f.MessageCounter))
sb.WriteString(fmt.Sprintf(",PowerAmplifierOn = %t", f.PowerAmplifierOn))
sb.WriteString(fmt.Sprintf(",AtpReqCrcCheckWrong = %t", f.AtpReqCrcCheckWrong))
sb.WriteString(fmt.Sprintf(",PowerAmplifierFailure = %t", f.PowerAmplifierFailure))
sb.WriteString(fmt.Sprintf(",AntennaFault = %t", f.AntennaFault))
sb.WriteString(fmt.Sprintf(",DetailedCode = %d", f.DetailedCode))
sb.WriteString(fmt.Sprintf(",TkTimeA = %d", f.TkTimeA))
return sb.String()
}

View File

@ -1,5 +1,10 @@
package message package message
import (
"fmt"
"strings"
)
// BtmTimeSyncCheckFrame BTM发往ATP的时间校验帧 // BtmTimeSyncCheckFrame BTM发往ATP的时间校验帧
type BtmTimeSyncCheckFrame struct { type BtmTimeSyncCheckFrame struct {
//帧ID //帧ID
@ -17,6 +22,7 @@ func NewBtmTimeSyncCheckFrame(sn byte) *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),
} }
} }
func (f *BtmTimeSyncCheckFrame) Decode(cf *CanetFrame) bool { func (f *BtmTimeSyncCheckFrame) Decode(cf *CanetFrame) bool {
f.FId = cf.CanId f.FId = cf.CanId
// //
@ -100,3 +106,11 @@ func (f *BtmTimeSyncCheckFrame) Encode() *CanetFrame {
cf.CanData = writer.(CanBusData).GetData() cf.CanData = writer.(CanBusData).GetData()
return cf return cf
} }
func (f *BtmTimeSyncCheckFrame) String() string {
sb := strings.Builder{}
sb.WriteString(fmt.Sprintf("BtmTimeSyncCheckFrame ID1 = 0x%0x, ID2 = 0x%0x, ID3 = 0x%0x, ID4 = 0x%0x,",
f.FId.ID1, f.FId.ID2, f.FId.ID3, f.FId.ID4))
sb.WriteString(fmt.Sprintf("T2 = %d", f.T2))
sb.WriteString(fmt.Sprintf(",T3 = %d", f.T3))
return sb.String()
}

View File

@ -147,6 +147,11 @@ func (s *VerifySimulation) GetComIdByUid(uid string) string {
} }
return es[uid].CommonId return es[uid].CommonId
} }
// GetBtmCanetConfig 获取CANET配置信息
func (s *VerifySimulation) GetBtmCanetConfig() config.BtmCanetConfig {
return s.runConfig.BtmCanet
}
func (s *VerifySimulation) GetLineAllRsspAxleCfgs() []config.RsspAxleConfig { func (s *VerifySimulation) GetLineAllRsspAxleCfgs() []config.RsspAxleConfig {
return s.runConfig.RsspAxleCfgs return s.runConfig.RsspAxleCfgs
} }
@ -502,6 +507,9 @@ func (s *VerifySimulation) GetRunConfigId() int32 {
} }
return s.runConfig.Id return s.runConfig.Id
} }
func (s *VerifySimulation) EvnWorld() ecs.World {
return s.World
}
// 初始化运行资源 // 初始化运行资源
func (s *VerifySimulation) initRepository() error { func (s *VerifySimulation) initRepository() error {
@ -1501,3 +1509,8 @@ func convertToProtoBaliseType(bt graphicData.Transponder_TransponderTypeEnum) pr
panic(fmt.Sprintf("graphicData.Transponder_TransponderTypeEnum[%d]无法映射到proto.Transponder_Type", bt)) panic(fmt.Sprintf("graphicData.Transponder_TransponderTypeEnum[%d]无法映射到proto.Transponder_Type", bt))
} }
} }
// VerifyEvn 测试环境
type VerifyEvn interface {
EvnWorld() ecs.World
}

View File

@ -2,6 +2,7 @@ package ts
import ( import (
"fmt" "fmt"
"joylink.club/bj-rtsts-server/third_party/can_btm"
"log/slog" "log/slog"
"runtime" "runtime"
"strconv" "strconv"
@ -117,6 +118,8 @@ func runThirdParty(s *memory.VerifySimulation) error {
axle_device.StartLineAllRsspAxleServices(s) axle_device.StartLineAllRsspAxleServices(s)
// 电机UDP启动 // 电机UDP启动
electrical_machinery.Default().Start(s) electrical_machinery.Default().Start(s)
// 车载BTM启动
can_btm.Default().Start(s)
return nil return nil
} }
@ -134,6 +137,9 @@ func stopThirdParty(s *memory.VerifySimulation) {
axle_device.StopLineAllRsspAxleServices() axle_device.StopLineAllRsspAxleServices()
// 电机UDP停止 // 电机UDP停止
electrical_machinery.Default().Stop() electrical_machinery.Default().Stop()
// 车载BTM停止
can_btm.Default().Stop()
} }
func createSimulationId(projectId int32) string { func createSimulationId(projectId int32) string {