重构动力学、半实物接口(结构完成,实现未完)

This commit is contained in:
walker 2023-10-19 09:33:40 +08:00
parent eb71e7cd73
commit 81724bd707
28 changed files with 1220 additions and 244 deletions

View File

@ -242,7 +242,7 @@ func switchOperation(c *gin.Context) {
panic(dto.ErrorDto{Code: dto.ArgumentParseError, Message: err.Error()})
}
simulation := checkDeviceDataAndReturn(req.SimulationId)
slog.Info("传入状态参数", req)
slog.Info("传入状态参数", "request", req)
memory.HandleTurnoutOperation(simulation, req)
// memory.ChangeTurnoutState(simulation, &state.SwitchState{
// Id: req.DeviceId,

View File

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

View File

@ -4,13 +4,13 @@ import (
"fmt"
"log/slog"
"math"
"net/http"
"strconv"
"sync"
"time"
"joylink.club/bj-rtsts-server/dto"
"joylink.club/bj-rtsts-server/dynamics"
"joylink.club/bj-rtsts-server/third_party/dynamics"
"joylink.club/bj-rtsts-server/third_party/message"
"google.golang.org/protobuf/proto"
"joylink.club/bj-rtsts-server/ats/verify/protos/state"
@ -38,7 +38,7 @@ func AddTrainState(vs *VerifySimulation, status *state.TrainState, mapId int32)
status.Up = up
status.PointTo = pointTo
status.TrainKilometer = kilometer
httpCode, _, err := dynamics.SendInitTrainReq(&dynamics.InitTrainInfo{
err := dynamics.Default().RequestAddTrain(&message.InitTrainInfo{
TrainIndex: uint16(trainIndex),
LinkIndex: uint16(linkId),
LinkOffset: uint32(loffset),
@ -46,10 +46,18 @@ func AddTrainState(vs *VerifySimulation, status *state.TrainState, mapId int32)
Up: status.Up,
TrainLength: uint16(status.TrainLength),
})
// httpCode, _, err := dynamics.SendInitTrainReq(&dynamics.InitTrainInfo{
// TrainIndex: uint16(trainIndex),
// LinkIndex: uint16(linkId),
// LinkOffset: uint32(loffset),
// Speed: uint16(math.Round(float64(status.Speed * 10))),
// Up: status.Up,
// TrainLength: uint16(status.TrainLength),
// })
slog.Debug("添加列车", "trainIndex", trainIndex, "HeadDeviceId", status.HeadDeviceId, "HeadOffset", status.HeadOffset)
slog.Debug("列车初始化", "trainIndex", trainIndex, "linkId", linkId, "loffset", loffset)
if err != nil || httpCode != http.StatusOK {
panic(dto.ErrorDto{Code: dto.DynamicsError, Message: fmt.Sprintf("动力学接口调用失败:[%d][%s]", httpCode, err)})
if err != nil {
panic(dto.ErrorDto{Code: dto.DynamicsError, Message: err.Error()})
}
// 调用成功后初始化列车的动力学
status.DynamicState = &state.TrainDynamicState{}
@ -84,11 +92,14 @@ func RemoveTrainState(vs *VerifySimulation, id string) {
if ok {
t := d.(*state.TrainState)
trainIndex, _ := strconv.ParseUint(id, 10, 16)
httpCode, _, err := dynamics.SendRemoveTrainReq(&dynamics.InitTrainInfo{
err := dynamics.Default().RequestRemoveTrain(&message.RemoveTrainReq{
TrainIndex: uint16(trainIndex),
})
if err != nil || httpCode != http.StatusOK {
panic(dto.ErrorDto{Code: dto.DynamicsError, Message: fmt.Sprintf("动力学接口调用失败:[%d][%s]", httpCode, err)})
// httpCode, _, err := dynamics.SendRemoveTrainReq(&dynamics.InitTrainInfo{
// TrainIndex: uint16(trainIndex),
// })
if err != nil {
panic(dto.ErrorDto{Code: dto.DynamicsError, Message: err.Error()})
}
// 从仿真内存中移除列车
t.Show = false

View File

@ -2,6 +2,8 @@ package memory
import (
"fmt"
"log/slog"
"math"
"sort"
"strconv"
"strings"
@ -12,6 +14,8 @@ import (
"joylink.club/bj-rtsts-server/ats/verify/protos/graphicData"
"joylink.club/bj-rtsts-server/ats/verify/protos/state"
"joylink.club/bj-rtsts-server/dto"
"joylink.club/bj-rtsts-server/third_party/deprecated/vobc"
"joylink.club/bj-rtsts-server/third_party/message"
"joylink.club/ecs"
"joylink.club/rtsssimulation/repository"
"joylink.club/rtsssimulation/repository/model/proto"
@ -163,6 +167,158 @@ func (s *VerifySimulation) GetComIdByUid(uid string) string {
return es[uid].CommonId
}
func (sim *VerifySimulation) BuildLineBaseInfo() *message.LineBaseInfo {
info := &message.LineBaseInfo{}
for _, model := range sim.Repo.LinkList() {
id, _ := strconv.Atoi(model.Id())
link := &message.Link{
ID: int32(id),
Len: int32(model.Length()),
}
info.LinkList = append(info.LinkList, link)
if model.ARelation() != nil {
turnoutId, _ := strconv.Atoi(sim.GetComIdByUid(model.ARelation().Device().Id()))
link.ARelTurnoutId = int32(turnoutId)
switch model.ARelation().Port() {
case proto.Port_A:
link.ARelTurnoutPoint = "A"
case proto.Port_B:
link.ARelTurnoutPoint = "B"
case proto.Port_C:
link.ARelTurnoutPoint = "C"
}
}
if model.BRelation() != nil {
turnoutId, _ := strconv.Atoi(sim.GetComIdByUid(model.BRelation().Device().Id()))
link.BRelTurnoutId = int32(turnoutId)
switch model.BRelation().Port() {
case proto.Port_A:
link.BRelTurnoutPoint = "A"
case proto.Port_B:
link.BRelTurnoutPoint = "B"
case proto.Port_C:
link.BRelTurnoutPoint = "C"
}
}
}
for _, model := range sim.Repo.SlopeList() {
id, _ := strconv.Atoi(sim.GetComIdByUid(model.Id()))
slope := &message.Slope{
ID: int32(id),
StartLinkOffset: int32(model.StartLinkPosition().Offset()),
EndLinkOffset: int32(model.EndLinkPosition().Offset()),
DegreeTrig: model.Degree(),
}
info.SlopeList = append(info.SlopeList, slope)
startLinkId, _ := strconv.Atoi(model.StartLinkPosition().Link().Id())
slope.StartLinkId = int32(startLinkId)
endLinkId, _ := strconv.Atoi(model.EndLinkPosition().Link().Id())
slope.EndLinkId = int32(endLinkId)
}
for _, model := range sim.Repo.SectionalCurvatureList() {
id, _ := strconv.Atoi(sim.GetComIdByUid(model.Id()))
curve := &message.Curve{
ID: int32(id),
StartLinkOffset: int32(model.StartLinkPosition().Offset()),
EndLinkOffset: int32(model.EndLinkPosition().Offset()),
Curvature: model.Radius(),
}
info.CurveList = append(info.CurveList, curve)
startLinkId, _ := strconv.Atoi(model.StartLinkPosition().Link().Id())
curve.StartLinkId = int32(startLinkId)
endLinkId, _ := strconv.Atoi(model.EndLinkPosition().Link().Id())
curve.EndLinkId = int32(endLinkId)
}
return info
}
// 采集动力学道岔状态
func (s *VerifySimulation) CollectDynamicsTurnoutInfo() []*message.DynamicsTurnoutInfo {
stateSlice := GetAllTurnoutState(s)
var turnoutStates []*message.DynamicsTurnoutInfo
for _, sta := range stateSlice {
code64, err := strconv.ParseUint(sta.Id, 10, 16)
if err != nil {
slog.Error("id转uint16报错", err)
}
info := message.DynamicsTurnoutInfo{
Code: uint16(code64),
NPosition: sta.Dw,
RPosition: sta.Fw,
}
turnoutStates = append(turnoutStates, &info)
}
return turnoutStates
}
// 处理动力学列车速度消息
func (s *VerifySimulation) HandleDynamicsTrainInfo(info *message.DynamicsTrainInfo) {
sta, ok := s.Memory.Status.TrainStateMap.Load(strconv.Itoa(int(info.Number)))
if !ok {
return
}
trainState := sta.(*state.TrainState)
// 给半实物仿真发送速度
vobc.SendTrainSpeedTask(math.Abs(float64(info.Speed * 36)))
// 更新列车状态
UpdateTrainState(s, convert(info, trainState, s))
}
func convert(info *message.DynamicsTrainInfo, sta *state.TrainState, simulation *VerifySimulation) *state.TrainState {
slog.Debug("收到动力学原始消息", "Number", info.Number, "Link", info.Link, "LinkOffset", info.LinkOffset)
id, port, offset, runDirection, pointTo, kilometer := QueryDeviceByCalcLink(simulation.Repo, strconv.Itoa(int(info.Link)), int64(info.LinkOffset), info.Up)
slog.Debug("处理动力学转换后的消息", "number", info.Number,
"车头位置", id, "偏移", offset, "是否上行", runDirection, "是否ab", pointTo)
sta.HeadDeviceId = simulation.GetComIdByUid(id)
sta.DevicePort = port
sta.HeadOffset = offset
sta.PointTo = pointTo
sta.TrainKilometer = kilometer
sta.RunDirection = runDirection
//判定车头方向
sta.HeadDirection = runDirection
if sta.VobcState != nil {
if sta.VobcState.DirectionForward {
sta.HeadDirection = runDirection
} else if sta.VobcState.DirectionBackward {
sta.HeadDirection = !runDirection
}
}
if info.Speed < 0 {
sta.RunDirection = !sta.RunDirection
}
// 赋值动力学信息
sta.DynamicState.Heartbeat = int32(info.LifeSignal)
sta.DynamicState.HeadLinkId = strconv.Itoa(int(info.Link))
sta.DynamicState.HeadLinkOffset = int64(info.LinkOffset)
sta.DynamicState.Slope = int32(info.Slope)
sta.DynamicState.Upslope = info.UpSlope
sta.DynamicState.RunningUp = info.Up
sta.DynamicState.RunningResistanceSum = float32(info.TotalResistance) / 1000
sta.DynamicState.AirResistance = float32(info.AirResistance) / 1000
sta.DynamicState.RampResistance = float32(info.SlopeResistance) / 1000
sta.DynamicState.CurveResistance = float32(info.CurveResistance) / 1000
sta.DynamicState.Speed = speedParse(info.Speed)
sta.DynamicState.HeadSensorSpeed1 = speedParse(info.HeadSpeed1)
sta.DynamicState.HeadSensorSpeed2 = speedParse(info.HeadSpeed2)
sta.DynamicState.TailSensorSpeed1 = speedParse(info.TailSpeed1)
sta.DynamicState.TailSensorSpeed2 = speedParse(info.TailSpeed2)
sta.DynamicState.HeadRadarSpeed = speedParse(info.HeadRadarSpeed)
sta.DynamicState.TailRadarSpeed = speedParse(info.TailRadarSpeed)
sta.DynamicState.Acceleration = info.Acceleration
return sta
}
// 发送给前端的速度格式化
func speedParse(speed float32) int32 {
return int32(math.Abs(float64(speed * 3.6 * 100)))
}
// 处理半实物仿真列车控制消息
func (s *VerifySimulation) HandleSemiPhysicalTrainControlMsg(msg *message.TrainControlMsg) {
}
func buildProtoRepository(mapIds []int32) (*proto.Repository, error) {
repo := &proto.Repository{}
var exceptStationGiMapIds []int32

@ -1 +1 @@
Subproject commit 3f409d717bab334f3d39a5af043c5f00881eaddd
Subproject commit 5ec37803d5699d93ae8d42a67e4f04a7b6b34afc

View File

@ -11,7 +11,7 @@ dynamics:
udpRemotePort: 3000
udpRemoteTrainPort: 3001
httpPort: 7800
open: false
open: true
# VOBC
vobc:
ip: 10.60.1.59

View File

@ -8,9 +8,9 @@ import (
"joylink.club/bj-rtsts-server/api"
"joylink.club/bj-rtsts-server/config"
"joylink.club/bj-rtsts-server/docs"
"joylink.club/bj-rtsts-server/dynamics"
"joylink.club/bj-rtsts-server/middleware"
"joylink.club/bj-rtsts-server/vobc"
"joylink.club/bj-rtsts-server/third_party"
)
// @title CBTC测试系统API
@ -26,6 +26,7 @@ import (
func main() {
engine := InitServer()
third_party.Init()
authMiddleware := middleware.InitGinJwtMiddleware()
router := engine.Group("/api")
api.InitUserRouter(router, authMiddleware)
@ -40,9 +41,6 @@ func main() {
docs.SwaggerInfo.Title = "CBTC测试系统API"
engine.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
go dynamics.RunUdpServer()
go vobc.RunUdpServer()
serverConfig := config.Config.Server
if serverConfig.Port == 0 {
serverConfig.Port = 8080

@ -1 +1 @@
Subproject commit 93725ce93431b07d1d56863eaa8a3f5f77d3a39f
Subproject commit 30c4210a5d0411bb52d383bef7e77de7b9484800

View File

@ -10,6 +10,11 @@ type InitTrainInfo struct {
TrainLength uint16 `json:"trainLength"`
}
// 移除列车请求参数
type RemoveTrainReq struct {
TrainIndex uint16 `json:"trainIndex"`
}
// LineBaseInfo 线路基础信息,提供给动力学作为计算依据
type LineBaseInfo struct {
LinkList []*Link `json:"linkList"`

239
third_party/dynamics/dynamics.go vendored Normal file
View File

@ -0,0 +1,239 @@
package dynamics
import (
"bytes"
"context"
"encoding/json"
"fmt"
"log/slog"
"net/http"
"time"
"joylink.club/bj-rtsts-server/config"
"joylink.club/bj-rtsts-server/third_party/message"
"joylink.club/bj-rtsts-server/third_party/udp"
)
type DynamicsMessageManager interface {
CollectDynamicsTurnoutInfo() []*message.DynamicsTurnoutInfo
HandleDynamicsTrainInfo(info *message.DynamicsTrainInfo)
}
// 动力学接口
type Dynamics interface {
// 请求启动仿真
RequestStartSimulation(base *message.LineBaseInfo) error
// 请求停止仿真
RequestStopSimulation() error
// 请求添加列车
RequestAddTrain(info *message.InitTrainInfo) error
// 请求移除列车
RequestRemoveTrain(req *message.RemoveTrainReq) error
// 启动动力学消息功能
Start(manager DynamicsMessageManager)
// 停止动力学消息功能
Stop()
// 发送列车控制消息
SendTrainControlMessage(b []byte)
}
var _default Dynamics
func Default() Dynamics {
if !config.Config.Dynamics.Open {
panic("动力学接口模块未开启")
}
return _default
}
func Init() {
if !config.Config.Dynamics.Open {
return
}
slog.Info("初始化动力学接口模块")
_default = newDynamics()
}
type dynamics struct {
trainInfoUdpServer udp.UdpServer
turnoutStateUdpClient udp.UdpClient
trainControlUdpClient udp.UdpClient
baseUrl string
httpClient *http.Client
manager DynamicsMessageManager
turnoutTaskCancel context.CancelFunc
}
func newDynamics() Dynamics {
d := &dynamics{
turnoutStateUdpClient: udp.NewClient(fmt.Sprintf("%v:%v", config.Config.Dynamics.Ip, config.Config.Dynamics.UdpRemotePort)),
trainControlUdpClient: udp.NewClient(fmt.Sprintf("%v:%v", config.Config.Dynamics.Ip, config.Config.Dynamics.UdpRemoteTrainPort)),
baseUrl: getUrlBase(),
httpClient: &http.Client{
Timeout: time.Second * 5,
},
}
d.trainInfoUdpServer = udp.NewServer(fmt.Sprintf(":%d", config.Config.Dynamics.UdpLocalPort), d.handleDynamicsTrainInfo)
return d
}
// 解码列车信息并处理
func (d *dynamics) handleDynamicsTrainInfo(b []byte) {
trainInfo := &message.DynamicsTrainInfo{}
err := trainInfo.Decode(b)
if err != nil {
panic(err)
}
handler := d.manager
if handler != nil {
handler.HandleDynamicsTrainInfo(trainInfo)
}
}
func getUrlBase() string {
ip := config.Config.Dynamics.Ip
var port string
if config.Config.Dynamics.HttpPort != 0 {
port = fmt.Sprintf(":%d", config.Config.Dynamics.HttpPort)
}
urlBase := "http://" + ip + port
return urlBase
}
func (d *dynamics) buildUrl(uri string) string {
return d.baseUrl + uri
}
func (d *dynamics) RequestStartSimulation(base *message.LineBaseInfo) error {
if !config.Config.Dynamics.Open {
return nil
}
url := d.buildUrl("/api/start/")
data, _ := json.Marshal(base)
resp, err := d.httpClient.Post(url, "application/json", bytes.NewBuffer(data))
if err != nil {
return fmt.Errorf("请求启动仿真异常: %v", err)
}
defer resp.Body.Close()
var buf []byte
_, err = resp.Body.Read(buf)
if err != nil {
return fmt.Errorf("请求启动仿真读取相应异常: %v", err)
}
return nil
}
func (d *dynamics) RequestStopSimulation() error {
if !config.Config.Dynamics.Open {
return nil
}
url := d.buildUrl("/api/end/")
resp, err := d.httpClient.Post(url, "application/json", nil)
if err != nil {
return fmt.Errorf("停止仿真请求异常: %v", err)
}
defer resp.Body.Close()
var buf []byte
_, err = resp.Body.Read(buf)
if err != nil {
return fmt.Errorf("停止仿真响应读取异常: %v", err)
}
return nil
}
func (d *dynamics) RequestAddTrain(info *message.InitTrainInfo) error {
if !config.Config.Dynamics.Open {
return nil
}
url := d.buildUrl("/api/aerodynamics/init/train/")
data, _ := json.Marshal(info)
resp, err := d.httpClient.Post(url, "application/json", bytes.NewBuffer(data))
if err != nil {
return fmt.Errorf("动力学添加列车请求异常: %v", err)
}
defer resp.Body.Close()
var buf []byte
_, err = resp.Body.Read(buf)
if err != nil {
return fmt.Errorf("动力学添加列车响应读取异常: %v", err)
}
return nil
}
func (d *dynamics) RequestRemoveTrain(req *message.RemoveTrainReq) error {
if !config.Config.Dynamics.Open {
return nil
}
url := d.buildUrl("/api/aerodynamics/remove/train/")
data, _ := json.Marshal(req)
resp, err := d.httpClient.Post(url, "application/json", bytes.NewBuffer(data))
if err != nil {
return fmt.Errorf("动力学移除列车请求异常: %v", err)
}
defer resp.Body.Close()
var buf []byte
_, err = resp.Body.Read(buf)
if err != nil {
return fmt.Errorf("动力学移除列车响应读取异常: %v", err)
}
return nil
}
func (d *dynamics) Start(manager DynamicsMessageManager) {
if manager == nil {
panic("启动动力学消息服务错误: DynamicsMessageManager不能为nil")
}
if d.manager != nil {
panic("启动动力学消息服务错误: 存在正在运行的任务")
}
d.manager = manager
ctx, cancle := context.WithCancel(context.Background())
go d.sendTurnoutStateTask(ctx)
d.turnoutTaskCancel = cancle
}
func (d *dynamics) Stop() {
if d.turnoutTaskCancel != nil {
d.turnoutTaskCancel()
d.manager = nil
}
}
const (
// 道岔消息发送间隔,单位ms
TurnoutMessageSendInterval = 50
)
var turnoutStateLifeSignal uint16 //道岔消息生命信号
// 定时发送道岔状态任务
func (d *dynamics) sendTurnoutStateTask(ctx context.Context) {
defer func() {
if err := recover(); err != nil {
slog.Error("定时发送道岔状态任务异常", err)
}
}()
for {
select {
case <-ctx.Done():
return
default:
}
turnoutStates := d.manager.CollectDynamicsTurnoutInfo()
slog.Debug("发送道岔状态", "count", len(turnoutStates))
for _, state := range turnoutStates {
turnoutStateLifeSignal++
state.LifeSignal = turnoutStateLifeSignal
d.turnoutStateUdpClient.SendMsg(state)
}
time.Sleep(time.Millisecond * TurnoutMessageSendInterval)
}
}
func (d *dynamics) SendTrainControlMessage(b []byte) {
d.trainControlUdpClient.Send(b)
}

43
third_party/example/main.go vendored Normal file
View File

@ -0,0 +1,43 @@
package main
import (
"log/slog"
"time"
"joylink.club/bj-rtsts-server/third_party/udp"
)
type TestMsg struct {
Msg string
Port int
}
func (t *TestMsg) Encode() []byte {
b := []byte(t.Msg)
// binary.BigEndian.PutUint16(b, uint16(t.Port))
return b
}
func (t *TestMsg) Decode(data []byte) error {
t.Msg = string(data)
// t.Port = int(binary.BigEndian.Uint16(data))
return nil
}
func handleUdpMsg(b []byte) {
slog.Info("udp server handle", "msg", string(b))
}
func main() {
udp.NewServer("127.0.0.1:6666", handleUdpMsg).Listen()
// client := udp.NewClient("127.0.0.1:7777")
// for i := 0; i < 1000; i++ {
// time.Sleep(time.Millisecond * 500)
// client.Send(&TestMsg{
// Msg: "test, port = 7777",
// })
// }
time.Sleep(time.Second * 60)
}

97
third_party/message/dynamics.go vendored Normal file
View File

@ -0,0 +1,97 @@
package message
import (
"encoding/binary"
"math"
)
type DynamicsTurnoutInfo struct {
LifeSignal uint16
Code uint16
NPosition bool
RPosition bool
}
func (t *DynamicsTurnoutInfo) Encode() []byte {
var data []byte
data = binary.BigEndian.AppendUint16(data, t.LifeSignal)
data = binary.BigEndian.AppendUint16(data, t.Code)
var b byte
if t.NPosition {
b |= 1 << 7
}
if t.RPosition {
b |= 1 << 6
}
data = append(data, b)
return data
}
type DynamicsTrainInfo struct {
//生命信号
LifeSignal uint16
//列车号(车辆)
Number uint8
//列车长度 cm
Len uint16
//列车所在轨道link
Link uint8
//列车所在link偏移量mm
LinkOffset uint32
//列车所在位置坡度值(‰)
Slope uint16
//列车所在位置坡度走势(上/下坡)
UpSlope bool
//列车当前运行方向(偏移量增大/减小方向)
Up bool
//实际运行阻力N
TotalResistance int32
//阻力1空气阻力N
AirResistance int32
//阻力2坡道阻力N
SlopeResistance int32
//阻力3曲线阻力N
CurveResistance int32
//列车运行速度m/s
Speed float32
//头车速传1速度值m/s
HeadSpeed1 float32
//头车速度2速度值m/s
HeadSpeed2 float32
//尾车速传1速度值m/s
TailSpeed1 float32
//尾车速度2速度值m/s
TailSpeed2 float32
//头车雷达速度值m/s
HeadRadarSpeed float32
//尾车雷达速度值m/s
TailRadarSpeed float32
//加速度(m/s^2)
Acceleration float32
}
// 解析动力学的列车信息
func (t *DynamicsTrainInfo) Decode(buf []byte) error {
t.LifeSignal = binary.BigEndian.Uint16(buf[0:2])
t.Number = buf[2]
t.Len = binary.BigEndian.Uint16(buf[3:5])
t.Link = buf[5]
t.LinkOffset = binary.BigEndian.Uint32(buf[6:10])
t.Slope = binary.BigEndian.Uint16(buf[10:12])
b := buf[12]
t.UpSlope = (b & (1 << 7)) != 0
t.Up = (b & (1 << 6)) != 0
t.TotalResistance = int32(binary.BigEndian.Uint32(buf[14:18]))
t.AirResistance = int32(binary.BigEndian.Uint32(buf[18:22]))
t.SlopeResistance = int32(binary.BigEndian.Uint32(buf[22:26]))
t.CurveResistance = int32(binary.BigEndian.Uint32(buf[26:30]))
t.Speed = math.Float32frombits(binary.BigEndian.Uint32(buf[30:34]))
t.HeadSpeed1 = math.Float32frombits(binary.BigEndian.Uint32(buf[34:38]))
t.HeadSpeed2 = math.Float32frombits(binary.BigEndian.Uint32(buf[38:42]))
t.TailSpeed1 = math.Float32frombits(binary.BigEndian.Uint32(buf[42:46]))
t.TailSpeed2 = math.Float32frombits(binary.BigEndian.Uint32(buf[46:50]))
t.HeadRadarSpeed = math.Float32frombits(binary.BigEndian.Uint32(buf[50:54]))
t.TailRadarSpeed = math.Float32frombits(binary.BigEndian.Uint32(buf[54:58]))
t.Acceleration = math.Float32frombits(binary.BigEndian.Uint32(buf[58:62]))
return nil
}

52
third_party/message/dynamics_http.go vendored Normal file
View File

@ -0,0 +1,52 @@
package message
type InitTrainInfo struct {
TrainIndex uint16 `json:"trainIndex"`
LinkIndex uint16 `json:"linkIndex"`
LinkOffset uint32 `json:"linkOffset"`
//单位0.1km/h
Speed uint16 `json:"speed"`
Up bool `json:"up"`
TrainLength uint16 `json:"trainLength"`
}
// 移除列车请求参数
type RemoveTrainReq struct {
TrainIndex uint16 `json:"trainIndex"`
}
// LineBaseInfo 线路基础信息,提供给动力学作为计算依据
type LineBaseInfo struct {
LinkList []*Link `json:"linkList"`
SlopeList []*Slope `json:"slopeList"`
CurveList []*Curve `json:"curveList"`
}
type Link struct {
ID int32 `json:"id"`
//长度 mm
Len int32 `json:"len"`
ARelTurnoutId int32 `json:"ARelTurnoutId"`
ARelTurnoutPoint string `json:"ARelTurnoutPoint"`
BRelTurnoutId int32 `json:"BRelTurnoutId"`
BRelTurnoutPoint string `json:"BRelTurnoutPoint"`
}
type Slope struct {
ID int32 `json:"id"`
StartLinkId int32 `json:"startLinkId"`
StartLinkOffset int32 `json:"startLinkOffset"`
EndLinkId int32 `json:"endLinkId"`
EndLinkOffset int32 `json:"endLinkOffset"`
//坡度的三角函数(猜是sin)值的*1000
DegreeTrig int32 `json:"degreeTrig"`
}
type Curve struct {
ID int32 `json:"id"`
StartLinkId int32 `json:"startLinkId"`
StartLinkOffset int32 `json:"startLinkOffset"`
EndLinkId int32 `json:"endLinkId"`
EndLinkOffset int32 `json:"endLinkOffset"`
Curvature int32 `json:"curvature"`
}

99
third_party/message/train_control.go vendored Normal file
View File

@ -0,0 +1,99 @@
package message
import "encoding/binary"
// 接收到的列车控制信息
type TrainControlMsg struct {
//【0 1】两个字节
// 生命信号 每个周期+1
LifeSignal uint16
//【2】 一个字节
// TC1激活状态 1=激活
Tc1Active bool
// TC2激活状态 1=激活
Tc2Active bool
// 列车方向向前 1=方向向前
DirectionForward bool
// 列车方向向后 1=方向向后
DirectionBackward bool
// 列车牵引状态 1=牵引
TractionStatus bool
// 列车制动状态 1=制动
BrakingStatus bool
// 列车紧急制动状态 1=紧急制动
EmergencyBrakingStatus bool
// 列车折返状态AR 1=折返
TurnbackStatus bool
//【3】 一个字节
// 跳跃状态 1=跳跃
JumpStatus bool
// ATO模式 1=ATO模式
ATO bool
// FAM模式 1=FAM模式
FAM bool
// CAM模式 1=CAM模式
CAM bool
// 牵引安全回路 1=牵引安全切除
TractionSafetyCircuit bool
// 停放制动状态 1=停放施加
ParkingBrakeStatus bool
// 保持制动状态 1=保持制动施加
MaintainBrakeStatus bool
//【4 5】 两个字节 列车牵引力 100=1KN
TractionForce uint16
//【6 7】 列车制动力 100=1KN
BrakeForce uint16
//【8 9】 列车载荷 100=1ton
TrainLoad uint16
// 【15】 一个字节
// 列车开左门指令 1=开门
LeftDoorOpenCommand bool
// 列车开右门指令 1=开门
RightDoorOpenCommand bool
// 列车关左门指令 1=关门
LeftDoorCloseCommand bool
// 列车关右门指令 1=关门
RightDoorCloseCommand bool
// 整列车门关好 1=门关好
AllDoorClose bool
}
// 解析VOBC列车信息
func (r *TrainControlMsg) DecoderVobcTrainInfo(buf []byte) *TrainControlMsg {
r.LifeSignal = binary.BigEndian.Uint16(buf[0:2])
b2 := buf[2]
r.Tc1Active = (b2 & (1 << 7)) != 0
r.Tc2Active = (b2 & (1 << 6)) != 0
r.DirectionForward = (b2 & (1 << 5)) != 0
r.DirectionBackward = (b2 & (1 << 4)) != 0
r.TractionStatus = (b2 & (1 << 3)) != 0
r.BrakingStatus = (b2 & (1 << 2)) != 0
r.EmergencyBrakingStatus = (b2 & (1 << 1)) != 0
r.TurnbackStatus = (b2 & 1) != 0
b3 := buf[3]
r.JumpStatus = (b3 & (1 << 7)) != 0
r.ATO = (b3 & (1 << 6)) != 0
r.FAM = (b3 & (1 << 5)) != 0
r.CAM = (b3 & (1 << 4)) != 0
r.TractionSafetyCircuit = (b3 & (1 << 3)) != 0
r.ParkingBrakeStatus = (b3 & (1 << 2)) != 0
r.MaintainBrakeStatus = (b3 & (1 << 1)) != 0
r.TractionForce = binary.BigEndian.Uint16(buf[4:6])
r.BrakeForce = binary.BigEndian.Uint16(buf[6:8])
r.TrainLoad = binary.BigEndian.Uint16(buf[8:10])
b4 := buf[15]
r.LeftDoorOpenCommand = (b4 & (1 << 7)) != 0
r.RightDoorOpenCommand = (b4 & (1 << 6)) != 0
r.LeftDoorCloseCommand = (b4 & (1 << 5)) != 0
r.RightDoorCloseCommand = (b4 & (1 << 4)) != 0
r.AllDoorClose = (b4 & (1 << 3)) != 0
return r
}
// 发送列车信息
type TrainSpeedMsg struct {
// 生命信号 每个周期+1
LifeSignal uint16
// 列车速度 10=1km/h
Speed uint16
}

View File

@ -0,0 +1,65 @@
package semi_physical_train
import (
"fmt"
"joylink.club/bj-rtsts-server/config"
"joylink.club/bj-rtsts-server/third_party/message"
"joylink.club/bj-rtsts-server/third_party/udp"
)
// 半实物仿真列车通信接口
type SemiPhysicalTrain interface {
// 启动半实物仿真消息处理
Start(manager SemiPhysicalMessageManager)
// 停止半实物仿真消息处理
Stop()
}
type SemiPhysicalMessageManager interface {
// 处理半实物仿真列车控制消息
HandleSemiPhysicalTrainControlMsg(msg *message.TrainControlMsg)
}
type semiPhysicalTrainImpl struct {
trainControlUdpServer udp.UdpServer
trainSpeedInfoUdpClient udp.UdpClient
manager SemiPhysicalMessageManager
}
var _default SemiPhysicalTrain
func Default() SemiPhysicalTrain {
if !config.Config.Vobc.Open {
panic("半实物仿真接口模块未开启")
}
return _default
}
func Init() {
if !config.Config.Vobc.Open {
return
}
_default = newSemiPhysicalTrain()
}
func newSemiPhysicalTrain() SemiPhysicalTrain {
s := &semiPhysicalTrainImpl{
trainSpeedInfoUdpClient: udp.NewClient("127.0.0.1:7777"),
}
s.trainControlUdpServer = udp.NewServer(fmt.Sprintf(":%d", config.Config.Dynamics.UdpLocalPort), s.handleTrainControlMsg)
return s
}
func (s *semiPhysicalTrainImpl) handleTrainControlMsg(b []byte) {
}
func (s *semiPhysicalTrainImpl) Start(manager SemiPhysicalMessageManager) {
s.manager = manager
}
func (s *semiPhysicalTrainImpl) Stop() {
s.manager = nil
}

11
third_party/third_party.go vendored Normal file
View File

@ -0,0 +1,11 @@
package third_party
import (
"joylink.club/bj-rtsts-server/third_party/dynamics"
"joylink.club/bj-rtsts-server/third_party/semi_physical_train"
)
func Init() {
dynamics.Init()
semi_physical_train.Init()
}

79
third_party/udp/udp_client.go vendored Normal file
View File

@ -0,0 +1,79 @@
package udp
import (
"log/slog"
"net"
)
type UdpClient interface {
SendMsg(msg UdpMessageEncoder)
Send(b []byte)
}
type client struct {
laddr *net.UDPAddr
raddr *net.UDPAddr
conn *net.UDPConn
}
// 新建UDP客户端
// remoteAddr - 远端地址, 如 127.0.0.1:7777
func NewClient(remoteAddr string) UdpClient {
addr, err := net.ResolveUDPAddr("udp", remoteAddr)
if err != nil {
panic(err)
}
var laddr *net.UDPAddr = nil
conn, err := net.DialUDP("udp", laddr, addr)
if err != nil {
panic(err)
}
c := &client{
laddr: laddr,
raddr: addr,
conn: conn,
}
return c
}
// 新建UDP客户端
// remoteAddr - 远端地址, 如 127.0.0.1:7777
// localAddr - 本地地址, 如 :9999
func NewClientWithLocalAddr(remoteAddr string, localAddr string) UdpClient {
addr, err := net.ResolveUDPAddr("udp", remoteAddr)
if err != nil {
panic(err)
}
laddr, err := net.ResolveUDPAddr("udp", localAddr)
if err != nil {
slog.Info("UDP客户端使用随机端口")
laddr = nil
}
conn, err := net.DialUDP("udp", laddr, addr)
if err != nil {
panic(err)
}
c := &client{
laddr: laddr,
raddr: addr,
conn: conn,
}
return c
}
func (c *client) SendMsg(msg UdpMessageEncoder) {
b := msg.Encode()
_, err := c.conn.Write(b)
if err != nil {
slog.Error("udp client send error", "error", err)
}
// slog.Debug("udp client send", "size", n)
}
func (c *client) Send(b []byte) {
_, err := c.conn.Write(b)
if err != nil {
slog.Error("udp client send error", "error", err)
}
// slog.Debug("udp client send", "size", n)
}

14
third_party/udp/udp_message.go vendored Normal file
View File

@ -0,0 +1,14 @@
package udp
type UdpMessageCodec interface {
UdpMessageEncoder
UdpMessageDecoder
}
type UdpMessageEncoder interface {
Encode() []byte
}
type UdpMessageDecoder interface {
Decode(data []byte) error
}

62
third_party/udp/udp_server.go vendored Normal file
View File

@ -0,0 +1,62 @@
package udp
import (
"log/slog"
"net"
)
type UdpServer interface {
Listen()
}
type UdpMsgHandler func(b []byte)
type server struct {
addr string
conn *net.UDPConn
handler UdpMsgHandler
}
// NewServer creates a new instance of UdpServer.
func NewServer(addr string, handler UdpMsgHandler) UdpServer {
return &server{addr: addr, handler: handler}
}
func (s *server) Listen() {
udpAddr, err := net.ResolveUDPAddr("udp", s.addr)
if err != nil {
panic(err)
}
conn, err := net.ListenUDP("udp", udpAddr)
if err != nil {
panic(err)
}
s.conn = conn
// 启动监听处理
go s.listenAndHandle()
}
func (s *server) listenAndHandle() {
defer s.conn.Close()
for {
b := make([]byte, 1024)
n, err := s.conn.Read(b)
if err != nil {
slog.Error("udp server read error", err)
return
}
if n > 0 {
go s.handle(b[0:n])
}
}
}
func (s *server) handle(b []byte) {
defer func() {
if err := recover(); err != nil {
slog.Error("udp server handle error", err)
}
}()
// slog.Info("udp server handle", "msg", string(b))
s.handler(b)
}

38
third_party/udp/udp_server_test.go vendored Normal file
View File

@ -0,0 +1,38 @@
package udp_test
import (
"log/slog"
"testing"
"joylink.club/bj-rtsts-server/third_party/udp"
)
type TestMsg struct {
Msg string
Port int
}
func (t *TestMsg) Encode() []byte {
b := []byte(t.Msg)
// binary.BigEndian.PutUint16(b, uint16(t.Port))
return b
}
func (t *TestMsg) Decode(data []byte) error {
t.Msg = string(data)
// t.Port = int(binary.BigEndian.Uint16(data))
return nil
}
func handleUdpMsg(b []byte) {
slog.Info("udp server handle", "msg", string(b))
}
func TestUdpServer(t *testing.T) {
udp.NewServer("127.0.0.1:7777", handleUdpMsg).Listen()
client := udp.NewClient("127.0.0.1:7777")
client.SendMsg(&TestMsg{
Msg: "test, port = 7777",
})
}