From f84f7e7f20fc0f8c89399ec40cdc599553b8f33c Mon Sep 17 00:00:00 2001 From: Robin Rainton Date: Wed, 31 Aug 2022 11:03:38 +0200 Subject: [PATCH] #329 Moved TIC collector to a lib --- lib/ticcollector.js | 122 ++++++++++++++++++++++++++++++++++++++++++ smartmeter.js | 125 ++------------------------------------------ 2 files changed, 127 insertions(+), 120 deletions(-) create mode 100644 lib/ticcollector.js diff --git a/lib/ticcollector.js b/lib/ticcollector.js new file mode 100644 index 0000000..9187b64 --- /dev/null +++ b/lib/ticcollector.js @@ -0,0 +1,122 @@ +// Types, units of TIC fields +// TODO: add descriptions with translations +// TODO: add tri-phase +// TODO: add 'standard' mode +const ticStateCommon = { + 'ADCO': { type: 'string' }, + 'OPTARIF': { type: 'string' }, + 'ISOUSC': { type: 'number', unit: 'A', role: 'value.current' }, + 'BASE': { type: 'number', unit: 'Wh', role: 'value.power.consumption' }, + + 'HCHC': { type: 'number', unit: 'Wh', role: 'value.power.consumption' }, + 'HCHP': { type: 'number', unit: 'Wh', role: 'value.power.consumption' }, + + 'EJPHN': { type: 'number', unit: 'Wh', role: 'value.power.consumption' }, + 'EJPHPM': { type: 'number', unit: 'Wh', role: 'value.power.consumption' }, + + 'BBRHCJB': { type: 'number', unit: 'Wh', role: 'value.power.consumption' }, + 'BBRHPJB': { type: 'number', unit: 'Wh', role: 'value.power.consumption' }, + 'BBRHCJW': { type: 'number', unit: 'Wh', role: 'value.power.consumption' }, + 'BBRHPJW': { type: 'number', unit: 'Wh', role: 'value.power.consumption' }, + 'BBRHCJR': { type: 'number', unit: 'Wh', role: 'value.power.consumption' }, + 'BBRHPJR': { type: 'number', unit: 'Wh', role: 'value.power.consumption' }, + + 'PEJP': { type: 'number' /* In minutes */ }, + + 'PTEC': { type: 'string' }, + 'DEMAIN': { type: 'string' }, + + 'IINST': { type: 'number', unit: 'A', role: 'value.current' }, + 'ADPS': { type: 'number', unit: 'A', role: 'value.current' }, + 'IMAX': { type: 'number', unit: 'A', role: 'value.current' }, + + 'PAPP': { type: 'number', unit: 'VA' }, // TODO: no role for VA documented + + 'HHPHC': { type: 'string' }, + 'MOTDETAT': { type: 'string' } +}; + +function processTic(smOptions) { + const adapter = smOptions.adapter; + adapter.log.debug('Starting TIC processing'); + + var adco = false; + + const port = new smOptions.SerialPort({ + path: smOptions.transportSerialPort, + baudRate: smOptions.transportSerialBaudrate, + // TODO: there are options to override this which we currently ignore. Maybe handle them? + dataBits: 7, + parity: 'even' + }); + // TODO: clean up port, etc. on termination + + const { ReadlineParser } = require('@serialport/parser-readline'); + const parser = port.pipe(new ReadlineParser({ delimiter: '\n' })); + var nameValueCache = []; + parser.on('data', (data) => { + // data is a string + adapter.log.silly(data); + + const parts = data.split(/\s+/); + const name = parts.shift(); + // convert string value to number if necessary + const value = parts.shift(); + const checksum = parts.shift(); + // TODO: verify checksum; + + // TODO: Does the protocol cater for multiple ADCO values in the same stream? + if (name === 'ADCO') { + adco = value; + adapter.log.info('Found ID ' + adco); + smOptions.setConnected(true); + } + + if (ticStateCommon[name] == undefined) { + // We don't know what this field is so ignore it + adapter.log.warn('Unknown label (ignoring): ' + name); + } else if (nameValueCache[name] === value) { + // Nothing has changed since the last time, do nothing + } else if (adco) { + // Label known & new value so create channel/state set value + nameValueCache[name] = value; + + // Only create/set objects if the channel (adco) is known. + // TODO: this needs cleaning up + adapter.setObjectNotExists(adco, { + type: 'channel', + common: { + name: adco, + }, + native: {} + }, (err) => { + if (err) { + adapter.log.error('Failed to create channel ' + adco); + } else { + const stateName = adco + '.' + name; + adapter.setObjectNotExists(stateName, { + type: 'state', + common: ticStateCommon[name] + // TODO: other attributes? + }, (err) => { + if (err) { + adapter.log.error('Failed to create state ' + stateName); + } else { + // convert string value to number if necessary + adapter.setState(stateName, { + ack: true, + val: ticStateCommon[name].type == 'number' ? Number(value) : value + }, (err) => { + if (err) { + adapter.log.error('Failed to set state ' + stateName); + } + }); + } + }); + } + }); + } + }); +} + +module.exports = processTic; \ No newline at end of file diff --git a/smartmeter.js b/smartmeter.js index db5361b..c61f032 100644 --- a/smartmeter.js +++ b/smartmeter.js @@ -359,6 +359,11 @@ function main() { adapter.log.debug('Smartmeter options: ' + JSON.stringify(smOptions)); if (smOptions.protocol === 'TicProtocol') { + // Add stuff to smOptions that the TIC collector needs. + smOptions.adapter = adapter; + smOptions.SerialPort = SerialPort; + smOptions.setConnected = setConnected; + const processTic = require('./lib/ticcollector'); processTic(smOptions); } else { // OBIS by default @@ -532,126 +537,6 @@ function processMessage(obj) { } } -// Types, units of TIC fields -// TODO: add descriptions with translations -// TODO: add tri-phase -// TODO: add 'standard' mode -const ticStateCommon = { - 'ADCO': { type: 'string' }, - 'OPTARIF': { type: 'string' }, - 'ISOUSC': { type: 'number', unit: 'A', role: 'value.current' }, - 'BASE': { type: 'number', unit: 'Wh', role: 'value.power.consumption' }, - - 'HCHC': { type: 'number', unit: 'Wh', role: 'value.power.consumption' }, - 'HCHP': { type: 'number', unit: 'Wh', role: 'value.power.consumption' }, - - 'EJPHN': { type: 'number', unit: 'Wh', role: 'value.power.consumption' }, - 'EJPHPM': { type: 'number', unit: 'Wh', role: 'value.power.consumption' }, - - 'BBRHCJB': { type: 'number', unit: 'Wh', role: 'value.power.consumption' }, - 'BBRHPJB': { type: 'number', unit: 'Wh', role: 'value.power.consumption' }, - 'BBRHCJW': { type: 'number', unit: 'Wh', role: 'value.power.consumption' }, - 'BBRHPJW': { type: 'number', unit: 'Wh', role: 'value.power.consumption' }, - 'BBRHCJR': { type: 'number', unit: 'Wh', role: 'value.power.consumption' }, - 'BBRHPJR': { type: 'number', unit: 'Wh', role: 'value.power.consumption' }, - - 'PEJP': { type: 'number' /* In minutes */ }, - - 'PTEC': { type: 'string' }, - 'DEMAIN': { type: 'string' }, - - 'IINST': { type: 'number', unit: 'A', role: 'value.current' }, - 'ADPS': { type: 'number', unit: 'A', role: 'value.current' }, - 'IMAX': { type: 'number', unit: 'A', role: 'value.current' }, - - 'PAPP': { type: 'number', unit: 'VA' }, // TODO: no role for VA documented - - 'HHPHC': { type: 'string' }, - 'MOTDETAT': { type: 'string' } -}; - -function processTic(smOptions) { - adapter.log.debug('Starting TIC processing'); - - var adco = false; - - const port = new SerialPort({ - path: smOptions.transportSerialPort, - baudRate: smOptions.transportSerialBaudrate, - // TODO: there are options to override this which we currently ignore. Maybe handle them? - dataBits: 7, - parity: 'even' - }); - // TODO: clean up port, etc. on termination - - const { ReadlineParser } = require('@serialport/parser-readline'); - const parser = port.pipe(new ReadlineParser({ delimiter: '\n' })); - var nameValueCache = []; - parser.on('data', (data) => { - // data is a string - adapter.log.silly(data); - - const parts = data.split(/\s+/); - const name = parts.shift(); - // convert string value to number if necessary - const value = parts.shift(); - const checksum = parts.shift(); - // TODO: verify checksum; - - // TODO: Does the protocol cater for multiple ADCO values in the same stream? - if (name === 'ADCO') { - adco = value; - adapter.log.info('Found ID ' + adco); - setConnected(true); - } - - if (ticStateCommon[name] == undefined) { - // We don't know what this field is so ignore it - adapter.log.warn('Unknown label (ignoring): ' + name); - } else if (nameValueCache[name] === value) { - // Nothing has changed since the last time, do nothing - } else if (adco) { - // Label known & new value so create channel/state set value - nameValueCache[name] = value; - - // Only create/set objects if the channel (adco) is known. - // TODO: this needs cleaning up - adapter.setObjectNotExists(adco, { - type: 'channel', - common: { - name: adco, - }, - native: {} - }, (err) => { - if (err) { - adapter.log.error('Failed to create channel ' + adco); - } else { - const stateName = adco + '.' + name; - adapter.setObjectNotExists(stateName, { - type: 'state', - common: ticStateCommon[name] - // TODO: other attributes? - }, (err) => { - if (err) { - adapter.log.error('Failed to create state ' + stateName); - } else { - // convert string value to number if necessary - adapter.setState(stateName, { - ack: true, - val: ticStateCommon[name].type == 'number' ? Number(value) : value - }, (err) => { - if (err) { - adapter.log.error('Failed to set state ' + stateName); - } - }); - } - }); - } - }); - } - }); -} - // If started as allInOne/compact mode => return function to create instance if (module && module.parent) { module.exports = startAdapter;