@1st-setup/cul
Version:
Module to interact with Busware CUL / culfw
366 lines (311 loc) • 11 kB
JavaScript
/* jshint strict:true */
/* jslint node: true */
/* jslint esversion: 6 */
;
/**
* CUL/COC / culfw Node.js module
* https://github.com/hobbyquaker/cul
*
* Licensed under GPL v2
* Copyright (c) 2014-2018 hobbyquaker <hq@ccu.io>
*
*/
const util = require('util');
const {EventEmitter} = require('events');
const protocol = {
em: require('./lib/em.js'),
fs20: require('./lib/fs20.js'),
hms: require('./lib/hms.js'),
moritz: require('./lib/moritz.js'),
uniroll: require('./lib/uniroll.js'),
ws: require('./lib/ws.js'),
fht: require('./lib/fht.js'),
esa: require('./lib/esa.js')
};
// http://culfw.de/commandref.html
const commands = {
F: 'FS20',
T: 'FHT',
E: 'EM',
W: 'WS',
H: 'HMS',
S: 'ESA',
R: 'Hoermann',
A: 'AskSin',
V: 'MORITZ',
Z: 'MORITZ',
o: 'Obis',
t: 'TX',
U: 'Uniroll',
K: 'WS'
};
const modes = {
slowrf: {
},
moritz: {
start: 'Zr',
stop: 'Zx'
},
asksin: {
start: 'Ar',
stop: 'Ax'
}
};
const Cul = function (options) {
const that = this;
options = options || {};
options.initCmd = 0x01;
options.mode = options.mode || 'SlowRF';
options.init = options.init || true;
options.parse = options.parse || true;
options.coc = options.coc || false;
options.scc = options.scc || false;
options.rssi = options.rssi || true;
options.debug = options.debug || false;
options.repeat = options.repeat || false;
options.connectionMode = options.connectionMode || 'serial';
options.networkTimeout = options.networkTimeout || true;
options.logger = options.logger || console.log;
that.getReport = 0;
if (options.coc) {
options.baudrate = options.baudrate || 38400;
options.serialport = options.serialport || '/dev/ttyACM0';
} else if (options.scc) {
options.baudrate = options.baudrate || 38400;
options.serialport = options.serialport || '/dev/ttyAMA0';
} else {
options.baudrate = options.baudrate || 9600;
options.serialport = options.serialport || '/dev/ttyAMA0';
}
if (options.rssi) {
// Set flag, binary or
options.initCmd |= 0x20;
}
if (options.repeat) {
// Set flag, binary or
options.initCmd |= 0x02;
}
options.initCmd = 'X' + ('0' + options.initCmd.toString(16)).slice(-2);
const modeCmd = modes[options.mode.toLowerCase()] ? modes[options.mode.toLowerCase()].start : undefined;
let stopCmd;
if (modes[options.mode.toLowerCase()] && modes[options.mode.toLowerCase()].stop) {
stopCmd = modes[options.mode.toLowerCase()].stop;
}
// Serial connection
if (options.connectionMode === 'serial') {
const SerialPort = require('serialport');
const {Readline} = SerialPort.parsers;
const parser = new Readline({
delimiter: '\r\n'
});
const spOptions = {
baudRate: options.baudrate
};
const serialPort = new SerialPort(options.serialport, spOptions);
serialPort.pipe(parser);
this.close = function (callback) {
if (!serialPort.isOpen) {
return;
}
if (options.init && stopCmd) {
that.write(stopCmd, () => {
serialPort.close(callback);
});
} else {
serialPort.close(callback);
}
};
serialPort.on('close', () => {
that.emit('close');
});
serialPort.on('open', () => {
if (options.init) {
setTimeout(() => { // Give CUL enough time to wakeup
that.write(options.initCmd, err => {
if (err) {
that.emit('error', err);
}
});
serialPort.drain(() => {
if (modeCmd) {
that.write(modeCmd, err => {
if (err) {
that.emit('error', err);
}
});
serialPort.drain(err => {
if (err) {
that.emit('error', err);
} else {
ready();
}
});
} else {
ready();
}
});
}, 1500);
} else {
ready();
}
function ready() {
parser.on('data', parse);
that.emit('ready');
}
});
serialPort.on('error', ex => {
that.emit('error', ex);
});
this.write = function (data, callback) {
if (options.debug) {
options.logger('->', data);
}
if (data == "X")
that.getReport++;
serialPort.write(data + '\r\n');
serialPort.drain(callback);
if (data[0] == "Z" && data[1] == "s") {
// What we send out for moritz protocol we echo back to ourself
// This so the morits controller can record it.
parse(`Z${data.substring(2)}FF`);
}
};
} else if (options.connectionMode === 'telnet') {
// Telnet connection
const net = require('net');
if (!options.host) {
throw new Error('no host defined!');
}
options.port = options.port || '2323';
const telnet = net.createConnection(Number.parseInt(options.port, 10), options.host);
if (options.networkTimeout) {
// WATCHDOG
this.telnetWatchdog = Date.now(); // Setup watchdog
this.telnetWatchdogSecondTry = false; // We try to times before the watchdog bites
setInterval(() => {
if (Date.now() - that.telnetWatchdog > 5000) { // Watchdog bites
if (that.telnetWatchdogSecondTry) { // Second time
// the answer to the command we have written has not arrived
// so we throw an error
that.emit('error', new Error('Connection Timeout!'));
} else { // First time
// if its the first time we are writing a
// simple command to the server and wait for an answer
that.telnetWatchdogSecondTry = true;
that.telnetWatchdog = Date.now();
that.write('V');
}
}
}, 2500);
this.patWatchdog = function () {
that.telnetWatchdog = Date.now(); // Save new time
that.telnetWatchdogSecondTry = false;
};
}
telnet.on('connect', () => {
options.logger('Connected');
if (options.init) {
that.write(options.initCmd);
if (modeCmd) {
that.write(modeCmd);
}
}
telnet.on('data', data => {
data.toString().split(/\r?\n/).forEach(parse);
that.patWatchdog(); // Pat Watchdog
});
that.emit('ready');
});
telnet.on('close', () => {
options.logger('Disconnected');
that.emit('close');
});
telnet.on('error', ex => {
that.emit('error', ex);
});
this.write = function (data, callback) {
if (options.debug) {
options.logger('->', data);
}
telnet.write(data + '\r\n');
if (callback) {
callback(false);
}
};
} else {
// If an unknown connection is defined
throw new Error('connection mode \'' + options.connectionMode + '\' is unknown!\nplease use \'serial\' or \'telnet\'');
}
this.cmd = function () {
let args = Array.prototype.slice.call(arguments);
let callback;
if (typeof args[args.length - 1] === 'function') {
callback = args.pop();
}
let c = args[0].toLowerCase();
args = args.slice(1);
if (commands[c.toUpperCase()]) {
c = commands[c.toUpperCase()].toLowerCase();
}
if (protocol[c] && typeof protocol[c].cmd === 'function') {
const message = protocol[c].cmd.apply(null, args);
if (message) {
that.write(message, callback);
return true;
}
if (typeof callback === 'function') {
callback('cmd ' + c + ' ' + JSON.stringify(args) + ' failed');
}
return false;
}
if (typeof callback === 'function') {
callback('cmd ' + c + ' not implemented');
}
return false;
};
function parse(data) {
if (!data) {
return;
}
data = data.toString();
let message;
let command;
let p;
let rssi;
if (options.parse) {
command = data[0];
message = {};
if (commands[command]) {
p = commands[command].toLowerCase();
if (protocol[p] && typeof protocol[p].parse === 'function') {
message = protocol[p].parse(data);
}
}
else {
if (that.getReport > 0) {
let reportData = Number.parseInt(data.substr(0,2), 16);
message.report = {
knownMessages: (reportData & 0x01) == 0x01,
eachPacket: (reportData & 0x02) == 0x02,
detailedData: (reportData & 0x04) == 0x04,
monitorMode: (reportData & 0x08) == 0x08,
timing: (reportData & 0x10) == 0x10,
rssi: (reportData & 0x20) == 0x20,
FHTprotocolMessage: (reportData & 0x40) == 0x40,
rawRssi: (reportData & 0x80) == 0x80
}
message.availableTime = Number.parseInt(data.substr(2).replace(" ",""));
that.getReport--;
}
}
if (options.rssi) {
rssi = Number.parseInt(data.slice(-2), 16);
message.rssi = (rssi >= 128 ? (((rssi - 256) / 2) - 74) : ((rssi / 2) - 74));
}
}
that.emit('data', data, message);
}
return this;
};
util.inherits(Cul, EventEmitter);
module.exports = Cul;