@signalk/vedirect-serial-usb 1.2.0 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,30 +2,88 @@
2
2
 
3
3
  # vedirect-serial-usb
4
4
 
5
- > Signal K Node.js server plugin that reads and parses VE.direct data via serial USB [interface](https://www.victronenergy.com/accessories/ve-direct-to-usb-interface).
5
+ This code is a [Signal K Node Server](https://github.com/SignalK/signalk-server-node) plugin. It reads and parses Victron VE.Direct data.
6
6
 
7
+ The common way to connect to a Victron product is with the Victron [VE.Direct to USB interface](https://www.victronenergy.com/accessories/ve-direct-to-usb-interface) cable.
8
+ Alternatively, this plugin also supports receiving the data via UDP and on a TCP socket.
9
+
10
+ Compatible Victron products:
11
+
12
+ - BMV-700 series of Battery Monitors (reported to work also on the BMV-600)
13
+ - SmartShunt series of Battery Monitors
14
+ - SmartSolar Chargers
15
+ - BlueSolar Chargers
16
+ - Phoenix Inverters, including the Smarts, having a VE.Direct comms port
17
+
18
+ Note that above list may not be complete.
19
+
20
+ The TCP and UDP connection methods are to allow connecting to a Victron product
21
+ too far away to run a serial cable. Ie. using a bridge to LAN/Wi-Fi that takes
22
+ the serial data and makes it available on a TCP socket or sends it out as UDP
23
+ packets. There are many of such devices available, as well as example DIY projects.
24
+ A good example on how to make it yourself is explained here:
25
+ https://pysselilivet.blogspot.com/2021/02/victron-vedirect-with-raspberry.html.
26
+
27
+ Lastly, note that when having a Victron GX Device, you won't be needing this
28
+ plugin. Details for that [here](https://github.com/sbender9/signalk-venus-plugin).
7
29
 
8
30
  ### Installation
9
31
 
10
32
  Use the Signal K app store or install via NPM in the Signal K server root directory: `npm install @signalk/vedirect-serial-usb`
11
33
 
12
-
13
34
  ### Usage
14
35
 
15
36
  Set up the appropiate device on the settings page of this plugin in the Signal K server admin UI, for instance to `/dev/ttyUSB0` and enable the plugin. Your VE.Direct data will be available in Signal K format via various clients and apps.
16
37
 
17
38
  ### Connections
18
39
 
19
- - USB: Enter device path e.g `/dev/ttyUSB0`
20
- - UDP: Enter `port` to listen, e.g. default `7878`. To use UDP, leave USB entry empty
21
- - TCP: Enter `host` and `port` to connect. To use TCP, leave both USB and UDP entries empty
40
+ **Select device**
41
+
42
+ - Serial, UDP or TCP
43
+
44
+ **Connection details**
45
+
46
+ - Serial: Enter device path e.g `/dev/ttyUSB0`
47
+ - UDP: _`ignored`_
48
+ - TCP: Enter host `IP address`
49
+
50
+ **Port**
22
51
 
23
- Ignore checksum option: If you need to ignore checksum, use this option
52
+ - Serial: _`ignored`_
53
+ - UDP/TCP: `port`
54
+
55
+ **Ignore Checksum**
56
+
57
+ - If you want to ignore checksum, use this option. Default `ON`
58
+
59
+ **SK Paths**
60
+
61
+ - Give each device unique SK paths
62
+
63
+ ### Usage as a library
64
+
65
+ It's possible to use this plugin as a library, in other Node.js code. This feature simply wraps the plugin in an easy-to-consume manner, so functionality is identical. Example:
66
+
67
+ ```javascript
68
+ const VEDirect = require('@signalk/vedirect-serial-usb/standalone')
69
+ const consumer = new VEDirect({
70
+ device: '/dev/ttyUSB0',
71
+ ignoreChecksum: true,
72
+ mainBatt: 'House',
73
+ auxBatt: 'Starter',
74
+ solar: 'Main',
75
+ })
76
+
77
+ consumer.on('delta', (delta) => console.log('[onDelta]', delta))
78
+ consumer.stop() // stop the plugin, destruct the connections
79
+ consumer.start() // (re-)start the plugin
80
+ ```
24
81
 
25
82
  ### License
26
83
 
27
84
  ```
28
- Copyright 2018 Joachim Bakke <github@heiamoss.com> & Fabian Tollenaar <fabian@decipher.industries>
85
+ Copyright 2018 Joachim Bakke <github@heiamoss.com>, Fabian Tollenaar <fabian@decipher.industries> and
86
+ Karl-Erik Gustafsson <ke.gustafsson@gmail.com>
29
87
 
30
88
  Licensed under the Apache License, Version 2.0 (the "License");
31
89
  you may not use this file except in compliance with the License.
package/index.js CHANGED
@@ -2,92 +2,152 @@ const serial = require('./lib/serial')
2
2
  const udp = require('./lib/udp')
3
3
  const tcp = require('./lib/tcp')
4
4
  const Parser = require('./lib/Parser')
5
+ const VEDirect = require('./standalone')
5
6
 
6
7
  module.exports = function (app) {
7
- let parser = null
8
+ let parser = []
9
+ let shaddow = null
8
10
  const plugin = {}
9
-
10
11
  plugin.id = 'vedirect-signalk'
11
12
  plugin.name = 'VE.Direct to Signal K'
12
13
  plugin.description = plugin.name
13
14
 
14
- plugin.schema = {
15
- type: 'object',
16
- required: [
17
- 'device'
18
- ],
19
- properties: {
20
- device: {
21
- type: 'string',
22
- title: 'USB device',
23
- description: '/dev/ttyUSB0 for example'
24
- },
25
- udpPort: {
26
- type: 'number',
27
- title: 'UDP port',
28
- description: 'Leave USB device and TCP empty to use UDP',
29
- default: 7878
30
- },
31
- host: {
32
- type: 'string',
33
- title: 'TCP host',
34
- description: 'Leave USB device and UDP empty to use TCP'
35
- },
36
- tcpPort: {
37
- type: 'number',
38
- title: 'TCP port',
39
- description: 'Leave USB device and UDP empty to use TCP',
40
- },
41
- ignoreChecksum: {
42
- type: 'boolean',
43
- title: 'Ignore Checksum',
44
- default: true
45
- },
46
- mainBatt: {
47
- type: 'string',
48
- title: 'Main Battery name in SK path',
49
- default: 'House'
50
- },
51
- auxBatt: {
52
- type: 'string',
53
- title: 'Aux Battery name in SK path',
54
- default: 'Starter'
55
- },
56
- solar: {
57
- type: 'string',
58
- title: 'Solar name in SK path',
59
- default: 'Main'
60
- }
61
- }
62
- }
63
-
64
15
  plugin.start = function (options) {
65
- parser = new Parser(options)
16
+ shaddow = options;
17
+
18
+ if (typeof options.vedirect !== 'undefined') {
19
+ Object.keys(options.vedirect).forEach(items => {
20
+ parser[items] = new Parser(options)
66
21
 
67
- parser.on('delta', delta => {
68
- app.handleMessage('pluginId', delta)
69
- })
22
+ parser[items].on('delta', delta => {
23
+ app.handleMessage('pluginId', delta)
24
+ })
70
25
 
71
- if (options.device) {
72
- serial.open(options.device, parser)
73
- } else if (options.udpPort) {
74
- udp.listen(options.udpPort, parser, app.debug)
75
- } else if (options.tcpPort && options.host) {
76
- tcp.connect(options.host, options.tcpPort, parser, app.debug)
26
+ let type = options.vedirect[items].device;
27
+ let connection = options.vedirect[items].connection;
28
+ let port = options.vedirect[items].port;
29
+ if (type == 'Serial') {
30
+ serial.open(connection, parser, app.debug, items)
31
+ } else if (type == 'UDP') {
32
+ udp.listen(port, parser, app.debug, items)
33
+ } else if (type == 'TCP') {
34
+ tcp.connect(connection, port, parser, app.debug, items)
35
+ }
36
+ });
77
37
  } else {
78
- app.error('Configure either USB device, UDP port or TCP host and port')
79
- }
38
+ if (options.device) {
39
+ oldConfig = {vedirect: [{device: 'Serial', connection: options.device, ignoreChecksum: options.ignoreChecksum,
40
+ mainBatt: options.mainBatt, auxBatt: options.auxBatt, solar: options.solar}]}
41
+ } else if (options.udpPort) {
42
+ oldConfig = {vedirect: [{device: 'UDP', port: options.udpPort, ignoreChecksum: options.ignoreChecksum,
43
+ mainBatt: options.mainBatt, auxBatt: options.auxBatt, solar: options.solar}]}
44
+ } else if (options.host) {
45
+ oldConfig = {vedirect: [{device: 'TCP', connection: options.host, port: options.tcpPort, ignoreChecksum: options.ignoreChecksum,
46
+ mainBatt: options.mainBatt, auxBatt: options.auxBatt, solar: options.solar}]}
47
+ }
48
+ parser[0] = new Parser(oldConfig)
80
49
 
50
+ parser[0].on('delta', delta => {
51
+ app.handleMessage('pluginId', delta)
52
+ })
53
+
54
+ if (options.device) {
55
+ serial.open(options.device, parser, app.debug, 0)
56
+ } else if (options.udpPort) {
57
+ udp.listen(options.udpPort, parser, app.debug, 0)
58
+ } else if (options.host) {
59
+ tcp.connect(options.host, options.tcpPort, parser, app.debug, 0)
60
+ }
61
+ }
81
62
  }
82
63
 
83
64
  plugin.stop = function () {
84
- if (parser) {
85
- parser.removeAllListeners()
86
- parser = null
65
+ if (shaddow) {
66
+ if (typeof shaddow.vedirect !== 'undefined') {
67
+ Object.keys(shaddow.vedirect).forEach(items => {
68
+ parser[items].removeAllListeners()
69
+ parser[items] = null
70
+ let type = shaddow.vedirect[items].device;
71
+ if (type == 'Serial') {
72
+ serial.close(app.debug, items)
73
+ } else if (type == 'UDP') {
74
+ udp.close(app.debug, items)
75
+ } else {
76
+ tcp.close(app.debug, items)
77
+ }
78
+ });
79
+ } else {
80
+ parser[0].removeAllListeners()
81
+ parser[0] = null
82
+ if (shaddow.device) {
83
+ serial.close(app.debug, 0)
84
+ } else if (shaddow.udpPort) {
85
+ udp.close(app.debug, 0)
86
+ } else if (shaddow.host) {
87
+ tcp.close(app.debug, 0)
88
+ }
89
+ }
90
+ shaddow = null
87
91
  }
88
- serial.close()
89
- udp.close(app.debug)
90
- tcp.close(app.debug)
92
+ }
93
+
94
+ plugin.schema = {
95
+ type: 'object',
96
+ properties: {
97
+ vedirect: {
98
+ type: 'array',
99
+ title: 'Connections',
100
+ description: 'Connections to VE.Direct devices',
101
+ items: {
102
+ type: 'object',
103
+ required: [],
104
+ properties: {
105
+ device: {
106
+ type: 'string',
107
+ default: 'Serial',
108
+ title: 'Select device',
109
+ enum: [
110
+ 'Serial',
111
+ 'UDP',
112
+ 'TCP',
113
+ ],
114
+ },
115
+ connection: {
116
+ type: 'string',
117
+ title: 'Connection details',
118
+ description: 'Serial: e.g. /dev/ttyUSB0, UDP: ignored or TCP: IP address',
119
+ default: '/dev/ttyUSB0'
120
+ },
121
+ port: {
122
+ type: 'number',
123
+ title: 'Port',
124
+ description: 'Serial: ignored, UDP/TCP: port',
125
+ default: 7878
126
+ },
127
+ ignoreChecksum: {
128
+ type: 'boolean',
129
+ title: 'Ignore Checksum',
130
+ default: true
131
+ },
132
+ mainBatt: {
133
+ type: 'string',
134
+ title: 'Main Battery name in SK path',
135
+ default: 'House'
136
+ },
137
+ auxBatt: {
138
+ type: 'string',
139
+ title: 'Aux Battery name in SK path',
140
+ default: 'Starter'
141
+ },
142
+ solar: {
143
+ type: 'string',
144
+ title: 'Solar name in SK path',
145
+ default: 'Main'
146
+ }
147
+ },
148
+ },
149
+ },
150
+ },
91
151
  }
92
152
 
93
153
  return plugin
package/lib/Parser.js CHANGED
@@ -22,7 +22,7 @@ class VEDirectParser extends EventEmitter {
22
22
  this.sum = 0
23
23
  }
24
24
 
25
- addChunk (buf) {
25
+ addChunk (buf, items) {
26
26
  if (!Buffer.isBuffer(buf)) {
27
27
  return this.warn('addChunk: incoming data is not a buffer: ' + typeof buf)
28
28
  }
@@ -36,7 +36,7 @@ class VEDirectParser extends EventEmitter {
36
36
  this.cache += chunk
37
37
  if (chunk.toLowerCase().includes('checksum')) {
38
38
  // Last line of block. Verify checksum of block in cache and parse line-by-line if checksum is correct.
39
- this._verifyCacheAndParse()
39
+ this._verifyCacheAndParse(items)
40
40
  }
41
41
  }
42
42
 
@@ -75,9 +75,9 @@ class VEDirectParser extends EventEmitter {
75
75
  return Object.assign({}, this.data)
76
76
  }
77
77
 
78
- _verifyCacheAndParse () {
78
+ _verifyCacheAndParse (items) {
79
79
  // Verify checksum if ignoreChecksum isn't true.
80
- if (this.options.ignoreChecksum !== true && this.sum % 256 !== 0) {
80
+ if (this.options.vedirect[items].ignoreChecksum !== true && this.sum % 256 !== 0) {
81
81
  this.warn(`block checksum doesn't equal 0: ${this.sum % 256}`)
82
82
  this.cache = ''
83
83
  this.sum = 0
@@ -96,7 +96,7 @@ class VEDirectParser extends EventEmitter {
96
96
 
97
97
  this.cache = ''
98
98
  this.sum = 0
99
- this.generateDelta()
99
+ this.generateDelta(items)
100
100
  }
101
101
 
102
102
  _parse () {
@@ -309,11 +309,11 @@ class VEDirectParser extends EventEmitter {
309
309
  return this.options.productIds[pid]
310
310
  }
311
311
 
312
- generateDelta () {
312
+ generateDelta (items) {
313
313
  const keys = Object.keys(this.data)
314
314
 
315
315
  const values = keys.map(name => {
316
- const path = this.getPath(name)
316
+ const path = this.getPath(name, items)
317
317
  if (path === null) {
318
318
  return null
319
319
  }
@@ -342,7 +342,7 @@ class VEDirectParser extends EventEmitter {
342
342
  })
343
343
  }
344
344
 
345
- getPath (name) {
345
+ getPath (name, items) {
346
346
  return Object.keys(this.fields).reduce((found, key) => {
347
347
  const field = this.fields[key]
348
348
  if (field.name === name && field.hasOwnProperty('path')) {
@@ -351,15 +351,15 @@ class VEDirectParser extends EventEmitter {
351
351
 
352
352
  if (field.hasOwnProperty('unitId')) {
353
353
  if (field.unitId === 'mainBatt' && this.options.hasOwnProperty('mainBatt')) {
354
- unitID = this.options.mainBatt
354
+ unitID = this.options.vedirect[items].mainBatt
355
355
  }
356
356
 
357
357
  if (field.unitId === 'auxBatt' && this.options.hasOwnProperty('auxBatt')) {
358
- unitID = this.options.auxBatt
358
+ unitID = this.options.vedirect[items].auxBatt
359
359
  }
360
360
 
361
361
  if (field.unitId === 'solar' && this.options.hasOwnProperty('solar')) {
362
- unitID = this.options.solar
362
+ unitID = this.options.vedirect[items].solar
363
363
  }
364
364
 
365
365
  if ((!unitID || unitID === '') && this.options.hasOwnProperty('defaultUnitId') && typeof this.options.defaultUnitId === 'string') {
package/lib/fields.js CHANGED
@@ -83,6 +83,22 @@ module.exports = {
83
83
  units: 'V',
84
84
  type: 'metric'
85
85
  },
86
+ V2: {
87
+ name: 'auxBattVoltage',
88
+ path: 'electrical.batteries.*.voltage',
89
+ unitId: 'auxBatt',
90
+ value: common.mV,
91
+ units: 'V',
92
+ type: 'metric'
93
+ },
94
+ V3: {
95
+ name: 'aux2BattVoltage',
96
+ path: 'electrical.batteries.*.voltage',
97
+ unitId: 'aux2Batt',
98
+ value: common.mV,
99
+ units: 'V',
100
+ type: 'metric'
101
+ },
86
102
  VS: {
87
103
  name: 'auxBatteryVoltage',
88
104
  path: 'electrical.batteries.*.voltage',
@@ -126,6 +142,22 @@ module.exports = {
126
142
  units: 'A',
127
143
  type: 'metric'
128
144
  },
145
+ I2: {
146
+ name: 'auxbatteryCurrent',
147
+ path: 'electrical.batteries.*.current',
148
+ unitId: 'auxBatt',
149
+ value: common.mA,
150
+ units: 'A',
151
+ type: 'metric'
152
+ },
153
+ I3: {
154
+ name: 'aux2batteryCurrent',
155
+ path: 'electrical.batteries.*.current',
156
+ unitId: 'aux2Batt',
157
+ value: common.mA,
158
+ units: 'A',
159
+ type: 'metric'
160
+ },
129
161
  IL: {
130
162
  name: 'loadCurrent',
131
163
  path: 'electrical.solar.*.loadCurrent',
@@ -78,5 +78,7 @@
78
78
  "0xA274": "Phoenix Inverter 48V 1200VA 230V",
79
79
  "0xA279": "Phoenix Inverter 12V 1200VA 120V",
80
80
  "0xA27A": "Phoenix Inverter 24V 1200VA 120V",
81
- "0xA27C": "Phoenix Inverter 48V 1200VA 120V"
81
+ "0xA27C": "Phoenix Inverter 48V 1200VA 120V",
82
+ "0xA345": "Phoenix Smart Charger 12V 30A 3 rev2",
83
+ "0xA381": "BMV-712 Smart"
82
84
  }
package/lib/serial.js CHANGED
@@ -1,38 +1,48 @@
1
- const SerialPort = require('serialport')
2
- const Delimiter = require('@serialport/parser-delimiter')
3
- const debug = require('debug')('signalk-vedirect-parser')
4
- let port = null
5
-
6
- exports.open = function openSerialConnection (device, parser) {
7
- if (port !== null) {
8
- try {
9
- port.close()
10
- port = null
11
- } catch (e) {}
12
- }
1
+ const { SerialPort } = require('serialport')
2
+ const { DelimiterParser } = require('@serialport/parser-delimiter')
3
+ let port = []
4
+ let delim = []
5
+
6
+ module.exports = {
7
+ open: (device, parser, debug, items) => {
8
+ if (typeof port[items] !== 'undefined' ) {
9
+ try {
10
+ port[items].close()
11
+ port[items] = undefined
12
+ } catch (e) {}
13
+ }
14
+
15
+ debug(`Serial: connecting to ${device}`)
13
16
 
14
- port = new SerialPort(device, { baudRate: 19200 }) // @NOTE FT: should this be configurable?
17
+ port[items] = new SerialPort({ path: device, baudRate: 19200 }) // @NOTE FT: should this be configurable?
15
18
 
16
- const delim = port.pipe(new Delimiter({delimiter: '\r' , includeDelimiter: true}))
19
+ port[items].on('open', function() {
20
+ debug(`Connected to ${device}`)
21
+ })
17
22
 
18
- delim.on('data', chunk => {
19
- // Chunk is a node.js Buffer
20
- parser.addChunk(chunk)
21
- })
23
+ port[items].on('data', chunk => {
24
+ debug(`${chunk}`)
25
+ })
22
26
 
23
- // @NOTE FT: should we implement reconenct/back-off code like TCP wrappers?
24
- // I assume the serial port only closes if the physical line is broken/disconnected?
27
+ delim[items] = port[items].pipe(new DelimiterParser({ delimiter: '\r', includeDelimiter: true }))
25
28
 
26
- port.on('error', err => {
27
- debug('SerialPort error: ' + err.message)
28
- })
29
- }
29
+ delim[items].on('data', chunk => {
30
+ // Chunk is a node.js Buffer
31
+ parser[items].addChunk(chunk, items)
32
+ debug(`${chunk}`)
33
+ })
30
34
 
31
- exports.close = function (device) {
32
- if (port !== null) {
33
- try {
34
- port.close()
35
- port = null
36
- } catch (e) {}
35
+ port[items].on('error', err => {
36
+ debug(`SerialPort error: ` + err.message)
37
+ })
38
+ },
39
+ close: (debug, items) => {
40
+ if (port[items]) {
41
+ try {
42
+ port[items].close()
43
+ port[items] = undefined
44
+ debug(`Serial port closed`);
45
+ } catch (e) {}
46
+ }
37
47
  }
38
- }
48
+ }
package/lib/tcp.js CHANGED
@@ -1,45 +1,47 @@
1
1
  const Net = require('net');
2
- let client;
2
+ let client = [];
3
3
 
4
- function makeConnection(host, port, debug) {
5
- client = new Net.Socket();
6
- client.connect({ port: port, host: host });
4
+ function makeConnection(host, port, debug, items) {
5
+ client[items] = new Net.Socket();
6
+ client[items].connect({ port: port, host: host });
7
7
  debug(`Connection to ${host}:${port}`);
8
- return client
8
+ return client[items]
9
9
  }
10
10
 
11
- function onData(msg, parser, debug) {
12
- parser.addChunk(msg);
11
+ function onData(msg, parser, debug, items) {
12
+ parser[items].addChunk(msg, items);
13
13
  debug(`${msg}`);
14
14
  }
15
15
 
16
- function onClose(host, port, parser, debug) {
17
- if(client) {
16
+ function onClose(host, port, parser, debug, items) {
17
+ if(client[items]) {
18
18
  setTimeout(() => {
19
19
  debug('Trying to reconnect');
20
- client.destroy();
21
- module.exports.connect(host, port, parser, debug);
20
+ client[items].destroy();
21
+ module.exports.connect(host, port, parser, debug, items);
22
22
  }, 10000)
23
23
  }
24
24
  }
25
25
 
26
26
  module.exports = {
27
- connect: (host, port, parser, debug) => {
28
- client = makeConnection(host, port, debug);
29
- client.on('data', (msg) => {
30
- onData(msg, parser, debug);
27
+ connect: (host, port, parser, debug, items) => {
28
+ client[items] = makeConnection(host, port, debug, items);
29
+ client[items].on('data', (msg) => {
30
+ onData(msg, parser, debug, items);
31
31
  });
32
- client.on('close', () => {
33
- debug('Connection closed');
34
- onClose(host, port, parser, debug);
32
+ client[items].on('close', () => {
33
+ debug('TCP connection closed');
34
+ onClose(host, port, parser, debug, items);
35
35
  });
36
- client.on('error', () => {
37
- debug('Connection error');
36
+ client[items].on('error', () => {
37
+ debug('TCP connection error');
38
38
  });
39
39
  },
40
- close: (debug) => {
41
- if (client) {
42
- client.destroy();
40
+ close: (debug, items) => {
41
+ if (client[items]) {
42
+ client[items].destroy();
43
+ client[items] = null;
44
+ debug(`TCP port closed`);
43
45
  }
44
46
  }
45
47
  }
package/lib/udp.js CHANGED
@@ -1,25 +1,25 @@
1
1
  const dgram = require('dgram');
2
- let socket;
2
+ let socket = [];
3
3
 
4
4
  module.exports = {
5
- listen: (port, parser, debug) => {
6
- socket = dgram.createSocket('udp4');
5
+ listen: (port, parser, debug, items) => {
6
+ socket[items] = dgram.createSocket({ type: 'udp4', reuseAddr: true });
7
7
 
8
- socket.on('listening', () => {
8
+ socket[items].on('listening', () => {
9
9
  debug(`listening on UDP ${port}`);
10
10
  })
11
11
 
12
- socket.on('message', (msg, rinfo) => {
12
+ socket[items].on('message', (msg, rinfo) => {
13
13
  debug(`${rinfo.address}:${rinfo.port}:${msg}`);
14
- parser.addChunk(msg);
14
+ parser[items].addChunk(msg, items);
15
15
  })
16
16
 
17
- socket.bind(port);
17
+ socket[items].bind(port);
18
18
  },
19
- close: (debug) => {
20
- if (socket) {
21
- socket.close();
22
- socket = null;
19
+ close: (debug, items) => {
20
+ if (socket[items]) {
21
+ socket[items].close();
22
+ socket[items] = null;
23
23
  debug(`UDP port closed`);
24
24
  }
25
25
  }
package/migration.js ADDED
@@ -0,0 +1,54 @@
1
+ const fs = require('fs')
2
+ const path = require('path');
3
+
4
+ jsonPath = __dirname + '/../../../plugin-config-data';
5
+ fs.readFile(path.resolve(jsonPath, 'vedirect-signalk.json'), 'UTF-8', (err, data) => {
6
+ if (err) {
7
+ console.error(err)
8
+ return
9
+ }
10
+
11
+ let obj = JSON.parse(data);
12
+
13
+ if (typeof obj.configuration.vedirect == 'undefined' ) {
14
+ if (obj.configuration.device) {
15
+ obj.configuration.vedirect = [{
16
+ 'device':'Serial',
17
+ 'connection': obj.configuration.device,
18
+ 'port': '',
19
+ 'ignoreChecksum': obj.configuration.ignoreChecksum,
20
+ 'mainBatt': obj.configuration.mainBatt,
21
+ 'auxBatt': obj.configuration.auxBatt,
22
+ 'solar': obj.configuration.solar
23
+ }]
24
+ } else if (obj.configuration.udpPort) {
25
+ obj.configuration.vedirect = [{
26
+ 'device':'UDP',
27
+ 'connection': 'localhost',
28
+ 'port': obj.configuration.udpPort,
29
+ 'ignoreChecksum': obj.configuration.ignoreChecksum,
30
+ 'mainBatt': obj.configuration.mainBatt,
31
+ 'auxBatt': obj.configuration.auxBatt,
32
+ 'solar': obj.configuration.solar
33
+ }]
34
+ } else if (obj.configuration.host) {
35
+ obj.configuration.vedirect = [{
36
+ 'device':'TCP',
37
+ 'connection': obj.configuration.host,
38
+ 'port': obj.configuration.tcpPort,
39
+ 'ignoreChecksum': obj.configuration.ignoreChecksum,
40
+ 'mainBatt': obj.configuration.mainBatt,
41
+ 'auxBatt': obj.configuration.auxBatt,
42
+ 'solar': obj.configuration.solar
43
+ }]
44
+ }
45
+
46
+ let dataOut = JSON.stringify(obj, null, 2)
47
+ fs.writeFile(path.resolve(jsonPath, 'vedirect-signalk.json'), dataOut, 'utf8', function(err) {
48
+ if (err) {
49
+ console.error(err)
50
+ return
51
+ }
52
+ });
53
+ }
54
+ });
package/package.json CHANGED
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "name": "@signalk/vedirect-serial-usb",
3
- "version": "1.2.0",
3
+ "version": "1.4.0",
4
4
  "description": "Signal K Node.js server plugin to read VE.Direct data via serial USB",
5
5
  "main": "index.js",
6
6
  "scripts": {
7
7
  "test": "mocha",
8
- "push": "npm publish --access public --scope @signalk ."
8
+ "push": "npm publish --access public --scope @signalk .",
9
+ "postinstall": "node ./migration.js"
9
10
  },
10
11
  "keywords": [
11
12
  "signalk-node-server-plugin"
@@ -24,7 +25,7 @@
24
25
  "dependencies": {
25
26
  "debug": "^4.0.1",
26
27
  "package-lock.json": "^1.0.0",
27
- "serialport": "^9.0.0"
28
+ "serialport": "^11.0.0"
28
29
  },
29
30
  "standard": {
30
31
  "globals": [
@@ -37,6 +38,6 @@
37
38
  "devDependencies": {
38
39
  "chai": "^4.1.2",
39
40
  "chai-things": "^0.2.0",
40
- "mocha": "^6.0.0"
41
+ "mocha": "^10.2.0"
41
42
  }
42
43
  }
package/standalone.js ADDED
@@ -0,0 +1,67 @@
1
+ /**
2
+ * standalone.js
3
+ *
4
+ * @description exports a class that shims the Signal K server, in order to make this plugin available as a library in other software
5
+ * @module @signalk/vedirect-serial-usb
6
+ * @author Fabian Tollenaar <fabian@essense.ai> (https://essense.ai)
7
+ * @license Apache_2.0
8
+ */
9
+
10
+ const EventEmitter = require('events')
11
+ const SKPlugin = require('./index')
12
+ class VEDirect extends EventEmitter {
13
+ constructor (config = {}, _debug = false) {
14
+ super()
15
+
16
+ this._debug = _debug
17
+ this.app = {
18
+ handleMessage: (kind, data) => {
19
+ if (kind === 'pluginId') {
20
+ this.emit('delta', data)
21
+ return
22
+ }
23
+ this.emit(kind, data)
24
+ },
25
+ debug: (args) => this.debug(args),
26
+ options: {
27
+ device: 'Serial',
28
+ connection: '/dev/ttyUSB0',
29
+ port: 7878,
30
+ ignoreChecksum: true,
31
+ mainBatt: 'House',
32
+ auxBatt: 'Starter',
33
+ solar: 'Main',
34
+ ...(config || {}),
35
+ }
36
+ }
37
+
38
+ this.plugin = SKPlugin(this.app)
39
+ this.start()
40
+ }
41
+
42
+ start () {
43
+ if (!this.plugin || !this.plugin.hasOwnProperty('start')) {
44
+ this.debug(`Plugin not initialised, can't start`)
45
+ return
46
+ }
47
+
48
+ this.plugin.start(this.app.options)
49
+ }
50
+
51
+ stop () {
52
+ if (!this.plugin || !this.plugin.hasOwnProperty('stop')) {
53
+ this.debug(`Plugin not initialised, can't stop`)
54
+ return
55
+ }
56
+
57
+ this.plugin.stop()
58
+ }
59
+
60
+ debug (...args) {
61
+ if (this._debug) {
62
+ console.log.apply(console, args)
63
+ }
64
+ }
65
+ }
66
+
67
+ module.exports = VEDirect
package/test/index.js CHANGED
@@ -5,7 +5,7 @@ const Parser = require('../lib/Parser')
5
5
  const expect = chai.expect
6
6
 
7
7
  const parser = new Parser({
8
- ignoreChecksum: true
8
+ vedirect: [ { device: 'UDP', connection: 'localhost', port: 7878, ignoreChecksum: true, mainBatt: 'House', auxBatt: 'Starter', solar: 'Main' } ]
9
9
  })
10
10
 
11
11
  let lines = []
@@ -30,7 +30,7 @@ describe('VE.Direct parser', () => {
30
30
  it('Parses each line that has a Signal K representation withouth error', done => {
31
31
  try {
32
32
  let parsed = []
33
-
33
+
34
34
  const listener = delta => {
35
35
  parsed.push(delta)
36
36
  }
@@ -38,7 +38,7 @@ describe('VE.Direct parser', () => {
38
38
  parser.on('delta', listener)
39
39
 
40
40
  lines.forEach(line => {
41
- parser.addChunk(Buffer.from(line))
41
+ parser.addChunk(Buffer.from(line), 0)
42
42
  })
43
43
 
44
44
  parser.removeListener('delta', listener)