Spaces:
Sleeping
Sleeping
| /** | |
| * 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; | |