From 81724bd707ca864a708c051ff278278acbb1728f Mon Sep 17 00:00:00 2001 From: walker Date: Thu, 19 Oct 2023 09:33:40 +0800 Subject: [PATCH] =?UTF-8?q?=E9=87=8D=E6=9E=84=E5=8A=A8=E5=8A=9B=E5=AD=A6?= =?UTF-8?q?=E3=80=81=E5=8D=8A=E5=AE=9E=E7=89=A9=E6=8E=A5=E5=8F=A3(?= =?UTF-8?q?=E7=BB=93=E6=9E=84=E5=AE=8C=E6=88=90=EF=BC=8C=E5=AE=9E=E7=8E=B0?= =?UTF-8?q?=E6=9C=AA=E5=AE=8C)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/simulation.go | 2 +- ats/verify/simulation/simulation_manage.go | 461 +++++++++--------- .../wayside/memory/wayside_memory_train.go | 27 +- .../wayside/memory/wayside_simulation.go | 156 ++++++ bj-rtss-message | 2 +- config/dev.yml | 2 +- main.go | 8 +- rtss_simulation | 2 +- .../deprecated}/example_test.go | 0 {dynamics => third_party/deprecated}/http.go | 0 .../deprecated}/httpData.go | 5 + .../deprecated}/http_test.go | 0 {dynamics => third_party/deprecated}/udp.go | 0 .../deprecated}/udpData.go | 0 .../deprecated}/udp_test.go | 0 {vobc => third_party/deprecated/vobc}/udp.go | 0 .../deprecated/vobc/udp_msg.go | 0 third_party/dynamics/dynamics.go | 239 +++++++++ third_party/example/main.go | 43 ++ third_party/message/dynamics.go | 97 ++++ third_party/message/dynamics_http.go | 52 ++ third_party/message/train_control.go | 99 ++++ .../semi_physical_train.go | 65 +++ third_party/third_party.go | 11 + third_party/udp/udp_client.go | 79 +++ third_party/udp/udp_message.go | 14 + third_party/udp/udp_server.go | 62 +++ third_party/udp/udp_server_test.go | 38 ++ 28 files changed, 1220 insertions(+), 244 deletions(-) rename {dynamics => third_party/deprecated}/example_test.go (100%) rename {dynamics => third_party/deprecated}/http.go (100%) rename {dynamics => third_party/deprecated}/httpData.go (93%) rename {dynamics => third_party/deprecated}/http_test.go (100%) rename {dynamics => third_party/deprecated}/udp.go (100%) rename {dynamics => third_party/deprecated}/udpData.go (100%) rename {dynamics => third_party/deprecated}/udp_test.go (100%) rename {vobc => third_party/deprecated/vobc}/udp.go (100%) rename vobc/udpData.go => third_party/deprecated/vobc/udp_msg.go (100%) create mode 100644 third_party/dynamics/dynamics.go create mode 100644 third_party/example/main.go create mode 100644 third_party/message/dynamics.go create mode 100644 third_party/message/dynamics_http.go create mode 100644 third_party/message/train_control.go create mode 100644 third_party/semi_physical_train/semi_physical_train.go create mode 100644 third_party/third_party.go create mode 100644 third_party/udp/udp_client.go create mode 100644 third_party/udp/udp_message.go create mode 100644 third_party/udp/udp_server.go create mode 100644 third_party/udp/udp_server_test.go diff --git a/api/simulation.go b/api/simulation.go index 4a67ccd..45ba6d2 100644 --- a/api/simulation.go +++ b/api/simulation.go @@ -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, diff --git a/ats/verify/simulation/simulation_manage.go b/ats/verify/simulation/simulation_manage.go index e0256aa..676b923 100644 --- a/ats/verify/simulation/simulation_manage.go +++ b/ats/verify/simulation/simulation_manage.go @@ -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))) +// } diff --git a/ats/verify/simulation/wayside/memory/wayside_memory_train.go b/ats/verify/simulation/wayside/memory/wayside_memory_train.go index aaf2e0f..b40abcf 100644 --- a/ats/verify/simulation/wayside/memory/wayside_memory_train.go +++ b/ats/verify/simulation/wayside/memory/wayside_memory_train.go @@ -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 diff --git a/ats/verify/simulation/wayside/memory/wayside_simulation.go b/ats/verify/simulation/wayside/memory/wayside_simulation.go index 8c07862..a2313ab 100644 --- a/ats/verify/simulation/wayside/memory/wayside_simulation.go +++ b/ats/verify/simulation/wayside/memory/wayside_simulation.go @@ -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 diff --git a/bj-rtss-message b/bj-rtss-message index 3f409d7..5ec3780 160000 --- a/bj-rtss-message +++ b/bj-rtss-message @@ -1 +1 @@ -Subproject commit 3f409d717bab334f3d39a5af043c5f00881eaddd +Subproject commit 5ec37803d5699d93ae8d42a67e4f04a7b6b34afc diff --git a/config/dev.yml b/config/dev.yml index 719b521..6061c35 100644 --- a/config/dev.yml +++ b/config/dev.yml @@ -11,7 +11,7 @@ dynamics: udpRemotePort: 3000 udpRemoteTrainPort: 3001 httpPort: 7800 - open: false + open: true # VOBC vobc: ip: 10.60.1.59 diff --git a/main.go b/main.go index ca776a9..c52ec08 100644 --- a/main.go +++ b/main.go @@ -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 diff --git a/rtss_simulation b/rtss_simulation index 93725ce..30c4210 160000 --- a/rtss_simulation +++ b/rtss_simulation @@ -1 +1 @@ -Subproject commit 93725ce93431b07d1d56863eaa8a3f5f77d3a39f +Subproject commit 30c4210a5d0411bb52d383bef7e77de7b9484800 diff --git a/dynamics/example_test.go b/third_party/deprecated/example_test.go similarity index 100% rename from dynamics/example_test.go rename to third_party/deprecated/example_test.go diff --git a/dynamics/http.go b/third_party/deprecated/http.go similarity index 100% rename from dynamics/http.go rename to third_party/deprecated/http.go diff --git a/dynamics/httpData.go b/third_party/deprecated/httpData.go similarity index 93% rename from dynamics/httpData.go rename to third_party/deprecated/httpData.go index 5321785..241c9a4 100644 --- a/dynamics/httpData.go +++ b/third_party/deprecated/httpData.go @@ -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"` diff --git a/dynamics/http_test.go b/third_party/deprecated/http_test.go similarity index 100% rename from dynamics/http_test.go rename to third_party/deprecated/http_test.go diff --git a/dynamics/udp.go b/third_party/deprecated/udp.go similarity index 100% rename from dynamics/udp.go rename to third_party/deprecated/udp.go diff --git a/dynamics/udpData.go b/third_party/deprecated/udpData.go similarity index 100% rename from dynamics/udpData.go rename to third_party/deprecated/udpData.go diff --git a/dynamics/udp_test.go b/third_party/deprecated/udp_test.go similarity index 100% rename from dynamics/udp_test.go rename to third_party/deprecated/udp_test.go diff --git a/vobc/udp.go b/third_party/deprecated/vobc/udp.go similarity index 100% rename from vobc/udp.go rename to third_party/deprecated/vobc/udp.go diff --git a/vobc/udpData.go b/third_party/deprecated/vobc/udp_msg.go similarity index 100% rename from vobc/udpData.go rename to third_party/deprecated/vobc/udp_msg.go diff --git a/third_party/dynamics/dynamics.go b/third_party/dynamics/dynamics.go new file mode 100644 index 0000000..a151c0f --- /dev/null +++ b/third_party/dynamics/dynamics.go @@ -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) +} diff --git a/third_party/example/main.go b/third_party/example/main.go new file mode 100644 index 0000000..be1b9f9 --- /dev/null +++ b/third_party/example/main.go @@ -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) +} diff --git a/third_party/message/dynamics.go b/third_party/message/dynamics.go new file mode 100644 index 0000000..ddf18e0 --- /dev/null +++ b/third_party/message/dynamics.go @@ -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 +} diff --git a/third_party/message/dynamics_http.go b/third_party/message/dynamics_http.go new file mode 100644 index 0000000..2d7dc66 --- /dev/null +++ b/third_party/message/dynamics_http.go @@ -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"` +} diff --git a/third_party/message/train_control.go b/third_party/message/train_control.go new file mode 100644 index 0000000..f429e6c --- /dev/null +++ b/third_party/message/train_control.go @@ -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 +} diff --git a/third_party/semi_physical_train/semi_physical_train.go b/third_party/semi_physical_train/semi_physical_train.go new file mode 100644 index 0000000..3e03f02 --- /dev/null +++ b/third_party/semi_physical_train/semi_physical_train.go @@ -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 +} diff --git a/third_party/third_party.go b/third_party/third_party.go new file mode 100644 index 0000000..d491530 --- /dev/null +++ b/third_party/third_party.go @@ -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() +} diff --git a/third_party/udp/udp_client.go b/third_party/udp/udp_client.go new file mode 100644 index 0000000..ddb3f94 --- /dev/null +++ b/third_party/udp/udp_client.go @@ -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) +} diff --git a/third_party/udp/udp_message.go b/third_party/udp/udp_message.go new file mode 100644 index 0000000..24a43e7 --- /dev/null +++ b/third_party/udp/udp_message.go @@ -0,0 +1,14 @@ +package udp + +type UdpMessageCodec interface { + UdpMessageEncoder + UdpMessageDecoder +} + +type UdpMessageEncoder interface { + Encode() []byte +} + +type UdpMessageDecoder interface { + Decode(data []byte) error +} diff --git a/third_party/udp/udp_server.go b/third_party/udp/udp_server.go new file mode 100644 index 0000000..3947246 --- /dev/null +++ b/third_party/udp/udp_server.go @@ -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) +} diff --git a/third_party/udp/udp_server_test.go b/third_party/udp/udp_server_test.go new file mode 100644 index 0000000..586f8d4 --- /dev/null +++ b/third_party/udp/udp_server_test.go @@ -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", + }) +}