Merge branch 'master' of https://git.code.tencent.com/beijing-rtss-test/bj-rtsts-server-go
This commit is contained in:
commit
0261158e06
|
@ -79,6 +79,7 @@ func createByProjectId(c *gin.Context) {
|
|||
if err := c.ShouldBind(&req); nil != err {
|
||||
panic(sys_error.New("测试启动失败,请求参数异常", err))
|
||||
}
|
||||
// 地图信息
|
||||
mapInfos := service.QueryProjectPublishedGi(req.ProjectId)
|
||||
if len(mapInfos) == 0 {
|
||||
panic(sys_error.New("测试启动失败,项目未关联发布图"))
|
||||
|
@ -87,11 +88,17 @@ func createByProjectId(c *gin.Context) {
|
|||
for i, mapInfo := range mapInfos {
|
||||
mapIds[i] = mapInfo.ID
|
||||
}
|
||||
rsp := dto.SimulationCreateRspDto{ProjectId: req.ProjectId, MapId: mapIds[0], MapIds: mapIds}
|
||||
simulationId, err := simulation.CreateSimulation(req.ProjectId, mapIds)
|
||||
// 运行环境配置
|
||||
var runConfigStr string
|
||||
runConfig := service.QueryRunConfig(req.ProjectRunConfigId)
|
||||
if runConfig != nil {
|
||||
runConfigStr = runConfig.ConfigContent
|
||||
}
|
||||
simulationId, err := simulation.CreateSimulation(req.ProjectId, mapIds, runConfigStr)
|
||||
if err != nil {
|
||||
panic(sys_error.New("测试启动失败", err))
|
||||
}
|
||||
rsp := dto.SimulationCreateRspDto{ProjectId: req.ProjectId, MapId: mapIds[0], MapIds: mapIds}
|
||||
rsp.SimulationId = simulationId
|
||||
c.JSON(http.StatusOK, &rsp)
|
||||
}
|
||||
|
|
|
@ -8,8 +8,6 @@ import (
|
|||
"joylink.club/bj-rtsts-server/config"
|
||||
"joylink.club/bj-rtsts-server/message_server"
|
||||
"joylink.club/bj-rtsts-server/sys_error"
|
||||
"joylink.club/bj-rtsts-server/third_party/dynamics"
|
||||
"joylink.club/bj-rtsts-server/third_party/semi_physical_train"
|
||||
|
||||
"joylink.club/bj-rtsts-server/dto"
|
||||
)
|
||||
|
@ -28,31 +26,18 @@ func IsExistSimulation() bool {
|
|||
}
|
||||
|
||||
// 创建仿真对象
|
||||
func CreateSimulation(projectId int32, mapIds []int32) (string, error) {
|
||||
func CreateSimulation(projectId int32, mapIds []int32, runConfig string) (string, error) {
|
||||
simulationId := createSimulationId(projectId)
|
||||
_, e := simulationMap.Load(simulationId)
|
||||
if !e && IsExistSimulation() {
|
||||
return "", sys_error.New("一套环境同时只能运行一个仿真")
|
||||
}
|
||||
if !e {
|
||||
verifySimulation, err := memory.CreateSimulation(projectId, mapIds)
|
||||
verifySimulation, err := memory.CreateSimulation(projectId, mapIds, runConfig)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
verifySimulation.SimulationId = simulationId
|
||||
if config.Config.Dynamics.Open {
|
||||
// 动力学接口调用
|
||||
lineBaseInfo := verifySimulation.BuildLineBaseInfo()
|
||||
err := dynamics.Default().RequestStartSimulation(lineBaseInfo)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
dynamics.Default().Start(verifySimulation)
|
||||
}
|
||||
if config.Config.Vobc.Open {
|
||||
// 半实物系统接口功能启动
|
||||
semi_physical_train.Default().Start(verifySimulation)
|
||||
}
|
||||
simulationMap.Store(simulationId, verifySimulation)
|
||||
// 全部成功,启动仿真
|
||||
verifySimulation.World.StartUp()
|
||||
|
@ -68,16 +53,10 @@ func DestroySimulation(simulationId string) {
|
|||
if !e {
|
||||
return
|
||||
}
|
||||
simulationInfo := s.(*memory.VerifySimulation)
|
||||
simulationMap.Delete(simulationId)
|
||||
// 停止ecs world
|
||||
simulationInfo.World.Close()
|
||||
simulationInfo := s.(*memory.VerifySimulation)
|
||||
message_server.Close(simulationInfo)
|
||||
if config.Config.Dynamics.Open {
|
||||
// 停止动力学接口功能
|
||||
dynamics.Default().Stop()
|
||||
dynamics.Default().RequestStopSimulation()
|
||||
}
|
||||
simulationInfo.StopSimulation()
|
||||
}
|
||||
|
||||
func createSimulationId(projectId int32) string {
|
||||
|
|
|
@ -13,9 +13,11 @@ import (
|
|||
|
||||
"google.golang.org/protobuf/proto"
|
||||
"joylink.club/bj-rtsts-server/ats/verify/protos/graphicData"
|
||||
"joylink.club/bj-rtsts-server/ats/verify/protos/state"
|
||||
"joylink.club/bj-rtsts-server/db/dbquery"
|
||||
"joylink.club/bj-rtsts-server/db/model"
|
||||
"joylink.club/bj-rtsts-server/dto"
|
||||
"joylink.club/bj-rtsts-server/sys_error"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -95,13 +97,14 @@ func QueryGiId(name string) int32 {
|
|||
}
|
||||
|
||||
// 根据区段,道岔偏移量返回linkID和link相对偏移量
|
||||
func QueryEcsLinkByDeviceInfo(repo *repository.Repository, mapId int32, id string, devicePort string, offset int64, runDirection bool) (int32, int64, bool, bool, int64) {
|
||||
if devicePort == "" {
|
||||
func QueryEcsLinkByDeviceInfo(repo *repository.Repository, mapId int32, status *state.TrainState) (int32, int64, bool, bool, int64) {
|
||||
id := status.HeadDeviceId
|
||||
if status.DevicePort == "" {
|
||||
uid := QueryUidByMidAndComId(mapId, id, &graphicData.Section{})
|
||||
return sectionMapToEcsLink(repo, uid, offset, runDirection)
|
||||
return sectionMapToEcsLink(repo, uid, status)
|
||||
} else {
|
||||
uid := QueryUidByMidAndComId(mapId, id, &graphicData.Turnout{})
|
||||
return turnoutMapToEcsLink(repo, uid, devicePort, offset, runDirection)
|
||||
return turnoutMapToEcsLink(repo, uid, status)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -122,12 +125,16 @@ func getStorageIBPMapData(mapCode string) *graphicData.IBPGraphicStorage {
|
|||
}
|
||||
|
||||
// 根据物理区段上的偏移量(基于区段A端),找到所在link的linkId与偏移量
|
||||
func sectionMapToEcsLink(repo *repository.Repository, id string, offset int64, runDirection bool) (int32, int64, bool, bool, int64) {
|
||||
func sectionMapToEcsLink(repo *repository.Repository, id string, status *state.TrainState) (int32, int64, bool, bool, int64) {
|
||||
runDirection := status.RunDirection
|
||||
section := repo.FindPhysicalSection(id)
|
||||
if section == nil {
|
||||
panic(&dto.ErrorDto{Code: dto.DataNotExist, Message: fmt.Sprintf("地图不存在uid:%s缓存", id)})
|
||||
panic(sys_error.New(fmt.Sprintf("地图不存在uid:%s缓存", id)))
|
||||
}
|
||||
ao, bo := section.ALinkPosition().Offset(), section.BLinkPosition().Offset()
|
||||
// 检查区段长度是否足够放下列车
|
||||
status.HeadOffset = checkDeviceContainTrain(status.HeadOffset, status.TrainLength, bo-ao, id)
|
||||
link := section.ALinkPosition().Link()
|
||||
// 是否从A到B,统一坐标
|
||||
ak, bk := convertRepoBaseKm(repo, section.AKilometer()), convertRepoBaseKm(repo, section.BKilometer())
|
||||
akv, bkv := ak.Value, bk.Value
|
||||
|
@ -148,20 +155,21 @@ func sectionMapToEcsLink(repo *repository.Repository, id string, offset int64, r
|
|||
up = ao > bo
|
||||
}
|
||||
}
|
||||
linkId, _ := strconv.Atoi(section.ALinkPosition().Link().Identity.Id())
|
||||
trainKilometer := concertTrainKilometer(akv, offset, up)
|
||||
linkId, _ := strconv.Atoi(link.Identity.Id())
|
||||
trainKilometer := concertTrainKilometer(akv, status.HeadOffset, up)
|
||||
if ao < bo {
|
||||
return int32(linkId), ao + offset, up, abDirection, trainKilometer
|
||||
return int32(linkId), ao + status.HeadOffset, up, abDirection, trainKilometer
|
||||
} else {
|
||||
return int32(linkId), ao - offset, up, abDirection, trainKilometer
|
||||
return int32(linkId), ao - status.HeadOffset, up, abDirection, trainKilometer
|
||||
}
|
||||
}
|
||||
|
||||
// 根据道岔上的偏移量(基于岔心位置),找到所在link的linkId与偏移量
|
||||
func turnoutMapToEcsLink(repo *repository.Repository, id string, port string, offset int64, runDirection bool) (int32, int64, bool, bool, int64) {
|
||||
func turnoutMapToEcsLink(repo *repository.Repository, id string, status *state.TrainState) (int32, int64, bool, bool, int64) {
|
||||
port, runDirection := status.DevicePort, status.RunDirection
|
||||
turnout := repo.FindTurnout(id)
|
||||
if turnout == nil {
|
||||
panic(dto.ErrorDto{Code: dto.DataNotExist, Message: fmt.Sprintf("不存在道岔【uid:%s】", id)})
|
||||
panic(sys_error.New(fmt.Sprintf("不存在道岔【uid:%s】", id)))
|
||||
}
|
||||
var portPosition *repository.LinkPosition
|
||||
var crossKm, portKm *proto2.Kilometer
|
||||
|
@ -176,14 +184,16 @@ func turnoutMapToEcsLink(repo *repository.Repository, id string, port string, of
|
|||
portPosition = turnout.FindLinkPositionByPort(proto2.Port_C)
|
||||
portKm = turnout.GetTurnoutKm(proto2.Port_C)
|
||||
default:
|
||||
panic(dto.ErrorDto{Code: dto.DataNotExist, Message: fmt.Sprintf("无效端口【%s】偏移量", port)})
|
||||
panic(sys_error.New(fmt.Sprintf("无效端口【%s】偏移量", port)))
|
||||
}
|
||||
// 岔心公里标
|
||||
crossKm = turnout.GetTurnoutKm(proto2.Port_None)
|
||||
portKm, err := repo.ConvertKilometer(portKm, crossKm.CoordinateSystem)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
panic(sys_error.New("公里标转换出错", err))
|
||||
}
|
||||
// 检查link偏移
|
||||
status.HeadOffset = checkDeviceContainTrain(status.HeadOffset, status.TrainLength, crossKm.Value-portKm.Value, id)
|
||||
// 关联link
|
||||
link := portPosition.Link()
|
||||
isStart := link.ARelation().Device().Id() == id
|
||||
|
@ -192,14 +202,14 @@ func turnoutMapToEcsLink(repo *repository.Repository, id string, port string, of
|
|||
up = !runDirection
|
||||
}
|
||||
pointTo := (portKm.Value > crossKm.Value) == runDirection
|
||||
trainKilometer := concertTrainKilometer(crossKm.Value, offset, pointTo)
|
||||
trainKilometer := concertTrainKilometer(crossKm.Value, status.HeadOffset, pointTo)
|
||||
linkId, _ := strconv.Atoi(link.Identity.Id())
|
||||
if isStart {
|
||||
return int32(linkId), offset, up, pointTo, trainKilometer
|
||||
return int32(linkId), status.HeadOffset, up, pointTo, trainKilometer
|
||||
} else {
|
||||
// 道岔长度
|
||||
turnoutLen := int64(math.Abs(float64(portKm.Value - crossKm.Value)))
|
||||
return int32(linkId), portPosition.Offset() + turnoutLen - offset, up, pointTo, trainKilometer
|
||||
return int32(linkId), portPosition.Offset() + turnoutLen - status.HeadOffset, up, pointTo, trainKilometer
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -355,3 +365,18 @@ func convertRepoBaseKm(r *repository.Repository, km *proto2.Kilometer) *proto2.K
|
|||
}
|
||||
return k
|
||||
}
|
||||
|
||||
// 检查列车是否可以放到设备上
|
||||
func checkDeviceContainTrain(offset, trainLen, deviceLen int64, deviceName string) int64 {
|
||||
l := int64(math.Abs(float64(deviceLen)))
|
||||
if offset > l {
|
||||
panic(sys_error.New(fmt.Sprintf("偏移【%d】超出【%s】范围【%d】", offset, deviceName, l)))
|
||||
}
|
||||
if trainLen > offset { // 如果列车长度超过设置offset
|
||||
offset = trainLen
|
||||
}
|
||||
if offset > l {
|
||||
panic(sys_error.New(fmt.Sprintf("列车长度【%d】超出【%s】范围【%d】", trainLen, deviceName, l)))
|
||||
}
|
||||
return offset
|
||||
}
|
||||
|
|
|
@ -26,7 +26,7 @@ func AddTrainState(vs *VerifySimulation, status *state.TrainState, mapId int32)
|
|||
//向动力学发送初始化请求
|
||||
trainIndex, _ := strconv.ParseUint(status.Id, 10, 16)
|
||||
// 映射link、偏移量、运行方向
|
||||
linkId, loffset, up, pointTo, kilometer := QueryEcsLinkByDeviceInfo(vs.Repo, mapId, status.HeadDeviceId, status.DevicePort, status.HeadOffset, status.RunDirection)
|
||||
linkId, loffset, up, pointTo, kilometer := QueryEcsLinkByDeviceInfo(vs.Repo, mapId, status)
|
||||
status.Up = up
|
||||
status.PointTo = pointTo
|
||||
status.TrainKilometer = kilometer
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package memory
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"math"
|
||||
|
@ -15,9 +16,11 @@ import (
|
|||
|
||||
"joylink.club/bj-rtsts-server/ats/verify/protos/graphicData"
|
||||
"joylink.club/bj-rtsts-server/ats/verify/protos/state"
|
||||
"joylink.club/bj-rtsts-server/config"
|
||||
"joylink.club/bj-rtsts-server/dto"
|
||||
"joylink.club/bj-rtsts-server/sys_error"
|
||||
"joylink.club/bj-rtsts-server/third_party/dynamics"
|
||||
"joylink.club/bj-rtsts-server/third_party/interlock"
|
||||
"joylink.club/bj-rtsts-server/third_party/message"
|
||||
"joylink.club/bj-rtsts-server/third_party/semi_physical_train"
|
||||
"joylink.club/ecs"
|
||||
|
@ -38,11 +41,12 @@ type VerifySimulation struct {
|
|||
Memory *WaysideMemory
|
||||
//模型仓库
|
||||
Repo *repository.Repository
|
||||
//Rtss仿真世界的id
|
||||
WorldId ecs.WorldId
|
||||
World ecs.World
|
||||
//Rtss仿真世界的
|
||||
World ecs.World
|
||||
//设备UID映射
|
||||
uidMap map[string]*elementIdStructure
|
||||
// 运行环境配置
|
||||
runConfig *config.ThridPartyConfig
|
||||
}
|
||||
|
||||
// 轨旁仿真内存模型
|
||||
|
@ -89,51 +93,32 @@ func NewWaysideMemory() *WaysideMemory {
|
|||
}
|
||||
|
||||
// 创建仿真对象
|
||||
func CreateSimulation(projectId int32, mapIds []int32) (*VerifySimulation, error) {
|
||||
//构建Repository
|
||||
func CreateSimulation(projectId int32, mapIds []int32, runConfig string) (*VerifySimulation, error) {
|
||||
// 地图信息
|
||||
sort.Slice(mapIds, func(i, j int) bool {
|
||||
return mapIds[i] < mapIds[j]
|
||||
})
|
||||
var mapIdStrSlice []string
|
||||
for _, id := range mapIds {
|
||||
mapIdStrSlice = append(mapIdStrSlice, strconv.Itoa(int(id)))
|
||||
}
|
||||
repoId := strings.Join(mapIdStrSlice, "|")
|
||||
repoVersion := "0.1"
|
||||
repo := repository.FindRepository(repoId, repoVersion)
|
||||
if repo == nil {
|
||||
protoRepo, err := buildProtoRepository(mapIds)
|
||||
if err != nil {
|
||||
return nil, sys_error.New("数据错误", err)
|
||||
}
|
||||
protoRepo.Id, protoRepo.Version = repoId, repoVersion
|
||||
newRepo, err := repository.BuildRepository(protoRepo)
|
||||
if err != nil {
|
||||
return nil, sys_error.New("数据错误", err)
|
||||
}
|
||||
repo = newRepo
|
||||
}
|
||||
// 构建所有UID映射关系,
|
||||
allUidMap := buildRepositoryAllUidsMap(mapIds, repo)
|
||||
//创建仿真
|
||||
w, err := rtss_simulation.NewSimulation(repo)
|
||||
verifySimulation := &VerifySimulation{ProjectId: projectId, MapIds: mapIds}
|
||||
// 设置运行环境
|
||||
err := verifySimulation.initRunConfig(runConfig)
|
||||
if err != nil {
|
||||
return nil, sys_error.New("仿真创建失败", err)
|
||||
return nil, err
|
||||
}
|
||||
verifySimulation := &VerifySimulation{
|
||||
MapIds: mapIds,
|
||||
ProjectId: projectId,
|
||||
Memory: NewWaysideMemory(),
|
||||
Repo: repo,
|
||||
World: w,
|
||||
WorldId: w.Id(),
|
||||
uidMap: allUidMap,
|
||||
// 构建Repository
|
||||
err = verifySimulation.initRepository()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// 创建world
|
||||
err = verifySimulation.initWorld()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// 运行第三方服务
|
||||
err = verifySimulation.runThirdParty()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// 保证World关闭
|
||||
runtime.SetFinalizer(verifySimulation, func(verifySimulation *VerifySimulation) {
|
||||
slog.Info("---关闭仿真World---")
|
||||
verifySimulation.World.Close()
|
||||
})
|
||||
return verifySimulation, nil
|
||||
}
|
||||
|
||||
|
@ -221,71 +206,6 @@ 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)
|
||||
|
@ -363,6 +283,81 @@ func convert(info *message.DynamicsTrainInfo, sta *state.TrainState, simulation
|
|||
return sta
|
||||
}
|
||||
|
||||
// 获取动力学配置信息
|
||||
func (s *VerifySimulation) GetDynamicsRunConfig() *config.DynamicsConfig {
|
||||
// TODO:目前为了兼容当前配置方式,做成查询方式后删除IP判断
|
||||
if config.Config.Dynamics.Ip != "" {
|
||||
return &config.Config.Dynamics
|
||||
}
|
||||
return &s.runConfig.Dynamics
|
||||
}
|
||||
|
||||
// 获取动力学运行资源
|
||||
func (s *VerifySimulation) GetDynamicsRunRepository() *message.LineBaseInfo {
|
||||
info := &message.LineBaseInfo{}
|
||||
for _, model := range s.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(s.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(s.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 s.Repo.SlopeList() {
|
||||
id, _ := strconv.Atoi(s.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 s.Repo.SectionalCurvatureList() {
|
||||
id, _ := strconv.Atoi(s.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 speedParse(speed float32) int32 {
|
||||
return int32(math.Abs(float64(speed * 3.6 * 100)))
|
||||
|
@ -390,6 +385,15 @@ func (s *VerifySimulation) HandleSemiPhysicalTrainControlMsg(b []byte) {
|
|||
})
|
||||
}
|
||||
|
||||
// 获取半实物运行配置信息
|
||||
func (s *VerifySimulation) GetSemiPhysicalRunConfig() *config.VobcConfig {
|
||||
// TODO:目前为了兼容当前配置方式,做成查询方式后删除IP判断
|
||||
if config.Config.Vobc.Ip != "" {
|
||||
return &config.Config.Vobc
|
||||
}
|
||||
return &s.runConfig.Vobc
|
||||
}
|
||||
|
||||
// 处理接到的联锁消息
|
||||
func (s *VerifySimulation) HandleDriverInfo(b []byte) {
|
||||
driverMsg := message.NewInterlockReceiveMsgPkg(0, 128, 8*131)
|
||||
|
@ -402,6 +406,15 @@ func (s *VerifySimulation) HandleDriverInfo(b []byte) {
|
|||
}
|
||||
}
|
||||
|
||||
// 获取联锁配置
|
||||
func (s *VerifySimulation) GetInterlockRunConfig() *config.InterlockConfig {
|
||||
// TODO:目前为了兼容当前配置方式,做成查询方式后删除IP判断
|
||||
if config.Config.Interlock.Ip != "" {
|
||||
return &config.Config.Interlock
|
||||
}
|
||||
return &s.runConfig.Interlock
|
||||
}
|
||||
|
||||
// 采集联锁中的继电器消息
|
||||
func (s *VerifySimulation) CollectRelayInfo() *message.InterlockSendMsgPkg {
|
||||
msg := &message.InterlockSendMsgPkg{}
|
||||
|
@ -417,6 +430,91 @@ func (s *VerifySimulation) CollectRelayInfo() *message.InterlockSendMsgPkg {
|
|||
return msg
|
||||
}
|
||||
|
||||
// 初始化仿真运行配置
|
||||
func (s *VerifySimulation) initRunConfig(configStr string) error {
|
||||
if configStr == "" {
|
||||
return nil
|
||||
}
|
||||
var configMap config.ThridPartyConfig
|
||||
err := json.Unmarshal([]byte(configStr), &configMap)
|
||||
if err != nil {
|
||||
return sys_error.New("配置信息格式错误", err)
|
||||
}
|
||||
s.runConfig = &configMap
|
||||
return nil
|
||||
}
|
||||
|
||||
// 初始化运行资源
|
||||
func (s *VerifySimulation) initRepository() error {
|
||||
// 构建Repository
|
||||
var mapIdStrSlice []string
|
||||
for _, id := range s.MapIds {
|
||||
mapIdStrSlice = append(mapIdStrSlice, strconv.Itoa(int(id)))
|
||||
}
|
||||
repoId := strings.Join(mapIdStrSlice, "|")
|
||||
repoVersion := "0.1"
|
||||
repo := repository.FindRepository(repoId, repoVersion)
|
||||
if repo == nil {
|
||||
protoRepo, err := buildProtoRepository(s.MapIds)
|
||||
if err != nil {
|
||||
return sys_error.New("数据错误", err)
|
||||
}
|
||||
protoRepo.Id, protoRepo.Version = repoId, repoVersion
|
||||
newRepo, err := repository.BuildRepository(protoRepo)
|
||||
if err != nil {
|
||||
return sys_error.New("数据错误", err)
|
||||
}
|
||||
repo = newRepo
|
||||
}
|
||||
s.Repo = repo
|
||||
s.Memory = NewWaysideMemory()
|
||||
// 构建所有UID映射关系,
|
||||
s.uidMap = buildRepositoryAllUidsMap(s.MapIds, s.Repo)
|
||||
return nil
|
||||
}
|
||||
|
||||
// 创建world
|
||||
func (s *VerifySimulation) initWorld() error {
|
||||
//创建仿真
|
||||
w, err := rtss_simulation.NewSimulation(s.Repo)
|
||||
if err != nil {
|
||||
return sys_error.New("仿真创建失败", err)
|
||||
}
|
||||
s.World = w
|
||||
// 保证World关闭
|
||||
runtime.SetFinalizer(s, func(verifySimulation *VerifySimulation) {
|
||||
slog.Info("---关闭仿真World---")
|
||||
verifySimulation.World.Close()
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
// 运行仿真第三方模块
|
||||
func (s *VerifySimulation) runThirdParty() error {
|
||||
// 动力学启动
|
||||
err := dynamics.Default().Start(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// 半实物启动
|
||||
semi_physical_train.Default().Start(s)
|
||||
// 联锁启动
|
||||
interlock.Default().Start(s)
|
||||
return nil
|
||||
}
|
||||
|
||||
// 停止仿真
|
||||
func (s *VerifySimulation) StopSimulation() {
|
||||
// 停止ecs world
|
||||
s.World.Close()
|
||||
// 停止动力学接口功能
|
||||
dynamics.Default().Stop()
|
||||
// 停止半实物
|
||||
semi_physical_train.Default().Stop()
|
||||
// 联锁启动
|
||||
interlock.Default().Stop()
|
||||
}
|
||||
|
||||
func buildProtoRepository(mapIds []int32) (*proto.Repository, error) {
|
||||
repo := &proto.Repository{}
|
||||
var exceptStationGiMapIds []int32
|
||||
|
|
|
@ -23,7 +23,7 @@ interlock:
|
|||
ip: 10.60.1.59
|
||||
localPort: 10000
|
||||
remotePort: 4000
|
||||
open: true
|
||||
open: false
|
||||
|
||||
# 数据源
|
||||
datasource:
|
||||
|
|
|
@ -18,9 +18,9 @@ type AppConfig struct {
|
|||
Datasource datasource
|
||||
Logging log
|
||||
Messaging messaging
|
||||
Dynamics dynamics
|
||||
Vobc vobc
|
||||
Interlock interlock
|
||||
Dynamics DynamicsConfig
|
||||
Vobc VobcConfig
|
||||
Interlock InterlockConfig
|
||||
}
|
||||
type server struct {
|
||||
Port int
|
||||
|
@ -49,26 +49,32 @@ type centrifugo struct {
|
|||
ApiEndpoint string
|
||||
Address string
|
||||
}
|
||||
type dynamics struct {
|
||||
Ip string
|
||||
UdpLocalPort int
|
||||
UdpRemotePort int
|
||||
UdpRemoteTrainPort int
|
||||
HttpPort int
|
||||
Open bool
|
||||
}
|
||||
type vobc struct {
|
||||
Ip string
|
||||
LocalPort int
|
||||
RemotePort int
|
||||
Open bool
|
||||
}
|
||||
|
||||
type interlock struct {
|
||||
Ip string
|
||||
LocalPort int
|
||||
RemotePort int
|
||||
Open bool
|
||||
// 第三方配置结构
|
||||
type ThridPartyConfig struct {
|
||||
Dynamics DynamicsConfig `json:"dynamics"`
|
||||
Vobc VobcConfig `json:"vobc"`
|
||||
Interlock InterlockConfig `json:"interlock"`
|
||||
}
|
||||
type DynamicsConfig struct {
|
||||
Ip string `json:"ip"`
|
||||
UdpLocalPort int `json:"udpLocalPort"`
|
||||
UdpRemotePort int `json:"udpRemotePort"`
|
||||
UdpRemoteTrainPort int `json:"udpRemoteTrainPort"`
|
||||
HttpPort int `json:"httpPort"`
|
||||
Open bool `json:"open"`
|
||||
}
|
||||
type VobcConfig struct {
|
||||
Ip string `json:"ip"`
|
||||
LocalPort int `json:"localPort"`
|
||||
RemotePort int `json:"remotePort"`
|
||||
Open bool `json:"open"`
|
||||
}
|
||||
type InterlockConfig struct {
|
||||
Ip string `json:"ip"`
|
||||
LocalPort int `json:"localPort"`
|
||||
RemotePort int `json:"remotePort"`
|
||||
Open bool `json:"open"`
|
||||
}
|
||||
|
||||
var Config AppConfig
|
||||
|
|
|
@ -23,7 +23,7 @@ interlock:
|
|||
ip: 10.60.1.59
|
||||
localPort: 10000
|
||||
remotePort: 4000
|
||||
open: true
|
||||
open: false
|
||||
|
||||
# 数据源
|
||||
datasource:
|
||||
|
|
|
@ -58,6 +58,21 @@ func QueryProjectRunConfig(id int32) *dto.ProjectRunConfigDto {
|
|||
return dto.ConvertToRunConfigDto(query)
|
||||
}
|
||||
|
||||
// 查询项目运行环境
|
||||
func QueryRunConfig(id int32) *dto.ProjectRunConfigDto {
|
||||
if id == 0 {
|
||||
return nil
|
||||
}
|
||||
query, err := dbquery.ProjectRunConfig.Where(dbquery.ProjectRunConfig.ID.Eq(id)).Find()
|
||||
if err != nil {
|
||||
panic(sys_error.New("查询失败", err))
|
||||
}
|
||||
if len(query) == 0 {
|
||||
return nil
|
||||
}
|
||||
return dto.ConvertToRunConfigDto(query[0])
|
||||
}
|
||||
|
||||
// 更新项目运行环境
|
||||
func UpdateProjectRunConfig(id int32, dd *dto.ProjectRunConfigReqDto) bool {
|
||||
findOldQuery := dbquery.ProjectRunConfig
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
"log/slog"
|
||||
"net/http"
|
||||
"runtime/debug"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"joylink.club/bj-rtsts-server/config"
|
||||
|
@ -19,21 +20,19 @@ import (
|
|||
type DynamicsMessageManager interface {
|
||||
CollectDynamicsTurnoutInfo() []*message.DynamicsTurnoutInfo
|
||||
HandleDynamicsTrainInfo(info *message.DynamicsTrainInfo)
|
||||
GetDynamicsRunConfig() *config.DynamicsConfig
|
||||
GetDynamicsRunRepository() *message.LineBaseInfo
|
||||
}
|
||||
|
||||
// 动力学接口
|
||||
type Dynamics interface {
|
||||
// 请求启动仿真
|
||||
RequestStartSimulation(base *message.LineBaseInfo) error
|
||||
// 请求停止仿真
|
||||
RequestStopSimulation() error
|
||||
// 请求添加列车
|
||||
RequestAddTrain(info *message.InitTrainInfo) error
|
||||
// 请求移除列车
|
||||
RequestRemoveTrain(req *message.RemoveTrainReq) error
|
||||
|
||||
// 启动动力学消息功能
|
||||
Start(manager DynamicsMessageManager)
|
||||
Start(manager DynamicsMessageManager) error
|
||||
// 停止动力学消息功能
|
||||
Stop()
|
||||
|
||||
|
@ -42,22 +41,17 @@ type Dynamics interface {
|
|||
}
|
||||
|
||||
var _default Dynamics
|
||||
var initMutex sync.Mutex
|
||||
|
||||
func Default() Dynamics {
|
||||
if !config.Config.Dynamics.Open {
|
||||
panic("动力学接口模块未开启")
|
||||
initMutex.Lock()
|
||||
defer initMutex.Unlock()
|
||||
if _default == nil {
|
||||
_default = &dynamics{}
|
||||
}
|
||||
return _default
|
||||
}
|
||||
|
||||
func Init() {
|
||||
if !config.Config.Dynamics.Open {
|
||||
return
|
||||
}
|
||||
slog.Info("初始化动力学接口模块")
|
||||
_default = newDynamics()
|
||||
}
|
||||
|
||||
type dynamics struct {
|
||||
trainInfoUdpServer udp.UdpServer
|
||||
turnoutStateUdpClient udp.UdpClient
|
||||
|
@ -67,20 +61,7 @@ type dynamics struct {
|
|||
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)
|
||||
d.trainInfoUdpServer.Listen()
|
||||
return d
|
||||
runConfig *config.DynamicsConfig
|
||||
}
|
||||
|
||||
// 解码列车信息并处理
|
||||
|
@ -96,11 +77,11 @@ func (d *dynamics) handleDynamicsTrainInfo(b []byte) {
|
|||
}
|
||||
}
|
||||
|
||||
func getUrlBase() string {
|
||||
ip := config.Config.Dynamics.Ip
|
||||
func getUrlBase(c *config.DynamicsConfig) string {
|
||||
ip := c.Ip
|
||||
var port string
|
||||
if config.Config.Dynamics.HttpPort != 0 {
|
||||
port = fmt.Sprintf(":%d", config.Config.Dynamics.HttpPort)
|
||||
if c.HttpPort != 0 {
|
||||
port = fmt.Sprintf(":%d", c.HttpPort)
|
||||
}
|
||||
urlBase := "http://" + ip + port
|
||||
return urlBase
|
||||
|
@ -110,8 +91,8 @@ func (d *dynamics) buildUrl(uri string) string {
|
|||
return d.baseUrl + uri
|
||||
}
|
||||
|
||||
func (d *dynamics) RequestStartSimulation(base *message.LineBaseInfo) error {
|
||||
if !config.Config.Dynamics.Open {
|
||||
func (d *dynamics) requestStartSimulation(base *message.LineBaseInfo) error {
|
||||
if !d.runConfig.Open {
|
||||
return nil
|
||||
}
|
||||
url := d.buildUrl("/api/start/")
|
||||
|
@ -130,8 +111,8 @@ func (d *dynamics) RequestStartSimulation(base *message.LineBaseInfo) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (d *dynamics) RequestStopSimulation() error {
|
||||
if !config.Config.Dynamics.Open {
|
||||
func (d *dynamics) requestStopSimulation() error {
|
||||
if !d.runConfig.Open {
|
||||
return nil
|
||||
}
|
||||
url := d.buildUrl("/api/end/")
|
||||
|
@ -149,7 +130,7 @@ func (d *dynamics) RequestStopSimulation() error {
|
|||
}
|
||||
|
||||
func (d *dynamics) RequestAddTrain(info *message.InitTrainInfo) error {
|
||||
if !config.Config.Dynamics.Open {
|
||||
if !d.runConfig.Open {
|
||||
return nil
|
||||
}
|
||||
url := d.buildUrl("/api/aerodynamics/init/train/")
|
||||
|
@ -168,7 +149,7 @@ func (d *dynamics) RequestAddTrain(info *message.InitTrainInfo) error {
|
|||
}
|
||||
|
||||
func (d *dynamics) RequestRemoveTrain(req *message.RemoveTrainReq) error {
|
||||
if !config.Config.Dynamics.Open {
|
||||
if !d.runConfig.Open {
|
||||
return nil
|
||||
}
|
||||
url := d.buildUrl("/api/aerodynamics/remove/train/")
|
||||
|
@ -186,20 +167,66 @@ func (d *dynamics) RequestRemoveTrain(req *message.RemoveTrainReq) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (d *dynamics) Start(manager DynamicsMessageManager) {
|
||||
func (d *dynamics) Start(manager DynamicsMessageManager) error {
|
||||
if manager == nil {
|
||||
panic("启动动力学消息服务错误: DynamicsMessageManager不能为nil")
|
||||
}
|
||||
if d.manager != nil {
|
||||
panic("启动动力学消息服务错误: 存在正在运行的任务")
|
||||
}
|
||||
d.runConfig = manager.GetDynamicsRunConfig()
|
||||
if d.runConfig == nil || d.runConfig.Ip == "" || !d.runConfig.Open {
|
||||
return nil
|
||||
}
|
||||
d.manager = manager
|
||||
// 初始化客户端信息
|
||||
d.initDynamics()
|
||||
// 初始化运行资源
|
||||
err := d.initDynamicsRunRepository()
|
||||
if err != nil {
|
||||
panic("启动动力学消息服务错误: 存在正在运行的任务")
|
||||
}
|
||||
ctx, cancle := context.WithCancel(context.Background())
|
||||
go d.sendTurnoutStateTask(ctx)
|
||||
d.turnoutTaskCancel = cancle
|
||||
return nil
|
||||
}
|
||||
|
||||
// 初始化客户端、服务等信息
|
||||
func (d *dynamics) initDynamics() {
|
||||
d.turnoutStateUdpClient = udp.NewClient(fmt.Sprintf("%v:%v", d.runConfig.Ip, d.runConfig.UdpRemotePort))
|
||||
d.trainControlUdpClient = udp.NewClient(fmt.Sprintf("%v:%v", d.runConfig.Ip, d.runConfig.UdpRemoteTrainPort))
|
||||
d.baseUrl = getUrlBase(d.runConfig)
|
||||
d.httpClient = &http.Client{Timeout: time.Second * 5}
|
||||
d.trainInfoUdpServer = udp.NewServer(fmt.Sprintf(":%d", d.runConfig.UdpLocalPort), d.handleDynamicsTrainInfo)
|
||||
d.trainInfoUdpServer.Listen()
|
||||
}
|
||||
|
||||
// 动力学运行所需数据
|
||||
func (d *dynamics) initDynamicsRunRepository() error {
|
||||
// 动力学接口调用
|
||||
lineBaseInfo := d.manager.GetDynamicsRunRepository()
|
||||
err := d.requestStartSimulation(lineBaseInfo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *dynamics) Stop() {
|
||||
if d.httpClient != nil {
|
||||
d.requestStopSimulation()
|
||||
d.httpClient = nil
|
||||
}
|
||||
if d.turnoutStateUdpClient != nil {
|
||||
d.turnoutStateUdpClient.Close()
|
||||
}
|
||||
if d.trainControlUdpClient != nil {
|
||||
d.trainControlUdpClient.Close()
|
||||
}
|
||||
if d.trainInfoUdpServer != nil {
|
||||
d.trainInfoUdpServer.Close()
|
||||
}
|
||||
if d.turnoutTaskCancel != nil {
|
||||
d.turnoutTaskCancel()
|
||||
d.manager = nil
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"fmt"
|
||||
"log/slog"
|
||||
"runtime/debug"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"joylink.club/bj-rtsts-server/config"
|
||||
|
@ -16,6 +17,7 @@ import (
|
|||
type InterlockMessageManager interface {
|
||||
CollectRelayInfo() *message.InterlockSendMsgPkg
|
||||
HandleDriverInfo(b []byte)
|
||||
GetInterlockRunConfig() *config.InterlockConfig
|
||||
}
|
||||
|
||||
// 联锁接口
|
||||
|
@ -29,10 +31,13 @@ type InterlockProxy interface {
|
|||
}
|
||||
|
||||
var _default InterlockProxy
|
||||
var initMutex sync.Mutex
|
||||
|
||||
func Default() InterlockProxy {
|
||||
if !config.Config.Interlock.Open { // TODO
|
||||
panic("联锁接口模块未开启")
|
||||
initMutex.Lock()
|
||||
defer initMutex.Unlock()
|
||||
if _default == nil { // TODO
|
||||
_default = &interlockProxy{}
|
||||
}
|
||||
return _default
|
||||
}
|
||||
|
@ -43,27 +48,34 @@ type interlockProxy struct {
|
|||
|
||||
manager InterlockMessageManager
|
||||
collectInfoTaskCancel context.CancelFunc
|
||||
runConfig *config.InterlockConfig
|
||||
}
|
||||
|
||||
// 驱动信息进行转发
|
||||
func (d *interlockProxy) handleDriverInfo(b []byte) {
|
||||
handler := d.manager
|
||||
func (i *interlockProxy) handleDriverInfo(b []byte) {
|
||||
handler := i.manager
|
||||
if handler != nil {
|
||||
handler.HandleDriverInfo(b)
|
||||
}
|
||||
}
|
||||
|
||||
func (d *interlockProxy) Start(manager InterlockMessageManager) {
|
||||
func (i *interlockProxy) Start(manager InterlockMessageManager) {
|
||||
if manager == nil {
|
||||
panic("启动联锁消息服务错误: InterlockMessageManager不能为nil")
|
||||
}
|
||||
if d.manager != nil {
|
||||
if i.manager != nil {
|
||||
panic("启动联锁消息服务错误: 存在正在运行的任务")
|
||||
}
|
||||
d.manager = manager
|
||||
i.runConfig = manager.GetInterlockRunConfig()
|
||||
if i.runConfig == nil || i.runConfig.Ip == "" || !i.runConfig.Open {
|
||||
return
|
||||
}
|
||||
i.manager = manager
|
||||
// 初始化客户端、服务端
|
||||
i.initInterlockProxy()
|
||||
ctx, cancle := context.WithCancel(context.Background())
|
||||
go d.collectInfoStateTask(ctx)
|
||||
d.collectInfoTaskCancel = cancle
|
||||
go i.collectInfoStateTask(ctx)
|
||||
i.collectInfoTaskCancel = cancle
|
||||
}
|
||||
|
||||
const (
|
||||
|
@ -72,7 +84,7 @@ const (
|
|||
)
|
||||
|
||||
// 定时发送采集电路状态任务
|
||||
func (d *interlockProxy) collectInfoStateTask(ctx context.Context) {
|
||||
func (i *interlockProxy) collectInfoStateTask(ctx context.Context) {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
slog.Error("定时发送道岔状态任务异常", "error", err, "stack", string(debug.Stack()))
|
||||
|
@ -85,36 +97,31 @@ func (d *interlockProxy) collectInfoStateTask(ctx context.Context) {
|
|||
return
|
||||
default:
|
||||
}
|
||||
collectInfoStates := d.manager.CollectRelayInfo()
|
||||
d.sendCollectUdpClient.SendMsg(collectInfoStates)
|
||||
collectInfoStates := i.manager.CollectRelayInfo()
|
||||
i.sendCollectUdpClient.SendMsg(collectInfoStates)
|
||||
time.Sleep(time.Millisecond * InterlockMessageSendInterval)
|
||||
}
|
||||
}
|
||||
|
||||
func (d *interlockProxy) Stop() {
|
||||
if d.collectInfoTaskCancel != nil {
|
||||
d.collectInfoTaskCancel()
|
||||
d.manager = nil
|
||||
func (i *interlockProxy) Stop() {
|
||||
if i.sendCollectUdpClient != nil {
|
||||
i.sendCollectUdpClient.Close()
|
||||
}
|
||||
if i.driveInfoUdpServer != nil {
|
||||
i.driveInfoUdpServer.Close()
|
||||
}
|
||||
if i.collectInfoTaskCancel != nil {
|
||||
i.collectInfoTaskCancel()
|
||||
i.manager = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (d *interlockProxy) SendCollectMessage(b []byte) {
|
||||
d.sendCollectUdpClient.Send(b)
|
||||
func (i *interlockProxy) SendCollectMessage(b []byte) {
|
||||
i.sendCollectUdpClient.Send(b)
|
||||
}
|
||||
|
||||
func newInterlockProxy() *interlockProxy {
|
||||
d := &interlockProxy{
|
||||
sendCollectUdpClient: udp.NewClient(fmt.Sprintf("%v:%v", config.Config.Interlock.Ip, config.Config.Interlock.RemotePort)),
|
||||
}
|
||||
d.driveInfoUdpServer = udp.NewServer(fmt.Sprintf(":%d", config.Config.Interlock.LocalPort), d.handleDriverInfo)
|
||||
d.driveInfoUdpServer.Listen()
|
||||
return d
|
||||
}
|
||||
|
||||
func Init() {
|
||||
if !config.Config.Interlock.Open { // TODO
|
||||
return
|
||||
}
|
||||
slog.Info("初始化联锁接口模块")
|
||||
_default = newInterlockProxy()
|
||||
func (i *interlockProxy) initInterlockProxy() {
|
||||
i.sendCollectUdpClient = udp.NewClient(fmt.Sprintf("%v:%v", i.runConfig.Ip, i.runConfig.RemotePort))
|
||||
i.driveInfoUdpServer = udp.NewServer(fmt.Sprintf(":%d", i.runConfig.LocalPort), i.handleDriverInfo)
|
||||
i.driveInfoUdpServer.Listen()
|
||||
}
|
||||
|
|
|
@ -77,10 +77,9 @@ func (m *InterlockSendMsgPkg) Encode() []byte {
|
|||
// bool数组转byte
|
||||
func boolsToByte(flags [8]bool) byte {
|
||||
var result uint8
|
||||
for _, b := range flags {
|
||||
result <<= 1
|
||||
for index, b := range flags {
|
||||
if b {
|
||||
result |= 1
|
||||
result = result + (1 << index)
|
||||
}
|
||||
}
|
||||
return result
|
||||
|
|
|
@ -0,0 +1,413 @@
|
|||
package message
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
//RSSP-1 V1.0 铁路信号安全通信协议
|
||||
|
||||
// RsspHead rssp报文头
|
||||
type RsspHead struct {
|
||||
//报文头-协议交互类别(1Byte)
|
||||
Pic byte
|
||||
//报文头-报文类别(1Byte)
|
||||
Mc byte
|
||||
//报文头-源地址(2Byte)
|
||||
Sa uint16
|
||||
//报文头-目地址(2Byte)
|
||||
Da uint16
|
||||
}
|
||||
|
||||
const (
|
||||
RsspCrc16GX uint32 = 0b1_0000_1000_0001_0001 //生成多项式 G(X)=X16+X11+X4+1
|
||||
)
|
||||
|
||||
func (h *RsspHead) Type() RsspType {
|
||||
return h.Mc
|
||||
}
|
||||
func (h *RsspHead) decode(buf []byte) {
|
||||
ri := 0
|
||||
//报文头
|
||||
h.Pic = buf[ri]
|
||||
ri++
|
||||
h.Mc = buf[ri]
|
||||
ri++
|
||||
h.Sa = binary.LittleEndian.Uint16(buf[ri : ri+2])
|
||||
ri += 2
|
||||
h.Da = binary.LittleEndian.Uint16(buf[ri : ri+2])
|
||||
ri += 2
|
||||
}
|
||||
func (h *RsspHead) encode() []byte {
|
||||
data := make([]byte, 0, 6)
|
||||
//报文头
|
||||
data = append(data, h.Pic)
|
||||
data = append(data, h.Mc)
|
||||
data = binary.LittleEndian.AppendUint16(data, h.Sa)
|
||||
data = binary.LittleEndian.AppendUint16(data, h.Da)
|
||||
//
|
||||
return data
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////
|
||||
|
||||
// RsspRsd 实时安全数据包
|
||||
type RsspRsd struct {
|
||||
RsspHead
|
||||
//安全校验域-序列号(4Byte)
|
||||
Sn uint32
|
||||
//安全校验域-安全数据长度(2Byte)
|
||||
Sdl uint16
|
||||
//安全校验域-安全校验通道1(4Byte)
|
||||
Svc1 uint32
|
||||
//安全校验域-安全校验通道2(4Byte)
|
||||
Svc2 uint32
|
||||
//用户数据包-安全应用数据(总字节数480)
|
||||
Sad []byte
|
||||
//报文尾-CRC16(2Byte)
|
||||
Crc16 uint16
|
||||
}
|
||||
|
||||
const (
|
||||
CRC32_G_C1 = (uint64(0x01) << 32) | uint64(0x100d4e63) //crc32生成多项式
|
||||
CRC32_G_C2 = (uint64(0x01) << 32) | uint64(0x8ce56011) //crc32生成多项式
|
||||
SCW_C1 = uint32(0xae390b5a) //SCW常
|
||||
SCW_C2 = uint32(0xc103589c) //SCW常
|
||||
SJC_C1 = uint32(0x0fc22f87) //时间戳生成多项式
|
||||
SJC_C2 = uint32(0xc3e887e1) //时间戳生成多项式
|
||||
)
|
||||
|
||||
func (r *RsspRsd) Encode() []byte {
|
||||
data := make([]byte, 0, 6+14+len(r.Sad)+2)
|
||||
//报文头
|
||||
data = append(data, r.RsspHead.encode()...)
|
||||
//安全校验域
|
||||
data = binary.LittleEndian.AppendUint32(data, r.Sn)
|
||||
data = binary.LittleEndian.AppendUint16(data, r.Sdl)
|
||||
data = binary.LittleEndian.AppendUint32(data, r.Svc1)
|
||||
data = binary.LittleEndian.AppendUint32(data, r.Svc2)
|
||||
//用户数据包
|
||||
data = append(data, r.Sad...)
|
||||
//报文尾-CRC16
|
||||
r.Crc16 = uint16(NewCrc(uint64(RsspCrc16GX), 17, data).Generate())
|
||||
data = binary.LittleEndian.AppendUint16(data, r.Crc16)
|
||||
//
|
||||
return data
|
||||
}
|
||||
func (r *RsspRsd) Decode(buf []byte) error {
|
||||
//报文头
|
||||
r.RsspHead.decode(buf)
|
||||
ri := 6
|
||||
//安全校验域
|
||||
r.Sn = binary.LittleEndian.Uint32(buf[ri : ri+4])
|
||||
ri += 4
|
||||
r.Sdl = binary.LittleEndian.Uint16(buf[ri : ri+2])
|
||||
ri += 2
|
||||
r.Svc1 = binary.LittleEndian.Uint32(buf[ri : ri+4])
|
||||
ri += 4
|
||||
r.Svc2 = binary.LittleEndian.Uint32(buf[ri : ri+4])
|
||||
ri += 4
|
||||
//用户数据
|
||||
sadLen := int(r.Sdl) - 8
|
||||
r.Sad = buf[ri : ri+sadLen]
|
||||
ri += sadLen
|
||||
//报文尾
|
||||
r.Crc16 = binary.LittleEndian.Uint16(buf[ri : ri+2])
|
||||
//
|
||||
return nil
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////
|
||||
|
||||
// RsspSse 时序校正请求包
|
||||
type RsspSse struct {
|
||||
RsspHead
|
||||
//安全校验域-序列号(4Byte)
|
||||
Sn uint32
|
||||
//安全校验域-时序校正请求通道1(4Byte)
|
||||
SeqEnq1 uint32
|
||||
//安全校验域-时序校正请求通道2(4Byte)
|
||||
SeqEnq2 uint32
|
||||
//报文尾-CRC16(2Byte)
|
||||
Crc16 uint16
|
||||
}
|
||||
|
||||
func (r *RsspSse) Encode() []byte {
|
||||
data := make([]byte, 0, 20)
|
||||
//报文头
|
||||
data = append(data, r.RsspHead.encode()...)
|
||||
//安全校验域
|
||||
data = binary.LittleEndian.AppendUint32(data, r.Sn)
|
||||
data = binary.LittleEndian.AppendUint32(data, r.SeqEnq1)
|
||||
data = binary.LittleEndian.AppendUint32(data, r.SeqEnq2)
|
||||
//报文尾-CRC16
|
||||
r.Crc16 = uint16(NewCrc(uint64(RsspCrc16GX), 17, data).Generate())
|
||||
data = binary.LittleEndian.AppendUint16(data, r.Crc16)
|
||||
return data
|
||||
}
|
||||
func (r *RsspSse) Decode(buf []byte) error {
|
||||
//报文头
|
||||
r.RsspHead.decode(buf)
|
||||
//安全校验域
|
||||
ri := 6
|
||||
r.Sn = binary.LittleEndian.Uint32(buf[ri : ri+4])
|
||||
ri += 4
|
||||
r.SeqEnq1 = binary.LittleEndian.Uint32(buf[ri : ri+4])
|
||||
ri += 4
|
||||
r.SeqEnq2 = binary.LittleEndian.Uint32(buf[ri : ri+4])
|
||||
ri += 4
|
||||
//报文尾
|
||||
r.Crc16 = binary.LittleEndian.Uint16(buf[ri : ri+2])
|
||||
//
|
||||
return nil
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////
|
||||
|
||||
// RsspSsr 时序校正应答包,用于回应SSE
|
||||
type RsspSsr struct {
|
||||
RsspHead
|
||||
//安全校验域-应答方的序列号(4Byte)
|
||||
SrSn uint32
|
||||
//安全校验域-请求方的序列号(4Byte)
|
||||
SeSn uint32
|
||||
//安全校验域-时序初始化通道1(4Byte)
|
||||
Tic1 uint32
|
||||
//安全校验域-时序初始化通道2(4Byte)
|
||||
Tic2 uint32
|
||||
//安全校验域-数据版本号(1Byte)
|
||||
Dvn byte
|
||||
//报文尾-CRC16(2Byte)
|
||||
Crc16 uint16
|
||||
}
|
||||
|
||||
func (r *RsspSsr) Encode() []byte {
|
||||
data := make([]byte, 0, 25)
|
||||
//报文头
|
||||
data = append(data, r.RsspHead.encode()...)
|
||||
//安全校验域
|
||||
data = binary.LittleEndian.AppendUint32(data, r.SrSn)
|
||||
data = binary.LittleEndian.AppendUint32(data, r.SeSn)
|
||||
data = binary.LittleEndian.AppendUint32(data, r.Tic1)
|
||||
data = binary.LittleEndian.AppendUint32(data, r.Tic2)
|
||||
data = append(data, r.Dvn)
|
||||
//报文尾-CRC16
|
||||
r.Crc16 = uint16(NewCrc(uint64(RsspCrc16GX), 17, data).Generate())
|
||||
data = binary.LittleEndian.AppendUint16(data, r.Crc16)
|
||||
return data
|
||||
}
|
||||
func (r *RsspSsr) Decode(buf []byte) error {
|
||||
//报文头
|
||||
r.RsspHead.decode(buf)
|
||||
//安全校验域
|
||||
ri := 6
|
||||
r.SrSn = binary.LittleEndian.Uint32(buf[ri : ri+4])
|
||||
ri += 4
|
||||
r.SeSn = binary.LittleEndian.Uint32(buf[ri : ri+4])
|
||||
ri += 4
|
||||
r.Tic1 = binary.LittleEndian.Uint32(buf[ri : ri+4])
|
||||
ri += 4
|
||||
r.Tic2 = binary.LittleEndian.Uint32(buf[ri : ri+4])
|
||||
ri += 4
|
||||
r.Dvn = buf[ri]
|
||||
ri += 1
|
||||
//报文尾
|
||||
r.Crc16 = binary.LittleEndian.Uint16(buf[ri : ri+2])
|
||||
//
|
||||
return nil
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
type RsspCodec interface {
|
||||
Encode() []byte
|
||||
Decode(buf []byte) error
|
||||
}
|
||||
type Rssper interface {
|
||||
Type() RsspType
|
||||
}
|
||||
type RsspType = byte
|
||||
|
||||
const (
|
||||
RSD_A = RsspType(0x80)
|
||||
RSD_B = RsspType(0x81)
|
||||
SSE = RsspType(0x90)
|
||||
SSR = RsspType(0x91)
|
||||
)
|
||||
|
||||
// ParseRsspPack 解析RSSP数据包
|
||||
func ParseRsspPack(pack []byte) (Rssper, error) {
|
||||
// pack 进行CRC16循环冗余校验,检测整个包的完整性
|
||||
gCrc16 := uint16(NewCrc(uint64(RsspCrc16GX), 17, pack[0:len(pack)-2]).Generate())
|
||||
pCrc16 := binary.LittleEndian.Uint16(pack[len(pack)-2 : len(pack)])
|
||||
if gCrc16 != pCrc16 {
|
||||
return nil, fmt.Errorf("ParseRsspPack 整个数据包CRC16校验未通过")
|
||||
}
|
||||
//
|
||||
ph := &RsspHead{}
|
||||
ph.decode(pack)
|
||||
//
|
||||
var codec RsspCodec
|
||||
switch ph.Mc {
|
||||
case RSD_A | RSD_B:
|
||||
codec = &RsspRsd{}
|
||||
case SSE:
|
||||
codec = &RsspSse{}
|
||||
case SSR:
|
||||
codec = &RsspSsr{}
|
||||
default:
|
||||
return nil, fmt.Errorf("ParseRsspPack 无法识别的报文类型码[0x%x]", ph.Mc)
|
||||
}
|
||||
//
|
||||
e := codec.Decode(pack)
|
||||
return codec.(Rssper), e
|
||||
}
|
||||
|
||||
// //////////////////CRC循环冗余校验--移位寄存器///////////////////////////
|
||||
|
||||
type crc struct {
|
||||
//生成多项式,即二进制位数,如生成多项式X4+X3+1对应二进制11001共5位,生成的校验码长度为4
|
||||
g uint64
|
||||
//生成多项式二进制长度
|
||||
gl int
|
||||
//移位寄存器
|
||||
reg *sReg
|
||||
//消息数据
|
||||
m *crcBitPipe
|
||||
//补零
|
||||
b *crcBitPipe
|
||||
}
|
||||
|
||||
// NewCrc CRC循环冗余校验
|
||||
//
|
||||
// g : 生成多项式
|
||||
// gl : 生成多项式的长度即二进制位数,gl值为8的倍数加1
|
||||
// m : 被校验的消息数据
|
||||
func NewCrc(g uint64, gl int, m []byte) *crc {
|
||||
|
||||
return &crc{g: g, gl: gl, m: NewCrcBitPipe(m), b: NewCrcBit0Pipe(gl - 1), reg: NewReg(gl)}
|
||||
}
|
||||
func (c *crc) canToReg() bool {
|
||||
if c.m.HasFlowBit() {
|
||||
return true
|
||||
}
|
||||
if c.b.HasFlowBit() {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// 被校验数据尽可能入移位寄存器
|
||||
func (c *crc) mToReg() {
|
||||
for c.reg.Glb() <= 0 { //寄存器左侧有0位
|
||||
if c.m.HasFlowBit() {
|
||||
c.reg.Ifr(c.m.FlowBit())
|
||||
} else if c.b.HasFlowBit() {
|
||||
c.reg.Ifr(c.b.FlowBit())
|
||||
} else {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Generate 生成CRC效验码
|
||||
func (c *crc) Generate() uint64 {
|
||||
for c.canToReg() {
|
||||
c.mToReg()
|
||||
if c.reg.Glb() >= 1 {
|
||||
c.reg.Xor(c.g)
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
return c.reg.RegV()
|
||||
}
|
||||
|
||||
// 把字节数组包装成bit流,bit从byte左侧流出
|
||||
type crcBitPipe struct {
|
||||
buf []byte
|
||||
bi int //当前流出的字节在buf中的下标,[0,len(buf)-1]
|
||||
i int //在当前流出的字节中,当前可流出的bit在字节中的位置,[7,0]
|
||||
}
|
||||
|
||||
func NewCrcBitPipe(buf []byte) *crcBitPipe {
|
||||
return &crcBitPipe{buf: buf, bi: 0, i: 7}
|
||||
}
|
||||
func NewCrcBit0Pipe(n int) *crcBitPipe {
|
||||
y := n % 8
|
||||
z := n / 8
|
||||
yy := 0
|
||||
if y > 0 {
|
||||
yy = 1
|
||||
}
|
||||
cap := z + yy
|
||||
buf := make([]byte, 0, cap)
|
||||
for i := 0; i < cap; i++ {
|
||||
buf = append(buf, 0x00)
|
||||
}
|
||||
si := 7
|
||||
if y > 0 {
|
||||
si = y - 1
|
||||
}
|
||||
return &crcBitPipe{buf: buf, bi: 0, i: si}
|
||||
}
|
||||
|
||||
// FlowBit 从左侧流出一个bit
|
||||
// 正常返回值为0或1,流结束返回大于1的值
|
||||
func (p *crcBitPipe) FlowBit() byte {
|
||||
if p.HasFlowBit() {
|
||||
rt := 0x01 & (p.buf[p.bi] >> p.i)
|
||||
p.i--
|
||||
if p.i < 0 {
|
||||
p.bi++
|
||||
p.i = 7
|
||||
}
|
||||
return rt
|
||||
}
|
||||
return 2
|
||||
}
|
||||
|
||||
func (p *crcBitPipe) HasFlowBit() bool {
|
||||
return p.bi < len(p.buf) && p.i >= 0
|
||||
}
|
||||
func (p *crcBitPipe) Reset() *crcBitPipe {
|
||||
p.bi = 0
|
||||
p.i = 7
|
||||
return p
|
||||
}
|
||||
|
||||
// 移位寄存器,最长64位
|
||||
type sReg struct {
|
||||
m uint64 //寄存器存储
|
||||
l int //寄存器长度
|
||||
}
|
||||
|
||||
func NewReg(l int) *sReg {
|
||||
return &sReg{m: 0, l: l}
|
||||
}
|
||||
|
||||
// Glb 寄存器最左侧bit位值
|
||||
func (r *sReg) Glb() byte {
|
||||
return byte(0x01 & (r.m >> (r.l - 1)))
|
||||
}
|
||||
|
||||
// Ifr 从寄存器右侧移入一个bit
|
||||
func (r *sReg) Ifr(bit byte) *sReg {
|
||||
r.m = (r.m << 1) | uint64(bit)
|
||||
return r
|
||||
}
|
||||
|
||||
// And 寄存器的值与v位与操作,结果存入寄存器
|
||||
func (r *sReg) And(v uint64) *sReg {
|
||||
r.m = r.m & v
|
||||
return r
|
||||
}
|
||||
func (r *sReg) Xor(v uint64) *sReg {
|
||||
r.m = r.m ^ v
|
||||
return r
|
||||
}
|
||||
|
||||
// RegV 获取寄存器中的值
|
||||
func (r *sReg) RegV() uint64 {
|
||||
return r.m
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
package message
|
||||
|
||||
import "fmt"
|
||||
|
||||
// SectionCmdMsg CI系统发送的区段操作命令
|
||||
type SectionCmdMsg struct {
|
||||
//bit5 计轴直接复位
|
||||
Drst bool
|
||||
//bit2 计轴预复位
|
||||
Pdrst bool
|
||||
}
|
||||
|
||||
func (s *SectionCmdMsg) Encode() byte {
|
||||
buf := 0x00
|
||||
if s.Drst {
|
||||
buf = buf | (0x01 << 5)
|
||||
}
|
||||
if s.Pdrst {
|
||||
buf = buf | (0x01 << 2)
|
||||
}
|
||||
return byte(buf)
|
||||
}
|
||||
func (s *SectionCmdMsg) Decode(buf byte) {
|
||||
s.Drst = buf&(0x01<<5) != 0
|
||||
s.Pdrst = buf&(0x01<<2) != 0
|
||||
}
|
||||
|
||||
// SectionStatusMsg 发送给CI系统的区段当前的状态
|
||||
type SectionStatusMsg struct {
|
||||
//0-bit7 计轴出清
|
||||
Clr bool
|
||||
//0-bit6 计轴占用
|
||||
Occ bool
|
||||
//1-bit6 计轴复位反馈
|
||||
Rac bool
|
||||
//1-bit5 运营原因拒绝计轴复位
|
||||
Rjo bool
|
||||
//1-bit4 技术原因拒绝计轴复位
|
||||
Rjt bool
|
||||
}
|
||||
|
||||
func (s *SectionStatusMsg) Encode() []byte {
|
||||
buf := []byte{0x00, 0x00}
|
||||
if s.Clr {
|
||||
buf[0] = buf[0] | (0x01 << 7)
|
||||
}
|
||||
if s.Occ {
|
||||
buf[0] = buf[0] | (0x01 << 6)
|
||||
}
|
||||
//
|
||||
if s.Rac {
|
||||
buf[1] = buf[1] | (0x01 << 6)
|
||||
}
|
||||
if s.Rjo {
|
||||
buf[1] = buf[1] | (0x01 << 5)
|
||||
}
|
||||
if s.Rjt {
|
||||
buf[1] = buf[1] | (0x01 << 4)
|
||||
}
|
||||
return buf
|
||||
}
|
||||
func (s *SectionStatusMsg) Decode(buf []byte) error {
|
||||
if len(buf) != 2 {
|
||||
return fmt.Errorf("buf 长度须为2")
|
||||
}
|
||||
s.Clr = buf[0]&(0x01<<7) != 0
|
||||
s.Occ = buf[0]&(0x01<<6) != 0
|
||||
s.Rac = buf[1]&(0x01<<6) != 0
|
||||
s.Rjo = buf[1]&(0x01<<5) != 0
|
||||
s.Rjt = buf[1]&(0x01<<4) != 0
|
||||
return nil
|
||||
}
|
|
@ -25,7 +25,7 @@ func (r *TrainControlMsg) Decode(buf []byte) error {
|
|||
t.TractionStatus = (b2 & (1 << 4)) != 0
|
||||
t.BrakingStatus = (b2 & (1 << 5)) != 0
|
||||
t.EmergencyBrakingStatus = (b2 & (1 << 6)) != 0
|
||||
t.TurnbackStatus = (b2 & 7) != 0
|
||||
t.TurnbackStatus = (b2 & (1 << 7)) != 0
|
||||
b3 := buf[3]
|
||||
t.JumpStatus = (b3 & 1) != 0
|
||||
t.Ato = (b3 & (1 << 1)) != 0
|
||||
|
|
|
@ -2,6 +2,7 @@ package semi_physical_train
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"joylink.club/bj-rtsts-server/config"
|
||||
"joylink.club/bj-rtsts-server/third_party/message"
|
||||
|
@ -21,13 +22,16 @@ type SemiPhysicalTrain interface {
|
|||
type SemiPhysicalMessageManager interface {
|
||||
// 处理半实物仿真列车控制消息
|
||||
HandleSemiPhysicalTrainControlMsg(b []byte)
|
||||
// 获取半实物启动参数
|
||||
GetSemiPhysicalRunConfig() *config.VobcConfig
|
||||
}
|
||||
|
||||
type semiPhysicalTrainImpl struct {
|
||||
trainControlUdpServer udp.UdpServer
|
||||
trainSpeedInfoUdpClient udp.UdpClient
|
||||
|
||||
manager SemiPhysicalMessageManager
|
||||
manager SemiPhysicalMessageManager
|
||||
runConfig *config.VobcConfig
|
||||
}
|
||||
|
||||
func (s *semiPhysicalTrainImpl) handleTrainControlMsg(b []byte) {
|
||||
|
@ -38,10 +42,28 @@ func (s *semiPhysicalTrainImpl) handleTrainControlMsg(b []byte) {
|
|||
}
|
||||
|
||||
func (s *semiPhysicalTrainImpl) Start(manager SemiPhysicalMessageManager) {
|
||||
if manager == nil {
|
||||
panic("启动半实物消息服务错误: SemiPhysicalMessageManager不能为nil")
|
||||
}
|
||||
if s.manager != nil {
|
||||
panic("启动半实物消息服务错误: 存在正在运行的任务")
|
||||
}
|
||||
s.runConfig = manager.GetSemiPhysicalRunConfig()
|
||||
if s.runConfig == nil || s.runConfig.Ip == "" || !s.runConfig.Open {
|
||||
return
|
||||
}
|
||||
// 初始化客户端、服务端
|
||||
s.initSemiPhysical()
|
||||
s.manager = manager
|
||||
}
|
||||
|
||||
func (s *semiPhysicalTrainImpl) Stop() {
|
||||
if s.trainControlUdpServer != nil {
|
||||
s.trainControlUdpServer.Close()
|
||||
}
|
||||
if s.trainSpeedInfoUdpClient != nil {
|
||||
s.trainSpeedInfoUdpClient.Close()
|
||||
}
|
||||
s.manager = nil
|
||||
}
|
||||
|
||||
|
@ -51,27 +73,20 @@ func (s *semiPhysicalTrainImpl) SendTrainControlMessage(info *message.DynamicsTr
|
|||
s.trainSpeedInfoUdpClient.Send(sendMsg.Encode())
|
||||
}
|
||||
|
||||
func newSemiPhysicalTrain() SemiPhysicalTrain {
|
||||
s := &semiPhysicalTrainImpl{
|
||||
trainSpeedInfoUdpClient: udp.NewClient(fmt.Sprintf("%v:%v", config.Config.Vobc.Ip, config.Config.Vobc.RemotePort)),
|
||||
}
|
||||
s.trainControlUdpServer = udp.NewServer(fmt.Sprintf(":%d", config.Config.Vobc.LocalPort), s.handleTrainControlMsg)
|
||||
func (s *semiPhysicalTrainImpl) initSemiPhysical() {
|
||||
s.trainSpeedInfoUdpClient = udp.NewClient(fmt.Sprintf("%v:%v", s.runConfig.Ip, s.runConfig.RemotePort))
|
||||
s.trainControlUdpServer = udp.NewServer(fmt.Sprintf(":%d", s.runConfig.LocalPort), s.handleTrainControlMsg)
|
||||
s.trainControlUdpServer.Listen()
|
||||
return s
|
||||
}
|
||||
|
||||
var _default SemiPhysicalTrain
|
||||
var initMutex sync.Mutex
|
||||
|
||||
func Default() SemiPhysicalTrain {
|
||||
if !config.Config.Vobc.Open {
|
||||
panic("半实物仿真接口模块未开启")
|
||||
initMutex.Lock()
|
||||
defer initMutex.Unlock()
|
||||
if _default == nil {
|
||||
_default = &semiPhysicalTrainImpl{}
|
||||
}
|
||||
return _default
|
||||
}
|
||||
|
||||
func Init() {
|
||||
if !config.Config.Vobc.Open {
|
||||
return
|
||||
}
|
||||
_default = newSemiPhysicalTrain()
|
||||
}
|
||||
|
|
|
@ -1,11 +1,5 @@
|
|||
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()
|
||||
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
type UdpClient interface {
|
||||
SendMsg(msg UdpMessageEncoder)
|
||||
Send(b []byte)
|
||||
Close()
|
||||
}
|
||||
|
||||
type client struct {
|
||||
|
@ -77,3 +78,10 @@ func (c *client) Send(b []byte) {
|
|||
}
|
||||
// slog.Debug("udp client send", "size", n)
|
||||
}
|
||||
|
||||
func (c *client) Close() {
|
||||
err := c.conn.Close()
|
||||
if err != nil {
|
||||
slog.Error("udp client close error", "error", err)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
|
||||
type UdpServer interface {
|
||||
Listen()
|
||||
Close()
|
||||
}
|
||||
|
||||
type UdpMsgHandler func(b []byte)
|
||||
|
@ -37,6 +38,13 @@ func (s *server) Listen() {
|
|||
go s.listenAndHandle()
|
||||
}
|
||||
|
||||
func (s *server) Close() {
|
||||
err := s.conn.Close()
|
||||
if err != nil {
|
||||
slog.Error("udp server close error", "error", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *server) listenAndHandle() {
|
||||
defer s.conn.Close()
|
||||
for {
|
||||
|
|
Loading…
Reference in New Issue