@signalk/vedirect-serial-usb 1.3.0 → 1.5.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 +31 -7
- package/index.js +9 -3
- package/lib/Parser.js +5 -0
- package/lib/fields.js +34 -0
- package/lib/productIds.json +3 -1
- package/lib/serial.js +6 -5
- package/lib/tcp.js +8 -2
- package/migration.js +3 -0
- package/package.json +3 -3
- package/standalone.js +68 -0
package/README.md
CHANGED
|
@@ -9,13 +9,13 @@ Alternatively, this plugin also supports receiving the data via UDP and on a TCP
|
|
|
9
9
|
|
|
10
10
|
Compatible Victron products:
|
|
11
11
|
|
|
12
|
-
- BMV-700 series of Battery Monitors
|
|
12
|
+
- BMV-700 series of Battery Monitors (reported to work also on the BMV-600)
|
|
13
13
|
- SmartShunt series of Battery Monitors
|
|
14
14
|
- SmartSolar Chargers
|
|
15
15
|
- BlueSolar Chargers
|
|
16
16
|
- Phoenix Inverters, including the Smarts, having a VE.Direct comms port
|
|
17
17
|
|
|
18
|
-
Note that above list may not be complete.
|
|
18
|
+
Note that above list may not be complete.
|
|
19
19
|
|
|
20
20
|
The TCP and UDP connection methods are to allow connecting to a Victron product
|
|
21
21
|
too far away to run a serial cable. Ie. using a bridge to LAN/Wi-Fi that takes
|
|
@@ -31,34 +31,58 @@ plugin. Details for that [here](https://github.com/sbender9/signalk-venus-plugin
|
|
|
31
31
|
|
|
32
32
|
Use the Signal K app store or install via NPM in the Signal K server root directory: `npm install @signalk/vedirect-serial-usb`
|
|
33
33
|
|
|
34
|
-
|
|
35
34
|
### Usage
|
|
36
35
|
|
|
37
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.
|
|
38
37
|
|
|
39
38
|
### Connections
|
|
39
|
+
|
|
40
40
|
**Select device**
|
|
41
|
+
|
|
41
42
|
- Serial, UDP or TCP
|
|
42
43
|
|
|
43
44
|
**Connection details**
|
|
45
|
+
|
|
44
46
|
- Serial: Enter device path e.g `/dev/ttyUSB0`
|
|
45
|
-
- UDP:
|
|
47
|
+
- UDP: _`ignored`_
|
|
46
48
|
- TCP: Enter host `IP address`
|
|
47
49
|
|
|
48
50
|
**Port**
|
|
49
|
-
|
|
51
|
+
|
|
52
|
+
- Serial: _`ignored`_
|
|
50
53
|
- UDP/TCP: `port`
|
|
51
54
|
|
|
52
55
|
**Ignore Checksum**
|
|
56
|
+
|
|
53
57
|
- If you want to ignore checksum, use this option. Default `ON`
|
|
54
58
|
|
|
55
59
|
**SK Paths**
|
|
56
|
-
|
|
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
|
+
```
|
|
57
81
|
|
|
58
82
|
### License
|
|
59
83
|
|
|
60
84
|
```
|
|
61
|
-
Copyright 2018 Joachim Bakke <github@heiamoss.com>, Fabian Tollenaar <fabian@decipher.industries> and
|
|
85
|
+
Copyright 2018 Joachim Bakke <github@heiamoss.com>, Fabian Tollenaar <fabian@decipher.industries> and
|
|
62
86
|
Karl-Erik Gustafsson <ke.gustafsson@gmail.com>
|
|
63
87
|
|
|
64
88
|
Licensed under the Apache License, Version 2.0 (the "License");
|
package/index.js
CHANGED
|
@@ -2,6 +2,7 @@ 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
8
|
let parser = []
|
|
@@ -36,13 +37,13 @@ module.exports = function (app) {
|
|
|
36
37
|
} else {
|
|
37
38
|
if (options.device) {
|
|
38
39
|
oldConfig = {vedirect: [{device: 'Serial', connection: options.device, ignoreChecksum: options.ignoreChecksum,
|
|
39
|
-
mainBatt: options.mainBatt, auxBatt: options.auxBatt, solar: options.solar}]}
|
|
40
|
+
mainBatt: options.mainBatt, auxBatt: options.auxBatt, bmv: options.bmv, solar: options.solar}]}
|
|
40
41
|
} else if (options.udpPort) {
|
|
41
42
|
oldConfig = {vedirect: [{device: 'UDP', port: options.udpPort, ignoreChecksum: options.ignoreChecksum,
|
|
42
|
-
mainBatt: options.mainBatt, auxBatt: options.auxBatt, solar: options.solar}]}
|
|
43
|
+
mainBatt: options.mainBatt, auxBatt: options.auxBatt, bmv: options.bmv, solar: options.solar}]}
|
|
43
44
|
} else if (options.host) {
|
|
44
45
|
oldConfig = {vedirect: [{device: 'TCP', connection: options.host, port: options.tcpPort, ignoreChecksum: options.ignoreChecksum,
|
|
45
|
-
mainBatt: options.mainBatt, auxBatt: options.auxBatt, solar: options.solar}]}
|
|
46
|
+
mainBatt: options.mainBatt, auxBatt: options.auxBatt, bmv: options.bmv, solar: options.solar}]}
|
|
46
47
|
}
|
|
47
48
|
parser[0] = new Parser(oldConfig)
|
|
48
49
|
|
|
@@ -138,6 +139,11 @@ module.exports = function (app) {
|
|
|
138
139
|
title: 'Aux Battery name in SK path',
|
|
139
140
|
default: 'Starter'
|
|
140
141
|
},
|
|
142
|
+
bmv: {
|
|
143
|
+
type: 'string',
|
|
144
|
+
title: 'BMV name in SK path',
|
|
145
|
+
default: 'bmv'
|
|
146
|
+
},
|
|
141
147
|
solar: {
|
|
142
148
|
type: 'string',
|
|
143
149
|
title: 'Solar name in SK path',
|
package/lib/Parser.js
CHANGED
|
@@ -7,6 +7,7 @@ const defaults = {
|
|
|
7
7
|
defaultUnitId: 'victronDevice',
|
|
8
8
|
mainBatt: 'house',
|
|
9
9
|
auxBatt: 'starter',
|
|
10
|
+
bmv: 'bmv',
|
|
10
11
|
solar: 'solar'
|
|
11
12
|
}
|
|
12
13
|
|
|
@@ -358,6 +359,10 @@ class VEDirectParser extends EventEmitter {
|
|
|
358
359
|
unitID = this.options.vedirect[items].auxBatt
|
|
359
360
|
}
|
|
360
361
|
|
|
362
|
+
if (field.unitId === 'bmv' && this.options.hasOwnProperty('bmv')) {
|
|
363
|
+
unitID = this.options.vedirect[items].bmv
|
|
364
|
+
}
|
|
365
|
+
|
|
361
366
|
if (field.unitId === 'solar' && this.options.hasOwnProperty('solar')) {
|
|
362
367
|
unitID = this.options.vedirect[items].solar
|
|
363
368
|
}
|
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',
|
|
@@ -209,6 +241,8 @@ module.exports = {
|
|
|
209
241
|
RELAY: {
|
|
210
242
|
name: 'relay',
|
|
211
243
|
type: 'boolean',
|
|
244
|
+
path: 'electrical.batteries.*.relay',
|
|
245
|
+
unitId: 'bmv',
|
|
212
246
|
value (value) {
|
|
213
247
|
value = value.toUpperCase()
|
|
214
248
|
return value === 'ON' ? 1 : 0
|
package/lib/productIds.json
CHANGED
|
@@ -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,11 +1,10 @@
|
|
|
1
|
-
const SerialPort = require('serialport')
|
|
2
|
-
const
|
|
1
|
+
const { SerialPort } = require('serialport')
|
|
2
|
+
const { DelimiterParser } = require('@serialport/parser-delimiter')
|
|
3
3
|
let port = []
|
|
4
4
|
let delim = []
|
|
5
5
|
|
|
6
6
|
module.exports = {
|
|
7
7
|
open: (device, parser, debug, items) => {
|
|
8
|
-
console.log(port[items])
|
|
9
8
|
if (typeof port[items] !== 'undefined' ) {
|
|
10
9
|
try {
|
|
11
10
|
port[items].close()
|
|
@@ -13,7 +12,9 @@ module.exports = {
|
|
|
13
12
|
} catch (e) {}
|
|
14
13
|
}
|
|
15
14
|
|
|
16
|
-
|
|
15
|
+
debug(`Serial: connecting to ${device}`)
|
|
16
|
+
|
|
17
|
+
port[items] = new SerialPort({ path: device, baudRate: 19200 }) // @NOTE FT: should this be configurable?
|
|
17
18
|
|
|
18
19
|
port[items].on('open', function() {
|
|
19
20
|
debug(`Connected to ${device}`)
|
|
@@ -23,7 +24,7 @@ module.exports = {
|
|
|
23
24
|
debug(`${chunk}`)
|
|
24
25
|
})
|
|
25
26
|
|
|
26
|
-
delim[items] = port[items].pipe(new
|
|
27
|
+
delim[items] = port[items].pipe(new DelimiterParser({ delimiter: '\r', includeDelimiter: true }))
|
|
27
28
|
|
|
28
29
|
delim[items].on('data', chunk => {
|
|
29
30
|
// Chunk is a node.js Buffer
|
package/lib/tcp.js
CHANGED
|
@@ -4,6 +4,7 @@ let client = [];
|
|
|
4
4
|
function makeConnection(host, port, debug, items) {
|
|
5
5
|
client[items] = new Net.Socket();
|
|
6
6
|
client[items].connect({ port: port, host: host });
|
|
7
|
+
client[items].setTimeout(5000);
|
|
7
8
|
debug(`Connection to ${host}:${port}`);
|
|
8
9
|
return client[items]
|
|
9
10
|
}
|
|
@@ -17,9 +18,8 @@ function onClose(host, port, parser, debug, items) {
|
|
|
17
18
|
if(client[items]) {
|
|
18
19
|
setTimeout(() => {
|
|
19
20
|
debug('Trying to reconnect');
|
|
20
|
-
client[items].destroy();
|
|
21
21
|
module.exports.connect(host, port, parser, debug, items);
|
|
22
|
-
}, 10000)
|
|
22
|
+
}, 10000);
|
|
23
23
|
}
|
|
24
24
|
}
|
|
25
25
|
|
|
@@ -31,11 +31,17 @@ module.exports = {
|
|
|
31
31
|
});
|
|
32
32
|
client[items].on('close', () => {
|
|
33
33
|
debug('TCP connection closed');
|
|
34
|
+
client[items].destroy();
|
|
34
35
|
onClose(host, port, parser, debug, items);
|
|
35
36
|
});
|
|
36
37
|
client[items].on('error', () => {
|
|
37
38
|
debug('TCP connection error');
|
|
39
|
+
client[items].destroy();
|
|
38
40
|
});
|
|
41
|
+
client[items].on('timeout', () => {
|
|
42
|
+
debug('TCP connection timeout');
|
|
43
|
+
client[items].destroy();
|
|
44
|
+
});
|
|
39
45
|
},
|
|
40
46
|
close: (debug, items) => {
|
|
41
47
|
if (client[items]) {
|
package/migration.js
CHANGED
|
@@ -19,6 +19,7 @@ fs.readFile(path.resolve(jsonPath, 'vedirect-signalk.json'), 'UTF-8', (err, data
|
|
|
19
19
|
'ignoreChecksum': obj.configuration.ignoreChecksum,
|
|
20
20
|
'mainBatt': obj.configuration.mainBatt,
|
|
21
21
|
'auxBatt': obj.configuration.auxBatt,
|
|
22
|
+
'bmv': obj.configuration.bmv,
|
|
22
23
|
'solar': obj.configuration.solar
|
|
23
24
|
}]
|
|
24
25
|
} else if (obj.configuration.udpPort) {
|
|
@@ -29,6 +30,7 @@ fs.readFile(path.resolve(jsonPath, 'vedirect-signalk.json'), 'UTF-8', (err, data
|
|
|
29
30
|
'ignoreChecksum': obj.configuration.ignoreChecksum,
|
|
30
31
|
'mainBatt': obj.configuration.mainBatt,
|
|
31
32
|
'auxBatt': obj.configuration.auxBatt,
|
|
33
|
+
'bmv': obj.configuration.bmv,
|
|
32
34
|
'solar': obj.configuration.solar
|
|
33
35
|
}]
|
|
34
36
|
} else if (obj.configuration.host) {
|
|
@@ -39,6 +41,7 @@ fs.readFile(path.resolve(jsonPath, 'vedirect-signalk.json'), 'UTF-8', (err, data
|
|
|
39
41
|
'ignoreChecksum': obj.configuration.ignoreChecksum,
|
|
40
42
|
'mainBatt': obj.configuration.mainBatt,
|
|
41
43
|
'auxBatt': obj.configuration.auxBatt,
|
|
44
|
+
'bmv': obj.configuration.bmv,
|
|
42
45
|
'solar': obj.configuration.solar
|
|
43
46
|
}]
|
|
44
47
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@signalk/vedirect-serial-usb",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.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": {
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"dependencies": {
|
|
26
26
|
"debug": "^4.0.1",
|
|
27
27
|
"package-lock.json": "^1.0.0",
|
|
28
|
-
"serialport": "^
|
|
28
|
+
"serialport": "^11.0.0"
|
|
29
29
|
},
|
|
30
30
|
"standard": {
|
|
31
31
|
"globals": [
|
|
@@ -38,6 +38,6 @@
|
|
|
38
38
|
"devDependencies": {
|
|
39
39
|
"chai": "^4.1.2",
|
|
40
40
|
"chai-things": "^0.2.0",
|
|
41
|
-
"mocha": "^
|
|
41
|
+
"mocha": "^10.2.0"
|
|
42
42
|
}
|
|
43
43
|
}
|
package/standalone.js
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
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
|
+
bmv: 'bmv',
|
|
34
|
+
solar: 'Main',
|
|
35
|
+
...(config || {}),
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
this.plugin = SKPlugin(this.app)
|
|
40
|
+
this.start()
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
start () {
|
|
44
|
+
if (!this.plugin || !this.plugin.hasOwnProperty('start')) {
|
|
45
|
+
this.debug(`Plugin not initialised, can't start`)
|
|
46
|
+
return
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
this.plugin.start(this.app.options)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
stop () {
|
|
53
|
+
if (!this.plugin || !this.plugin.hasOwnProperty('stop')) {
|
|
54
|
+
this.debug(`Plugin not initialised, can't stop`)
|
|
55
|
+
return
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
this.plugin.stop()
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
debug (...args) {
|
|
62
|
+
if (this._debug) {
|
|
63
|
+
console.log.apply(console, args)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
module.exports = VEDirect
|