knx
Version:
KNXnet/IP protocol implementation for Node(>=6.x)
133 lines (122 loc) • 4.03 kB
JavaScript
/**
* knx.js - a KNX protocol stack in pure Javascript
* (C) 2016-2018 Elias Karakoulakis
*/
const util = require('util');
const DPTLib = require('./dptlib');
const KnxLog = require('./KnxLog');
const { EventEmitter } = require('events');
/*
* A Datapoint is always bound to:
* - a group address (eg. '1/2/3')
* - (optionally) a datapoint type (defaults to DPT1.001)
* You can also supply a valid connection to skip calling bind()
*/
class Datapoint extends EventEmitter {
constructor(options, conn) {
if (options == null || options.ga == null)
throw 'must supply at least { ga, dpt }!';
super();
this.options = options;
this.dptid = options.dpt || 'DPT1.001';
this.dpt = DPTLib.resolve(this.dptid);
KnxLog.get().trace('resolved %s to %j', this.dptid, this.dpt);
this.current_value = null;
if (conn) this.bind(conn);
}
/*
* Bind the datapoint to a bus connection
*/
bind(conn) {
if (!conn) throw 'must supply a valid KNX connection to bind to';
this.conn = conn;
// bind generic event handler for our group address
const gaevent = util.format('event_%s', this.options.ga);
conn.on(gaevent, (evt, src, buf) => {
// get the Javascript value from the raw buffer, if the DPT defines fromBuffer()
switch (evt) {
case 'GroupValue_Write':
case 'GroupValue_Response':
if (buf) {
const jsvalue = DPTLib.fromBuffer(buf, this.dpt);
this.emit('event', evt, jsvalue);
this.update(jsvalue); // update internal state
}
break;
default:
this.emit('event', evt);
// TODO: add default handler; maybe emit warning?
}
});
// issue a GroupValue_Read request to try to get the initial state from the bus (if any)
if (this.options.autoread)
if (conn.conntime) {
// immediately or...
this.read();
} else {
// ... when the connection is established
conn.on('connected', () => {
this.read();
});
}
}
update(jsvalue) {
const old_value = this.current_value;
if (old_value === jsvalue) return;
this.emit('change', this.current_value, jsvalue, this.options.ga);
this.current_value = jsvalue; // TODO: This should probably change before the event is emitted
const ts = new Date().toISOString().replace(/T/, ' ').replace(/\..+/, ''); // TODO: Why are we timestamping, the logger formatter already timestamps.
KnxLog.get().trace(
'%s **** %s DATAPOINT CHANGE (was: %j)',
ts,
this.toString(),
old_value
);
}
/* format a Javascript value into the APDU format dictated by the DPT
and submit a GroupValue_Write to the connection */
write(value) {
if (!this.conn) throw 'must supply a valid KNX connection to bind to';
if (this.dpt.hasOwnProperty('range')) {
// check if value is in range
const { range } = this.dpt.basetype;
const [min, max] = range;
if (value < min || value > max) {
throw util.format(
'Value %j(%s) out of bounds(%j) for %s',
value,
typeof value,
range,
this.dptid
);
}
}
this.conn.write(
this.options.ga,
value,
this.dptid,
// once we've written to the bus, update internal state
() => this.update(value)
);
}
/*
* Issue a GroupValue_Read request to the bus for this datapoint
* use the optional callback() to get notified upon response
*/
read(callback) {
if (!this.conn) throw 'must supply a valid KNX connection to bind to';
this.conn.read(this.options.ga, (src, buf) => {
const jsvalue = DPTLib.fromBuffer(buf, this.dpt);
if (typeof callback == 'function') callback(src, jsvalue);
});
}
toString() {
return util.format(
'(%s) %s %s',
this.options.ga,
this.current_value,
(this.dpt.subtype && this.dpt.subtype.unit) || ''
);
}
}
module.exports = Datapoint;