一、说明
GB28181设备在注册到服务端之后,服务端通过发送Catalog命令向设备端发送请求命令指令。
范例说明(来自GB28181国标文档):
M.11设备目录查询消息消息示范例
M.11.1 1.1和2.1
message sip:目的设备 编码 @目的域名或IP地址端口 SIP/2.0
To: <sip:目的设备编码@目的域名>;tag=852843529
Content-Length: 消息实体的字节长度
CSeq: 2 MESSAGE
Call-ID: wlss-3a65dfb6-7ee86970ea84c5b2284ad158b3d4fdee@172.18.16.5
Via: SIP/2.0/ UDP 源域名或IP地址
From: <sip:源设备编码@源域名>;tag=e40807c0
Content-Type: Application/MANSCDP+xml
Max-Forwards: 70
<?xml version="1.0"?>
<Query>
<CmdType> Catalog </CmdType>
<SN>17430</SN>
<DeviceID>64010000001110000001</DeviceID>
</Query>
123456789101112131415
M.11.2 1.2和2.2
SIP/2.0 200 OK
Via: SIP/2.0/UDP 源域名或IP地址
From: <sip:源设备编码@源域名>;tag=e40807c0
To: <sip:目的设备编码@目的域名>;tag=852843529
Call-ID: wlss-3a65dfb6-7ee86970ea84c5b2284ad158b3d4fdee@172.18.16.5
CSeq: 2 MESSAGE
Content-Length: 0
1234567
M.11.3 3.1和4.1
MESSAGE sip:目的设备编码@目的域名或IP地址端口 SIP/2.0
Via: SIP/2.0/UDP 源域名或IP地址
From: <sip:源设备编码@源域名>;tag=852843529
To: <sip:目的设备编码@目的域名>;tag=e40807c0
Call-ID: wlss-3a65dfb6-7ee86970ea84c5b2284ad158b3d4fdee@172.18.16.5
CSeq: 2 MESSAGE
Max-Forwards: 70
Content-Type: Application/MANSCDP+xml
Content-Length: 消息实体的字节长度
<?xml version="1.0"?>
<Response>
<CmdType> Catalog </CmdType>
<SN>17430</SN>
<DeviceID>64010000001110000001</DeviceID>
<DeviceList Num=2>
<Item>
<DeviceID>64010000001110000001</DeviceID>
<Name>Camera</ Name >
<Status >OK</ Status >
<Longitude> 71.3</ Longitude >
<Latitude>34.2</ Latitude >
<Item>
<Item>
<DeviceID>64010000001110000001</DeviceID>
<Name >Camera</ Name >
<Status > OK </Status>
<Longitude > 171.3</Longitude>
<Latitude > 34.2 </Latitude>
<Item>
</DeviceList>
</Response>
12345678910111213141516171819202122232425262728293031
二、抓包截图:
发给设备的查询指令
收到的目录信息
三、Demo源码
依赖:
npm install log4js
npm install log4js-extend
npm install xml2js
npm install fast-xml-parser
源码
var log4js = require('log4js');
log4js.configure({
appenders: {
out: { type: 'stdout' },
app: { type: 'file', filename: 'application.log' }
},
categories: {
// getLogger 参数为空时,默认使用该分类
default: { appenders: [ 'out', 'app' ], level: 'debug' }
}
});
var logger = log4js.getLogger();
const log4js_extend = require("log4js-extend");
log4js_extend(log4js, {
path: __dirname + "/a.log",
format: "at @name (@file:@line:@column)"
});
var parseString = require('xml2js').parseString;
var xml2js = require('xml2js');
var sip = require('sip');
var digest = require('sip/digest');
var server_account = '34020000002000000001';
// 注册的设备列表
var devices=[];
// 设备信息
var registry = {
'34020000001110000001': {password: 'yourpass'}
};
debugger;
var realm ='3402000000';
logger.info('localhost name='+realm);
sip.start({
logger: {
send: function ( MESSAGE , address ) {
logger.info("==send==:" , message,address);
},
recv: function(message, address) {
logger.info("==recv==:" , message,address);
}
}
},
function(rq) {
try {
if(rq.method ==='REGISTER') {
var username = sip.parseUri(rq.headers.to.uri).user;
var userinfo = registry[username];
if(!userinfo) {
// 没有登记的用户,这里直接禁止授权
//logger.error('没有登记的用户,这里直接禁止授权:' , username);
var session = {realm: realm};
sip.send(digest.challenge(session, sip.makeResponse(rq, 401, 'Unauthorized')));
return;
}
else {
userinfo.session = userinfo.session || {realm: realm};
if(!digest.authenticateRequest(userinfo.session, rq, {user: username, password: userinfo.password})) {
sip.send(digest.challenge(userinfo.session, sip.makeResponse(rq, 401, 'Unauthorized')));
}
else {
// 完成授权
userinfo. contact = rq.headers.contact;
var rs = sip.makeResponse(rq, 200, 'Ok');
sip.send(rs);
logger.info('请求设备目录');
command.catalog(rq,username);
}
}
}
else if(rq.method === 'INVITE') {
var username = sip.parseUri(rq.uri).user;
var userinfo = registry[username]
if(userinfo && Array.isArray(userinfo.contact) && userinfo.contact.length > 0) {
var rs = sip.makeResponse(rq, 302, 'Moved');
rs.headers.contact = userinfo.contact;
sip.send(rs);
}
else {
logger.error(userinfo);
sip.send(sip.makeResponse(rq, 404, 'Not Found'));
}
}
else if(rq.method==='MESSAGE'){
parseString(rq.content,function(err,result){
if(result.Notify){
var CmdType = result.Notify.CmdType[0];
if(CmdType==='Keepalive'){
// 心跳
var rs = sip.makeResponse(rq,200,'OK');
sip.send(rs);
}else{
}
}else if(result.Response){
var CmdType = result.Response.CmdType;
if(CmdType=='Catalog'){
// 获取到的设备列表
if(result.Response.DeviceList){
for(var s in result.Response.DeviceList[0].Item){
var temp = result.Response.DeviceList[0].Item[s];
var exists = devices.indexOf(temp)>-1;
if(!exists) {
devices.push(temp);
}
}
logger.info('----------devices---------',devices);
sip.send(sip.makeResponse(rq,200,'OK'));
}
}
}
});
}
} catch(e) {
logger.error(e);
sip.send(sip.makeResponse(rq, 500, "Server Internal Error"));
}
});
var SN=1;
/**
* 发送目录命令
* @param {request} rq 获取到的参数
*/var command={
// 请求目录
catalog: function(rs,deviceId){
const json = {
Query: {
CmdType: 'Catalog',
SN: SN,
DeviceID: deviceId
}
};
const builder = new xml2js.Builder();
const content = builder.buildObject(json);
const username = sip.parseUri(rs.headers.to.uri).user;
// 从via解析摄像头的ip 端口
const via = sip.parseUri(rs.headers.via)[0];
var cseq = rs.headers.cseq;
cseq.method = 'MESSAGE';
var res = {
method:'MESSAGE',
uri: 'sip:' + username + '@' + via.host + ':' + via.port,
version:rs.version,
headers:{
via : rs.headers.via,
from : rs.headers.from,
to : rs.headers.to,
'call-id' : rs.headers['call-id'],
cseq:cseq,
'Content-Type':'Application/MANSCDP+xml',
'expires':'3600',
'Max-Forwards':70
},
content:content
};
logger.info(res);
sip.send(res);
}
};