node-red-contrib-deconz
Advanced tools
Comparing version 2.0.0-rc.2 to 2.0.0-rc.3
@@ -164,5 +164,9 @@ const NODE_PATH = '/node-red-contrib-deconz/'; | ||
let server = RED.nodes.getNode(config.server); | ||
let configMigration = new ConfigMigration(data.type, config, server); | ||
let result = configMigration.migrate(config); | ||
res.json(result); | ||
if (server.state.ready === true) { | ||
let configMigration = new ConfigMigration(data.type, config, server); | ||
let result = configMigration.migrate(config); | ||
res.json(result); | ||
} else { | ||
res.json({errors: [`The server node is not ready. Please check the server configuration.`]}); | ||
} | ||
} catch (e) { | ||
@@ -169,0 +173,0 @@ console.warn(e.toString()); |
@@ -101,4 +101,9 @@ const OutputMsgFormatter = require("../src/runtime/OutputMsgFormatter"); | ||
if (options.errorEvent === true) { | ||
node.status(options.errorCode || "Unknown Error"); | ||
node.error(options.errorMsg || "Unknown Error"); | ||
node.status({ | ||
fill: "red", | ||
shape: "dot", | ||
text: options.errorCode || "Unknown Error" | ||
}); | ||
if (options.isGlobalError === false) | ||
node.error(options.errorMsg || "Unknown Error"); | ||
return; | ||
@@ -156,3 +161,3 @@ } | ||
shape: "ring", | ||
text: "node-red-contrib-deconz/battery:status.not_reachable" | ||
text: "node-red-contrib-deconz/battery:status.device_not_reachable" | ||
}); | ||
@@ -163,3 +168,3 @@ } else if ("config" in device && "reachable" in device.config && device.config.reachable === false) { | ||
shape: "ring", | ||
text: "node-red-contrib-deconz/battery:status.not_reachable" | ||
text: "node-red-contrib-deconz/battery:status.device_not_reachable" | ||
}); | ||
@@ -166,0 +171,0 @@ } else { |
@@ -9,3 +9,7 @@ module.exports = function (RED) { | ||
node.status({}); | ||
node.status({ | ||
fill: "blue", | ||
shape: "dot", | ||
text: "node-red-contrib-deconz/server:status.starting" | ||
}); | ||
@@ -28,4 +32,9 @@ //get server node | ||
if (options.errorEvent === true) { | ||
node.status(options.errorCode || "Unknown Error"); | ||
node.error(options.errorMsg || "Unknown Error"); | ||
node.status({ | ||
fill: "red", | ||
shape: "dot", | ||
text: options.errorCode || "Unknown Error" | ||
}); | ||
if (options.isGlobalError === false) | ||
node.error(options.errorMsg || "Unknown Error"); | ||
return; | ||
@@ -32,0 +41,0 @@ } |
@@ -103,4 +103,9 @@ const dotProp = require('dot-prop'); | ||
if (options.errorEvent === true) { | ||
node.status(options.errorCode || "Unknown Error"); | ||
node.error(options.errorMsg || "Unknown Error"); | ||
node.status({ | ||
fill: "red", | ||
shape: "dot", | ||
text: options.errorCode || "Unknown Error" | ||
}); | ||
if (options.isGlobalError === false) | ||
node.error(options.errorMsg || "Unknown Error"); | ||
return; | ||
@@ -107,0 +112,0 @@ } |
@@ -28,3 +28,5 @@ { | ||
"server_node_error": "server node error", | ||
"not_reachable": "not reachable", | ||
"deconz_not_reachable": "deconz not reachable", | ||
"device_not_reachable": "device not reachable", | ||
"invalid_api_key": "invalid api key", | ||
"device_not_set": "device not set", | ||
@@ -31,0 +33,0 @@ "reconnecting": "reconnecting...", |
@@ -17,5 +17,22 @@ const got = require('got'); | ||
node.config = config; | ||
node.discoverProcessRunning = false; | ||
node.ready = false; | ||
node.event_count = 0; | ||
node.state = { | ||
ready: false, | ||
startFailed: false, | ||
pooling: { | ||
isValid: false, | ||
reachable: false, | ||
discoverProcessRunning: false, | ||
lastPooling: undefined, | ||
failCount: 0, | ||
errorTriggered: false | ||
}, | ||
websocket: { | ||
isValid: false, | ||
reachable: false, | ||
lastConnected: undefined, | ||
lastEvent: undefined, | ||
lastDisconnected: undefined, | ||
eventCount: 0 | ||
} | ||
}; | ||
@@ -27,3 +44,3 @@ // Config migration | ||
migrationResult.errors.forEach( | ||
error => console.error(`Error with migration of node ${node.type} with id ${node.id}`, error) | ||
error => node.error(`Error with migration of node ${node.type} with id ${node.id}`, error) | ||
); | ||
@@ -56,9 +73,42 @@ } | ||
let pooling = async () => { | ||
let result = await node.discoverDevices({ | ||
forceRefresh: true | ||
}); | ||
// Wait for a valid device discovery before connecting to the websocket | ||
let result = await node.discoverDevices({forceRefresh: true}); | ||
if (result === true) { | ||
if (node.socket === undefined) this.setupDeconzSocket(node); | ||
node.ready = true; | ||
if (node.state.pooling.isValid === false) { | ||
node.state.pooling.isValid = true; | ||
node.state.ready = true; | ||
this.setupDeconzSocket(node); | ||
node.emit('onStart'); | ||
} | ||
node.state.pooling.reachable = true; | ||
node.state.pooling.lastPooling = Date.now(); | ||
node.state.pooling.failCount = 0; | ||
if (node.state.pooling.errorTriggered === true) { | ||
node.log(`discoverDevices: Connected to deconz API.`); | ||
} | ||
node.state.pooling.errorTriggered = false; | ||
} else if (node.state.pooling.isValid === false) { | ||
if (node.state.startFailed) return; | ||
node.state.pooling.failCount++; | ||
let code = RED._('node-red-contrib-deconz/server:status.deconz_not_reachable'); | ||
let reason = "discoverDevices: Can't connect to deconz API since starting. " + | ||
"Please check server configuration."; | ||
if (node.state.pooling.errorTriggered === false) { | ||
node.state.pooling.errorTriggered = true; | ||
node.propagateErrorNews(code, reason, true); | ||
} | ||
if (node.state.pooling.failCount % 4 === 2) { | ||
node.error(reason); | ||
} | ||
} else { | ||
node.state.pooling.failCount++; | ||
let code = RED._('node-red-contrib-deconz/server:status.deconz_not_reachable'); | ||
let reason = "discoverDevices: Can't connect to deconz API."; | ||
if (node.state.pooling.errorTriggered === false) { | ||
node.state.pooling.errorTriggered = true; | ||
node.propagateErrorNews(code, reason, true); | ||
} | ||
if (node.state.pooling.failCount % 4 === 2) { | ||
node.error(reason); | ||
} | ||
} | ||
@@ -68,9 +118,9 @@ }; | ||
await pooling(); | ||
this.refreshDiscoverTimer = setInterval(pooling, node.refreshDiscoverInterval); | ||
if (node.state.startFailed !== true) | ||
this.refreshDiscoverTimer = setInterval(pooling, node.refreshDiscoverInterval); | ||
} catch (e) { | ||
node.ready = false; | ||
node.state.ready = false; | ||
node.error("Deconz Server node error " + e.toString()); | ||
} | ||
node.emit('onStart'); | ||
})(); | ||
@@ -97,18 +147,37 @@ } | ||
}); | ||
node.socket.on('open', () => { | ||
node.log(`WebSocket opened`); | ||
node.state.websocket.isValid = true; | ||
node.state.websocket.reachable = true; | ||
node.state.websocket.lastConnected = Date.now(); | ||
// This is used only on websocket reconnect, not the initial connection. | ||
if (node.state.ready) node.propagateStartNews(); | ||
}); | ||
node.socket.on('message', (payload) => this.onSocketMessage(payload)); | ||
node.socket.on('error', (err) => { | ||
let node = this; | ||
node.state.websocket.reachable = false; | ||
node.state.websocket.lastDisconnected = Date.now(); | ||
node.error(`WebSocket error: ${err}`); | ||
}); | ||
node.socket.on('close', (code, reason) => { | ||
node.state.websocket.reachable = false; | ||
node.state.websocket.lastDisconnected = Date.now(); | ||
if (reason) { // don't bother the user unless there's a reason | ||
node.warn(`WebSocket disconnected: ${code} - ${reason}`); | ||
} | ||
if (node.ready) node.propagateErrorNews(code, reason); | ||
if (node.state.ready) node.propagateErrorNews(code, reason); | ||
}); | ||
node.socket.on('unauthorized', () => this.onSocketUnauthorized()); | ||
node.socket.on('open', () => { | ||
node.log(`WebSocket opened`); | ||
// This is used only on websocket reconnect, not the initial connection. | ||
if (node.ready) node.propagateStartNews(); | ||
node.socket.on('pong-timeout', () => { | ||
let node = this; | ||
node.state.websocket.reachable = false; | ||
node.state.websocket.lastDisconnected = Date.now(); | ||
node.warn('WebSocket connection timeout, reconnecting'); | ||
}); | ||
node.socket.on('message', (payload) => this.onSocketMessage(payload)); | ||
node.socket.on('error', (err) => this.onSocketError(err)); | ||
node.socket.on('pong-timeout', () => this.onSocketPongTimeout()); | ||
node.socket.on('unauthorized', () => () => { | ||
let node = this; | ||
node.state.websocket.isValid = false; | ||
node.state.websocket.lastDisconnected = Date.now(); | ||
node.warn('WebSocket authentication failed'); | ||
}); | ||
} | ||
@@ -124,17 +193,27 @@ | ||
if (node.ready && (options.forceRefresh === false || node.discoverProcessRunning === true)) { | ||
node.log('discoverDevices: Using cached devices'); | ||
if (options.forceRefresh === false || node.state.pooling.discoverProcessRunning === true) { | ||
//node.log('discoverDevices: Using cached devices'); | ||
return; | ||
} | ||
node.discoverProcessRunning = true; | ||
node.state.pooling.discoverProcessRunning = true; | ||
try { | ||
const response = await got(node.api.url.main()).json(); | ||
const response = await got(node.api.url.main(), {retry: 1, timeout: 2000}).json(); | ||
node.device_list.parse(response); | ||
node.log(`discoverDevices: Updated ${node.device_list.count}`); | ||
node.discoverProcessRunning = false; | ||
//node.log(`discoverDevices: Updated ${node.device_list.count}`); | ||
node.state.pooling.discoverProcessRunning = false; | ||
return true; | ||
} catch (e) { | ||
node.error(`discoverDevices: Can't connect to deconz API.`); | ||
node.discoverProcessRunning = false; | ||
if (e.response !== undefined && e.response.statusCode === 403) { | ||
node.state.startFailed = true; | ||
console.log("invalid_api_key"); | ||
let code = RED._('node-red-contrib-deconz/server:status.invalid_api_key'); | ||
let reason = "discoverDevices: Can't use to deconz API, invalid api key. " + | ||
"Please check server configuration."; | ||
node.error(reason); | ||
node.propagateErrorNews(code, reason, true); | ||
node.onClose(); | ||
} | ||
//node.error(`discoverDevices: Can't connect to deconz API.`); | ||
node.state.pooling.discoverProcessRunning = false; | ||
return false; | ||
@@ -192,6 +271,20 @@ } | ||
propagateErrorNews(code, reason) { | ||
propagateErrorNews(code, reason, isGlobalError = false) { | ||
let node = this; | ||
if (!reason) return; | ||
if (node.state.ready === false) { | ||
RED.nodes.eachNode((target) => { | ||
if (['deconz-input', 'deconz-battery', 'deconz-get', 'deconz-out', 'deconz-event'].includes(target.type)) { | ||
let targetNode = RED.nodes.getNode(target.id); | ||
if (targetNode) targetNode.status({ | ||
fill: "red", | ||
shape: "dot", | ||
text: code | ||
}); | ||
} | ||
}); | ||
return; | ||
} | ||
// Node with device selected | ||
@@ -204,3 +297,4 @@ for (let [device_path, nodeIDs] of Object.entries(node.nodesByDevicePath)) { | ||
errorCode: code, | ||
errorMsg: `WebSocket disconnected: ${reason || 'no reason provided'}` | ||
errorMsg: reason || "Unknown error", | ||
isGlobalError | ||
}); | ||
@@ -234,3 +328,4 @@ } | ||
errorCode: code, | ||
errorMsg: `WebSocket disconnected: ${reason || 'no reason provided'}` | ||
errorMsg: reason || "Unknown error", | ||
isGlobalError | ||
}); | ||
@@ -329,3 +424,3 @@ } | ||
text: RED._('node-red-contrib-deconz/server:status.event_count') | ||
.replace('{{event_count}}', node.event_count) | ||
.replace('{{event_count}}', node.state.websocket.eventCount) | ||
}); | ||
@@ -434,28 +529,12 @@ } else { | ||
let node = this; | ||
node.log('Shutting down deconz server node.'); | ||
clearInterval(node.refreshDiscoverTimer); | ||
node.ready = false; | ||
node.log('WebSocket connection closed'); | ||
node.state.ready = false; | ||
if (node.socket !== undefined) { | ||
node.socket.close(); | ||
node.socket = undefined; | ||
} | ||
node.emit('onClose'); | ||
node.socket.close(); | ||
node.socket = undefined; | ||
} | ||
onSocketPongTimeout() { | ||
let node = this; | ||
node.warn('WebSocket connection timeout, reconnecting'); | ||
node.emit('onSocketPongTimeout'); | ||
} | ||
onSocketUnauthorized() { | ||
let node = this; | ||
node.warn('WebSocket authentication failed'); | ||
node.emit('onSocketUnauthorized'); | ||
} | ||
onSocketError(err) { | ||
let node = this; | ||
node.error(`WebSocket error: ${err}`); | ||
node.emit('onSocketError'); | ||
} | ||
updateDevice(device, dataParsed) { | ||
@@ -488,4 +567,11 @@ let node = this; | ||
let node = this; | ||
if (node.event_count >= Number.MAX_SAFE_INTEGER) node.event_count = 0; | ||
node.event_count++; | ||
node.state.websocket.lastEvent = Date.now(); | ||
node.state.websocket.isValid = true; | ||
node.state.websocket.reachable = true; | ||
if (node.state.websocket.eventCount >= Number.MAX_SAFE_INTEGER) node.state.websocket.eventCount = 0; | ||
node.state.websocket.eventCount++; | ||
// Drop websocket msgs if the pooling don't work | ||
if (node.state.pooling.isValid === false) return console.error('Got websocket msg but the pooling is invalid. This should not happen.'); | ||
node.emit('onSocketMessage', dataParsed); //Used by event node, TODO Really used ? | ||
@@ -501,3 +587,3 @@ | ||
// TODO handle case if device is not found | ||
if (device === undefined) return; | ||
if (device === undefined) return console.error('Got websocket msg but the device does not exist. ' + JSON.stringify(dataParsed)); | ||
let changed = node.updateDevice(device, dataParsed); | ||
@@ -613,3 +699,3 @@ | ||
shape: "ring", | ||
text: "node-red-contrib-deconz/server:status.not_reachable" | ||
text: "node-red-contrib-deconz/server:status.device_not_reachable" | ||
}); | ||
@@ -616,0 +702,0 @@ return; |
@@ -51,3 +51,3 @@ { | ||
}, | ||
"version": "2.0.0-rc.2", | ||
"version": "2.0.0-rc.3", | ||
"devDependencies": { | ||
@@ -54,0 +54,0 @@ "grunt": "^1.3.0", |
@@ -178,4 +178,5 @@ const Utils = require("./Utils"); | ||
} | ||
if (HK.TargetHorizontalTiltAngle !== undefined) { | ||
this.result.state.tilt = Utils.convertRange(HK.TargetHorizontalTiltAngle, [-90, 90], [0, 100]); | ||
for (const side of ['Horizontal', 'Vertical']) { | ||
if (HK[`Target${side}TiltAngle`] !== undefined) | ||
this.result.state.tilt = Utils.convertRange(HK[`Target${side}TiltAngle`], [-90, 90], [0, 100]); | ||
} | ||
@@ -182,0 +183,0 @@ this.result.state.transitiontime = this.getNodeProperty(this.arg.transitiontime); |
@@ -45,2 +45,3 @@ const Query = require('./Query'); | ||
let domain = parts.shift(); | ||
if (resourceName === 'uniqueid') return this.getDeviceByUniqueID(domain); | ||
let sub_path = parts.join("/"); | ||
@@ -47,0 +48,0 @@ switch (domain) { |
@@ -248,5 +248,8 @@ const dotProp = require('dot-prop'); | ||
case 'onchange': | ||
let payloadPath = payloadFormat; | ||
if (this.rule.type === 'state' || this.rule.type === 'config') | ||
payloadPath = `${this.rule.type}.${payloadPath}`; | ||
return device && Array.isArray(device.changed) && ( | ||
(payloadFormat === '__complete__' && device.changed.length > 0) || | ||
(payloadFormat !== '__complete__' && device.changed.includes(payloadFormat)) | ||
(payloadFormat !== '__complete__' && device.changed.includes(payloadPath)) | ||
); | ||
@@ -253,0 +256,0 @@ case 'onupdate': |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
484150
6717