本文主要讲述SSIP(Signaling SCADA Integration Platform)人机界面与DMS信息之间的订阅问题。主要包括实时数据库表定义,怎样使用protobuf结构化数据作为订阅的单元?
在SCADA实时监控系统中,图形组态时可以按照常规的模拟量和数字量订阅方式。但是我们也会面临这样的实时结构化的数据,订阅时需要作为整体,那该怎么办呢。
本文将使用一个DMS例子,构思这样的特殊订阅过程。
序号 |
内容 |
字节数 |
内容说明 |
1 |
帧类型 |
1 |
0x07 |
2 |
ATP时间 |
4 |
UNIX时间 |
3 |
服务器时间 |
4 |
UNIX时间 |
4 |
机车号 |
2 |
WORD型 |
5 |
机车型号 |
2 |
WORD型 |
6 |
车次 |
8 |
字符串直接显示 |
7 |
司机号 |
2 |
WORD型 |
8 |
副司机号 |
2 |
WORD型 |
9 |
输入交路号 |
1 |
|
10 |
实际交路号 |
1 |
|
11 |
车站号 |
2 |
WORD型,TMIS号,全路统一且唯一 |
12 |
机车运行方向 |
1 |
1:上行 2:下行 |
13 |
常用制动速度 |
2 |
WORD型 |
14 |
紧急制动速度 |
2 |
WORD型 |
15 |
目标速度 |
2 |
WORD型 |
16 |
目标距离 |
2 |
WORD型 |
17 |
最大限制速度 |
2 |
WORD型 |
18 |
允许速度 |
2 |
WORD型 |
19 |
临时限速速度 |
2 |
WORD型 |
20 |
ATP等级 |
1 |
1:CTCS-0 LKJ控车 2:CTCS-1 LKJ控车 3:CTCS-2 ATP控车 其他值预留 |
21 |
ATP模式 |
1 |
1:FS【完全监控】 2:PS【部分监控】 3:R0【反向运行】 4:C0【引导】 5:OS【目视】 6:SH【调车】 7:SL休眠模式 8:SB【备用】 9:TR【冒进】 10:PT【冒后】 11:SF【系统故障】 12:IS【隔离】 其他值预留 |
22 |
制动信息 |
1 |
1:一级常用制动 2:四级常用制动 3:七级常用制动 4:施加紧急制动 5:缓解紧急制动 6:施加常用制动 7:缓解常用制动 其他预留 |
23 |
DMI文本信息长度 |
1 |
|
24 |
DMI文本信息 |
n |
字符串 |
25 |
司机对DMI操作信息长度 |
1 |
|
26 |
司机对DMI操作信息 |
n |
既有线CTCS-2: 包含司机对DMI的操作信息、隔离开关的状态信息、牵引手柄的位置信息和方向手柄的位置信息。 客运专线CTCS-3: 包含司机输入数据信息、司机对列车数据的确认信息。 |
27 |
牵引手柄信息 |
1 |
1:牵引 2:零位 3:制动 4:异常 其他预留 |
28 |
方向手柄信息 |
1 |
1:向前 2:向后 其他:预留 |
29 |
轨道电路区段名称长度 |
1 |
|
30 |
轨道电路区段名称 |
n |
|
31 |
入口电压 |
2 |
WORD型 |
32 |
出口电压 |
2 |
WORD型 |
33 |
最低电压 |
2 |
WORD型 |
34 |
最高电压 |
2 |
WORD型 |
35 |
载频编码 |
1 |
|
36 |
低频编码 |
1 |
|
37 |
载频 |
2 |
载频×10 (Hz)(0xFFFF为未知) |
38 |
低频 |
2 |
低频×10(Hz)(0xFFFF为未知) |
39 |
幅度 |
2 |
幅度×10(mv)(0xFFFF为未知) |
40 |
RBC报文信息 |
1 |
|
41 |
RBC报文信息 |
n |
|
42 |
经度 |
4 |
GPS经度 |
43 |
纬度 |
4 |
GPS纬度 |
44 |
当前速度 |
2 |
GPS速度,WORD型 |
45 |
动车编号 |
2 |
WORD型 |
46 |
里程 |
4 |
动车组当前运行位置的公里标信息,浮点数FLOAT32 |
47 |
线路编号 |
2 |
当前运行的线路,全路统一且惟一 |
48 |
线路名称长度 |
1 |
|
49 |
线路名称 |
N |
|
50 |
线路行别 |
1 |
1上行;2下行 |
51 |
前方车站 |
2 |
WORD型 |
52 |
停发车信息 |
1 |
1:区间停车 2:区间发车 3:站内停车 4:站内发车 5:非正常发车 其他:预留 |
53 |
信号机种类制式 |
1 |
1:进出站 2:出站 3:进站 4:通过 5:预告 6:容许 7:分割 9:第1预告 10:第2预告 其他:预留 |
54 |
信号机编号 |
2 |
WORD型 |
55 |
信号机灯位 |
2 |
Bit8=1:闪光 Bit7=1:白 Bit6=1:红 Bit5=1:红黄 Bit4=1:双黄 Bit3=1:黄2 Bit2=1:黄 Bit1=1:绿黄 Bit0=1:绿 |
56 |
前方信号种类制式 |
1 |
1:进出站 2:出站 3:进站 4:通过 5:预告 6:容许 7:分割 9:第1预告 10:第2预告 其他:预留 |
57 |
前方信号机编号 |
2 |
WORD型 |
58 |
前方信号机灯位 |
1 |
Bit7=1:白 Bit6=1:红 Bit5=1:红黄 Bit4=1:双黄 Bit3=1:黄2 Bit2=1:黄 Bit1=1:绿黄 Bit0=1:绿 |
59 |
距前方信号机距离 |
2 |
WORD型,单位m |
60 |
应答器编号 |
2 |
WORD型 |
61 |
应答器报文信息长度 |
1 |
|
62 |
应答器报文信息 |
n |
|
63 |
应答器监测结果 |
1 |
1:OK 0:为异常 其他:预留 |
64 |
应有电容 |
4 |
FLOAT32 |
65 |
实有电容 |
4 |
FLOAT32 |
66 |
电容是否失效 |
1 |
1:失效 2:正常 其他:预留 |
id |
列车编号 |
char(8) |
atp_time |
ATP时间 |
uint32 |
server_time |
服务器时间 |
uint32 |
train_id |
机车号 |
uint16 |
train_type |
机车型号 |
uint16 |
train_number |
车次 |
char(8) |
pilot_no |
司机号 |
uint16 |
copilot_no |
副司机号 |
uint16 |
input_crossroadno |
输入交路号 |
uint8 |
real_crossroadno |
实际交路号 |
uint8 |
station_no |
车站号 |
uint16 |
dir |
机车运行方向 |
uint8 |
normalspeed |
常用制动速度 |
uint16 |
breakspeed |
紧急制动速度 |
uint16 |
targetspeed |
目标速度 |
uint16 |
targetdistance |
目标距离 |
uint16 |
maxspeed |
最大限制速度 |
uint16 |
permitspeed |
允许速度 |
uint16 |
tempspeed |
临时限速速度 |
uint16 |
atp_priority |
ATP等级 |
uint8 |
atp_mode |
ATP模式 |
uint8 |
break_info |
制动信息 |
uint8 |
dmitextlen |
DMI文本信息长度 |
uint8 |
dmitext |
DMI文本信息 |
char(32) |
driverdmilen |
司机对DMI操作信息长度 |
uint8 |
driverdmitext |
司机对DMI操作信息 |
char(32) |
pullhandle |
牵引手柄信息 |
uint8 |
pullhandle2 |
方向手柄信息 |
uint8 |
trackregionnamelen |
轨道电路区段名称长度 |
uint8 |
trackregionname |
轨道电路区段名称 |
char(32) |
involtage |
入口电压 |
uint16 |
outvoltage |
出口电压 |
uint16 |
lowvoltage |
最低电压 |
uint16 |
maxvoltage |
最高电压 |
uint16 |
carryfrequencycode |
载频编码 |
uint8 |
lowfrequencycode |
低频编码 |
uint8 |
carryfrequency |
载频 |
uint16 |
lowfrequency |
低频 |
uint16 |
scope |
幅度 |
uint16 |
rbc_text |
RBC报文信息 |
uint8 |
rbc_text2 |
RBC报文信息 |
char(32) |
longitude |
经度 |
int32 |
latitude |
纬度 |
int32 |
currentspeed |
当前速度 |
uint16 |
runcarcno |
动车编号 |
uint16 |
mileage |
里程 |
float32 |
lineno |
线路编号 |
uint16 |
linenamelen |
线路名称长度 |
uint8 |
linename |
线路名称 |
char(32) |
linelevel |
线路行别 |
uint16 |
forwardstation |
前方车站 |
uint16 |
stopcartext |
停发车信息 |
uint8 |
signaltype |
信号机种类制式 |
uint8 |
signalno |
信号机编号 |
uint16 |
signallampposition |
信号机灯位 |
uint16 |
forwardsignaltype |
前方信号种类制式 |
uint8 |
forwardsignalno |
前方信号机编号 |
uint16 |
forwardlampposition |
前方信号机灯位 |
uint8 |
forwardsignaldistance |
距前方信号机距离 |
uint16 |
apno |
应答器编号 |
uint16 |
apgramlen |
应答器报文信息长度 |
uint8 |
apgramtext |
应答器报文信息 |
char(32) |
propercap |
应有电容 |
float32 |
apresult |
应答器监测结果 |
uint16 |
realcap |
实有电容 |
float32 |
capenable |
电容是否失效 |
uint8 |
注:id主键。
id |
列车编号 |
char(8) |
data |
数据区 |
blob(512) |
注:id主键。
package SSIP;
message dmsData
{
required string id = 1;
optional int32 atp_time = 2;
optional int32 server_time = 3;
optional int32 train_id = 4;
optional int32 train_type = 5;
optional string train_number = 6;
optional int32 pilot_no = 7;
optional int32 copilot_no = 8;
optional uint32 input_crossroadno = 9;
optional uint32 real_crossroadno = 10;
optional int32 station_no = 11;
optional uint32 dir = 12;
optional int32 normalspeed = 13;
optional int32 breakspeed = 14;
optional int32 targetspeed = 15;
optional int32 targetdistance = 16;
optional int32 maxspeed = 17;
optional int32 permitspeed = 18;
optional int32 tempspeed = 19;
optional uint32 atp_priority = 20;
optional uint32 atp_mode = 21;
optional uint32 break_info = 22;
optional string dmitext = 23;
optional string driverdmitext = 24;
optional uint32 pullhandle = 25;
optional uint32 pullhandle2 = 26;
optional string trackregionname = 27;
optional int32 involtage = 28;
optional int32 outvoltage = 29;
optional int32 lowvoltage = 30;
optional int32 maxvoltage = 31;
optional uint32 carryfrequencycode = 32;
optional uint32 lowfrequencycode = 33;
optional int32 carryfrequency = 34;
optional int32 lowfrequency = 35;
optional int32 scope = 36;
optional string rbc_text = 37;
optional int32 longitude = 38;
optional int32 latitude = 39;
optional int32 currentspeed = 40;
optional int32 runcarcno = 41;
optional float mileage = 42;
optional int32 lineno = 43;
optional string linename = 44;
optional uint32 linelevel = 45;
optional int32 forwardstation = 46;
optional uint32 stopcartext = 47;
optional uint32 signaltype = 48;
optional int32 signalno = 49;
optional int32 signallampposition = 50;
optional uint32 forwardsignaltype = 51;
optional int32 forwardsignalno = 52;
optional uint32 forwardlampposition = 53;
optional int32 forwardsignaldistance = 54;
optional int32 apno = 55;
optional string apgramtext = 56;
optional int32 apresult = 57;
optional float propercap = 58;
optional float realcap = 59;
optional uint32 capenable = 60;
}
message dmsDataSet
{
repeated dmsData dms = 1;
}
表“dms_tempdata”的字段id与表“dms_data”的字段成一一映射关系。表“dms_tempdata”的字段data主要由两块组成:header+body。Header包含当前data数据体的长度,类型以及描述;body是由表“dms_data”中的字段组合,使用protobuf序列成数据体。
下面的过程为实时库表dms_data结构,序列化到dmsDataSet接口中。
::SSIP::dmsDataSet dms;
::SSIP::dmsData* data = dms.add_dms();
data->set_dms_id(d.dms_id);
data->set_atp_time(d.atp_time);
data->set_server_time(d.server_time);
data->set_train_id(d.train_id);
data->set_train_type(d.train_type);
data->set_train_number(d.train_number);
data->set_pilot_no(d.pilot_no);
data->set_copilot_no(d.copilot_no);
data->set_input_crossroadno(d.input_crossroadno);
data->set_real_crossroadno(d.real_crossroadno);
data->set_station_no(d.station_no);
data->set_dir(d.dir);
data->set_normalspeed(d.normalspeed);
data->set_breakspeed(d.breakspeed);
data->set_targetspeed(d.targetspeed);
data->set_targetdistance(d.targetdistance);
data->set_maxspeed(d.maxspeed);
data->set_permitspeed(d.permitspeed);
data->set_tempspeed(d.tempspeed);
data->set_atp_priority(d.atp_priority);
data->set_atp_mode(d.atp_mode);
data->set_break_info(d.break_info);
data->set_dmitext(d.dmitext);
data->set_driverdmitext(d.driverdmitext);
data->set_pullhandle(d.pullhandle);
data->set_pullhandle2(d.pullhandle2);
data->set_trackregionname(d.trackregionname);
data->set_involtage(d.involtage);
data->set_outvoltage(d.outvoltage);
data->set_lowvoltage(d.lowvoltage);
data->set_maxvoltage(d.maxvoltage);
data->set_carryfrequencycode(d.carryfrequencycode);
data->set_lowfrequencycode(d.lowfrequencycode);
data->set_carryfrequency(d.carryfrequency);
data->set_lowfrequency(d.lowfrequency);
data->set_scope(d.scope);
data->set_rbc_text(d.rbc_text);
data->set_longitude(d.longitude);
data->set_latitude(d.latitude);
data->set_currentspeed(d.currentspeed);
data->set_runcarcno(d.runcarcno);
data->set_mileage(d.mileage);
data->set_lineno(d.lineno);
data->set_linename(d.linename);
data->set_linelevel(d.linelevel);
data->set_forwardstation(d.forwardstation);
data->set_stopcartext(d.stopcartext);
data->set_signaltype(d.signaltype);
data->set_signalno(d.station_no);
data->set_signallampposition(d.signallampposition);
data->set_forwardsignaltype(d.forwardsignaltype);
data->set_forwardsignalno(d.forwardsignalno);
data->set_forwardlampposition(d.forwardlampposition);
data->set_forwardsignaldistance(d.forwardsignaldistance);
data->set_apno(d.apno);
data->set_apgramtext(d.apgramtext);
data->set_apresult(d.apresult);
data->set_propercap(d.propercap);
data->set_realcap(d.realcap);
data->set_capenable(d.capenable);
std::string strOut = dms.SerializeAsString();
int nlen = dms.ByteSize();
BLOB_HEADER hd;
hd.type = NET_BITS_PROTOBUF;
hd.len = strOut.length();
strncpy(hd.desc,"SSIP.dmsDataSet",sizeof(hd.desc));
。。。
dms_tempdata d;
memset(&d, 0,sizeof(tempdata));
strncpy(d.id, d.dms_id.c_str(),sizeof(d.id));
assert(strOut.length() <= (sizeof(d.data) -sizeof(BLOB_HEADER)));
memcpy(d.data, &hd,sizeof(BLOB_HEADER));
memcpy(d.data +sizeof(BLOB_HEADER), strOut.c_str(), strOut.length());
。。。
这里的strOut就是表“dms_tempdata”的protobuf,nlen就是Header信息体的长度。毫无疑问Protobuf结构化数据已经跨平台了,BLOB_HEADER定义的结构也应考虑跨平台封装。
订阅规则表达式:
1) key [a-z0-9A-Z]模拟量和数字量
2) [email protected] 自定义表和字段
3) [email protected][;field.type]适用blob少量字段分解
4) [email protected][field.xml] 适用blob批量字段分解
当1003列车信息数据产生变化,data数据将主动把这块数据(blob)推送给人机界面。然后HMI使用一个标准动态链接库解析器接口,把data元素进行相应的分解。在这里我们将封装一个接口程序,这个解析器可以回调人机界面处理函数,也可以使用getValue接口分别调用处理。
例:
1003@dms_tempdata.data.blob[;atp_time.uint32;server_time.uint32]
列车编号:1003
实时库表:dms_tempdata
字段域:data
类型:blob
考虑软件工程中的封闭开放原则,能够集成不同的业务范围,不影响整个HMI软件重构。因此,我们使用动态链接库封装此接口—显示调用方式。随着业务范围的变化,我们可以对libblob.dll扩展业务功能,而不需要编译HMI软件。
l libblob.dll接口
extern"C"__declspec(dllexport)
bool
parserBlob(void* buf,constint& type,const int& len,
const std::string& typedesc, LPFUNC_PARSELUA lpfnPaser)
{
。。。
}
extern"C"__declspec(dllexport)
bool
getValue(const std::string& object, CBaseValue& value)
{
。。。
}
l HMI调用接口
typedefbool (*LPFUNC_PARSELUA)(const std::string& data);
typedefbool (*PARSEBLOB)(void* buf,constint& type,const int& len,
const std::string& desc, LPFUNC_PARSELUA lpfnParser);
typedefbool (*GETVALUE)(const std::string& object, CBaseValue& value);
PARSEBLOB lpfnParseBlob;
GETVALUE lpfnGetValue;
hInst = LoadLibrary(_T("libblob.dll"));
lpfnParseBlob = (PARSEBLOB)GetProcAddress(hInst,"parserBlob");
lpfnGetValue = (GETVALUE)GetProcAddress(hInst,"getValue");
。。。
FreeLibrary(hInst);
l HMI订阅接口
。。。
int nBlobSize;
void* lpBlobData;
CBaseValue value;
LPFUNC_PARSELUA lpfnParser = (LPFUNC_PARSELUA)parser;
。。。
if (nBlobSize > sizeof(BLOB_HEADER))
{
BLOB_HEADER* hd = (BLOB_HEADER*)lpBlobData;
if (hd->len <= (nBlobSize - sizeof(BLOB_HEADER)))
{
void* buf = (char*)lpBlobData + sizeof(BLOB_HEADER);
if (lpfnParseBlob && lpfnGetValue)
{
(*lpfnParseBlob)(buf, hd->type, hd->len, hd->desc, lpfnParser);
//下面可能循环处理,把data分解的内容释放到不同的动画对象中。
。。。
(*lpfnGetValue)(data->obj, value);
。。。
}
}
}
。。。
省略。