欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

GB28181学习笔记4 实现Server端获取目录功能(NodeJS版)

程序员文章站 2022-07-05 23:13:27
...

一、说明

GB28181设备在注册到服务端之后,服务端通过发送Catalog命令向设备端发送请求命令指令。

范例:

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: aaa@qq.com 
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> 

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: aaa@qq.com 
CSeq: 2 MESSAGE 
Content-Length: 0 

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: aaa@qq.com 
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>

二、抓包截图:

发给设备的查询指令

GB28181学习笔记4 实现Server端获取目录功能(NodeJS版)

收到的目录信息

GB28181学习笔记4 实现Server端获取目录功能(NodeJS版)

三、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);

  }
};
相关标签: 音视频处理