obcon-scada / app /interfaces /thing_plug.js
chanmin0723's picture
Initial obcon SCADA deploy
e4bf523
Raw
History Blame Contribute Delete
21.5 kB
'use strict'
/**
* Copyright (c) 2017~2019, OBCon Inc.
* All rights reserved.
*/
/**
* @file
* @copyright 2017~2019, OBCon Inc.
* @author gye hyun james kim [pnuskgh@gmail.com]
*/
//--- https://www.npmjs.com/package/request
//--- https://github.com/request/request/blob/master/request.js
let request = require('request');
let xml2js = require('xml2js');
//--- ThingPlug에서 전달되는 데이터의 최대 크기는 65 bytes 임
//--- 전송 가능한 HTTP 수신 대상의 갯수는 4개임
//--- 사용하는 곳
//--- modules/thingplugs/view.js
class ThingPlug {
constructor() {
}
exs(code) { //--- Execute Status (장치 제어 상태 정보)
let msg = '';
switch (code) {
case 1: msg = 'INITIATED'; break;
case 2: msg = 'PENDING'; break;
case 3: msg = 'FINISHED'; break;
case 4: msg = 'CANCELLING'; break;
case 5: msg = 'CANCELLED'; break;
case 6: msg = 'STATUS_NON_CANCELLABLE'; break;
}
return msg;
}
exr(code) {
let msg = '';
switch (code) {
case 0: msg = 'STATUS_REQUEST_SUCCEED'; break;
case 1: msg = 'STATUS_REQUEST_UNSUPPORTED'; break;
case 2: msg = 'STATUS_REQUEST_DENIED'; break;
case 3: msg = 'STATUS_CANCELLATION_DENIED'; break;
case 4: msg = 'STATUS_INTERNAL_ERROR'; break;
case 5: msg = 'STATUS_INVALID_ARGUMENTS'; break;
case 6: msg = 'STATUS_RESOURCES_EXCEEDED'; break;
case 7: msg = 'STATUS_FILE_TRANSFER_FAILED'; break;
case 8: msg = 'STATUS_FILE_TRANSFER_SERVER_AUTHENTICATION_FAILURE'; break;
case 9: msg = 'STATUS_UNSUPPORTED_PROTOCOL'; break;
case 10: msg = 'STATUS_UPLOAD_FAILED'; break;
case 11: msg = 'STATUS_FILE_TRANSFER_FAILED_MULTICAST_GROUP_UNABLE_JOIN'; break;
case 12: msg = 'STATUS_FILE_TRANSFER_FAILED_SERVER_CONTACT_FAILED'; break;
case 13: msg = 'STATUS_FILE_TRANSFER_FAILED_FILE_ACCESS_FAILED'; break;
case 14: msg = 'STATUS_FILE_TRANSFER_FAILED_DOWNLOAD_INCOMPLETE'; break;
case 15: msg = 'STATUS_FILE_TRANSFER_FAILED_FILE_CORRUPTED'; break;
case 16: msg = 'STATUS_FILE_TRANSFER_FILE_AUTHENTICATION_FAILURE'; break;
case 17: msg = 'STATUS_FILE_TRANSFER_FAILED'; break;
case 18: msg = 'STATUS_FILE_TRANSFER_SERVER_AUTHENTICATION_FAILURE'; break;
case 19: msg = 'STATUS_FILE_TRANSFER_WINDOW_EXCEEDED'; break;
case 20: msg = 'STATUS_INVALID_UUID_FORMAT'; break;
case 21: msg = 'STATUS_UNKNOWN_EXECUTION_ENVIRONMENT'; break;
case 22: msg = 'STATUS_DISABLED_EXECUTION_ENVIRONMENT'; break;
case 23: msg = 'STATUS_EXECUTION_ENVIRONMENT_MISMATCH'; break;
case 24: msg = 'STATUS_DUPLICATE_DEPLOYMENT_UNIT'; break;
case 25: msg = 'STATUS_SYSTEM_RESOURCES_EXCEEDED'; break;
case 26: msg = 'STATUS_UNKNOWN_DEPLOYMENT_UNIT'; break;
case 27: msg = 'STATUS_INVALID_DEPLOYMENT_UNIT_STATE'; break;
case 28: msg = 'STATUS_INVALID_DEPLOYMENT_UNIT_UPDATE_DOWNGRADE_DISALLOWED'; break;
case 29: msg = 'STATUS_INVALID_DEPLOYMENT_UNIT_UPDATE_UPGRADE_DISALLOWED'; break;
case 30: msg = 'STATUS_INVALID_DEPLOYMENT_UNIT_UPDATE_VERSION_EXISTS'; break;
case 31: msg = 'STATUS_DEVICE_MANAGEMENT_TIME_OUT'; break;
case 32: msg = 'STATUS_NW_SVR_TRANSFER_FAILED'; break;
}
return msg;
}
ltid2eui(ltid) {
return ltid.substr(8);
}
_getThingPlugInfo(site, device) {
if (config.ThingPlug.apply == 'device') {
if ((device != null) && (device.appEUI != '') && (device.uKey != '')) {
return {
appEUI: security.decrypt(device.appEUI),
uKey: security.decrypt(device.uKey)
};
}
}
if ((config.ThingPlug.apply == 'site') || (config.ThingPlug.apply == 'device')) {
if ((site.appEUI != '') && (site.uKey != '')) {
return {
appEUI: security.decrypt(site.appEUI),
uKey: security.decrypt(site.uKey)
};
}
}
return {
appEUI: security.decrypt(config.ThingPlug.appEUI),
uKey: security.decrypt(config.ThingPlug.uKey)
};
}
eui2ltid(site, device, eui) {
let info = this._getThingPlugInfo(site, device);
return info.appEUI.substr(8) + eui;
}
stringToHex(str) {
const buf = Buffer.from(str, 'utf8');
return buf.toString('hex');
}
hexToString(str) {
const buf = new Buffer(str, 'hex');
return buf.toString('utf8');
}
_getServerUrl() {
return config.ThingPlug.protocol + '://' + config.ThingPlug.host + ':' + config.ThingPlug.port;
}
randomInt (low, high) {
return Math.floor(Math.random() * (high - low + 1) + low);
}
getOptions(site, device, path, isJson=true, randomSeq = '000001') {
let info = this._getThingPlugInfo(site, device);
randomSeq = this.randomInt(100000, 999999)
let options = {
uri: this._getServerUrl() + path,
headers: {
'X-M2M-RI': info.appEUI + '_' + randomSeq,
'X-M2M-Origin': info.appEUI,
'ukey': info.uKey
}
};
if (isJson) {
options.headers.Accept = 'application/json';
} else {
options.headers.Accept = 'application/xml';
}
return options;
}
isJsonRequest(options) {
return options.headers.Accept == 'application/json';
}
// getUserKey(callback) {
// //--- To-Do : Site별 설정 적용
// let options = {
// uri: this._getServerUrl() + '/ThingPlug?division=user&function=login',
// headers: {
// 'Accept': 'application/json',
// 'user_id': config.ThingPlug.userid,
// 'password': security.decrypt(config.ThingPlug.passwd)
// }
// };
// request.put(options, function (error, response, body) {
// if (!error && response.statusCode == 200) {
// let resp = JSON.parse(body);
// if (resp.result_code == '200') {
// callback({ code: 0, msg: 'normal'} , { uKey: resp.userVO.uKey }, body);
// } else {
// callback({ code: 2, msg: 'error'} , null, body);
// }
// } else {
// logger.error('interfaces/thing_plug.js :: error : ' + JSON.stringify(error));
// logger.error('interfaces/thing_plug.js :: response: ' + JSON.stringify(response));
// callback({ code: 1, msg: 'error' }, null, null);
// }
// });
// }
//--- response.total_list_count : 전체 갯수
//--- response.deviceSearchAPIList : Device 정보 배열
getDeviceList(site, device, startIndex = 1, callback) {
let options = this.getOptions(site, device, '/ThingPlug?division=searchDevice&function=myDevice&' +
'startIndex=' + startIndex + '&countPerPage=1000');
request.get(options, function (error, response, body) {
// console.log('body', body);
if (!error && response.statusCode == 200) {
let resp = JSON.parse(body);
if (resp.result_code == '200') {
callback({ code: 0, msg: 'normal'} , {
total: resp.total_list_count,
devices: resp.deviceSearchAPIList
}, body);
} else {
callback({ code: 2, msg: 'error'} , null, body);
}
} else {
logger.error('interfaces/thing_plug.js :: error : ' + JSON.stringify(error));
logger.error('interfaces/thing_plug.js :: response: ' + JSON.stringify(response));
callback({ code: 1, msg: 'error' }, null, null);
}
});
}
getDevice(site, device, ltid, callback) {
let info = this._getThingPlugInfo(site, device);
let options = this.getOptions(site, device, '/' + info.appEUI +
'/' + config.ThingPlug.appVersion +
'/node-' + ltid);
request.get(options, function (error, response, body) {
// console.log('body', body);
if (!error && response.statusCode == 200) {
let resp = JSON.parse(body);
callback({ code: 0, msg: 'normal'} , resp, body);
} else {
logger.error('interfaces/thing_plug.js :: error : ' + JSON.stringify(error));
logger.error('interfaces/thing_plug.js :: response: ' + JSON.stringify(response));
callback({ code: 1, msg: 'error' }, null, null);
}
});
}
//--- response.cin.con (Hex로 저장되어 있음)
getLatestData(site, device, ltid, callback) {
let info = this._getThingPlugInfo(site, device);
let options = this.getOptions(site, device, '/' + info.appEUI +
'/' + config.ThingPlug.appVersion +
'/remoteCSE-' + ltid + '/container-LoRa/latest');
request.get(options, function (error, response, body) {
console.log('body', body);
if (!error && response.statusCode == 200) {
let resp = JSON.parse(body);
callback({ code: 0, msg: 'normal'} , resp, body);
} else {
logger.error('interfaces/thing_plug.js :: error : ' + JSON.stringify(error));
logger.error('interfaces/thing_plug.js :: response: ' + JSON.stringify(response));
callback({ code: 1, msg: 'error' }, null, null);
}
});
}
//--- execInc : exin.ri
// <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
// <m2m:mgc xmlns:m2m="http://www.onem2m.org/xml/protocols" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
// <ty>12</ty>
// <ri>MC00000000000001489571</ri>
// <rn>00000592702c1ffffe4f1640_extDevMgmt</rn>
// <pi>CB00000000000000000656</pi>
// <ct>2019-04-05T11:14:49+09:00</ct>
// <lt>2019-04-05T11:14:49+09:00</lt>
// <cmt>extDevMgmt</cmt>
// <exe>false</exe>
// <ext>ND00000000000000372613</ext>
// <m2m:exin>
// <ty>8</ty>
// <ri>EI00000000000364925833</ri>
// <rn>EI00000000000364925833</rn>
// <pi>MC00000000000001489571</pi>
// <ct>2019-04-09T18:43:00+09:00</ct>
// <lt>2019-04-09T18:43:00+09:00</lt>
// <et>2019-04-10T18:43:00+09:00</et>
// <exs>2</exs>
// <ext>ND00000000000000372613</ext>
// <exra>01020304</exra>
// </m2m:exin>
// </m2m:mgc>
deviceControl(site, device, ltid, data, callback) {
let info = this._getThingPlugInfo(site, device);
let options = this.getOptions(site, device, '/' + info.appEUI +
'/' + config.ThingPlug.appVersion +
'/mgmtCmd-' + ltid + '_extDevMgmt', false);
options.headers['Content-Type'] = 'application/xml';
options.body = '<?xml version="1.0" encoding="UTF-8"?>'
+ '<m2m:mgc'
+ ' xmlns:m2m="http://www.onem2m.org/xml/protocols"'
+ ' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">'
+ ' <exe>true</exe>'
+ ' <exra>' + this.stringToHex(data) + '</exra>' //--- Hex로 표시할 것
+ '</m2m:mgc>';
// options.json = true;
// console.log(options);
request.put(options, function (error, response, body) {
// console.log('body', body);
if (!error && response.statusCode == 200) {
xml2js.parseString(body, function (err, result) {
if (err) {
logger.error('interfaces/thing_plug.js :: err : ' + JSON.stringify(err));
callback({ code: 2, msg: 'error' }, null, body);
return;
}
callback({ code: 0, msg: 'normal'} , result, body);
});
} else {
logger.error('interfaces/thing_plug.js :: error : ' + JSON.stringify(error));
//--- response.statusCode : 404 - Not Found
logger.error('interfaces/thing_plug.js :: response: ' + JSON.stringify(response));
callback({ code: 1, msg: 'error' }, null, null);
}
});
}
//--- 001_ThingPlug_API_Document_v1_7.pdf의 43 페이지 참조
//--- exs = 3 : 처리됨
//--- exr = 0 : 정상
getControlResult(site, device, ltid, execIns, callback) {
let info = this._getThingPlugInfo(site, device);
let options = this.getOptions(site, device, '/' + info.appEUI +
'/' + config.ThingPlug.appVersion +
'/mgmtCmd-' + ltid + '_extDevMgmt/execInstance-' + execIns);
request.get(options, function (error, response, body) {
console.log('body', body);
if (!error && response.statusCode == 200) {
let resp = JSON.parse(body);
callback({ code: 0, msg: 'normal'} , resp, body);
} else {
logger.error('interfaces/thing_plug.js :: error : ' + JSON.stringify(error));
logger.error('interfaces/thing_plug.js :: response: ' + JSON.stringify(response));
callback({ code: 1, msg: 'error' }, null, null);
}
});
}
subCreate(site, device, ltid, callback) {
let info = this._getThingPlugInfo(site, device);
let options = this.getOptions(site, device, '/' + info.appEUI +
'/' + config.ThingPlug.appVersion +
'/remoteCSE-' + ltid + '/container-LoRa', false);
options.headers['X-M2M-NM'] = config.ThingPlug.conSubName;
options.headers['Content-Type'] = 'application/vnd.onem2m-res+xml;ty=23';
let protocol = (config.ThingPlug.appServer.startsWith('http://')) ? 'HTTP':'HTTP';
options.body = '<?xml version="1.0" encoding="UTF-8"?>'
+ '<m2m:sub'
+ ' xmlns:m2m="http://www.onem2m.org/xml/protocols"'
+ ' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">'
+ ' <enc>'
+ ' <rss>1</rss>'
+ ' </enc>'
+ ` <nu>${protocol}|${config.ThingPlug.appServer}</nu>`
+ ' <nct>2</nct>'
+ '</m2m:sub>';
console.log(options);
request.post(options, function (error, response, body) {
console.log('body', body);
// console.log('response', response);
console.log('response.statusCode', response.statusCode); //--- 201 코드가 반환 된다.
if (response.statusCode == 404) {
callback({ code: 3, msg: '모뎀이 없음' }, null, body);
return;
}
if (!error && response.complete == true) {
xml2js.parseString(body, function (err, result) {
if (err) {
logger.error('interfaces/thing_plug.js :: err : ' + JSON.stringify(err));
callback({ code: 2, msg: 'error' }, null, body);
return;
}
// console.log(result);
// console.log(JSON.stringify(result.ThingPlug.user));
callback({ code: 0, msg: 'normal'} , result, body);
});
} else {
logger.error('interfaces/thing_plug.js :: error : ' + JSON.stringify(error));
logger.error('interfaces/thing_plug.js :: response: ' + JSON.stringify(response));
callback({ code: 1, msg: 'error' }, null, null);
}
});
}
//--- response.sub.nu[0]
subRetrieve(site, device, ltid, callback) {
let info = this._getThingPlugInfo(site, device);
let options = this.getOptions(site, device, '/' + info.appEUI +
'/' + config.ThingPlug.appVersion +
'/remoteCSE-' + ltid + '/container-LoRa/subscription-' + config.ThingPlug.conSubName);
request.get(options, function (error, response, body) {
if (!error && response.statusCode == 200) {
let resp = JSON.parse(body);
callback({ code: 0, msg: 'normal'} , resp, body);
} else {
logger.error('interfaces/thing_plug.js :: error : ' + JSON.stringify(error));
logger.error('interfaces/thing_plug.js :: response: ' + JSON.stringify(response));
callback({ code: 1, msg: 'error' }, null, null);
}
});
}
//--- To-Do : 테스트를 하지 않음
subUpdate(site, device, ltid, callback) {
logger.info('interfaces/thing_plug.js :: Start subUpdate');
let info = this._getThingPlugInfo(site, device);
let options = this.getOptions(site, device, '/' + info.appEUI +
'/' + config.ThingPlug.appVersion +
'/remoteCSE-' + ltid + '/container-LoRa/subscription-' + config.ThingPlug.conSubName, false);
options.headers['X-M2M-NM'] = config.ThingPlug.conSubName;
options.headers['Content-Type'] = 'application/vnd.onem2m-res+xml;ty=23';
let protocol = (config.ThingPlug.appServer.startsWith('http://')) ? 'HTTP':'HTTP';
options.body = '<?xml version="1.0" encoding="UTF-8"?>'
+ '<m2m:sub'
+ ' xmlns:m2m="http://www.onem2m.org/xml/protocols"'
+ ' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">'
+ ' <enc>'
+ ' <rss>1</rss>'
+ ' </enc>'
+ ` <nu>${protocol}|${config.ThingPlug.appServer}</nu>`
+ ' <nct>2</nct>'
+ '</m2m:sub>';
console.log(options);
request.put(options, function (error, response, body) {
console.log('body', body);
console.log('response.statusCode', response.statusCode);
if (!error && response.complete == true) {
xml2js.parseString(body, function (err, result) {
if (err) {
logger.error('interfaces/thing_plug.js :: err : ' + JSON.stringify(err));
callback({ code: 2, msg: 'error' }, null, body);
return;
}
callback({ code: 0, msg: 'normal'} , result, body);
});
} else {
logger.error('interfaces/thing_plug.js :: error : ' + JSON.stringify(error));
logger.error('interfaces/thing_plug.js :: response: ' + JSON.stringify(response));
callback({ code: 1, msg: 'error' }, null, null);
}
});
}
subDelete(site, device, ltid, callback) {
logger.info('interfaces/thing_plug.js :: Start subDelete');
let info = this._getThingPlugInfo(site, device);
let options = this.getOptions(site, device, '/' + info.appEUI +
'/' + config.ThingPlug.appVersion +
'/remoteCSE-' + ltid + '/container-LoRa/subscription-' + config.ThingPlug.conSubName, false);
request.del(options, function (error, response, body) {
if (!error && response.statusCode == 200) {
xml2js.parseString(body, function (err, result) {
if (err) {
logger.error('err : ' + JSON.stringify(err));
callback({ code: 2, msg: 'error' }, null, body);
return;
}
callback({ code: 0, msg: 'normal'} , result, body);
});
} else {
logger.error('interfaces/thing_plug.js :: error : ' + JSON.stringify(error));
logger.error('interfaces/thing_plug.js :: response: ' + JSON.stringify(response));
callback({ code: 1, msg: 'error' }, null, null);
}
});
}
}
module.exports = ThingPlug;