这个项目是我们与浦厂智能部合作的一个项目。 == 网络架构概述 == [[File:PIS200KM系统拓扑结构图.png]] 如上图所示,车厢之间以及车厢内的黑色实线表示网线。车厢之间的绿色虚线表示568线,作为冗入的音频传输线,在车厢网络不能工作时起工作。车厢内的网络设备节点之间的蓝色虚线表示485线,用来接收网络功放的车厢号拨码开关的值。车厢内的DU与屏连接的蓝色实线是控制屏设备的485线。 == MVB通信 == MVB通信由一块专门的MVB板卡(Duagon)提供服务。 === MVB开发调试板 === 工作目录 '''/home/ubuntu/MVB_working-NanJing/D013_modded/D013 Linux Driver_Serial/linux_d013_ser''' sudo make sudo ./tcn_demo 主程序:'''/home/ubuntu/MVB_working-NanJing/D013_modded/D013 Linux Driver_Serial/based_on/d-000543-nnnnnn/sources/src/tcn_demo.c''' === 司机室车厢 === 一列车有前后司机室车厢,任何时候其中的一个司机室车厢是处于激活状态,作为主司机室,另外一个作为从司机室。 通过激活钥匙可以切换主从司机室。司机室车厢中有一个多媒体主机MSU和一个广播主机PSU,这两个主机直接通过网线直连。 === 乘客车厢 === 一列车中的每一节乘客车厢中有一个客室主机PCU。 客室主机PCU的面板由电源模块PW、交换机SW、AMP、DU、HDD、CCTV组成。其中DU通过485控制动态地图和车内屏的显示。其中的AMP面板有一个车厢号码的拔码开关,用来设置当前所在车厢的车厢号,当前车厢的所有网络设备通过485线可以检测到AMP设备的拔码开关的车厢号。 客室主机PCU中连接的网络节点设备类型有DU、报警面板PECU、CCTV、客室摄像头、AMP、IP电视。 === 设备信息 === {| class="wikitable sortable" |- ! 设备类型 !! 设备类型编号 !! 操作系统 !! 远程登录用户名 || 密码 || 主要服务组成 |- | PIS主机 || 11 || Ubuntu14.04 64位|| ntdeck || ntdeck || luna-pudge-broadcast、luna-pudge-ipalloc、ntpis |- | 监控触摸屏 || 无 || Ubuntu14.04 64位 || ntdeck || ntdeck || ntpis-cmon |- | 网络功放 || 31 || ARM Linux || root || 123456 || luna-pudge-2digit、luna-pudge-console |- | PECU || 110开始 || ARM Linux || root || 123456 || luna-pudge-ipalloc、luna-pudge-console |- | DACU || 81 ||ARM Linux || root || 123456 || luna-pudge-ipalloc、luna-pudge-server、ntdriver-box |- | DU || 21 || ARM Ubuntu 14.04 || root || ntdeck || pudge-led-hb、luna-pudge-ipalloc、udp_serialport.rb |- | VES || 50 || Ubuntu 14.04 64位 || ntdeck || ntdeck || luna-pudge-ipalloc、luna-vss、luna-msync |} == 优先级控制 == {| class="wikitable sortable" |- | 操作|| PA || PEI || CI || PEB || DVA || 优先级 |- | PA || — || × || × || × || × || 高 |- | PEI|| || — || × || × || × || |- | CI || || || — || √ || √ || |- | PEB || || || || — || × || |- | DVA || || || || || — || |- | 高 || || || || || || 低 |} 5大功能优先级从高到低: PA:人工广播;PEI:乘客对讲;CI:司机对讲;PEB:预录紧急广播;DVA:数字报站。 表中打钩表示兼容,功能可同时进行; 打叉表示不兼容,当有高优先级功能正在进行,低优先级功能不能激活;当有低优先级功能正在进行,高优先级功能可以激活且将其打断。 例如: 1.当正在进行人工广播,此时点击触摸屏欲进行预录紧急广播,应无响应,忽略此请求。 2.当正在进行数字报站,此时想要进行人工广播,应打断原报站语音功能,激活新功能。 == 功能接口和协议 == === 心跳广播协议 === 心跳广播协议(LunaHeartBeat)用与在当前的PIS网络中广播自身在线的信息,通过UDP广播到全网中,广播地址是'''255.255.255.255''',端口是4096, 数据以大端的方式排列,数据包格式如下: {| class="wikitable sortable" |- ! 帧头 4 !! 设备类型 2 !! COOKIE 4 !! 消息长度 2 !! 消息体 !! CRC 2 |- | 0x4C 0x55 0x48 0x42,即"LUHB"4个字符 || 设备类型,见LunaHeartBeat项目 || 32位随机无符号整数 || 消息体的长度,16位随机无符号整数 || 消息体内容 || 从第0位到CRC之前的数据进行CRC验证 |} 一般建议每2秒发送一次数据包。 当前的设备类型在 luna-heartbeat的头文件lhb.h中有定义,如下:
typedef enum
{
LHB_SERVICE_TYPE_NONE = 0,
LHB_SERVICE_TYPE_PUDGE = 1,
LHB_SERVICE_TYPE_PANEL = 2,
LHB_SERVICE_TYPE_AMP = 3,
LHB_SERVICE_TYPE_PIS = 5,
LHB_SERVICE_TYPE_DACU = 6,
LHB_SERVICE_TYPE_BRCU = 7,
LHB_SERVICE_TYPE_CMON = 8,
LHB_SERVICE_TYPE_DU = 9,
LHB_SERVICE_TYPE_VSS = 10,
LHB_SERVICE_TYPE_LMC = 11,
LHB_SERVICE_TYPE_CCTV_HOST = 50,
LHB_SERVICE_TYPE_CCTV_TERM = 51
}LHBServiceType;
以上的广播协议已经有封装好了库SO,头文件是
static void lhb_test_service_payload_data_feed_cb(guint8 *data, guint16 *size,
gpointer user_data)
{
memcpy(data, "Hello, world!", 14);
*size = 14;
}
=== PIS报站协议 ===
PIS报站协议是PIS主机通过HTTP的方式提供服务的,包括WEB UI的HTML接口,报站接口、预录广播接口和背景音乐的接口。首先需要获取当前车厢网络中PIS主机的IP地址,这个可以通过心跳广播协议获取得到,相关的解析代码片断如下:
1. 初始化lhb接收端,并设置数据接收的回调函数:
lhb_client_init(NULL, "eth0", 0);
lhb_client_set_receive_callback(client_heartbeat_receive_cb, this);
2. 接收心跳数据的回调函数:
static void client_heartbeat_receive_cb(struct sockaddr_in *source_addr,
LHBServiceType service_type,
const guint8 *payload_data, guint16 payload_size, gpointer user_data)
{
PudgeClient *pudge_client = static_cast
{ "route_id" : 1, "route_name" : "G888", "station_id" : 2, "station_status" : 2, "voice_id" : -1, "bg_voice_id" : -1, "is_master" : 1 }
void PudgeClient::process_heartbeat_signal(const QString &host, int host_type, const quint8 *payload_data, quint16 payload_size) {
switch(host_type) {
case LHB_SERVICE_TYPE_PUDGE: {
break;
}
case LHB_SERVICE_TYPE_PIS: {
bool is_master = false; // reference used in follow process_heartbeat_data() fuction.
iPisClient.process_heartbeat_data(payload_data, payload_size, is_master);
if(!is_master)
return;
bool pis_host_online_status_changed = false;
if(!_pis_host_online_) {
LedController::instance().light_connected_led(true);
pis_host_online_status_changed = true;
_pis_host_online_ = true;
}
if(_pis_host_ != host || pis_host_online_status_changed) {
_pis_host_ = host;
PisLogger::instance().info(QString("Found pis host %1").arg(host));
emit pis_host_changed(_pis_host_);
LedController::instance().light_connected_led(true);
} else {
_pis_host_last_onilne_ = QTime::currentTime();
}
break;
}
case LHB_SERVICE_TYPE_VSS: {
break;
}
case LHB_SERVICE_TYPE_LMC: {
break;
}
default: {
break;
}
}
}
==== PIS报站协议的测试方法 ====
为了快速验证PIS报站协议的接口,可以使用curl这个命令行工具进行调试,curl的安装方法:
sudo apt-get install curl
curl发送GET请求: curl https://example.com/resource.cgi
curl发送POST请求: curl curl --data "" https://example.com/resource.cgi
curl发送POST请求并携带数据: curl --data "param1=value1¶m2=value2" https://example.com/resource.cgi
==== WEB UI的HTML接口 ====
WEB UI的HTML接口可以直接用网页浏览器直接访问来测试。
* 报站界面 http://PIS_SERVER_IP:3000/
* 预录音频界面 http://PIS_SERVER_IP:3000/voices.html
* 背景音乐界面 http://PIS_SERVER_IP:3000/bg_music.html
==== 获取所有路线 ====
GET http://192.168.104.11:3000/routes.json
返回结果:
{
"routes": [
{
"id": 1,
"name": "T65",
"current_route_station_id": 0,
"current_station_status": 1,
"running": false,
"position": 0,
"direction": 0,
"stations_count": 4,
"reverse_route_id": 2,
"reverse_route_name": ""
},
{
"id": 2,
"name": "T66",
"current_route_station_id": 6,
"current_station_status": 1,
"running": false,
"position": 0,
"direction": 1,
"stations_count": 6,
"reverse_route_id": 1,
"reverse_route_name": ""
},
{
"id": 3,
"name": "G888",
"current_route_station_id": 14,
"current_station_status": 2,
"running": true,
"position": 0,
"direction": 0,
"stations_count": 26,
"reverse_route_id": 4,
"reverse_route_name": ""
},
{
"id": 4,
"name": "G887",
"current_route_station_id": 37,
"current_station_status": 1,
"running": false,
"position": 0,
"direction": 1,
"stations_count": 26,
"reverse_route_id": 3,
"reverse_route_name": ""
}
],
"count": 4,
"current_route_id": 3
}
==== 获取路线站点数据 ====
GET http://PIS_SERVER_IP:3000/routes/1.json
{
"route": {
"id": 1,
"name": "T65",
"current_route_station_id": 0,
"current_station_status": 1,
"running": false,
"position": 0,
"direction": 0,
"stations_count": 4
},
"stations": [
{
"id": 1,
"station_id": 1,
"name": "北京站",
"position": 0
},
{
"id": 2,
"station_id": 2,
"name": "徐州站",
"position": 1
},
{
"id": 3,
"station_id": 3,
"name": "蚌埠站",
"position": 2
},
{
"id": 4,
"station_id": 4,
"name": "南京站",
"position": 3
}
]
}
==== 获取当前路线站点状态 ====
GET http://192.168.104.11:3000/station_status.json
返回结果:
{
"current_station_id": 14,
"current_station_status": 2,
"current_station_index": 3
}
测试方法: curl http://192.168.104.11:3000/station_status.json
==== 站点播报 ====
POST http://PIS_SERVER_IP:3000/pa?route_station_id=14&status=2
返回结果:
ok
==== 获取预录音频 ====
GET http://PIS_SERVER_IP:3000/voices.json
返回结果:
{
"voices": [
{
"id": 11,
"remark": "请给需要帮助的乘客让个座",
"ticker": "请给需要帮助的乘客让个座",
"file_name": "voices/请给需要帮助的乘客让个座.mp3",
"position": 0,
"play_duration": 4
},
{
"id": 12,
"remark": "禁烟提示",
"ticker": "女士们、先生们,本次列车是无烟列车,请不要在车内吸烟,感谢您的配合",
"file_name": "voices/禁烟提示.mp3",
"position": 0,
"play_duration": 8
},
{
"id": 13,
"remark": "临时停车",
"ticker": "女士们、先生们,列车没有到站,现在是临时停车,请您在座位上耐心等候,不要随意走动,感谢您的配合",
"file_name": "voices/临时停车.mp3",
"position": 0,
"play_duration": 12
}
],
"count": 2,
"current_voice_id": -1
}
==== 获取音量接口 ====
GET http://PIS_SERVER_IP:3000/volumes.json
返回结果:
{
"station_pa": 20,
"broadcast": 20,
"bgmusic": 4
}
==== 获取广播状态 ====
GET http://PIS_SERVER_IP:3000/voice_broadcast_status.json
返回结果:
{
"voice_id": -1,
"bg_voice_id": -1
}
==== 播放预录音频播放 ====
GET http://PIS_SERVER_IP:3000/play_voice?id=音频记录ID
返回结果:
ok
==== 播放背景音乐 ====
GET http://PIS_SERVER_IP:3000/play_bg_voice?id=音频记录ID
返回结果:
ok
==== 停止预录音频播放 ====
GET http://PIS_SERVER_IP:3000/stop_voice
返回结果:
ok
==== 获取背景音月列表 ====
GET http://PIS_SERVER_IP:3000/bg_voices.json
返回结果:
{
"voices": [
{
"id": 1,
"remark": "tianzhiheng",
"ticker": "",
"file_name": "bg_voices/天之痕.mp3",
"position": 0,
"play_duration": 228
},
{
"id": 2,
"remark": "梦中的婚礼",
"ticker": "",
"file_name": "bg_voices/梦中的婚礼.mp3",
"position": 0,
"play_duration": 232
},
{
"id": 3,
"remark": "天空之城",
"ticker": "",
"file_name": "bg_voices/天空之城.mp3",
"position": 0,
"play_duration": 253
},
{
"id": 4,
"remark": "322_Fly away",
"ticker": "",
"file_name": "bg_voices/322_Fly away.mp3",
"position": 0,
"play_duration": 251
},
{
"id": 5,
"remark": "龙猫",
"ticker": "",
"file_name": "bg_voices/龙猫.mp3",
"position": 0,
"play_duration": 257
},
{
"id": 6,
"remark": "Angel",
"ticker": "",
"file_name": "bg_voices/Angel.mp3",
"position": 0,
"play_duration": 271
},
{
"id": 7,
"remark": "光辉岁月",
"ticker": "",
"file_name": "bg_voices/光辉岁月.mp3",
"position": 0,
"play_duration": 298
},
{
"id": 8,
"remark": "童年的回忆",
"ticker": "",
"file_name": "bg_voices/童年的回忆.mp3",
"position": 0,
"play_duration": 238
}
],
"count": 8,
"current_voice_id": -1
}
==== 停止背景音乐播放 ====
GET http://PIS_SERVER_IP:3000/stop_bg_voice
返回结果:
ok
==== PIS数据库结构 ====
/var/lib/ntpis/data.db
===== routes表 =====
id, name, current_route_station_id, current_station_status, current_station_name, stations_count, direction, reverse_route_id, reverse_route_name, running, position
* current_station_status:0预到站,1到站,2出站
* direction: 0上行,1下行
===== route_stations表 =====
id, station_id, station_name, position, route_id, position, ticker_in, ticker_at, ticker_out
===== 报站音频 =====
/var/lib/ntpis/station_voices/{route_station_id}_{in,at,out}.mp3
=== 司机对讲 ===
司机对讲服务对应的程序是LunaPudgeServer,端口是2101,TCP连接。数据都是JSON格式。
==== 发起对讲 ====
QString command_json = QString("{\"command\": \"unicast-outgoing-call\", \"type\": \"driver\", \"dialno\": \"%1\", \"priority\": 0 }\n").arg(target_dialno);
QByteArray data;
data.append(command_json);
socket.write(data);
==== 接听对讲来电 ====
QString command = QString("{\"command\": \"unicast-incoming-accept\", \"uuid\": \"%1\" }\n").arg(call_id);
QByteArray data;
data.append(command);
socket.write(data);
==== 拒绝接听来电 ====
QString command = QString("{\"command\": \"unicast-stop\", \"uuid\": \"%1\", \"reason\": \"user\" }\n").arg(call_id);
QByteArray data;
data.append(command);
int result = socket.write(data);
==== 停止对讲 ====
QString command_json = QString("{\"command\": \"unicast-stop\", \"uuid\": \"%1\" }\n").arg(_current_call_->uuid());
QByteArray data;
data.append(command_json);
socket.write(data);
==== 发起人工广播 ====
QByteArray data("{\"command\": \"broadcast-microphone-start\", \"channel\": 0 }\n");
socket.write(data);
==== 停止人工广播 ====
QByteArray data("{\"command\": \"broadcast-microphone-stop\" }\n");
socket.write(data);
=== 乘客紧急呼叫 ===
=== DU协议 ===
DU是控制信息屏和动态地图的控制单元,它与屏是通过485连接的。
{| class="wikitable sortable"
|-
! 屏类型 !! 发送的内容 !! 说明
|-
| 车外屏显示 || ntroute*15:40*G887*松江南站*杨高中路 || 发送当前时间、起点站、终点站到车外屏
|-
| 动态地图 || ntroute2*AA1A0EBB020214000102FF || 详情见LED动态地图的协议
|-
| 车内信息显示 || 0*女士们、先生们,本次列车是无烟列车,请不要在车内吸烟,感谢您的配合 || 发送字幕到车内显示屏
|}