'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 // // // 12 // MC00000000000001489571 // 00000592702c1ffffe4f1640_extDevMgmt // CB00000000000000000656 // 2019-04-05T11:14:49+09:00 // 2019-04-05T11:14:49+09:00 // extDevMgmt // false // ND00000000000000372613 // // 8 // EI00000000000364925833 // EI00000000000364925833 // MC00000000000001489571 // 2019-04-09T18:43:00+09:00 // 2019-04-09T18:43:00+09:00 // 2019-04-10T18:43:00+09:00 // 2 // ND00000000000000372613 // 01020304 // // 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 = '' + '' + ' true' + ' ' + this.stringToHex(data) + '' //--- Hex로 표시할 것 + ''; // 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 = '' + '' + ' ' + ' 1' + ' ' + ` ${protocol}|${config.ThingPlug.appServer}` + ' 2' + ''; 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 = '' + '' + ' ' + ' 1' + ' ' + ` ${protocol}|${config.ThingPlug.appServer}` + ' 2' + ''; 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;