新link数据构建;与动力学通信使用新link;增加新旧link转换关系

This commit is contained in:
joylink_zhangsai 2023-08-09 15:34:19 +08:00
parent 300232fda4
commit 00b396d41c
11 changed files with 332 additions and 72 deletions

View File

@ -1,6 +1,8 @@
package simulation
import (
"joylink.club/bj-rtsts-server/ats/verify/protos/graphicData"
"joylink.club/bj-rtsts-server/ats/verify/simulation/wayside/model/device"
"net"
"strings"
@ -39,7 +41,7 @@ func init() {
for _, simulation := range GetSimulationArr() {
sta, ok := simulation.Memory.Status.TrainStateMap.Load(strconv.Itoa(int(info.Number)))
if ok {
memory.UpdateTrainState(simulation, convert(info, *sta.(*state.TrainState)))
memory.UpdateTrainState(simulation, convert(info, *sta.(*state.TrainState), memory.QueryMapVerifyStructure(simulation.MapId)))
break
}
}
@ -54,15 +56,14 @@ func CreateSimulation(mapId int32) string {
simulationId := createSimulationId(mapId)
_, e := simulationMap.Load(simulationId)
if !e {
////通知动力学
//httpCode, _, err := dynamics.SendSimulationStartReq()
//if httpCode != http.StatusOK || err != nil {
// panic(dto.ErrorDto{Code: dto.LogicError, Message: fmt.Sprintf("动力学接口调用失败:[%d][%s]", httpCode, err)})
//}
verifySimulation := memory.CreateSimulation(mapId, simulationId)
//通知动力学
httpCode, _, err := dynamics.SendSimulationStartReq(buildLineBaseInfo(memory.QueryMapVerifyStructure(verifySimulation.MapId)))
if httpCode != http.StatusOK || err != nil {
panic(dto.ErrorDto{Code: dto.LogicError, Message: fmt.Sprintf("动力学接口调用失败:[%d][%s]", httpCode, err)})
}
simulationMap.Store(simulationId, verifySimulation)
//道岔状态发送
startSendTurnoutInfo(simulationId, verifySimulation)
dynamicsRun(verifySimulation)
}
return simulationId
}
@ -70,7 +71,7 @@ func CreateSimulation(mapId int32) string {
// 删除仿真对象
func DestroySimulation(simulationId string) {
//移除道岔状态发送
dynamics.RemoveTurnoutInfoSource(simulationId)
dynamics.Stop()
//通知动力学
httpCode, _, err := dynamics.SendSimulationEndReq()
if httpCode != http.StatusOK || err != nil {
@ -118,9 +119,30 @@ func GetSimulationArr() []*memory.VerifySimulation {
return result
}
func convert(info *dynamics.TrainInfo, sta state.TrainState) *state.TrainState {
sta.HeadLinkId = strconv.Itoa(int(info.Link))
sta.HeadLinkOffset = int64(info.LinkOffset * 10)
func convert(info *dynamics.TrainInfo, sta state.TrainState, vs *memory.VerifyStructure) *state.TrainState {
zap.S().Debugf("原始消息:[%d-%d-%d]", info.Number, info.Link, info.LinkOffset)
modeller := vs.LinkModelMap[int32(info.Link)]
model := modeller.(*device.LinkModel)
for i, dp := range model.DevicePositions {
if uint32(dp.Offset) >= info.LinkOffset {
var linkRef *graphicData.RelatedRef
if i == 0 {
linkRef = model.SectionLinkMap[dp.Device.GetGraphicId()]
} else {
linkRef = model.SectionLinkMap[model.DevicePositions[i-1].Device.GetGraphicId()]
}
switch linkRef.DevicePort {
case 0:
sta.HeadLinkId = linkRef.GetId()
sta.HeadLinkOffset = int64(info.LinkOffset - uint32(dp.Offset))
case 1:
sta.HeadLinkId = linkRef.GetId()
sta.HeadLinkOffset = int64(uint32(model.DevicePositions[i+1].Offset) - info.LinkOffset)
}
break
}
}
zap.S().Debugf("转换后的消息:[%d-%s-%d]", info.Number, sta.HeadLinkId, sta.HeadLinkOffset)
sta.Slope = int32(info.Slope)
sta.Upslope = info.UpSlope
sta.RunningUp = info.Up
@ -138,8 +160,8 @@ func convert(info *dynamics.TrainInfo, sta state.TrainState) *state.TrainState {
return &sta
}
func startSendTurnoutInfo(simulationId string, verifySimulation *memory.VerifySimulation) {
dynamics.AddTurnoutInfoSource(simulationId, func() []*dynamics.TurnoutInfo {
func dynamicsRun(verifySimulation *memory.VerifySimulation) {
_ = dynamics.Run(func() []*dynamics.TurnoutInfo {
stateSlice := memory.GetAllTurnoutState(verifySimulation)
var turnoutInfoSlice []*dynamics.TurnoutInfo
for _, sta := range stateSlice {
@ -157,3 +179,32 @@ func startSendTurnoutInfo(simulationId string, verifySimulation *memory.VerifySi
return turnoutInfoSlice
})
}
func buildLineBaseInfo(vs *memory.VerifyStructure) *dynamics.LineBaseInfo {
var links []*dynamics.Link
for _, modeller := range vs.LinkModelMap {
link := modeller.(*device.LinkModel)
id, _ := strconv.Atoi(link.Index)
var aTurnoutId int
if link.ARelatedSwitchRef.SwitchDevice != nil {
aTurnoutId, _ = strconv.Atoi(link.ARelatedSwitchRef.SwitchDevice.GetIndex())
}
var bTurnoutId int
if link.BRelatedSwitchRef.SwitchDevice != nil {
bTurnoutId, _ = strconv.Atoi(link.BRelatedSwitchRef.SwitchDevice.GetIndex())
}
links = append(links, &dynamics.Link{
ID: int32(id),
Len: link.Length,
ARelTurnoutId: int32(aTurnoutId),
ARelTurnoutPoint: link.ARelatedSwitchRef.Port.Name(),
BRelTurnoutId: int32(bTurnoutId),
BRelTurnoutPoint: link.BRelatedSwitchRef.Port.Name(),
})
}
return &dynamics.LineBaseInfo{
LinkList: links,
SlopeList: nil,
CurveList: nil,
}
}

View File

@ -1,8 +1,11 @@
package face
import "strings"
import (
"fmt"
"strings"
)
//设备模型基类
// 设备模型基类
type DeviceModel struct {
//图形id,即由前端作图时生成,且全局唯一
GraphicId string
@ -10,29 +13,55 @@ type DeviceModel struct {
Index string
}
//判断是否同一个设备
// 判断是否同一个设备
func (dm *DeviceModel) IsSame(other *DeviceModel) bool {
return strings.EqualFold(dm.GraphicId, other.GraphicId)
}
//模型图形id
// 模型图形id
func (dm *DeviceModel) GetGraphicId() string {
return dm.GraphicId
}
//模型索引
// 模型索引
func (dm *DeviceModel) GetIndex() string {
return dm.Index
}
//////////////////////////////////////////////////////////////
//设备端口枚举定义
// 设备端口枚举定义
type PortEnum int8
//设备端口枚举值
func (pe PortEnum) Name() string {
switch pe {
case A:
return "A"
case B:
return "B"
case C:
return "C"
default:
panic(fmt.Sprintf("未知的PortEnum%d", pe))
}
}
// 设备端口枚举值
const (
A PortEnum = iota
B
C
)
func GetPortEnum(i int8) PortEnum {
switch i {
case int8(A):
return A
case int8(B):
return B
case int8(C):
return C
default:
panic(fmt.Sprintf("未知的PortEnum%d", i))
}
}

View File

@ -3,6 +3,7 @@ package memory
import (
"container/list"
"fmt"
"sort"
"strconv"
"sync"
@ -36,6 +37,8 @@ type VerifyStructure struct {
LogicalSectionModelMap map[string]face.LogicalSectionModeller
//信号机模型集合,key为索引编号
SignalDeviceModelMap map[string]face.SignalDeviceModeller
//Link模型集合key为索引编号
LinkModelMap map[int32]face.DeviceModeller
}
// 设备地图ID对应map结构体(建立关系时便于查找使用)注意这里的key 为 Common.Id
@ -61,6 +64,7 @@ func PublishMapVerifyStructure(graphic *model.PublishedGi) *VerifyStructure {
PhysicalSectionModelMap: make(map[string]face.PhysicalSectionModeller),
LogicalSectionModelMap: make(map[string]face.LogicalSectionModeller),
SignalDeviceModelMap: make(map[string]face.SignalDeviceModeller),
LinkModelMap: make(map[int32]face.DeviceModeller),
}
// 地图数据转为map存储建立关系时方便使用
graphicInfoMap := &GraphicInfoMapStructure{
@ -120,6 +124,77 @@ func initGraphicStructure(graphicData *graphicData.RtssGraphicStorage, verifyStr
initGraphicSignal(graphicData.Signals, verifyStructure, graphicDataMap)
// 初始化计算link数据
initCalcLink(graphicData.CalculateLink, verifyStructure, graphicDataMap)
// 初始化Link信息
initLink(graphicData.CalculateLink, verifyStructure, graphicDataMap, graphicData)
}
func initLink(links []*graphicData.CalculateLink, vs *VerifyStructure, dataMap *GraphicInfoMapStructure, data *graphicData.RtssGraphicStorage) {
axleTurnoutIdMap := make(map[string]face.AxlePointDeviceModeller)
for _, modeller := range vs.AxlePointDeviceModelMap {
axleTurnoutIdMap[modeller.GetGraphicId()] = modeller
}
for _, modeller := range vs.SwitchDeviceModelMap {
axleTurnoutIdMap[modeller.GetGraphicId()] = modeller
}
for _, link := range links {
sectionLinkMap := make(map[string]*graphicData.RelatedRef)
//构建DevicePositionDP切片(暂时仅计轴和道岔)
var dps []*ref.DevicePosition
for _, dp := range link.DevicePositions {
modeller := axleTurnoutIdMap[dp.DeviceId]
if modeller == nil {
continue
}
dps = append(dps, &ref.DevicePosition{
Device: modeller,
Offset: dp.Offset,
})
}
//对DP切片按Offset排序
sort.Slice(dps, func(i, j int) bool {
return dps[i].Offset < dps[j].Offset
})
//构建SectionLinkMap
for i := 1; i < len(dps); i++ {
a := dps[i-1]
b := dps[i]
aId := a.Device.GetGraphicId()
bId := b.Device.GetGraphicId()
for _, sl := range data.SectionLinks {
if aId == sl.ASimRef.Id && bId == sl.BSimRef.Id {
sectionLinkMap[aId] = &graphicData.RelatedRef{
DeviceType: 0, //这个字段用不到,所以随便赋值
Id: strconv.Itoa(int(sl.Index)), //因为发给前端的是索引,所以这里用索引
DevicePort: 0,
}
} else if aId == sl.BSimRef.Id && bId == sl.ASimRef.Id {
sectionLinkMap[aId] = &graphicData.RelatedRef{
DeviceType: 0, //这个字段用不到,所以随便赋值
Id: strconv.Itoa(int(sl.Index)), //因为发给前端的是索引,所以这里用索引
DevicePort: 1,
}
}
}
}
vs.LinkModelMap[link.Index] = &device.LinkModel{
DeviceModel: face.DeviceModel{
GraphicId: link.Common.Id,
Index: strconv.Itoa(int(link.Index)),
},
Length: link.Length,
ARelatedSwitchRef: ref.SwitchRef{
SwitchDevice: axleTurnoutIdMap[link.ARelatedRef.Id],
Port: face.GetPortEnum(int8(link.ARelatedRef.DevicePort)),
},
BRelatedSwitchRef: ref.SwitchRef{
SwitchDevice: axleTurnoutIdMap[link.BRelatedRef.Id],
Port: face.GetPortEnum(int8(link.BRelatedRef.DevicePort)),
},
DevicePositions: dps,
SectionLinkMap: sectionLinkMap,
}
}
}
// 初始化计轴信息

View File

@ -2,6 +2,8 @@ package memory
import (
"fmt"
"go.uber.org/zap"
"joylink.club/bj-rtsts-server/ats/verify/simulation/wayside/model/device"
"math"
"net/http"
"strconv"
@ -25,14 +27,39 @@ func AddTrainState(simulation *VerifySimulation, status *state.TrainState) {
status.Show = true
//向动力学发送初始化请求
trainIndex, _ := strconv.ParseUint(status.Id, 10, 16)
linkIndex, _ := strconv.ParseUint(status.HeadLinkId, 10, 16)
var linkIndex uint64
var linkOffset uint32
vs := QueryMapVerifyStructure(simulation.MapId)
Outer:
for _, modeller := range vs.LinkModelMap {
link := modeller.(*device.LinkModel)
for dId, sl := range link.SectionLinkMap {
if sl.Id != status.HeadLinkId {
continue
}
linkIndex, _ = strconv.ParseUint(link.Index, 10, 16)
for i, dp := range link.DevicePositions {
if dp.Device.GetGraphicId() == dId {
switch sl.DevicePort {
case 0:
linkOffset = uint32(dp.Offset) + uint32(status.HeadLinkOffset)
case 1:
linkOffset = uint32(link.DevicePositions[i+1].Offset) - uint32(status.HeadLinkOffset)
}
break Outer
}
}
}
}
httpCode, _, err := dynamics.SendInitTrainReq(&dynamics.InitTrainInfo{
TrainIndex: uint16(trainIndex),
LinkIndex: uint16(linkIndex),
LinkOffset: uint16(status.HeadLinkOffset / 10),
LinkOffset: linkOffset,
Speed: uint16(math.Round(float64(status.Speed * 10))),
Up: status.Up,
})
zap.S().Debugf("添加列车:[%d-%s-%d]", trainIndex, status.HeadLinkId, status.HeadLinkOffset)
zap.S().Debugf("列车初始化:[%d-%d-%d]", trainIndex, linkIndex, linkOffset)
if err != nil || httpCode != http.StatusOK {
panic(dto.ErrorDto{Code: dto.LogicError, Message: fmt.Sprintf("动力学接口调用失败:[%d][%s]", httpCode, err)})
}

View File

@ -0,0 +1,21 @@
package device
import (
"joylink.club/bj-rtsts-server/ats/verify/protos/graphicData"
"joylink.club/bj-rtsts-server/ats/verify/simulation/wayside/face"
"joylink.club/bj-rtsts-server/ats/verify/simulation/wayside/model/ref"
)
type LinkModel struct {
face.DeviceModel
//长度
Length int32
//A端连接的道岔端点
ARelatedSwitchRef ref.SwitchRef
//B端连接的道岔端点
BRelatedSwitchRef ref.SwitchRef
//Link上的设备及位置(将A、B端的道岔也填进来了)(按offset排序)
DevicePositions []*ref.DevicePosition
//key-在link上最小偏移量端点设备idvalue-SectionLink端点
SectionLinkMap map[string]*graphicData.RelatedRef
}

View File

@ -0,0 +1,8 @@
package ref
import "joylink.club/bj-rtsts-server/ats/verify/simulation/wayside/face"
type DevicePosition struct {
Device face.DeviceModeller
Offset int32
}

View File

@ -6,6 +6,7 @@ import (
"fmt"
"joylink.club/bj-rtsts-server/config"
"net/http"
"time"
)
func SendInitTrainReq(info *InitTrainInfo) (int, *[]byte, error) {
@ -13,7 +14,10 @@ func SendInitTrainReq(info *InitTrainInfo) (int, *[]byte, error) {
uri := "/api/aerodynamics/init/train/"
url := baseUrl + uri
data, _ := json.Marshal(info)
resp, err := http.Post(url, "application/json", bytes.NewBuffer(data))
client := http.Client{
Timeout: time.Second * 5,
}
resp, err := client.Post(url, "application/json", bytes.NewBuffer(data))
if err != nil {
s := err.Error()
println(s)

View File

@ -3,7 +3,7 @@ package dynamics
type InitTrainInfo struct {
TrainIndex uint16 `json:"trainIndex"`
LinkIndex uint16 `json:"linkIndex"`
LinkOffset uint16 `json:"linkOffset"`
LinkOffset uint32 `json:"linkOffset"`
//单位0.1km/h
Speed uint16 `json:"speed"`
Up bool `json:"up"`
@ -11,9 +11,9 @@ type InitTrainInfo struct {
// LineBaseInfo 线路基础信息,提供给动力学作为计算依据
type LineBaseInfo struct {
LinkList []Link `json:"linkList"`
SlopeList []Slope `json:"slopeList"`
CurveList []Curve `json:"curveList"`
LinkList []*Link `json:"linkList"`
SlopeList []*Slope `json:"slopeList"`
CurveList []*Curve `json:"curveList"`
}
type Link struct {

View File

@ -3,6 +3,7 @@ package dynamics
import (
"container/list"
"encoding/binary"
"errors"
"fmt"
"github.com/panjf2000/gnet/v2"
"go.uber.org/zap"
@ -13,44 +14,84 @@ import (
"time"
)
func init() {
runSendTurnoutStateTask()
}
var (
m sync.Map
)
type TurnoutInfoFunc func() []*TurnoutInfo
func runSendTurnoutStateTask() {
var (
running bool
mutex sync.Mutex
turnoutInfoFunc TurnoutInfoFunc
//道岔消息生命信号
turnoutLifeSignal uint16
//列车消息生命信号
trainLifeSignal uint16
//列车生命信号是否初始化
trainLifeSignalInit bool
//用于处理生命信号循环使用产生的各种特殊处理
limit uint16 = 10000
)
func Run(tiFunc TurnoutInfoFunc) error {
mutex.Lock()
defer mutex.Unlock()
trainLifeSignalInit = false
return runSendTurnoutStateTask(tiFunc)
}
func Stop() {
mutex.Lock()
defer mutex.Unlock()
turnoutInfoFunc = nil
}
func runSendTurnoutStateTask(tiFunc TurnoutInfoFunc) error {
if running {
return nil
}
if tiFunc == nil {
return errors.New("tiFunc不能为空")
}
turnoutInfoFunc = tiFunc
go func() {
defer func() {
if r := recover(); r != nil {
fmt.Println("Recovered from panic:", r)
}
}()
tick := time.Tick(50 * time.Millisecond)
for range tick {
m.Range(func(key, value any) bool {
slice := value.(TurnoutInfoFunc)()
for _, turnoutInfo := range slice {
err := SendTurnoutInfo(turnoutInfo)
if err != nil {
zap.S().Error(err)
}
if turnoutInfoFunc == nil {
continue
}
slice := turnoutInfoFunc()
for _, turnoutInfo := range slice {
turnoutInfo.lifeSignal = turnoutLifeSignal
err := sendTurnoutInfo(turnoutInfo)
if err != nil {
zap.S().Error(err)
}
return true
})
}
turnoutLifeSignal++
}
}()
return nil
}
func AddTurnoutInfoSource(simId string, tiFunc TurnoutInfoFunc) {
m.Store(simId, tiFunc)
var handlerList list.List
type TrainInfoHandler func(info *TrainInfo)
func RegisterTrainInfoHandler(handler TrainInfoHandler) {
handlerList.PushBack(handler)
}
func RemoveTurnoutInfoSource(simId string) {
m.Delete(simId)
func RunUdpServer() {
server := &udpServer{addr: fmt.Sprintf("udp://:%d", config.Config.Dynamics.UdpLocalPort), multicore: false}
err := gnet.Run(server, server.addr, gnet.WithMulticore(server.multicore))
zap.L().Fatal("udp服务启动失败", zap.Error(err))
}
// SendTurnoutInfo 发送道岔信息
func SendTurnoutInfo(info *TurnoutInfo) error {
// sendTurnoutInfo 发送道岔信息
func sendTurnoutInfo(info *TurnoutInfo) error {
defer func() {
if r := recover(); r != nil {
zap.S().Error("发送道岔信息失败", r)
@ -70,6 +111,7 @@ func SendTurnoutInfo(info *TurnoutInfo) error {
}
}(conn)
var data []byte
data = binary.BigEndian.AppendUint16(data, info.lifeSignal)
data = binary.BigEndian.AppendUint16(data, info.Code)
var b byte
if info.NPosition {
@ -105,12 +147,28 @@ func (server *udpServer) OnBoot(eng gnet.Engine) gnet.Action {
func (server *udpServer) OnTraffic(c gnet.Conn) gnet.Action {
defer func() {
if r := recover(); r != nil {
zap.S().Error("udp服务数据解析异常", r)
zap.L().Error("udp服务数据解析异常", zap.Any("panic", r))
}
}()
buf, _ := c.Next(-1)
trainInfo := TrainInfo{}
trainInfo.LifeSignal = binary.BigEndian.Uint16(buf[0:2])
if !trainLifeSignalInit {
trainLifeSignalInit = true
trainLifeSignal = trainInfo.LifeSignal
} else if trainLifeSignal < limit {
if trainInfo.LifeSignal < trainLifeSignal || trainInfo.LifeSignal > trainLifeSignal-limit {
return gnet.None
}
} else if trainLifeSignal < math.MaxUint16-10000 {
if trainInfo.LifeSignal < trainLifeSignal {
return gnet.None
}
} else {
if trainInfo.LifeSignal < trainLifeSignal && trainInfo.LifeSignal > trainLifeSignal+10000 {
return gnet.None
}
}
trainInfo.Number = buf[2]
trainInfo.Len = binary.BigEndian.Uint16(buf[3:5])
trainInfo.Link = buf[5]
@ -138,17 +196,3 @@ func (server *udpServer) OnTraffic(c gnet.Conn) gnet.Action {
return gnet.None
}
var handlerList list.List
type TrainInfoHandler func(info *TrainInfo)
func RegisterTrainInfoHandler(handler TrainInfoHandler) {
handlerList.PushBack(handler)
}
func RunUdpServer() {
server := &udpServer{addr: fmt.Sprintf("udp://:%d", config.Config.Dynamics.UdpLocalPort), multicore: false}
err := gnet.Run(server, server.addr, gnet.WithMulticore(server.multicore))
zap.L().Fatal("udp服务启动失败", zap.Error(err))
}

View File

@ -1,9 +1,10 @@
package dynamics
type TurnoutInfo struct {
Code uint16
NPosition bool
RPosition bool
lifeSignal uint16
Code uint16
NPosition bool
RPosition bool
}
type TrainInfo struct {
@ -21,7 +22,7 @@ type TrainInfo struct {
Slope uint16
//列车所在位置坡度走势(上/下坡)
UpSlope bool
//列车当前运行方向(上/下行
//列车当前运行方向(偏移量增大/减小方向
Up bool
//实际运行阻力N
TotalResistance uint32

View File

@ -29,7 +29,7 @@ func TestSendTurnoutInfo(t *testing.T) {
tick := time.Tick(50 * time.Millisecond)
for range tick {
for i := 1; i <= 9; i++ {
SendTurnoutInfo(&TurnoutInfo{
sendTurnoutInfo(&TurnoutInfo{
Code: uint16(i),
NPosition: true,
RPosition: false,