diff --git a/101-SmartThings.html b/101-SmartThings.html index ec57206f..ec2bb817 100644 --- a/101-SmartThings.html +++ b/101-SmartThings.html @@ -374,7 +374,7 @@

Properties

-
+
Log to editor debug panel
@@ -868,14 +868,6 @@

Properties

} } - var validateAutomationNext = function (id) { - //Automation 뒤에는 on device(event) 만 올 수 있다 (input 1, 해당 input port 에 연결된 wires 도 하나) - var backLinks = RED.nodes.filterLinks({"source": {"id": id}}); - if (backLinks.length != 1) return false; - if (backLinks[0].target["type"] !== "event-device") return false; - return true; - } - const SmartThingsProfile = new _SmartThingsProfile(); SmartThingsProfile.loadMydeviceInfo() .then(SmartThingsProfile.setMyDeviceInfos) @@ -895,7 +887,6 @@

Properties

const ST_AUTOMATION = 'automation'; const ST_DEVICE_NODES=[ST_EVENT_DEVICE,ST_STATUS_DEVICE,ST_COMMAND_DEVICE] const ST_NODES=[ST_AUTOMATION,ST_DEVICE_PROFILE,ST_MY_DEVICE,ST_EVENT_DEVICE,ST_STATUS_DEVICE,ST_COMMAND_DEVICE]; - var stDevice = {}; function STRefCheck() { @@ -1119,9 +1110,7 @@

Properties

return this.name|| this.alias || "My Device"; }, onpaletteadd: function(){ - - }, oneditsave: function () { if(document.querySelectorAll('#md_list .md_list__device.md_list__item--selected').length>0){ @@ -1324,11 +1313,12 @@

Properties

required: true }, urlDate: {value: ""}, - eqtype: { - value: "", validate: function (v) { - return validateAutomationNext(this.id); - } - }, + eqtype: { + value: "", validate: function (v) { + const after = RED.nodes.filterLinks({"source": {"id": this.id}}); + return after.every(link=>link.target && link.target.type===ST_EVENT_DEVICE); + } + }, loggingEditor:{value:false}, loggingConsole:{value:false} }, @@ -1837,8 +1827,8 @@

Properties

fieldConfig.types.push({ value:"enum", label:"enum", - options:valueProp.enum || valueProp.items.enum, - icon:"red/images/typedInput/AZ.png" + // icon:"red/images/typedInput/AZ.png", + options:valueProp.enum || valueProp.items.enum }); fieldConfig.default = 'enum'; }else if (valueProp.type == 'string' ) { @@ -1906,7 +1896,7 @@

Properties

fieldConfig.types.push({ value:"color", label:"color", - icon:"red/images/typedInput/target.png", + // icon:"red/images/typedInput/target.png", hasValue:false }); fieldConfig.default = 'color'; diff --git a/101-SmartThings.js b/101-SmartThings.js index 5c6dff25..6ed5b5c6 100644 --- a/101-SmartThings.js +++ b/101-SmartThings.js @@ -14,800 +14,871 @@ limitations under the License. **/ module.exports = function (RED) { - "use strict"; - - const ST_EVENT_DEVICE = "event-device"; - const ST_STATUS_DEVICE = "status-device"; - const ST_COMMAND_DEVICE = "command-device"; - const ST_DEVICE_PROFILE = 'device-profile'; - const ST_MY_DEVICE = 'installed-device'; - const ST_AUTOMATION = 'automation'; - const ST_NODES=[ST_EVENT_DEVICE,ST_STATUS_DEVICE,ST_COMMAND_DEVICE,ST_DEVICE_PROFILE,ST_MY_DEVICE,ST_AUTOMATION]; - - //process.env.NODE_TLS_REJECT_UNAUTHORIZED = 0; - - var os = require('os'); - var bodyParser = require("body-parser"); - var cors = require('cors'); - var jsonParser = bodyParser.json(); - // var httpSignature = require('http-signature'); - var reqApi = require('./lib/oneapigateway'); - var SmartThingsAPI = require('./lib/SmartThingsAPI'); - var SmartThingsProfile = require('./lib/SmartThingsProfile'); - var corsHandler = cors({origin: "*", methods: "POST"}); - - RED.httpNode.options("*", corsHandler); - var nextHandler = function (req, res, next) { - next(); - } - - RED.httpNode.get('/_smartthings/capabilities',RED.auth.needsPermission("settings.read"),(req,res)=>{ - res.json(SmartThingsProfile.getCapabilities()||{}); - }) - RED.httpNode.get('/_smartthings/mydevices',RED.auth.needsPermission("credentials.read"),(req,res)=>{ - SmartThingsProfile.getMyDevices().then(mydevices=>{ - res.json(mydevices||{}); - }) - }) - - var operators = { - //parameter a : condition value set by user - //parameter b : value get from device - eq: function (a, b) { - if(typeof b ==='object'){ - if(typeof a != 'object'){ - try{ a=JSON.parse(a); } - catch(e){ return false; } - } - return Object.keys(a).every(key=>{ - return b.hasOwnProperty(key) && this.eq(a[key],b[key]); - }) - }else{ - return a==b; - } - }, - neq: function (a, b) { - return !this.eq(a,b); - }, - lt: function (a, b) { - return a < b; - }, - lte: function (a, b) { - return a <= b; - }, - gt: function (a, b) { - return a > b; - }, - gte: function (a, b) { - return a >= b; - }, - in: function(v,arr){ - return Array.isArray(arr) && arr.some(dv=>this.eq(v,dv)) - }, - nin: function(v,arr){ - return Array.isArray(arr) && !(this.in(v,arr)) - }, - o_eq: function(v,obj){ - if(typeof v != 'object'){ - try{ - v=JSON.parse(v) - }catch(e){ - return false; - } - } - if(obj==undefined || obj==null || typeof obj != 'object'){ - return false; - } - return Object.keys(v).every(k=>{ - return obj && obj.hasOwnProperty(k) && v[k] == obj[k] - }) - }, - o_neq: function(v,obj){ - if(typeof v != 'object'){ - try{ - v=JSON.parse(v) - }catch(e){ - return false - } - } - if(typeof obj != 'object'){ - return false - } - - return Object.keys(v).every(k=>{ - return obj && obj.hasOwnProperty(k) && v[k] != obj[k] - }) - }, - - }; - - var usedDeviceNodes = []; - var eventDeviceNodes = []; - var actionDeviceNodes = []; - - var OneApi = { - executeDeleteSubscription: function (keyParam, token) { - return new Promise(function (resolve, reject) { - reqApi.callOneApi("subscriptions", "executeDeleteSubscription", keyParam, undefined, undefined, token, function (body) { - try { - resolve(body); - } catch (e) { - reject(body); - } - }); - },true); - }, - - executeCreateSubscription: function (keyParam, bodyParam, token) { - return new Promise(function (resolve, reject) { - reqApi.callOneApi("subscriptions", "executeCreateSubscription", keyParam, undefined, bodyParam, token, function (body) { - try { - resolve(body); - } catch (e) { - reject(body); - } - }); - },true); - }, - - getDeviceStates: function (keyParam, token, isLogging) { - return new Promise(function (resolve, reject) { - reqApi.callOneApi("device", "getDeviceStates", keyParam, undefined, undefined, token, function (data) { - try { - resolve(data); - } catch (e) { - reject(data); - } - }, isLogging); - }); - - }, - - executeDeviceCommands: function (keyParam, bodyParam, token, isLogging) { - return new Promise(function (resolve, reject) { - reqApi.callOneApi("device", "executeDeviceCommands", keyParam, undefined, bodyParam, token,function (data) { - if (typeof data === 'object') { - if (data.hasOwnProperty("errMsg")) { - reject(data); - } else { - resolve(data); - } - } else { - resolve(data); - } - }, isLogging); - }); - } - }; - - var RES = { - ok: function (res, obj) { - // console.log(`RES ok : ${JSON.stringify(obj)}`) - res.json(obj) - }, - error: function (res, status, msg) { - // console.log(`RES error[${status}] : ${msg}`) - res.status(status).send(msg) - } - } - - function Automation(n) { - RED.nodes.createNode(this, n) - Object.assign(this,n) - stCompatibleCheck(this) - - var NODE = this; - - var Eventattribute; - var allnodes = []; - RED.nodes.eachNode(function (nn) { - var node = Object.assign({}, nn); - if (/^(event|status|command)-device$/g.test(node.type)){ - stCompatibleCheck(node) - } - allnodes[nn.id] = node - }); - - // 사용하는 config node id 저장 - usedDeviceNodes = [] - eventDeviceNodes = [] - actionDeviceNodes = [] - - var queue = []; - queue.push(this.id); - - var visited = new Set([this.id]) - var sectionConfig={} - var eventDevices=[] - - while (queue.length > 0) { - var currentId = queue.shift(); - var node = allnodes[currentId]; - if(node.hasOwnProperty('capabilityId')){ - node.capabilityId = node.capabilityId.split('_v')[0]||'' - } - if (/^(event|status|command)-device$/g.test(node.type) && allnodes[node.deviceNodeId] && allnodes[node.deviceNodeId].type === ST_DEVICE_PROFILE) { - if(!sectionConfig.hasOwnProperty(node.deviceNodeId)){ - var deviceNode = allnodes[node.deviceNodeId] - deviceNode.capabilityId = deviceNode.capabilityId.split('_v')[0] - sectionConfig[deviceNode.id]={ - name: deviceNode.name, - settings: [ - { - id: deviceNode.id, - name: deviceNode.name||deviceNode.alias, - description: (deviceNode.name||deviceNode.alias) + ":" + deviceNode.capabilityId, - type: 'DEVICE', - required: true, - multiple: false, - capabilities: [deviceNode.capabilityId], - permissions: ['r'] - } - ] - } - } - if(node.type == ST_COMMAND_DEVICE && sectionConfig[node.deviceNodeId].settings[0].permissions.includes('x') == false) { - sectionConfig[node.deviceNodeId].settings[0].permissions.push('x') - } - if(node.type == ST_EVENT_DEVICE){ - eventDevices.push({sectionId:node.deviceNodeId,capabilityId:node.capabilityId,attributeId:node.attributeId}); - } - } - - node.wires.flat() - .filter(nodeId=>!visited.has(nodeId)) - .filter((nodeId,idx,arr)=>arr.indexOf(nodeId)==idx) - .forEach(nodeId=>{ - visited.add(nodeId); - queue.push(nodeId); - }) - } - - if (!n.url) { - this.warn(RED._("SmartThings.error.missing-path")); - return; - } - - this.url = n.url; - if (this.url[0] !== '/') { - this.url = '/' + this.url; - } - this.errorHandler = function (err, req, res, next) { - NODE.error(err); - RES.error(res, 500, "Internal Server Errror"); - }; - - this.lifecycleHandler = function (req, res) { - if(req.body.lifecycle){ - NODE.loggingEditor&&debugLog(`[${req.body.lifecycle}] : ${JSON.stringify(req.body)}`); - NODE.loggingConsole&&console.log(`SmartThings Automation[${req.body.lifecycle}] : ${JSON.stringify(req.body)}`) - } - - switch(req.body.lifecycle){ - case "PING": - var reqBody = req.body; - if (!reqBody.pingData) { - var msg = "Required parameter doesn't exist"; - NODE.loggingEditor&&debugLog("[error] " + msg); - RES.error(res, 400, msg); - return; - }else{ - RES.ok(res, {statusCode: 200, pingData: {challenge: reqBody.pingData.challenge}}); - } - break; - - case "CONFIRMATION": - var data = req.body.confirmationData; - if( !!data === false || data.hasOwnProperty("confirmationUrl") === false){ - msg = "cannot find 'confirmationUrl'"; - NODE.loggingEditor&&debugLog("[error] " + msg); - RES.error(res,400, {msg:msg}); - } - - SmartThingsAPI.confirmUrl(data.confirmationUrl) - .then(response=>{ - if([200,202].includes(response.statusCode)){ - RES.ok(res,{targetUrl:data.confirmationUrl}); - }else{ - RES.error(res,response.statusCode,'confirm URL faile : ',response.statusMessage); - } - }) - .catch(error=>{ - RES.error(res,500,error) - }) - break; - case "CONFIGURATION": - var reqBody = req.body; - if (!reqBody.configurationData) { - var msg = "Required parameter doesn't exist"; - NODE.loggingEditor&&debugLog("[error] " + msg); - RES.error(res, 400, msg); - return; - } - - var responseConfigData = {}; - if (reqBody.configurationData.phase == "INITIALIZE") { - responseConfigData.initialize = { - id: "app_" +NODE.id, - name: NODE.name||NODE.alias, - description: NODE.name||NODE.alias, - firstPageId: "1", - permissions: [] - } - } else if (reqBody.configurationData.phase == "PAGE") { - if (reqBody.configurationData.pageId !== "1") { - RES.error(res, 400, `Unsupported page id: ${reqBody.configurationData.pageId}`); - return; - } - const sections = Object.values(sectionConfig); - responseConfigData.page = { - pageId: '1', - name: NODE.name||NODE.alias, - nextPageId: null, - previousPageId: null, - complete: true, - sections: sections - }; - } else { - RES.error(res, 400, `Unsupported phase: ${reqBody.configurationData.phase}`); - return; - } - RES.ok(res, {statusCode: 200, configurationData: responseConfigData}); - break; - case "INSTALL": - var reqBody = req.body; - var installedAppId = reqBody.installData.installedApp.installedAppId; - var authToken = reqBody.installData.authToken; - - NODE.loggingConsole&&console.log('subscription devices : '+eventDevices); - - eventDevices.forEach((eventDevice)=>{ - eventDevice.capabilityId = eventDevice.capabilityId.split('_v')[0] - var installedConfig = reqBody.installData.installedApp.config[eventDevice.sectionId][0] - var subscriptionBody = { - sourceType: 'DEVICE', - device: { - deviceId: installedConfig.deviceConfig.deviceId, - componentId: installedConfig.deviceConfig.componentId, - capability: eventDevice.capabilityId, - attribute: eventDevice.attributeId, - stateChangeOnly: true, - subscriptionName: (eventDevice.name+'_'+eventDevice.capabilityId+'_'+eventDevice.attributeId+'_SUBS').slice(0,32), - value: "*" - } - } - NODE.loggingConsole&&console.log('[ST_AUTOMATION] INSTALL_createSubscription:',JSON.stringify(subscriptionBody)) - - const keyParam = {installedAppId:installedAppId}; - OneApi.executeCreateSubscription(keyParam, subscriptionBody, authToken) - .then(function (data) { - NODE.loggingEditor&&debugLog("Create Subscription : " + JSON.stringify(data)) - NODE.loggingConsole&&console.log("Create Subscription : " + JSON.stringify(data)) - }).catch(function (err) { - NODE.loggingEditor&&debugLog("[error] " + err.errCd + ", " + err.errMsg) - NODE.loggingConsole&&console.log("[error] " + err.errCd + ", " + err.errMsg) - }) - }) - RES.ok(res, {statusCode: 200, installData: {}}) - break; - - case "UPDATE": - var reqBody = req.body; - var installedAppId = reqBody.updateData.installedApp.installedAppId; - var authToken = reqBody.updateData.authToken; - - const keyParam = {installedAppId:installedAppId}; - - OneApi.executeDeleteSubscription(keyParam, authToken).then(function (data) { - eventDevices.forEach(function(eventDevice){ - eventDevice.capabilityId = eventDevice.capabilityId.split('_v')[0] - var installedConfig = reqBody.updateData.installedApp.config[eventDevice.sectionId][0] - var subRequest = { - sourceType: 'DEVICE', - device: { - deviceId: installedConfig.deviceConfig.deviceId, - componentId: installedConfig.deviceConfig.componentId, - capability: eventDevice.capabilityId, - attribute: eventDevice.attributeId, - stateChangeOnly: true, - subscriptionName: (eventDevice.name+'_'+eventDevice.capabilityId+'_'+eventDevice.attributeId+'_SUBS').slice(0,32), - value: "*" - } - } - NODE.loggingConsole&&console.log('[ST_AUTOMATION] UPDATE_createSubscription:',JSON.stringify(subscriptionBody)) - - const keyParam = {installedAppId:installedAppId}; - OneApi.executeCreateSubscription(keyParam, subRequest, authToken).then(function (data) { - NODE.loggingEditor&&debugLog("Update Subscription : " + JSON.stringify(data)) - NODE.loggingConsole&&console.log("Update Subscription : " + JSON.stringify(data)) - }).catch(function (err) { - NODE.loggingEditor&&debugLog("Update Subscription Fail: " + JSON.stringify(data)+" / ("+err.errCd +")"+err.errMsg) - NODE.loggingConsole&&console.log("Install errorCode : " + err.errCd + " message : " + err.errMsg); - }) - }) - }).catch(function (err) { - NODE.loggingEditor&&debugLog("Delete Subscription : " + installedAppId +" / ("+err.errCd +")"+err.errMsg) - NODE.loggingConsole&&console.dir(err) - }); - RES.ok(res, {statusCode: 200, updateData: {}}); - break; - case "UNINSTALL": - RES.ok(res, {statusCode: 200, uninstallData: {}}); - break; - case "EVENT": - RES.ok(res, {statusCode: 200, eventData: {}}); - NODE.context().flow.set('evt', req.body); - NODE.send({payload: req.body}); - break; - default: - var msg = "No matched lifecycle : "+req.body.lifecycle; - NODE.loggingEditor&&debugLog("[error] " + msg); - RES.error(res, 400, msg); - break; - } - }; - - function x509headerParse(headerStr){ - var result = {}; - - ['keyId','signature','headers','algorithm'].forEach(k=>{ - if(headerStr.indexOf(k+'=')>-1){ - var v = headerStr.substr(headerStr.indexOf(k+'=')+k.length+2); - v=v.substr(0,v.indexOf('"')); - result[k]=v; - } - }) - return result; - } - - var httpMiddleware = function (req, res, next) { - // console.log('===========Automation Request header / body ==========') - // console.dir(req.headers,{depth:null}) - // console.dir(req.body,{depth:null}) - - var authHeader = x509headerParse(req.headers.authorization); - if(['PING','CONFIRMATION'].includes(req.body.lifecycle)){ - next() - }else{ - SmartThingsAPI.keyValidate(authHeader.keyId) - .then(response=>{ - if([200,202].includes(response.statusCode)){ - next() - }else{ - RES.error(res, response.statusCode, response.statusMessage); - } - }) - .catch(e=>{ - var msg = "Invalid keyId, internal server error"; - NODE.loggingEditor&&debugLog("[error] " + msg + e + e.msg); - RES.error(res, 500, msg); - }) - } - }; - function debugLog(log) { - try { - RED.comms.publish("debug", {id: NODE.id, z: NODE.z, name: NODE.name||NODE.alias, topic: "Automation", msg: log}); - } catch (err) { - console.error(err); - } - } - RED.httpNode.post(this.url, nextHandler, httpMiddleware, corsHandler, nextHandler, jsonParser, nextHandler, this.lifecycleHandler, this.errorHandler); - this.on("close", function () { - var node = this; - RED.httpNode._router.stack.forEach(function (route, i, routes) { - if (route.route && route.route.path === node.url) { - routes.splice(i, 1); - } - }); - }); - } - RED.nodes.registerType(ST_AUTOMATION, Automation); - - function DeviceConfigNode(n) { - let pat = null; - if(RED.nodes.getCredentials(n.id)&&RED.nodes.getCredentials(n.id).stAccessToken){ - pat = RED.nodes.getCredentials(n.id).stAccessToken; - } - SmartThingsProfile.addPersonalToken(n.id,pat); - - RED.nodes.createNode(this, n); - Object.assign(this,n) - stCompatibleCheck(this) - - this.on('close', function(removed, done) { - const pat = (RED.nodes.getCredentials(this.id))?RED.nodes.getCredentials(this.id).stAccessToken:null; - SmartThingsProfile.addPersonalToken(this.id,pat); - done(); - }); - } - RED.nodes.registerType(ST_DEVICE_PROFILE, DeviceConfigNode,{credentials:{ - stAccessToken:{type:'text'} - }}); - - function installedDeviceConfigNode(n) { - let pat = null; - if(RED.nodes.getCredentials(n.id)&&RED.nodes.getCredentials(n.id).stAccessToken){ - pat = RED.nodes.getCredentials(n.id).stAccessToken - } - SmartThingsProfile.addPersonalToken(n.id,pat); - - RED.nodes.createNode(this, n); - Object.assign(this,n); - stCompatibleCheck(this); - - this.on('close', function(removed, done) { - const pat = (RED.nodes.getCredentials(this.id))?RED.nodes.getCredentials(this.id).stAccessToken:null; - SmartThingsProfile.addPersonalToken(this.id,pat); - done(); - }); - } - - RED.nodes.registerType(ST_MY_DEVICE, installedDeviceConfigNode,{credentials:{ - stAccessToken:{type:'text', required:true} - }}); - - function ThingNode(n) { - RED.nodes.createNode(this, n) - this.rules = n.rules || [] - Object.assign(this,n) - stCompatibleCheck(this) - - if(n.deviceNodeId===''){ - this.warn('Deivce Node error: deviceNodeId is undefined') - return - } - - var NODE = this; - - function sendDebug(message) { - var msg = {}; - RED.comms.publish("debug", {topic:'debug',id:NODE.id,msg: message}); - } - - this.on('input', function (msg) { - var onward = []; - try { - const automationEvent = NODE.context().flow.get('evt'); - - var deviceConfig = "" - var authToken = ""; - var param = {deviceId:""}; - - if (RED.nodes.getNode(NODE.deviceNodeId).type == ST_MY_DEVICE){ - authToken = RED.nodes.getCredentials(NODE.deviceNodeId).stAccessToken; - param.deviceId = NODE.deviceId || RED.nodes.getNode(NODE.deviceNodeId).device.deviceId - }else{ - deviceConfig = automationEvent.eventData.installedApp.config[NODE.deviceNodeId][0].deviceConfig;//현재는 section 당 한개의 기기만으로 제한되어있음.[0]처리 - authToken = automationEvent.eventData.authToken; - param.deviceId = deviceConfig.deviceId; - } - - if (NODE.type == ST_EVENT_DEVICE) { - var resultMsg=[]; - NODE.loggingEditor&&NODE.warn("[SmartThings] Event:" + NODE.name||NODE.alias||NODE.type); - - automationEvent.eventData.events.forEach(function(event){ - var deviceEvent = event.deviceEvent; - if(deviceEvent && deviceEvent.deviceId == deviceConfig.deviceId && deviceEvent.componentId == deviceConfig.componentId ){ - resultMsg=[]; - NODE.rules.forEach(function(rule){ - var opCheck = false; - rule.capaId=rule.capaId.split('_v')[0]; - if(deviceEvent.capability == rule.capaId && deviceEvent.attribute == rule.attrId){ - let ruleValue = rule.value; - if(rule.argType==='jsonata'){ - ruleValue = RED.util.evaluateJSONataExpression(ruleValue,msg); - }else{ - ruleValue = RED.util.evaluateNodeProperty(ruleValue,rule.argType,NODE,msg); - } - opCheck=operators[rule.operator](ruleValue, deviceEvent.value); - } - - if (opCheck) { - RED.util.setMessageProperty(msg, 'payload', deviceEvent); - resultMsg.push(msg); - } else { - resultMsg.push(null); - } - }) - } - }) - if (NODE.rules.length == 0) { - RED.util.setMessageProperty(msg, 'payload', automationEvent.eventData.events); - resultMsg.push(msg); - } - NODE.send(resultMsg); - } else if (NODE.type == ST_STATUS_DEVICE) { - OneApi.getDeviceStates(param, authToken, NODE.logging).then(function (data) { - NODE.loggingEditor&&NODE.warn("[SmartThings] Status :" + NODE.name||NODE.alias||NODE.type); - var deviceStatus = data; - var opCheck = false; - NODE.capabilityId = NODE.capabilityId.split('_v')[0] - NODE.rules.forEach((rule,idx)=>{ - var attributeValue = deviceStatus.components[deviceConfig.componentId||'main'][NODE.capabilityId][NODE.attributeId].value; - - let ruleValue = rule.value; - - if(rule.valueType=='object'){ - ruleValue={}; - for(let k in rule.value){ - const data = rule.value[k]; - ruleValue[k] = RED.util.evaluateNodeProperty(data.value,rule.type,NODE,msg); + const ST_EVENT_DEVICE = "event-device"; + const ST_STATUS_DEVICE = "status-device"; + const ST_COMMAND_DEVICE = "command-device"; + const ST_DEVICE_PROFILE = 'device-profile'; + const ST_MY_DEVICE = 'installed-device'; + const ST_AUTOMATION = 'automation'; + const ST_NODES = [ST_EVENT_DEVICE, ST_STATUS_DEVICE, ST_COMMAND_DEVICE, ST_DEVICE_PROFILE, ST_MY_DEVICE, ST_AUTOMATION]; + + //process.env.NODE_TLS_REJECT_UNAUTHORIZED = 0; + + const bodyParser = require("body-parser"); + const cors = require('cors'); + const jsonParser = bodyParser.json(); + const reqApi = require('./lib/oneapigateway'); + const SmartThingsAPI = require('./lib/SmartThingsAPI'); + const SmartThingsProfile = require('./lib/SmartThingsProfile'); + const corsHandler = cors({ + origin: "*", + methods: "POST" + }); + + RED.httpNode.get('/_smartthings/capabilities', RED.auth.needsPermission("settings.read"), (req, res) => res.json(SmartThingsProfile.getCapabilities() || {})) + RED.httpNode.get('/_smartthings/mydevices', RED.auth.needsPermission("credentials.read"), (req, res) => { + SmartThingsProfile.getMyDevices() + .then(mydevices => res.json(mydevices || {})) + }) + + const operators = { + //parameter a : condition value set by user + //parameter b : value get from device + eq: function (a, b) { + if (typeof b === 'object') { + if (typeof a != 'object') { + try { + a = JSON.parse(a); + } catch (e) { + return false; + } + } + return Object.keys(a) + .every(key => { + return b.hasOwnProperty(key) && this.eq(a[key], b[key]); + }) + } else { + return a == b; + } + }, + neq: function (a, b) { + return !this.eq(a, b); + }, + lt: function (a, b) { + return a < b; + }, + lte: function (a, b) { + return a <= b; + }, + gt: function (a, b) { + return a > b; + }, + gte: function (a, b) { + return a >= b; + }, + in: function (v, arr) { + return Array.isArray(arr) && arr.some(dv => this.eq(v, dv)) + }, + nin: function (v, arr) { + return Array.isArray(arr) && !(this.in(v, arr)) + }, + o_eq: function (v, obj) { + if (typeof v != 'object') { + try { + v = JSON.parse(v) + } catch (e) { + return false; + } + } + if (obj == undefined || obj == null || typeof obj != 'object') { + return false; + } + return Object.keys(v) + .every(k => { + return obj && obj.hasOwnProperty(k) && v[k] == obj[k] + }) + }, + o_neq: function (v, obj) { + if (typeof v != 'object') { + try { + v = JSON.parse(v) + } catch (e) { + return false + } + } + if (typeof obj != 'object') { + return false + } + + return Object.keys(v) + .every(k => { + return obj && obj.hasOwnProperty(k) && v[k] != obj[k] + }) + }, + + }; + + var usedDeviceNodes = []; + var eventDeviceNodes = []; + var actionDeviceNodes = []; + + var OneApi = { + executeDeleteSubscription: function (keyParam, token) { + return new Promise(function (resolve, reject) { + reqApi.callOneApi("subscriptions", "executeDeleteSubscription", keyParam, undefined, undefined, token, function (body) { + try { + resolve(body); + } catch (e) { + reject(body); + } + }); + }, true); + }, + + executeCreateSubscription: function (keyParam, bodyParam, token) { + return new Promise(function (resolve, reject) { + reqApi.callOneApi("subscriptions", "executeCreateSubscription", keyParam, undefined, bodyParam, token, function (body) { + try { + resolve(body); + } catch (e) { + reject(body); + } + }); + }, true); + }, + + getDeviceStates: function (keyParam, token, isLogging) { + return new Promise(function (resolve, reject) { + reqApi.callOneApi("device", "getDeviceStates", keyParam, undefined, undefined, token, function (data) { + try { + resolve(data); + } catch (e) { + reject(data); + } + }, isLogging); + }); + + }, + + executeDeviceCommands: function (keyParam, bodyParam, token, isLogging) { + return new Promise(function (resolve, reject) { + reqApi.callOneApi("device", "executeDeviceCommands", keyParam, undefined, bodyParam, token, function (data) { + if (typeof data === 'object') { + if (data.hasOwnProperty("errMsg")) { + reject(data); + } else { + resolve(data); + } + } else { + resolve(data); + } + }, isLogging); + }); + } + }; + + function Automation (n) { + RED.nodes.createNode(this, n) + Object.assign(this, n) + stCompatibleCheck(this) + + var NODE = this; + + var allnodes = []; + RED.nodes.eachNode(function (nn) { + var node = Object.assign({}, nn); + if (/^(event|status|command)-device$/g.test(node.type)) { + stCompatibleCheck(node) + } + allnodes[nn.id] = node + }); + + // 사용하는 config node id 저장 + usedDeviceNodes = [] + eventDeviceNodes = [] + actionDeviceNodes = [] + + var queue = []; + queue.push(this.id); + + var visited = new Set([this.id]) + var sectionConfig = {} + var subscriptionDevices = [] + + while (queue.length > 0) { + var currentId = queue.shift(); + var node = allnodes[currentId]; + if (node.hasOwnProperty('capabilityId')) { + node.capabilityId = node.capabilityId.split('_v')[0] || '' + } + if (/^(event|status|command)-device$/g.test(node.type) && allnodes[node.deviceNodeId] && allnodes[node.deviceNodeId].type === ST_DEVICE_PROFILE) { + if (!sectionConfig.hasOwnProperty(node.deviceNodeId)) { + var deviceNode = allnodes[node.deviceNodeId] + deviceNode.capabilityId = deviceNode.capabilityId.split('_v')[0] + sectionConfig[deviceNode.id] = { + name: deviceNode.name, + settings: [{ + id: deviceNode.id, + name: deviceNode.name || deviceNode.alias, + description: (deviceNode.name || deviceNode.alias) + ":" + deviceNode.capabilityId, + type: 'DEVICE', + required: true, + multiple: false, + capabilities: [deviceNode.capabilityId], + permissions: ['r'] + }] + } + } + if (node.type == ST_COMMAND_DEVICE && sectionConfig[node.deviceNodeId].settings[0].permissions.includes('x') == false) { + sectionConfig[node.deviceNodeId].settings[0].permissions.push('x') + } + if (node.type == ST_EVENT_DEVICE) { + subscriptionDevices.push({ + name: node.id, + sectionId: node.deviceNodeId, + capabilityId: node.capabilityId, + attributeId: node.attributeId + }); + } + } + + node.wires.flat() + .filter(nodeId => !visited.has(nodeId)) + .filter((nodeId, idx, arr) => arr.indexOf(nodeId) == idx) + .forEach(nodeId => { + visited.add(nodeId); + queue.push(nodeId); + }) + } + + if (!n.url) { + this.warn(RED._("SmartThings.error.missing-path")); + return; + } + + this.url = n.url; + if (this.url[0] !== '/') { + this.url = '/' + this.url; + } + this.errorHandler = function (err, req, res, next) { + NODE.error(err); + res.status(500) + .send('internal server error'); + }; + + this.lifecycleHandler = function (req, res) { + if (req.body.lifecycle) { + NODE.loggingEditor && debugLog(`[${req.body.lifecycle}] : ${JSON.stringify(req.body)}`); + NODE.loggingConsole && console.log(`SmartThings Automation[${req.body.lifecycle}] : ${JSON.stringify(req.body)}`) + } + + switch (req.body.lifecycle) { + case "PING": + var reqBody = req.body; + if (!reqBody.pingData) { + var msg = "Required parameter doesn't exist"; + NODE.loggingEditor && debugLog("[error] " + msg); + res.status(400) + .send(msg); + return; + } else { + res.status(200) + .send({ + statusCode: 200, + pingData: {challenge: reqBody.pingData.challenge} + }) + } + break; + + case "CONFIRMATION": + var data = req.body.confirmationData; + if (!!data === false || data.hasOwnProperty("confirmationUrl") === false) { + msg = "cannot find 'confirmationUrl'"; + NODE.loggingEditor && debugLog("[error] " + msg); + res.status(400) + .send({msg: msg}); + } + + SmartThingsAPI.confirmUrl(data.confirmationUrl) + .then(response => { + if ([200, 202].includes(response.statusCode)) { + res.status(response.statusCode) + .send({targetUrl: data.confirmationUrl}); + } else { + res.status(response.statusCode) + .send('confirm URL faile : ', response.statusMessage); + } + }) + .catch(error => { + res.status(500) + .send(error); + }) + break; + case "CONFIGURATION": + var reqBody = req.body; + if (!reqBody.configurationData) { + var msg = "Required parameter doesn't exist"; + NODE.loggingEditor && debugLog("[error] " + msg); + res.send(400) + .send(msg); + return; + } + + var responseConfigData = {}; + if (reqBody.configurationData.phase == "INITIALIZE") { + responseConfigData.initialize = { + id: "app_" + NODE.id, + name: NODE.name || NODE.alias, + description: NODE.name || NODE.alias, + firstPageId: "1", + permissions: [] + } + } else if (reqBody.configurationData.phase == "PAGE") { + if (reqBody.configurationData.pageId !== "1") { + res.status(400) + .send(`Unsupported page id: ${reqBody.configurationData.pageId}`); + return; + } + const sections = Object.values(sectionConfig); + responseConfigData.page = { + pageId: '1', + name: NODE.name || NODE.alias, + nextPageId: null, + previousPageId: null, + complete: true, + sections: sections + }; + } else { + res.status(400) + .send(`Unsupported phase: ${reqBody.configurationData.phase}`); + return; + } + res.status(200) + .send({ + statusCode: 200, + configurationData: responseConfigData + }) + break; + case "INSTALL": + var reqBody = req.body; + var installedAppId = reqBody.installData.installedApp.installedAppId; + var authToken = reqBody.installData.authToken; + + NODE.loggingConsole && console.log('subscription devices : ' + subscriptionDevices); + + subscriptionDevices.forEach((subscription) => { + subscription.capabilityId = subscription.capabilityId.split('_v')[0] + var installedConfig = reqBody.installData.installedApp.config[subscription.sectionId][0] + var subscriptionBody = { + sourceType: 'DEVICE', + device: { + deviceId: installedConfig.deviceConfig.deviceId, + componentId: installedConfig.deviceConfig.componentId, + capability: subscription.capabilityId, + attribute: subscription.attributeId, + stateChangeOnly: true, + subscriptionName: subscription.name + '_SUBS', + value: "*" + } + } + NODE.loggingConsole && console.log('[ST_AUTOMATION] INSTALL_createSubscription:', JSON.stringify(subscriptionBody)) + + const keyParam = {installedAppId: installedAppId}; + OneApi.executeCreateSubscription(keyParam, subscriptionBody, authToken) + .then(function (data) { + NODE.loggingEditor && debugLog("Create Subscription : " + JSON.stringify(data)) + NODE.loggingConsole && console.log("Create Subscription : " + JSON.stringify(data)) + }) + .catch(function (err) { + NODE.loggingEditor && debugLog("[error] " + err.errCd + ", " + err.errMsg) + NODE.loggingConsole && console.log("[error] " + err.errCd + ", " + err.errMsg) + }) + }) + res.status(200) + .send({ + statusCode: 200, + installData: {} + }) + break; + + case "UPDATE": + var reqBody = req.body; + var installedAppId = reqBody.updateData.installedApp.installedAppId; + var authToken = reqBody.updateData.authToken; + + const keyParam = {installedAppId: installedAppId}; + + OneApi.executeDeleteSubscription(keyParam, authToken) + .then(function (data) { + subscriptionDevices.forEach(function (subscription) { + subscription.capabilityId = subscription.capabilityId.split('_v')[0] + var installedConfig = reqBody.updateData.installedApp.config[subscription.sectionId][0] + var subscriptionBody = { + sourceType: 'DEVICE', + device: { + deviceId: installedConfig.deviceConfig.deviceId, + componentId: installedConfig.deviceConfig.componentId, + capability: subscription.capabilityId, + attribute: subscription.attributeId, + stateChangeOnly: true, + subscriptionName: subscription.name + '_SUBS', + value: "*" + } + } + NODE.loggingConsole && console.log('[ST_AUTOMATION] UPDATE_createSubscription:', JSON.stringify(subscriptionBody)) + + const keyParam = {installedAppId: installedAppId}; + OneApi.executeCreateSubscription(keyParam, subscriptionBody, authToken) + .then(function (data) { + NODE.loggingEditor && debugLog("Update Subscription : " + JSON.stringify(data)) + NODE.loggingConsole && console.log("Update Subscription : " + JSON.stringify(data)) + }) + .catch(function (err) { + NODE.loggingEditor && debugLog("Update Subscription Fail: " + JSON.stringify(data) + " / (" + err.errCd + ")" + err.errMsg) + NODE.loggingConsole && console.log("Install errorCode : " + err.errCd + " message : " + err.errMsg); + }) + }) + }) + .catch(function (err) { + NODE.loggingEditor && debugLog("Delete Subscription : " + installedAppId + " / (" + err.errCd + ")" + err.errMsg) + NODE.loggingConsole && console.dir(err) + }); + res.status(200) + .send({ + statusCode: 200, + updateData: {} + }); + break; + case "UNINSTALL": + res.status(200) + .send({ + statusCode: 200, + uninstallData: {} + }); + break; + case "EVENT": + res.status(200) + .send({ + statusCode: 200, + eventData: {} + }); + NODE.context() + .flow + .set('evt', req.body); + NODE.send({payload: req.body}); + break; + default: + var msg = "No matched lifecycle : " + req.body.lifecycle; + NODE.loggingEditor && debugLog("[error] " + msg); + res.status(400) + .send(msg); + break; + } + }; + + function x509headerParse (headerStr) { + var result = {}; + + ['keyId', 'signature', 'headers', 'algorithm'].forEach(k => { + if (headerStr.indexOf(k + '=') > -1) { + var v = headerStr.substr(headerStr.indexOf(k + '=') + k.length + 2); + v = v.substr(0, v.indexOf('"')); + result[k] = v; + } + }) + return result; + } + + var httpMiddleware = function (req, res, next) { + // console.log('===========Automation Request header / body ==========') + // console.dir(req.headers,{depth:null}) + // console.dir(req.body,{depth:null}) + + var authHeader = x509headerParse(req.headers.authorization); + if (['PING', 'CONFIRMATION'].includes(req.body.lifecycle)) { + next() + } else { + SmartThingsAPI.keyValidate(authHeader.keyId) + .then(response => { + if ([200, 202].includes(response.statusCode)) { + next() + } else { + res.status(response.statusCode) + .send(response.statusMessage); + } + }) + .catch(e => { + var msg = "Invalid keyId, internal server error"; + NODE.loggingEditor && debugLog("[error] " + msg + e + e.msg); + res.status(500) + .send(msg); + }) + } + }; + + function debugLog (log) { + try { + RED.comms.publish("debug", { + id: NODE.id, + z: NODE.z, + name: NODE.name || NODE.alias, + topic: "Automation", + msg: log + }); + } catch (err) { + console.error(err); + } + } + + RED.httpNode.post(this.url, httpMiddleware, corsHandler, jsonParser, this.lifecycleHandler, this.errorHandler); + this.on("close", function () { + var node = this; + RED.httpNode._router.stack.forEach(function (route, i, routes) { + if (route.route && route.route.path === node.url) { + routes.splice(i, 1); + } + }); + }); + } + + RED.nodes.registerType(ST_AUTOMATION, Automation); + + function DeviceConfigNode (n) { + let pat = null; + if (RED.nodes.getCredentials(n.id) && RED.nodes.getCredentials(n.id).stAccessToken) { + pat = RED.nodes.getCredentials(n.id).stAccessToken; + } + SmartThingsProfile.addPersonalToken(n.id, pat); + + RED.nodes.createNode(this, n); + Object.assign(this, n) + stCompatibleCheck(this) + + this.on('close', function (removed, done) { + const pat = (RED.nodes.getCredentials(this.id)) ? RED.nodes.getCredentials(this.id).stAccessToken : null; + SmartThingsProfile.addPersonalToken(this.id, pat); + done(); + }); + } + + RED.nodes.registerType(ST_DEVICE_PROFILE, DeviceConfigNode, { + credentials: { + stAccessToken: {type: 'text'} + } + }); + + function installedDeviceConfigNode (n) { + let pat = null; + if (RED.nodes.getCredentials(n.id) && RED.nodes.getCredentials(n.id).stAccessToken) { + pat = RED.nodes.getCredentials(n.id).stAccessToken + } + SmartThingsProfile.addPersonalToken(n.id, pat); + + RED.nodes.createNode(this, n); + Object.assign(this, n); + stCompatibleCheck(this); + + this.on('close', function (removed, done) { + const pat = (RED.nodes.getCredentials(this.id)) ? RED.nodes.getCredentials(this.id).stAccessToken : null; + SmartThingsProfile.addPersonalToken(this.id, pat); + done(); + }); + } + + RED.nodes.registerType(ST_MY_DEVICE, installedDeviceConfigNode, { + credentials: { + stAccessToken: { + type: 'text', + required: true + } + } + }); + + function ThingNode (n) { + RED.nodes.createNode(this, n) + this.rules = n.rules || [] + Object.assign(this, n) + stCompatibleCheck(this) + + if (n.deviceNodeId === '') { + this.warn('Deivce Node error: deviceNodeId is undefined') + return + } + + var NODE = this; + + function sendDebug (message) { + var msg = {}; + RED.comms.publish("debug", { + topic: 'debug', + id: NODE.id, + msg: message + }); + } + + this.on('input', function (msg) { + var onward = []; + try { + const automationEvent = NODE.context() + .flow + .get('evt'); + + var deviceConfig = "" + var authToken = ""; + var param = {deviceId: ""}; + + if (RED.nodes.getNode(NODE.deviceNodeId).type == ST_MY_DEVICE) { + authToken = RED.nodes.getCredentials(NODE.deviceNodeId).stAccessToken; + param.deviceId = NODE.deviceId || RED.nodes.getNode(NODE.deviceNodeId).device.deviceId + } else { + deviceConfig = automationEvent.eventData.installedApp.config[NODE.deviceNodeId][0].deviceConfig;//현재는 section 당 한개의 기기만으로 제한되어있음.[0]처리 + authToken = automationEvent.eventData.authToken; + param.deviceId = deviceConfig.deviceId; + } + + if (NODE.type == ST_EVENT_DEVICE) { + var resultMsg = []; + NODE.loggingEditor && NODE.warn("[SmartThings] Event:" + NODE.name || NODE.alias || NODE.type); + + automationEvent.eventData.events.forEach(function (event) { + var deviceEvent = event.deviceEvent; + if (deviceEvent && deviceEvent.deviceId == deviceConfig.deviceId && deviceEvent.componentId == deviceConfig.componentId) { + resultMsg = []; + NODE.rules.forEach(function (rule) { + var opCheck = false; + rule.capaId = rule.capaId.split('_v')[0]; + if (deviceEvent.capability == rule.capaId && deviceEvent.attribute == rule.attrId) { + let ruleValue = rule.value; + if (rule.argType === 'jsonata') { + ruleValue = RED.util.evaluateJSONataExpression(ruleValue, msg); + } else { + ruleValue = RED.util.evaluateNodeProperty(ruleValue, rule.argType, NODE, msg); + } + opCheck = operators[rule.operator](ruleValue, deviceEvent.value); } - }else{ - if(rule.argType==='jsonata'){ - ruleValue = RED.util.evaluateJSONataExpression(ruleValue,msg); - }else{ - ruleValue = RED.util.evaluateNodeProperty(ruleValue,rule.argType,NODE,msg); + + if (opCheck) { + RED.util.setMessageProperty(msg, 'payload', deviceEvent); + resultMsg.push(msg); + } else { + resultMsg.push(null); } + }) + } + }) + if (NODE.rules.length == 0) { + RED.util.setMessageProperty(msg, 'payload', automationEvent.eventData.events); + resultMsg.push(msg); + } + NODE.send(resultMsg); + } else if (NODE.type == ST_STATUS_DEVICE) { + OneApi.getDeviceStates(param, authToken, NODE.logging) + .then(function (data) { + NODE.loggingEditor && NODE.warn("[SmartThings] Status :" + NODE.name || NODE.alias || NODE.type); + var deviceStatus = data; + var opCheck = false; + NODE.capabilityId = NODE.capabilityId.split('_v')[0] + NODE.rules.forEach((rule, idx) => { + var attributeValue = deviceStatus.components[deviceConfig.componentId || 'main'][NODE.capabilityId][NODE.attributeId].value; + + let ruleValue = rule.value; + + if (rule.valueType == 'object') { + ruleValue = {}; + for (let k in rule.value) { + const data = rule.value[k]; + ruleValue[k] = RED.util.evaluateNodeProperty(data.value, rule.type, NODE, msg); + } + } else { + if (rule.argType === 'jsonata') { + ruleValue = RED.util.evaluateJSONataExpression(ruleValue, msg); + } else { + ruleValue = RED.util.evaluateNodeProperty(ruleValue, rule.argType, NODE, msg); + } + } + + if (ruleValue == '' || ruleValue == undefined) { + ruleValue = "\'\'"; + } + if (rule.valueType == 'Iso8601Date') { + opCheck = operators[rule.operator](new Date(ruleValue), new Date(attributeValue)); + } else { + opCheck = operators[rule.operator](ruleValue, attributeValue); + } + if (opCheck) { + // sendDebug(NODE.attributeId+"=\""+attributeValue+"\", ("+idx+")port success") + RED.util.setMessageProperty(msg, 'payload', attributeValue); + onward.push(msg); + } else { + // sendDebug(NODE.attributeId+"=\""+attributeValue+"\", ("+idx+")port fail") + onward.push(null); + } + }) + + if (NODE.rules.length == 0) { + RED.util.setMessageProperty(msg, 'payload', data); + onward.push(msg); + } + NODE.send(onward); + + }) + .catch(function (err) { + NODE.loggingEditor && console.error(err); + NODE.loggingConsole && NODE.error("[error] " + err.errCd + ", " + err.errMsg); + }); + } else { + NODE.loggingEditor && NODE.warn("[SmartThings] Action:" + NODE.name || NODE.alias || NODE.type); + + var commandArr = []; + var componentId = (deviceConfig && deviceConfig.componentId) ? deviceConfig.componentId : 'main'; + for (var rule of NODE.rules) { + rule.capaId = rule.capaId.split('_v')[0]; + var cmd = { + component: componentId || "main", + capability: rule.capaId, + command: rule.attrId + }; + cmd.arguments = []; + + var argObj = {}; + + rule.args.forEach(arg => { + var argValue = arg.value; + + if (arg.argType == 'jsonata') { + argValue = RED.util.evaluateJSONataExpression(argValue, msg); + } else { + argValue = RED.util.evaluateNodeProperty(argValue, arg.argType, NODE, msg); } - - if (ruleValue == '' || ruleValue == undefined){ - ruleValue = "\'\'"; - } - if(rule.valueType == 'Iso8601Date'){ - opCheck = operators[rule.operator](new Date(ruleValue), new Date(attributeValue)); - }else{ - opCheck = operators[rule.operator](ruleValue, attributeValue); - } - if (opCheck) { - // sendDebug(NODE.attributeId+"=\""+attributeValue+"\", ("+idx+")port success") - RED.util.setMessageProperty(msg, 'payload', attributeValue); - onward.push(msg); - } else { - // sendDebug(NODE.attributeId+"=\""+attributeValue+"\", ("+idx+")port fail") - onward.push(null); - } - }) - - if (NODE.rules.length == 0) { - RED.util.setMessageProperty(msg, 'payload', data); - onward.push(msg); - } - NODE.send(onward); - - }).catch(function (err) { - NODE.loggingEditor&&console.error(err); - NODE.loggingConsole&&NODE.error("[error] " + err.errCd + ", " + err.errMsg); - }); - } else { - NODE.loggingEditor&&NODE.warn("[SmartThings] Action:" + NODE.name||NODE.alias||NODE.type); - - var commandArr = []; - var componentId = (deviceConfig && deviceConfig.componentId) ? deviceConfig.componentId : 'main'; - for(var rule of NODE.rules){ - rule.capaId=rule.capaId.split('_v')[0]; - var cmd = {component: componentId || "main", capability: rule.capaId, command: rule.attrId}; - cmd.arguments = []; - - var argObj={}; - - rule.args.forEach(arg=>{ - var argValue=arg.value; - - if(arg.argType=='jsonata'){ - argValue = RED.util.evaluateJSONataExpression(argValue,msg); - }else{ - argValue = RED.util.evaluateNodeProperty(argValue,arg.argType,NODE,msg); - } - if(arg.type != 'object'||arg.argType=='json'){ - arg.type = arg.type || ''; - if(arg.type.toLowerCase().indexOf('integer')>-1||arg.type.toLowerCase().indexOf('number')>-1) { - cmd.arguments.push(Number(argValue)); - }else if(arg.type === 'json'){ - cmd.arguments.push(JSON.parse(argValue)); - }else{ - cmd.arguments.push(argValue); - } - }else{ - arg.propType=arg.propType || ''; - if(arg.propType.toLowerCase().indexOf('integer')>-1||arg.propType.toLowerCase().indexOf('number')>-1) { - argValue = Number(argValue); - }else{ - argValue = argValue; - } - argObj[arg.propId]=argValue; - } - }) - if(Object.keys(argObj).length>0){ - cmd.arguments.push(argObj); - } - commandArr.push(cmd); - } - if (commandArr.length > 0) { - OneApi.executeDeviceCommands(param, {commands: commandArr}, authToken, NODE.logging).then(function (data) { - RED.util.setMessageProperty(msg, 'payload', commandArr); - onward.push(msg); - NODE.send(onward); - }).catch(function (err) { - NODE.loggingConsole&&console.error(err); - NODE.loggingEditor&&NODE.error("[error] " + err.errCd + ", " + err.errMsg); - }) - }else { - RED.util.setMessageProperty(msg, 'payload', "command is empty"); - onward.push(msg); - NODE.send(onward); - } - } - } catch (err) { - var msg='Invalid property or argument value, '+err.message - NODE.error(msg); - } - }); - } - - RED.nodes.registerType(ST_EVENT_DEVICE, ThingNode); - RED.nodes.registerType(ST_STATUS_DEVICE, ThingNode); - RED.nodes.registerType(ST_COMMAND_DEVICE, ThingNode); - function stCompatibleCheck(node){ - if(ST_NODES.includes(node.type) && node.capabilityId && node.capabilityId.indexOf('_v')===-1){ - node.capabilityId+='_v1' - } - if(ST_NODES.includes(node.type) && (node.capability || (node.device && typeof node.device.capabilities == 'object'))) { - switch (node.type) { - case ST_DEVICE_PROFILE: - node.capabilityId = node.capability - node.profileId = node.id - - delete node.capability - delete node.device - break; - case ST_MY_DEVICE: - if(node.device && node.device.components){ - var capas = [] - node.device.components.forEach(component=>{ - component.capabilities.forEach(capability=>{ - capas.push(capability.id+'_v'+capability.version) - }) - }) - node.device.capabilities = capas - } - node.profileId = node.id - break; - case ST_EVENT_DEVICE: - case ST_STATUS_DEVICE: - case ST_COMMAND_DEVICE: - node.deviceNodeId=node.deviceId - node.capabilityId=node.capability - node.attributeId=node.attribute - - node.rules=[] - if(node.sensorAttrDs && node.sensorAttrDs.length>0){ - for(var r of node.sensorAttrDs){ - var rule = {} - rule.capaId=node.capabilityId - rule.attrId=r.col1 - rule.operator=r.col2 - rule.value=r.col3 - rule.valueType=(r.col4=='num')?'integer':'string' - node.rules.push(rule) - } - } - if(node.sensorCapaDs && node.sensorCapaDs.length>0){ - for(var r of node.sensorCapaDs){ - var rule = {} - rule.capaId=r.col1 - rule.attrId=r.col2 - rule.args=[] - var typesObj = JSON.parse(r.argType) - var idx = 0 - for(var argName in typesObj){ - var arg = {name:argName,type:typesObj[argName],value:r['col'+(3+idx)]} - rule.args.push(arg) - idx++ - } - node.rules.push(rule) - } - } - - delete node.deviceId - delete node.capability - delete node.attribute - delete node.sensorAttrDs - delete node.sensorCapaDs - - break; - } - } - } + if (arg.type != 'object' || arg.argType == 'json') { + arg.type = arg.type || ''; + if (arg.type.toLowerCase() + .indexOf('integer') > -1 || arg.type.toLowerCase() + .indexOf('number') > -1) { + cmd.arguments.push(Number(argValue)); + } else if (arg.type === 'json') { + cmd.arguments.push(JSON.parse(argValue)); + } else { + cmd.arguments.push(argValue); + } + } else { + arg.propType = arg.propType || ''; + if (arg.propType.toLowerCase() + .indexOf('integer') > -1 || arg.propType.toLowerCase() + .indexOf('number') > -1) { + argValue = Number(argValue); + } else { + argValue = argValue; + } + argObj[arg.propId] = argValue; + } + }) + if (Object.keys(argObj).length > 0) { + cmd.arguments.push(argObj); + } + commandArr.push(cmd); + } + if (commandArr.length > 0) { + OneApi.executeDeviceCommands(param, {commands: commandArr}, authToken, NODE.logging) + .then(function (data) { + RED.util.setMessageProperty(msg, 'payload', commandArr); + onward.push(msg); + NODE.send(onward); + }) + .catch(function (err) { + NODE.loggingConsole && console.error(err); + NODE.loggingEditor && NODE.error("[error] " + err.errCd + ", " + err.errMsg); + }) + } else { + RED.util.setMessageProperty(msg, 'payload', "command is empty"); + onward.push(msg); + NODE.send(onward); + } + } + } catch (err) { + var msg = 'Invalid property or argument value, ' + err.message + NODE.error(msg); + } + }); + } + + RED.nodes.registerType(ST_EVENT_DEVICE, ThingNode); + RED.nodes.registerType(ST_STATUS_DEVICE, ThingNode); + RED.nodes.registerType(ST_COMMAND_DEVICE, ThingNode); + + function stCompatibleCheck (node) { + if (ST_NODES.includes(node.type) && node.capabilityId && node.capabilityId.indexOf('_v') === -1) { + node.capabilityId += '_v1' + } + if (ST_NODES.includes(node.type) && (node.capability || (node.device && typeof node.device.capabilities == 'object'))) { + switch (node.type) { + case ST_DEVICE_PROFILE: + node.capabilityId = node.capability + node.profileId = node.id + + delete node.capability + delete node.device + break; + case ST_MY_DEVICE: + if (node.device && node.device.components) { + var capas = [] + node.device.components.forEach(component => { + component.capabilities.forEach(capability => { + capas.push(capability.id + '_v' + capability.version) + }) + }) + node.device.capabilities = capas + } + node.profileId = node.id + break; + case ST_EVENT_DEVICE: + case ST_STATUS_DEVICE: + case ST_COMMAND_DEVICE: + node.deviceNodeId = node.deviceId + node.capabilityId = node.capability + node.attributeId = node.attribute + + node.rules = [] + if (node.sensorAttrDs && node.sensorAttrDs.length > 0) { + for (var r of node.sensorAttrDs) { + var rule = {} + rule.capaId = node.capabilityId + rule.attrId = r.col1 + rule.operator = r.col2 + rule.value = r.col3 + rule.valueType = (r.col4 == 'num') ? 'integer' : 'string' + node.rules.push(rule) + } + } + if (node.sensorCapaDs && node.sensorCapaDs.length > 0) { + for (var r of node.sensorCapaDs) { + var rule = {} + rule.capaId = r.col1 + rule.attrId = r.col2 + rule.args = [] + var typesObj = JSON.parse(r.argType) + var idx = 0 + for (var argName in typesObj) { + var arg = { + name: argName, + type: typesObj[argName], + value: r['col' + (3 + idx)] + } + rule.args.push(arg) + idx++ + } + node.rules.push(rule) + } + } + + delete node.deviceId + delete node.capability + delete node.attribute + delete node.sensorAttrDs + delete node.sensorCapaDs + + break; + } + } + } } \ No newline at end of file diff --git a/package.json b/package.json index f5aef5d1..ea30bc5a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-red-contrib-samsung-automation-studio-nodes", - "version": "1.1.6", + "version": "1.1.7", "description": "Samsung Automation Studio Nodes for Node-RED", "keywords": [ "SmartThings",