node-red-contrib-denon
Advanced tools
Comparing version 0.1.2 to 0.1.3
475
denon.js
@@ -5,278 +5,261 @@ /** | ||
var util = require('util'); | ||
var DEBUG = false; | ||
var connectionFSM = require('./lib/connectionFSM.js'); | ||
const util = require('util'), | ||
debug = require('debug')('node-red-contrib-denon'), | ||
connectionFSM = require('./lib/connectionFSM.js'); | ||
module.exports = function(RED) { | ||
module.exports = function (RED) { | ||
/** | ||
* ====== Denon-controller ================ | ||
* Holds configuration for denonjs host+port, | ||
* initializes new denonjs connections | ||
* ======================================= | ||
*/ | ||
function DenonControllerNode(config) { | ||
RED.nodes.createNode(this, config); | ||
this.name = config.name; | ||
this.host = config.host; | ||
this.port = config.port; | ||
this.denon = null; | ||
var node = this; | ||
/** | ||
* ====== Denon-controller ================ | ||
* Holds configuration for denonjs host+port, | ||
* initializes new denonjs connections | ||
* ======================================= | ||
*/ | ||
function DenonControllerNode(config) { | ||
RED.nodes.createNode(this, config); | ||
this.name = config.name; | ||
this.host = config.host; | ||
this.port = config.port; | ||
this.denon = null; | ||
var node = this; | ||
/** | ||
* Initialize an denon_telnet socket, calling the handler function | ||
* when successfully connected, passing it the denon_telnet connection | ||
*/ | ||
this.initializeDenonConnection = function (handler) { | ||
if (node.denon) { | ||
debug(`${node.name}: already configured connection to Denon[${config.host}:${config.port}`); | ||
if (handler && (typeof handler === 'function')) { | ||
if (node.denon.connection && node.denon.connected) | ||
handler(node.denon); | ||
else { | ||
if (node.denon.connection && !node.denon.connected) | ||
node.denon.connect(); | ||
node.denon.on('connected', function () { | ||
handler(node.denon); | ||
}); | ||
} | ||
} | ||
return node.denon; | ||
} | ||
debug(`${node.name}: initializing connection to Denon[${config.host}:${config.port}`); | ||
node.denon = new connectionFSM({ | ||
host: config.host, | ||
port: config.port, | ||
debug: false | ||
}); | ||
node.denon.connect(); | ||
if (handler && (typeof handler === 'function')) { | ||
node.denon.on('connected', function () { | ||
handler(node.denon); | ||
}); | ||
} | ||
debug(`${node.name}: successfully connected to to Denon[${config.host}:${config.port}`); | ||
return node.denon; | ||
}; | ||
this.on("close", function () { | ||
debug(`${node.name}: disconnecting Denon[${config.host}:${config.port}`); | ||
node.denon && node.denon.disconnect && node.denon.disconnect(); | ||
node.denon = null; | ||
}); | ||
} | ||
RED.nodes.registerType("denon-controller", DenonControllerNode); | ||
/** | ||
* Initialize an denon_telnet socket, calling the handler function | ||
* when successfully connected, passing it the denon_telnet connection | ||
* ====== Denon-out ======================= | ||
* Sends outgoing Denon player from | ||
* messages received via node-red flows | ||
* ======================================= | ||
*/ | ||
this.initializeDenonConnection = function(handler) { | ||
if (node.denon) { | ||
DEBUG && RED.comms.publish("debug", { | ||
name: node.name, | ||
msg: 'already configured connection to Denon player at ' + config.host + ':' + config.port | ||
}); | ||
if (handler && (typeof handler === 'function')) { | ||
if (node.denon.connection && node.denon.connected) | ||
handler(node.denon); | ||
else { | ||
if (node.denon.connection && !node.denon.connected) | ||
node.denon.connect(); | ||
node.denon.on('connected', function() { | ||
handler(node.denon); | ||
function DenonOut(config) { | ||
RED.nodes.createNode(this, config); | ||
this.name = config.name; | ||
var controllerNode = RED.nodes.getNode(config.controller); | ||
this.unit_number = config.unit_number; | ||
this.denoncommand = config.denoncommand; | ||
var node = this; | ||
this.on("input", function (msg) { | ||
debug(node.name, `denonout.onInput msg[${util.inspect(msg)}]`); | ||
if (!(msg && msg.hasOwnProperty('payload'))) return; | ||
var payload = msg.payload; | ||
if (typeof(msg.payload) === "object") { | ||
payload = msg.payload; | ||
} else if (typeof(msg.payload) === "string") { | ||
try { | ||
payload = JSON.parse(msg.payload); | ||
if (typeof (payload) === 'number') | ||
payload = {cmd: msg.payload.toString()}; | ||
} catch (e) { | ||
payload = {cmd: msg.payload.toString()}; | ||
} | ||
} | ||
else | ||
payload = {cmd: msg.payload.toString()}; | ||
if (payload == null) { | ||
node.log('denonout.onInput: illegal msg.payload!'); | ||
return; | ||
} | ||
//If msg.topic is filled, than set it as cmd | ||
if (msg.topic) { | ||
if (payload.value === null || payload.value === undefined) | ||
payload.value = payload.cmd; | ||
payload = {cmd: msg.topic.toString(), value: payload.value}; | ||
} | ||
if (node.denoncommand && node.denoncommand !== 'empty') { | ||
try { | ||
payload = JSON.parse(node.denoncommand); | ||
if (typeof (payload) === 'number') | ||
payload.cmd = node.denoncommand.toString(); | ||
} catch (e) { | ||
payload.cmd = node.denoncommand.toString(); | ||
} | ||
} | ||
node.send(payload, function (err) { | ||
if (err) { | ||
node.error('send error: ' + err); | ||
} | ||
if (typeof(msg.cb) === 'function') | ||
msg.cb(err); | ||
}); | ||
} | ||
} | ||
return node.denon; | ||
} | ||
node.log('configuring connection to Denon player at ' + config.host + ':' + config.port); | ||
node.denon = new connectionFSM({ | ||
host: config.host, | ||
port: config.port, | ||
debug: DEBUG | ||
}); | ||
node.denon.connect(); | ||
if (handler && (typeof handler === 'function')) { | ||
node.denon.on('connected', function() { | ||
handler(node.denon); | ||
}); | ||
} | ||
DEBUG && RED.comms.publish("debug", { | ||
name: node.name, | ||
msg: 'Denon: successfully connected to ' + config.host + ':' + config.port | ||
}); | ||
this.on("close", function () { | ||
node.log('denonOut.close'); | ||
}); | ||
return node.denon; | ||
}; | ||
this.on("close", function() { | ||
node.log('disconnecting from denon device at ' + config.host + ':' + config.port); | ||
node.denon && node.denon.disconnect && node.denon.disconnect(); | ||
node.denon = null; | ||
}); | ||
} | ||
node.status({fill: "yellow", shape: "dot", text: "inactive"}); | ||
RED.nodes.registerType("denon-controller", DenonControllerNode); | ||
function nodeStatusConnected() { | ||
node.status({fill: "green", shape: "dot", text: "connected"}); | ||
} | ||
/** | ||
* ====== Denon-out ======================= | ||
* Sends outgoing Denon player from | ||
* messages received via node-red flows | ||
* ======================================= | ||
*/ | ||
function DenonOut(config) { | ||
RED.nodes.createNode(this, config); | ||
this.name = config.name; | ||
var controllerNode = RED.nodes.getNode(config.controller); | ||
this.unit_number = config.unit_number; | ||
this.denoncommand = config.denoncommand; | ||
var node = this; | ||
this.on("input", function(msg) { | ||
DEBUG && RED.comms.publish("debug", { | ||
name: node.name, | ||
msg: 'denonout.onInput msg[' + util.inspect(msg) + ']' | ||
}); | ||
//node.log('denonout.onInput msg[' + util.inspect(msg) + ']'); | ||
if (!(msg && msg.hasOwnProperty('payload'))) return; | ||
var payload = msg.payload; | ||
if (typeof(msg.payload) === "object") { | ||
payload = msg.payload; | ||
} else if (typeof(msg.payload) === "string") { | ||
try { | ||
payload = JSON.parse(msg.payload); | ||
if (typeof (payload) === 'number') | ||
payload = {cmd: msg.payload.toString()}; | ||
} catch (e) { | ||
payload = {cmd: msg.payload.toString()}; | ||
function nodeStatusDisconnected() { | ||
node.status({fill: "red", shape: "dot", text: "disconnected"}); | ||
} | ||
} | ||
else | ||
payload = {cmd: msg.payload.toString()}; | ||
if (payload == null) { | ||
node.log('denonout.onInput: illegal msg.payload!'); | ||
return; | ||
} | ||
//If msg.topic is filled, than set it as cmd | ||
if (msg.topic) { | ||
if (payload.value === null || payload.value === undefined) | ||
payload.value = payload.cmd; | ||
payload = {cmd: msg.topic.toString(), value: payload.value}; | ||
} | ||
if (node.denoncommand && node.denoncommand !== 'empty') { | ||
try { | ||
payload = JSON.parse(node.denoncommand); | ||
if (typeof (payload) === 'number') | ||
payload.cmd = node.denoncommand.toString(); | ||
} catch (e) { | ||
payload.cmd = node.denoncommand.toString(); | ||
function nodeStatusReconnect() { | ||
node.status({fill: "yellow", shape: "ring", text: "reconnecting"}); | ||
} | ||
} | ||
node.send(payload, function(err) { | ||
if (err) { | ||
node.error('send error: ' + err); | ||
function nodeStatusConnecting() { | ||
node.status({fill: "green", shape: "ring", text: "connecting"}); | ||
} | ||
if (typeof(msg.cb) === 'function') | ||
msg.cb(err); | ||
}); | ||
}); | ||
this.on("close", function() { | ||
node.log('denonOut.close'); | ||
}); | ||
controllerNode.initializeDenonConnection(function (fsm) { | ||
if (fsm.connected) | ||
nodeStatusConnected(); | ||
else | ||
nodeStatusDisconnected(); | ||
fsm.off('connecting', nodeStatusConnecting); | ||
fsm.on('connecting', nodeStatusConnecting); | ||
fsm.off('connected', nodeStatusConnected); | ||
fsm.on('connected', nodeStatusConnected); | ||
fsm.off('disconnected', nodeStatusDisconnected); | ||
fsm.on('disconnected', nodeStatusDisconnected); | ||
fsm.off('reconnect', nodeStatusReconnect); | ||
fsm.on('reconnect', nodeStatusReconnect); | ||
}); | ||
node.status({fill: "yellow", shape: "dot", text: "inactive"}); | ||
function nodeStatusConnected() { | ||
node.status({fill: "green", shape: "dot", text: "connected"}); | ||
this.send = function (data, callback) { | ||
debug(`${node.name}: send data[${JSON.stringify(data)}`); | ||
controllerNode.initializeDenonConnection(function (fsm) { | ||
try { | ||
data.cmd = data.cmd || data.method; | ||
data.value = data.value || data.params; | ||
switch (data.cmd.toLowerCase()) { | ||
case 'setvolumedb': | ||
fsm.connection.setVolumeDb(parseFloat(data.value), function (error, response) { | ||
if (!callback) | ||
return; | ||
if (error) | ||
callback && callback(error, response); | ||
else | ||
callback(response); | ||
}); | ||
break; | ||
default: | ||
fsm.connection.send(data.cmd, (data.cmd.substring(0, 2) || 'UNKWN' /*UNKWN means unknown*/), function (error, response) { | ||
if (!callback) | ||
return; | ||
if (error) | ||
callback && callback(error, response); | ||
else | ||
callback(response); | ||
}); | ||
} | ||
} | ||
catch (err) { | ||
node.error('error calling send: ' + err); | ||
callback(err); | ||
} | ||
}); | ||
} | ||
} | ||
function nodeStatusDisconnected() { | ||
node.status({fill: "red", shape: "dot", text: "disconnected"}); | ||
} | ||
// | ||
RED.nodes.registerType("denon-out", DenonOut); | ||
function nodeStatusReconnect() { | ||
node.status({fill: "yellow", shape: "ring", text: "reconnecting"}); | ||
} | ||
/** | ||
* ====== Denon-IN ======================== | ||
* Handles incoming Global Cache, injecting | ||
* json into node-red flows | ||
* ======================================= | ||
*/ | ||
function DenonIn(config) { | ||
RED.nodes.createNode(this, config); | ||
this.name = config.name; | ||
this.connection = null; | ||
var node = this; | ||
var controllerNode = RED.nodes.getNode(config.controller); | ||
function nodeStatusConnecting() { | ||
node.status({fill: "green", shape: "ring", text: "connecting"}); | ||
} | ||
/* ===== Node-Red events ===== */ | ||
function nodeStatusConnecting() { | ||
node.status({fill: "green", shape: "ring", text: "connecting"}); | ||
} | ||
controllerNode.initializeDenonConnection(function(fsm) { | ||
if (fsm.connected) | ||
nodeStatusConnected(); | ||
else | ||
nodeStatusDisconnected(); | ||
fsm.off('connecting', nodeStatusConnecting); | ||
fsm.on('connecting', nodeStatusConnecting); | ||
fsm.off('connected', nodeStatusConnected); | ||
fsm.on('connected', nodeStatusConnected); | ||
fsm.off('disconnected', nodeStatusDisconnected); | ||
fsm.on('disconnected', nodeStatusDisconnected); | ||
fsm.off('reconnect', nodeStatusReconnect); | ||
fsm.on('reconnect', nodeStatusReconnect); | ||
}); | ||
function nodeStatusConnected() { | ||
node.status({fill: "green", shape: "dot", text: "connected"}); | ||
} | ||
this.send = function(data, callback) { | ||
DEBUG && RED.comms.publish("debug", {name: node.name, msg: 'send data[' + JSON.stringify(data) + ']'}); | ||
controllerNode.initializeDenonConnection(function(fsm) { | ||
try { | ||
DEBUG && RED.comms.publish("debug", {name: node.name, msg: "send: " + JSON.stringify(data)}); | ||
data.cmd = data.cmd || data.method; | ||
data.value = data.value || data.params; | ||
switch (data.cmd.toLowerCase()) { | ||
case 'setvolumedb': | ||
fsm.connection.setVolumeDb(parseFloat(data.value), function(error, response) { | ||
if (!callback) | ||
return; | ||
if (error) | ||
callback && callback(error, response); | ||
else | ||
callback(response); | ||
}); | ||
break; | ||
default: | ||
fsm.connection.send(data.cmd, function(error, response) { | ||
if (!callback) | ||
return; | ||
if (error) | ||
callback && callback(error, response); | ||
else | ||
callback(response); | ||
}); | ||
} | ||
function nodeStatusDisconnected() { | ||
node.status({fill: "red", shape: "dot", text: "disconnected"}); | ||
} | ||
catch (err) { | ||
node.error('error calling send: ' + err); | ||
callback(err); | ||
function nodeStatusReconnect() { | ||
node.status({fill: "yellow", shape: "ring", text: "reconnecting"}); | ||
} | ||
}); | ||
} | ||
} | ||
// | ||
RED.nodes.registerType("denon-out", DenonOut); | ||
function receiveNotification(data) { | ||
debug(`${node.name}: receiveNotification data[${JSON.stringify(data)}`); | ||
node.send({ | ||
topic: 'denon', | ||
payload: data | ||
}); | ||
}; | ||
/** | ||
* ====== Denon-IN ======================== | ||
* Handles incoming Global Cache, injecting | ||
* json into node-red flows | ||
* ======================================= | ||
*/ | ||
function DenonIn(config) { | ||
RED.nodes.createNode(this, config); | ||
this.name = config.name; | ||
this.connection = null; | ||
var node = this; | ||
//node.log('new DenonIn, config: %j', config); | ||
var controllerNode = RED.nodes.getNode(config.controller); | ||
/* ===== Node-Red events ===== */ | ||
function nodeStatusConnecting() { | ||
node.status({fill: "green", shape: "ring", text: "connecting"}); | ||
controllerNode.initializeDenonConnection(function (fsm) { | ||
if (fsm.connected) | ||
nodeStatusConnected(); | ||
else | ||
nodeStatusDisconnected(); | ||
fsm.off('connecting', nodeStatusConnecting); | ||
fsm.on('connecting', nodeStatusConnecting); | ||
fsm.off('connected', nodeStatusConnected); | ||
fsm.on('connected', nodeStatusConnected); | ||
fsm.off('disconnected', nodeStatusDisconnected); | ||
fsm.on('disconnected', nodeStatusDisconnected); | ||
fsm.off('reconnect', nodeStatusReconnect); | ||
fsm.on('reconnect', nodeStatusReconnect); | ||
fsm.off('data', receiveNotification); | ||
fsm.on('data', receiveNotification); | ||
}); | ||
} | ||
function nodeStatusConnected() { | ||
node.status({fill: "green", shape: "dot", text: "connected"}); | ||
} | ||
function nodeStatusDisconnected() { | ||
node.status({fill: "red", shape: "dot", text: "disconnected"}); | ||
} | ||
function nodeStatusReconnect() { | ||
node.status({fill: "yellow", shape: "ring", text: "reconnecting"}); | ||
} | ||
node.receiveNotification = function(notification, data) { | ||
DEBUG && RED.comms.publish("debug", { | ||
name: node.name, | ||
msg: 'denon event data[' + JSON.stringify(data) + ']' | ||
}); | ||
node.send({ | ||
topic: 'denon', | ||
payload: { | ||
'notification': notification, | ||
'data': data | ||
} | ||
}); | ||
}; | ||
controllerNode.initializeDenonConnection(function(fsm) { | ||
if (fsm.connected) | ||
nodeStatusConnected(); | ||
else | ||
nodeStatusDisconnected(); | ||
fsm.off('connecting', nodeStatusConnecting); | ||
fsm.on('connecting', nodeStatusConnecting); | ||
fsm.off('connected', nodeStatusConnected); | ||
fsm.on('connected', nodeStatusConnected); | ||
fsm.off('disconnected', nodeStatusDisconnected); | ||
fsm.on('disconnected', nodeStatusDisconnected); | ||
fsm.off('reconnect', nodeStatusReconnect); | ||
fsm.on('reconnect', nodeStatusReconnect); | ||
}); | ||
} | ||
RED.nodes.registerType("denon-in", DenonIn); | ||
RED.nodes.registerType("denon-in", DenonIn); | ||
} |
@@ -5,2 +5,7 @@ ~function (undefined) { | ||
var machina = require('machina'); | ||
function receiveData(data) { | ||
connectionFSM && connectionFSM.emit('data', data); | ||
} | ||
var connectionFSM = new machina.Fsm({ | ||
@@ -112,5 +117,7 @@ debug: options.debug ? true : false, | ||
}.bind(this), this.PING_INTERVAL); | ||
this.connection.getConnection().on('data', receiveData); | ||
}, | ||
_onExit: function () { | ||
clearTimeout(this.pingTimer); | ||
this.connection.getConnection().off('data', receiveData); | ||
} | ||
@@ -117,0 +124,0 @@ }, |
{ | ||
"name": "node-red-contrib-denon", | ||
"version": "0.1.2", | ||
"version": "0.1.3", | ||
"description": "Node-RED nodes for communicating with a Denon AVR.", | ||
@@ -35,2 +35,3 @@ "repository": { | ||
"dependencies": { | ||
"debug": "^3.1.0", | ||
"denon-avr": "git+https://github.com/estbeetoo/denon-avr.git", | ||
@@ -37,0 +38,0 @@ "machina": "^1.1.2" |
node-red-contrib-denon | ||
======================== | ||
A <a href="http://nodered.org" target="_new">Node-RED</a> node to communicate [Denon AVR receivers](http://www.denon.com). | ||
A <a href="http://nodered.org" target="_new">Node-RED</a> node to communicate [Denon AVR receivers](http://www.denon.com) over telnet (port 23) TCP/IP connection. | ||
@@ -13,7 +13,2 @@ # Install | ||
# Pre-reqs | ||
-------- | ||
TODO: fill it | ||
# Usage | ||
@@ -24,4 +19,66 @@ ----- | ||
If you want to use this node simply inject message's payload as string: | ||
## `Denon-Out` node sends commands to Denon devices | ||
### Commands with no arguments | ||
#### Master Volume | ||
`MV20` | ||
Master Volume set to 20 - command from Denon AVR protocol: [DENON_PROTOCOL_V7.6.0.pdf](doc/AVR3312CI_AVR3312_PROTOCOL_V7.6.0.pdf) | ||
#### Master Volume Up | ||
`MVUP` | ||
Master Volume UP by 0.5 - command from Denon AVR protocol: [DENON_PROTOCOL_V7.6.0.pdf](doc/AVR3312CI_AVR3312_PROTOCOL_V7.6.0.pdf) | ||
#### Master Volume Down | ||
`MVDOWN` | ||
Master Volume DOWN by 0.5 - command from Denon AVR protocol: [DENON_PROTOCOL_V7.6.0.pdf](doc/AVR3312CI_AVR3312_PROTOCOL_V7.6.0.pdf) | ||
### Commands with arguments | ||
Currently, there is only one such command implemented: `SetVolumeDB`. | ||
One may use `setvolumedb` too, cause it's case insensitive. | ||
So, `msg` object shoud looks like: | ||
```javascript | ||
{ | ||
topic; "setvolumedb", | ||
payload: -65.5 | ||
} | ||
``` | ||
It will set the master volume to -65.5dB. | ||
## `Denon-In` | ||
Node sends to single output messages from Denon quipment with structure: | ||
```javascript | ||
{ | ||
topic: 'denon', | ||
payload: 'PWON' | ||
} | ||
``` | ||
where `notification` could be `PWON` and `data` contain additional info, arguments for current notification. | ||
# Collect debug log for issues | ||
Current package use debug package: https://www.npmjs.com/package/debug. | ||
Run you `node-red` with command to enable debug output: | ||
`set DEBUG=node-red-contrib-denon node-red` | ||
for globally installed Node-RED, or | ||
`set DEBUG=node-red-contrib-denon node node-red/red.js` | ||
for local Node-RED. | ||
# Additinal documentation | ||
Take a look at Denon AVR protocol: [DENON_PROTOCOL_V7.6.0.pdf](doc/AVR3312CI_AVR3312_PROTOCOL_V7.6.0.pdf) |
Sorry, the diff of this file is not supported yet
803792
82
3
391
+ Addeddebug@^3.1.0
+ Addeddebug@3.2.7(transitive)
+ Addedms@2.1.3(transitive)