UNPKG

iobroker.rfxcom

Version:
814 lines (711 loc) 33.4 kB
/* jshint -W097 */ /* jshint strict: false */ /* jslint node: true */ 'use strict'; // you have to require the utils module and call adapter function const utils = require('@iobroker/adapter-core'); // Get common adapter utils const adapterName = require('./package.json').name.split('.').pop(); const rfxcom = require('rfxcom'); let channels = {}; const Device = { RFY: require('./lib/rfy'), Lighting3: require('./lib/lighting3'), Curtain1: require('./lib/curtain1'), Lighting1: require('./lib/lighting1'), Weight: require('./lib/weight') }; let inclusionOn = false; let inclusionTimeout = null; const lastReceived = {}; let repairInterval = null; let connection = null; const devices = {}; let stateTasks = []; let rfyAll = null; let comm; let adapter; function startAdapter(options) { options = options || {}; Object.assign(options, { name: adapterName, }); adapter = new utils.Adapter(options); // is called when adapter shuts down - callback has to be called under any circumstances! adapter.on('unload', callback => { setConnState(false); try { if (repairInterval) { clearInterval(repairInterval); repairInterval = null; } if (comm) { comm.close(); comm.removeAllListeners(); } comm = null; callback && callback(); } catch (e) { callback && callback(); } }); // is called if a subscribed state changes adapter.on('stateChange', (id, state) => { if (!state || state.ack || !comm) { return; } const parts = id.split('.'); const command = parts.pop(); const channel = parts.join('.'); if (!channels[channel] || !devices[channel]) { adapter.log.warn(`Unknown device "${channel}"`); } else if (!devices[channel].commands.includes(command)) { adapter.log.warn(`Unknown command "${command}" for "${channel}"`); } else { devices[channel].sendCommand(command, state.val, err => { if (err) { adapter.log.error(`Cannot control "${command}" for "${channel}": ${err}`); } else { adapter.setForeignState(id, false, true); } }); } }); adapter.on('objectChange', (id, obj) => { if (!obj) { if (channels[id]) { delete channels[id]; } if (lastReceived[id]) { delete lastReceived[id]; } } else { if (obj.type === 'channel') { if (obj.native.autoRepair) { lastReceived[id] = new Date().getTime(); } else if (lastReceived[id]) { delete lastReceived[id]; } channels[id] = obj; } } }); adapter.on('message', obj => { if (obj) { switch (obj.command) { case 'listUart': if (obj.callback) { try { const serialport = require('serialport'); if (serialport) { // read all found serial ports serialport.list() .then(ports => { adapter.log.info(`List of port: ${JSON.stringify(ports)}`); adapter.sendTo(obj.from, obj.command, ports, obj.callback); }) .catch(e => { adapter.sendTo(obj.from, obj.command, [], obj.callback); adapter.log.error(e) }); } else { adapter.log.warn('Module serialport is not available'); adapter.sendTo(obj.from, obj.command, [{comName: 'Not available'}], obj.callback); } } catch (e) { adapter.sendTo(obj.from, obj.command, [{comName: 'Not available'}], obj.callback); } } break; case 'program': // find or create device with such DeviceID if (obj.message) { const id = `${adapter.namespace}.${obj.message.type || 'rfy'}.${obj.message.deviceId}_${obj.message.unitCode}`; if (devices[id]) { if (connection) { devices[id].program((err, response, seqnbr) => { if (err) { adapter.log.error(`Cannot program "${id}": ${err}`); } if (obj.callback) { adapter.sendTo(obj.from, obj.command, [{result: err}], obj.callback); } // request list of devices rfyAll.listRemotes((err, resp, seqNr) => err && adapter.log.error('Cannot ask listRemotes: ' + err)); }); } else { adapter.sendTo(obj.from, obj.command, [{result: 'No connection to RfxCom'}], obj.callback); } } else { if (connection) { if (Device[obj.message.type]) { // create device let device = new Device[obj.message.type](comm, { deviceId: obj.message.deviceId + '/' + obj.message.unitCode, subtype: obj.message.subtype }, adapter.log); device.program(err => { if (err) { adapter.log.error(`Cannot program "${obj.message.deviceId}/${obj.message.unitCode}": ${err}`); } if (obj.callback) { adapter.sendTo(obj.from, obj.command, [{result: err}], obj.callback); } // request list of devices rfyAll.listRemotes((err, resp, seqNr) => err && adapter.log.error('Cannot ask listRemotes: ' + err)); }); device = null; } else { adapter.log.error(`Unknown type "${obj.message.type}"`); adapter.sendTo(obj.from, obj.command, [{result: `Unknown type "${obj.message.type}"`}], obj.callback); } } else { adapter.sendTo(obj.from, obj.command, [{result: 'No connection to RfxCom'}], obj.callback); } } } break; case 'erase': // find or create device with such DeviceID if (obj.message) { if (obj.message.type !== 'RFY') { if (obj.callback) { adapter.sendTo(obj.from, obj.command, [{result: 'Only RFY devices can be erased'}], obj.callback); } return; } const idd = `${adapter.namespace}.${obj.message.type || 'rfy'}.${obj.message.deviceId}_${obj.message.unitCode}`; if (devices[idd]) { devices[idd].erase(err => { if (err) { adapter.log.error(`Cannot erase "${idd}": ${err}`); } if (obj.callback) { adapter.sendTo(obj.from, obj.command, [{result: err}], obj.callback); } // request list of devices rfyAll.listRemotes((err, resp, seqNr) => err && adapter.log.error('Cannot ask listRemotes: ' + err)); }); } else { if (Device[obj.message.type]) { // create device let ddevice = new Device[obj.message.type](comm, { deviceId: obj.message.deviceId + '/' + obj.message.unitCode, subtype: obj.message.subtype }, adapter.log); ddevice.erase(err => { if (err) { adapter.log.error(`Cannot erase "${obj.message.deviceId}/${obj.message.unitCode}": ${err}`); } if (obj.callback) { adapter.sendTo(obj.from, obj.command, [{result: err}], obj.callback); } // request list of devices rfyAll.listRemotes((err, resp, seqNr) => err && adapter.log.error('Cannot ask listRemotes: ' + err)); }); ddevice = null; } } } break; case 'eraseAll': // find or create device with such DeviceID if (obj.message) { if (obj.message.type !== 'RFY') { if (obj.callback) { adapter.sendTo(obj.from, obj.command, [{result: 'Only RFY devices can be erased'}], obj.callback); } return; } if (rfyAll) { rfyAll.eraseAll(err => { if (err) { adapter.log.error(`Cannot eraseAll: ${err}`); } if (obj.callback) { adapter.sendTo(obj.from, obj.command, [{result: err}], obj.callback); } // request list of devices rfyAll.listRemotes((err, resp, seqNr) => err && adapter.log.error('Cannot ask listRemotes: ' + err)); }); } else if (obj.callback) { adapter.sendTo(obj.from, obj.command, [{result: 'no connection to RfxCom'}], obj.callback); } } break; default: adapter.log.error('Unknown command: ' + obj.command); break; } } }); adapter.on('ready', () => main()); return adapter; } function setConnState(isConnected) { if (isConnected !== connection) { connection = isConnected; if (adapter && adapter.log) { adapter.log.info(`State: ${isConnected ? 'connected' : 'disconnected'}`); } if (adapter && adapter.setState) { adapter.setState('info.connection', isConnected, true); } } } function setInclusionState(val) { val = val === 'true' || val === true || val === 1 || val === '1'; inclusionOn = val; if (inclusionTimeout) { clearTimeout(inclusionTimeout); } inclusionTimeout = null; if (inclusionOn && adapter.config.inclusionTimeout) { inclusionTimeout = setTimeout(() => { inclusionOn = false; adapter.setState('inclusionOn', false, true); }, adapter.config.inclusionTimeout * 1000); } } const supportedEvents = { security1: processEvents, // Emitted when an X10 or similar security device reports a status change. bbq1: processSensors, // Emitted when a message is received from a Maverick ET-732 BBQ temperature sensor. temprain1: processSensors, // Emitted when a message is received from an Allecto temperature/rainfall weather sensor. temp1: processSensors, // Emitted when a message is received from a temperature sensor (inside/outside air temperature; pool water temperature). temp2: processSensors, // Emitted when a message is received from a temperature sensor (inside/outside air temperature; pool water temperature). temp3: processSensors, // Emitted when a message is received from a temperature sensor (inside/outside air temperature; pool water temperature). temp4: processSensors, // Emitted when a message is received from a temperature sensor (inside/outside air temperature; pool water temperature). temp5: processSensors, // Emitted when a message is received from a temperature sensor (inside/outside air temperature; pool water temperature). temp6: processSensors, // Emitted when a message is received from a temperature sensor (inside/outside air temperature; pool water temperature). temp7: processSensors, // Emitted when a message is received from a temperature sensor (inside/outside air temperature; pool water temperature). temp8: processSensors, // Emitted when a message is received from a temperature sensor (inside/outside air temperature; pool water temperature). temp9: processSensors, // Emitted when a message is received from a temperature sensor (inside/outside air temperature; pool water temperature). temp10: processSensors, // Emitted when a message is received from a temperature sensor (inside/outside air temperature; pool water temperature). temp11: processSensors, // Emitted when a message is received from a temperature sensor (inside/outside air temperature; pool water temperature). humidity1: processSensors, // Emitted when data arrives from humidity sensing devices th1: processSensors, // Emitted when a message is received from Oregon Scientific and other temperature/humidity sensors. th2: processSensors, // Emitted when a message is received from Oregon Scientific and other temperature/humidity sensors. th3: processSensors, // Emitted when a message is received from Oregon Scientific and other temperature/humidity sensors. th4: processSensors, // Emitted when a message is received from Oregon Scientific and other temperature/humidity sensors. th5: processSensors, // Emitted when a message is received from Oregon Scientific and other temperature/humidity sensors. th6: processSensors, // Emitted when a message is received from Oregon Scientific and other temperature/humidity sensors. th7: processSensors, // Emitted when a message is received from Oregon Scientific and other temperature/humidity sensors. th8: processSensors, // Emitted when a message is received from Oregon Scientific and other temperature/humidity sensors. th9: processSensors, // Emitted when a message is received from Oregon Scientific and other temperature/humidity sensors. th10: processSensors, // Emitted when a message is received from Oregon Scientific and other temperature/humidity sensors. th12: processSensors, // Emitted when a message is received from Oregon Scientific and other temperature/humidity sensors. th13: processSensors, // Emitted when a message is received from Oregon Scientific and other temperature/humidity sensors. th14: processSensors, // Emitted when a message is received from Oregon Scientific and other temperature/humidity sensors. thb1: processSensors, // Emitted when a message is received from an Oregon Scientific temperature/humidity/barometric pressure sensor. thb2: processSensors, // Emitted when a message is received from an Oregon Scientific temperature/humidity/barometric pressure sensor. rain1: processSensors, // Emitted when data arrives from rainfall sensing devices rain2: processSensors, // Emitted when data arrives from rainfall sensing devices rain3: processSensors, // Emitted when data arrives from rainfall sensing devices rain4: processSensors, // Emitted when data arrives from rainfall sensing devices rain5: processSensors, // Emitted when data arrives from rainfall sensing devices rain6: processSensors, // Emitted when data arrives from rainfall sensing devices rain7: processSensors, // Emitted when data arrives from rainfall sensing devices wind1: processSensors, // Emitted when data arrives from wind speed/direction sensors wind2: processSensors, // Emitted when data arrives from wind speed/direction sensors wind3: processSensors, // Emitted when data arrives from wind speed/direction sensors wind4: processSensors, // Emitted when data arrives from wind speed/direction sensors wind5: processSensors, // Emitted when data arrives from wind speed/direction sensors wind6: processSensors, // Emitted when data arrives from wind speed/direction sensors wind7: processSensors, // Emitted when data arrives from wind speed/direction sensors uv1: processSensors, // Emiied when data arrives from ultraviolet radiation sensors uv2: processSensors, // Emiied when data arrives from ultraviolet radiation sensors uv3: processSensors, // Emiied when data arrives from ultraviolet radiation sensors weight1: processWeight, // Emitted when a message is received from a weighing scale device. weight2: processWeight, // Emitted when a message is received from a weighing scale device. elec1: processEnergy, // Emitted when data is received from OWL or REVOLT electricity monitoring devices. elec2: processEnergy, // Emitted when data is received from OWL or REVOLT electricity monitoring devices. elec3: processEnergy, // Emitted when data is received from OWL or REVOLT electricity monitoring devices. elec4: processEnergy, // Emitted when data is received from OWL or REVOLT electricity monitoring devices. elec5: processEnergy, // Emitted when data is received from OWL or REVOLT electricity monitoring devices. rfxmeter: processEvents, // Emitted whan a message is received from an RFXCOM rfxmeter device. rfxsensor: processEvents, // Emitted when a message is received from an RFXCOM rfxsensor device. lighting1: processLighting, // Emitted when a message is received from X10, ARC, Energenie or similar lighting remote control devices. lighting2: processLighting, // Emitted when a message is received from AC/HomeEasy type remote control devices. lighting4: processLighting4, // Emitted when a message is received from devices using the PT2262 family chipset. lighting5: processLighting5, // Emitted when a message is received from LightwaveRF/Siemens type remote control devices. lighting6: processLighting6, // Emitted when a message is received from Blyss lighting remote control devices. blinds1: processBlinds, // Emitted when a message arrives from a compatible type 1 blinds remote controller (only a few subtypes can be received) chime1: processEvents // Emitted when data arrives from Byron or similar doorbell pushbutton }; function processEvents(event, data) { // evt.rssi // evt.housecode // evt.commandNumber // evt.unitcode // evt.temperature '°C' // evt.barometer 'hPa' // evt.direction '°' // evt.averageSpeed 'm/s' // evt.averageSpeed 'm/s' // evt.gustSpeed 'm/s' // evt.chillfactor '°C' // evt.humidity '%' // evt.rainfall 'mm' // evt.rainfallRate 'mm/hr' // evt.rainfallIncrement 'mm' // evt.uv 'UVIndex' // evt.forecast //if (event === 'lighting1') { // //} adapter.log.debug(`[${event}]: ${JSON.stringify(data)}`); } function syncStates(isChanged) { if (!stateTasks || !stateTasks.length) { return; } const task = stateTasks.shift(); if (typeof task.val === 'object' && task.val !== null && task.val !== undefined) { task.val = task.val.toString(); } if (isChanged) { adapter.setForeignStateChanged(task.id, task.val, true, () => setImmediate(syncStates, isChanged)); } else { adapter.setForeignState(task.id, task.val, true, () => setImmediate(syncStates, isChanged)); } } function getDevice(deviceId, unitCode, type, subType) { for (const id in channels) { if (channels.hasOwnProperty(id) && channels[id].native.deviceId === deviceId && channels[id].native.unitCode === unitCode) { if (!devices[id] && Device[type]) { devices[id] = new Device[type](deviceId + '/' + unitCode, subType); } return channels[id]; } } if (inclusionOn) { if (Device[type]) { const prefix = adapter.namespace + '.' + type + '.'; const idd = prefix + deviceId + '_' + unitCode; subType = Device[type].subTypes[subType]; if (subType === undefined) { adapter.log.warn(`Unknown device subType for "${type}": ${subType}`); } else { devices[idd] = new Device[type](comm, { deviceId: deviceId + '/' + unitCode, subtype: subType }, adapter.log); syncObjects(devices[idd].getObjects(prefix)); } } else { adapter.log.warn('Unknown device type: ' + type); } } else { return null; } } function processLighting(event, data) { event = event[0].toUpperCase() + event.substring(1); const dev = getDevice(data.housecode, data.unitcode, event, data.subtype); if (dev) { const isStart = !stateTasks.length; stateTasks = stateTasks.concat(dev.device.getStates(adapter.namespace + '.' + event + '.', data)); if (isStart) syncStates(true); } else { adapter.log.debug(event + ' ignored: ' + JSON.stringify(data)); } } function processLighting5(event, data) { // data.rssi // data.housecode // data.commandNumber // data.unitcode // data.subtype let val = false; switch (data.subtype) { case 0: // Lightwave RF switch (data.commandNumber) { case 0: case 2: val = false; break; case 1: val = true; break; case 3: case 4: case 5: case 6: case 7: msg.payload = 'Mood' + (evt.commandNumber - 2); break; case 16: val = data.level / 31 * 100; break; case 17: case 18: case 19: adapter.log.warn('Unrecognised Lighting5 LightwaveRF command ' + data.commandNumber.toString(16)); break; default: return; } break; case 2: case 4: // BBSB & CONRAD switch (data.commandNumber) { case 0: case 2: val = false; break; case 1: case 3: val = true; break; default: return; } break; case 6: // TRC02 switch (data.commandNumber) { case 0: val = false; break; case 1: val = true; break; case 2: val = 'Bright'; break; case 3: val = 'Dim'; break; default: adapter.log.warn('Unrecognised Lighting5 TRC02 command ' + data.commandNumber.toString(16)); return; } break; } } function processLighting6(event, data) { // data.rssi // data.housecode // data.commandNumber // data.unitcode // data.subtype let val = false; switch (data.commandNumber) { case 1: case 3: val = false; break; case 0: case 2: val = true; break; default: adapter.log.warn(`Unrecognised Lighting6 command ${data.commandNumber.toString(16)}`); return; } } // PT622 function processLighting4(event, data) { adapter.log.warn(`Unrecognised Lighting4 command ${JSON.stringify(data)}`); } function processSensors(event, data) { // evt.rssi // evt.housecode // evt.commandNumber // evt.unitcode // evt.temperature '°C' // evt.barometer 'hPa' // evt.direction '°' // evt.averageSpeed 'm/s' // evt.averageSpeed 'm/s' // evt.gustSpeed 'm/s' // evt.chillfactor '°C' // evt.humidity '%' // evt.rainfall 'mm' // evt.rainfallRate 'mm/hr' // evt.rainfallIncrement 'mm' // evt.uv 'UVIndex' // evt.forecast adapter.log.debug('[' + event + ']: ' + JSON.stringify(data)); } function processWeight(event, data) { event = event.replace(/\d$/, ''); const dev = getDevice(data.housecode, data.unitcode, event, data.subtype); if (dev) { const isStart = !stateTasks.length; stateTasks = stateTasks.concat(dev.device.getStates(adapter.namespace + '.' + event + '.', data)); if (isStart) { syncStates(true); } } else { adapter.log.debug(`${event} ignored: ${JSON.stringify(data)}`); } } function processEnergy(event, data) { // rssi // batteryLevel // voltage 'V' // current 'A' // power 'W' // energy 'Wh' // powerFactor // frequency 'Hz' } function processBlinds(event, data) { // rssi } function syncObjects(objs, callback) { if (!objs || !objs.length) { return callback && callback(); } const task = objs.shift(); adapter.getForeignObject(task._id, (err, obj) => { if (!obj) { adapter.setForeignObject(task._id, task, () => setImmediate(syncObjects, objs, callback)); } else { obj.native = task.native; obj.common.name = task.common.name; adapter.setForeignObject(obj._id, obj, () => setImmediate(syncObjects, objs, callback)); } }); } const responseCodes = [ 'ACK - transmit OK', 'ACK - transmit delayed', 'NAK - transmitter did not lock onto frequency', 'NAK - AC address not allowed', 'Command unknown or not supported by this device', 'Unknown RFY remote ID', 'Timed out waiting for response' ]; function device2string(device) { let id = parseInt(device.deviceId, 16); id = id.toString(16); if (id.length < 2) { id = '00000' + id; } else if (id.length < 3) { id = '0000' + id; } else if (id.length < 4) { id = '000' + id; } else if (id.length < 5) { id = '00' + id; } else if (id.length < 6) { id = '0' + id; } id = '0x' + id.substring(0, 6); id += '/' + parseInt(device.unitCode, 10); return id; } function start() { comm = new rfxcom.RfxCom(adapter.config.comName, {debug: true}); comm.debugLog = function (text) { adapter.log.debug('[rfxcom] ' + text); }; comm.on('ready', () => setConnState(true)); comm.on('disconnect', msg => { adapter.log.debug('Disconnected: ' + msg); setConnState(false); }); comm.on('connectfailed', () => { setConnState(false); adapter.log.error(`unable to open the serial port: "${adapter.config.comName}"`); }); comm.on('response', (desc, sequenceNum, responseCode) => adapter.log.debug(`Response: ${desc}, SeqNr: ${sequenceNum}, ${responseCodes[responseCode] ? responseCodes[responseCode] : '0x' + responseCode.toString(16)}`)); comm.on('receive', data => adapter.log.debug('Raw data: ' + data.toString())); comm.on('status', status => adapter.log.debug('JSON Status: ' + JSON.stringify(status))); comm.on('rfyremoteslist', list => { adapter.log.debug(`rfyremoteslist delivered ${list.length} devices`); if (list) { for (let d = 0; d < list.length; d++) { let found = false; // try to find this device for (let dd = 0; dd < adapter.config.devices.length; dd++) { if (list[d].deviceId === device2string(adapter.config.devices[dd])) { found = true; adapter.config.devices[dd].found = true; break; } } if (!found) { adapter.log.warn(`Device "${list[d].deviceId}" found in RfxCom, but not found in the configuration`); } } for (let ddd = 0; ddd < adapter.config.devices.length; ddd++) { if (!adapter.config.devices[ddd].found) { adapter.log.warn(`Device "${adapter.config.devices[ddd].name}(${device2string(adapter.config.devices[ddd])}) " found in configuration, but not found in the RfxCom`); } } } }); for (const event in supportedEvents) { (function (evt) { comm.on(evt, e => { adapter.log.debug(`Event "${evt}": ${JSON.stringify(e)}`); supportedEvents[evt](evt, e); }); })(event); } comm.initialise(() => { adapter.log.info('RfxCom initialised on ' + comm.device); rfyAll && rfyAll.listRemotes((err, resp, seqNr) => err && adapter.log.error('Cannot ask listRemotes: ' + err)); }); rfyAll = new rfxcom.Rfy(comm, 'RFY'); let objs = []; // create rfy devices for (let d = 0; d < adapter.config.devices.length; d++) { if (Device[adapter.config.devices[d].type]) { const prefix = adapter.namespace + '.' + adapter.config.devices[d].type + '.'; const id = prefix + adapter.config.devices[d].deviceId + '_' + adapter.config.devices[d].unitCode; devices[id] = new Device[adapter.config.devices[d].type](comm, { deviceId: adapter.config.devices[d].deviceId + '/' + adapter.config.devices[d].unitCode, subtype: adapter.config.devices[d].subType }, adapter.log); objs = objs.concat(objs, devices[id].getObjects(prefix, adapter.config.devices[d].name)); } else { adapter.log.warn('Unknown device type: ' + adapter.config.devices[d].type); } } syncObjects(objs); } function main() { adapter.config.inclusionTimeout = parseInt(adapter.config.inclusionTimeout, 10) || 0; adapter.getState('inclusionOn', (err, state) => setInclusionState(state ? state.val : false)); adapter.setState('info.connection', false, true); // there are two types of devices: rfy (only write) and all others // read current existing objects adapter.getForeignObjects(adapter.namespace + '.*', 'channel', (err, _channels) => { channels = _channels; // subscribe on changes adapter.subscribeStates('*'); adapter.subscribeObjects('*'); /*for (const id in channels) { if (!channels.hasOwnProperty(id)) continue; if (channels[id].native.autoRepair) lastReceived[id] = new Date().getTime(); }*/ if (adapter.config.comName) { start(); } else { adapter.log.warn('No COM port defined'); } }); } // If started as allInOne/compact mode => return function to create instance if (module && module.parent) { module.exports = startAdapter; } else { // or start the instance directly startAdapter(); }