signalk-czone 0.3.0-beta.14 → 0.3.0-beta.16
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/CHANGELOG.md +13 -0
- package/README.md +46 -3
- package/index.js +113 -10
- package/lib/inputs.js +94 -0
- package/lib/victron/switches.js +635 -0
- package/lib/victron/vedbus.js +182 -0
- package/package.json +3 -3
- package/public/index.html +227 -4
- package/public/remoteEntry.js +35 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.3.0-beta.16
|
|
4
|
+
|
|
5
|
+
- Adds an opt-in Victron GX switch pane for CZone circuits on the GX, Remote Console and VRM (#33).
|
|
6
|
+
- Preserves write-control and confirmation rules, synchronises observed state, and keeps GX names and grouping settings.
|
|
7
|
+
- Resets on circuits when changing to momentary mode; refuses blocked changes and restores the previous type if CZone does not confirm OFF.
|
|
8
|
+
|
|
9
|
+
## 0.3.0-beta.15
|
|
10
|
+
|
|
11
|
+
- Preserves plugin settings when uploading a ZCF or selecting a network configuration (#32).
|
|
12
|
+
- Publishes switch-input states for Signal Interface, COI and Control X PLUS modules (#30).
|
|
13
|
+
- Adds the Navico chartplotter touch layout, drag scrolling and reduced redraw cost (#31).
|
|
14
|
+
- Updates the canonical ZCF parser to 0.1.0-beta.2 for newer Configuration Tool layouts and corrected tank settings.
|
|
15
|
+
|
|
3
16
|
## 0.3.0-beta.14
|
|
4
17
|
|
|
5
18
|
- Adds bidirectional CZone links to Shelly Gen2+ switches and Victron GX outputs, with startup, confirmation and relay-function safeguards.
|
package/README.md
CHANGED
|
@@ -57,6 +57,28 @@ The existing electrical-monitoring definition endpoint remains:
|
|
|
57
57
|
GET /plugins/signalk-czone/monitoring
|
|
58
58
|
```
|
|
59
59
|
|
|
60
|
+
## Switch inputs
|
|
61
|
+
|
|
62
|
+
Each switch input in the ZCF (a switch, float switch or signal wired to a CZone module) is published as `electrical.czone.inputs.<input name>.state`, `true` when the input is on. The name is cleaned the same way as circuit names; a repeated name gets `2`, `3` and so on. A value is published when the plugin first hears the module and then only on change.
|
|
63
|
+
|
|
64
|
+
Where an input's state is read depends on the kind of module (its type in the ZCF):
|
|
65
|
+
|
|
66
|
+
| Module | Message read | Input `i` is at |
|
|
67
|
+
| --- | --- | --- |
|
|
68
|
+
| Signal Interface | 65284 status and 65308 input message | bit `2i+1` |
|
|
69
|
+
| COI | 65308 input message only (its 65284 is its outputs) | bit `2(i-16)+1` |
|
|
70
|
+
| Control X PLUS | 65284 status only | bit `i`, or `i+1` when wired to switch to negative |
|
|
71
|
+
|
|
72
|
+
Inputs on other kinds of module are listed but not given a state. Checked on a Signal Interface on a test bench, COIs on SugarShack and a Control X PLUS on Compass Rose (Anchor Up, Ignition, High Bilge Water Alarm). Only one Control X PLUS input wired to negative has been seen.
|
|
73
|
+
|
|
74
|
+
```text
|
|
75
|
+
GET /plugins/signalk-czone/inputs
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
lists each input with its module, input number, wiring, module type, state and when it last changed.
|
|
79
|
+
|
|
80
|
+
The input table needs `signalk-czone-zcf` with `signalInputs` (the newer Configuration Tool layout included).
|
|
81
|
+
|
|
60
82
|
## Design
|
|
61
83
|
|
|
62
84
|
The plugin does **not** modify Signal K Server, canboatjs, n2k-signalk, or any global PGN definitions.
|
|
@@ -213,9 +235,21 @@ The switch follows the CZone circuit, and the circuit follows the switch when it
|
|
|
213
235
|
|
|
214
236
|
The switch is read every 2 s; a CZone change is acted on as its status message arrives. `GET /plugins/signalk-czone/bridge/status` shows each link; `/bridge/outputs` lists the GX's outputs; `/bridge/shelly?host=&channel=` asks a Shelly. Tested on a bench (Shelly PM1 both ways) and on a boat (GX relay 2 switched from a CZone display; the alarm relay refused).
|
|
215
237
|
|
|
238
|
+
## Victron switch pane
|
|
239
|
+
|
|
240
|
+
On a Victron GX (Venus OS 3.60 or later), tick **Show CZone circuits in the Victron switch pane** in the configuration panel and the circuits appear as switches on the GX touch screen, Remote Console and VRM. The plugin registers `com.victronenergy.switch.czone` on the GX's D-Bus, following Victron's switchable-output API.
|
|
241
|
+
|
|
242
|
+
- **Which circuits:** those the webapp lists, one card per CZone category (the first sub-category, in a fixed priority, when there are several; DC or AC when there are none). A circuit linked to a third-party switch is left out, since that switch is already in the pane.
|
|
243
|
+
- **Both ways:** a tap in the pane switches the circuit with the same code as a Signal K PUT, so it needs **Enable CZone read/write control**, and a circuit set to confirm before turning off (on) is not turned off (on) from the pane unless other apps are allowed to; the pane's switch goes back. A change made anywhere else shows in the pane, from the plugin's own decoded 65284 state, checked every two seconds.
|
|
244
|
+
- **Label:** the circuit name, plus the current while it is on ("Freezer (2.9 A)"; untick **Show circuit current** to leave it off). A reading that only wobbles does not change the label, since every rename makes the GX re-sort its lists. Names, groups, types and the device's name can be edited on the GX and are kept (`victron-switches.json` in the plugin's data folder).
|
|
245
|
+
- **Device list:** Settings > Devices lists each switch as "CZone: name".
|
|
246
|
+
- `GET /plugins/signalk-czone/victron/status` reports whether it is running, which circuits the pane has as on, any that disagree with the plugin, and the last few requests from the pane.
|
|
247
|
+
|
|
248
|
+
Off a GX, or without `dbus-native` (an optional dependency), it reports that D-Bus is not available and the rest of the plugin is unaffected.
|
|
249
|
+
|
|
216
250
|
## Installation
|
|
217
251
|
|
|
218
|
-
The plugin installs the pinned `signalk-czone-zcf@0.1.0-beta.
|
|
252
|
+
The plugin installs the pinned `signalk-czone-zcf@0.1.0-beta.2` npm dependency automatically. No separate parser installation or Git checkout is required.
|
|
219
253
|
|
|
220
254
|
### Signal K App Store
|
|
221
255
|
|
|
@@ -239,11 +273,11 @@ npm install signalk-czone@latest --omit=dev --ignore-scripts
|
|
|
239
273
|
|
|
240
274
|
Restart Signal K and follow the configuration steps above. For Docker or appliance installations, use the corresponding container/configuration directory and restart method.
|
|
241
275
|
|
|
242
|
-
For installation from a maintainer-provided npm tarball, run `npm install /path/to/signalk-czone-0.3.0-beta.
|
|
276
|
+
For installation from a maintainer-provided npm tarball, run `npm install /path/to/signalk-czone-0.3.0-beta.16.tgz --omit=dev --ignore-scripts` from the same configuration directory. Its pinned parser dependency must already be published to npm. Do not simply extract the plugin into `node_modules`: that does not install dependencies.
|
|
243
277
|
|
|
244
278
|
### Publishing (maintainers)
|
|
245
279
|
|
|
246
|
-
Publish `signalk-czone-zcf@0.1.0-beta.
|
|
280
|
+
Publish `signalk-czone-zcf@0.1.0-beta.2` first. Then run `npm install`, `npm test`, and `npm pack --dry-run` in this repository, verify the runtime files and App Store assets, and run `npm publish --tag latest` after npm authentication.
|
|
247
281
|
|
|
248
282
|
The package defaults to `latest`, including versions named `-beta.N`. The GitHub publishing workflow runs when `package.json` or the workflow changes on `main`: it tests and packs the release, publishes it if the version is new, and sets and verifies the `latest` tag. If the version already exists, the workflow promotes that existing version without republishing its contents.
|
|
249
283
|
|
|
@@ -442,6 +476,15 @@ Only the installed path is stored in plugin configuration; the binary ZCF is not
|
|
|
442
476
|
|
|
443
477
|
The beta is intended for extended real-world testing before a stable `0.3.0` release.
|
|
444
478
|
|
|
479
|
+
## On a Navico chartplotter (B&G, Simrad, Lowrance)
|
|
480
|
+
|
|
481
|
+
A Navico plotter shows the webapp in its own browser (Chromium 69), driven by a touch screen whose touches arrive as mouse events. The page recognises the plotter from the parameters the plotter adds to the address (`mfd_name`, `mfd_model_detail`, `brand`), or from the login token left by `signalk-navico-embedder`, and switches to a touch layout. `?layout=mfd` shows it in a desktop browser for testing. Nothing changes anywhere else.
|
|
482
|
+
|
|
483
|
+
- **Touch layout:** larger rows, buttons and navigation; no banner, overview or category chips; drawn icons in place of emoji and symbol characters the plotter's fonts lack; no shadows, gradients, blur or transitions, which are slow to draw there.
|
|
484
|
+
- **Scrolling:** a drag scrolls the list or the left column instead of selecting text, applied once per frame, and the tap that ends a drag is not taken as a press. Redraws caused by live data wait while a finger is down.
|
|
485
|
+
- **Updates:** the plotter keeps showing the page it first loaded. Once a minute the page checks whether its script has changed on the server and reloads once if so.
|
|
486
|
+
- **Diagnosis:** five taps on the connection line show the last input events, the frame time and the time to redraw the list.
|
|
487
|
+
|
|
445
488
|
## Fullscreen and tablet home-screen launch
|
|
446
489
|
|
|
447
490
|
Open `/signalk-czone/` directly in your browser. The **fullscreen icon** beside
|
package/index.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
const path = require('path')
|
|
4
4
|
const fs = require('fs')
|
|
5
5
|
const zcf = require('./lib/zcf')
|
|
6
|
+
const inputs = require('./lib/inputs')
|
|
6
7
|
const czone = require('./lib/czone')
|
|
7
8
|
const nmea = require('./lib/nmea2000')
|
|
8
9
|
const monitoring = require('./lib/monitoring')
|
|
@@ -12,6 +13,7 @@ const confirmOffLib = require('./lib/confirm-off')
|
|
|
12
13
|
const { createBridge, bridgeEntries } = require('./lib/bridge')
|
|
13
14
|
const { createShellyDriver } = require('./lib/bridge/shelly')
|
|
14
15
|
const { createVictronDriver } = require('./lib/bridge/victron')
|
|
16
|
+
const { createVictronSwitches } = require('./lib/victron/switches')
|
|
15
17
|
|
|
16
18
|
const MAX_UPLOAD_BYTES = 1024 * 1024
|
|
17
19
|
const CZONE_CONFIG_BLOCK_HEADER = 23
|
|
@@ -150,6 +152,44 @@ module.exports = function (app) {
|
|
|
150
152
|
},
|
|
151
153
|
drivers: bridgeDrivers
|
|
152
154
|
})
|
|
155
|
+
// Victron switch pane (lib/victron/switches): the CZone circuits as
|
|
156
|
+
// switches on the GX touch screen, Remote Console and VRM. Off unless
|
|
157
|
+
// ticked. A tap there switches the circuit through the same code as a
|
|
158
|
+
// Signal K PUT, so "Enable CZone read/write control" and the confirm-before
|
|
159
|
+
// rules apply; the state shown is the plugin's own decoded 65284 state.
|
|
160
|
+
const switchPane = createVictronSwitches(app, {
|
|
161
|
+
log: m => log(m),
|
|
162
|
+
version: require('./package.json').version,
|
|
163
|
+
getCurrent: skPath => {
|
|
164
|
+
const m = /^electrical\.czone\.([^.]+)\.current$/.exec(skPath || '')
|
|
165
|
+
const c = m && mapping && mapping.circuits.find(x => x.slug === m[1])
|
|
166
|
+
const st = c && runtimeState.get(c.name)
|
|
167
|
+
return st && Number.isFinite(st.current) ? st.current : null
|
|
168
|
+
},
|
|
169
|
+
controls: () => ({
|
|
170
|
+
state: (slug, on) => {
|
|
171
|
+
const c = circuitByName(slug)
|
|
172
|
+
if (on) refuseUnconfirmedOn(c)
|
|
173
|
+
else refuseUnconfirmedOff(c)
|
|
174
|
+
sendCircuitState(c, on)
|
|
175
|
+
const st = runtimeState.get(c.name)
|
|
176
|
+
if (st) st.lastRequested = on ? 'ON' : 'OFF'
|
|
177
|
+
},
|
|
178
|
+
brightness: (slug, normalized) => {
|
|
179
|
+
const c = circuitByName(slug)
|
|
180
|
+
const st = runtimeState.get(c.name)
|
|
181
|
+
if (normalized <= 0) refuseUnconfirmedOff(c)
|
|
182
|
+
else if (!st || st.state !== 'ON') refuseUnconfirmedOn(c)
|
|
183
|
+
sendCircuitBrightness(c, normalized)
|
|
184
|
+
if (st) st.lastRequestedPercent = Math.round(normalized * 100)
|
|
185
|
+
},
|
|
186
|
+
getState: slug => {
|
|
187
|
+
const c = mapping && mapping.circuits.find(x => x.slug === slug)
|
|
188
|
+
const st = c && runtimeState.get(c.name)
|
|
189
|
+
return st && st.statusObserved ? { state: st.state, percent: st.percent } : null
|
|
190
|
+
}
|
|
191
|
+
})
|
|
192
|
+
})
|
|
153
193
|
let monitoringRuntime = null
|
|
154
194
|
// Trend recording of circuit current (see lib/trends).
|
|
155
195
|
let trends = null
|
|
@@ -171,6 +211,10 @@ module.exports = function (app) {
|
|
|
171
211
|
// Circuits nominated to confirm before switching, by circuit name.
|
|
172
212
|
let confirmOff = new Set()
|
|
173
213
|
let confirmOn = new Set()
|
|
214
|
+
// Switch inputs of the loaded configuration (lib/inputs) and their last
|
|
215
|
+
// decoded state, by Signal K path.
|
|
216
|
+
let switchInputs = []
|
|
217
|
+
let inputState = new Map()
|
|
174
218
|
|
|
175
219
|
const configRoot = app.config && app.config.configPath
|
|
176
220
|
? path.join(app.config.configPath, 'plugin-config-data')
|
|
@@ -213,12 +257,16 @@ module.exports = function (app) {
|
|
|
213
257
|
function loadConfiguredZcf () {
|
|
214
258
|
if (!fs.existsSync(zcfPath())) {
|
|
215
259
|
mapping = null
|
|
260
|
+
switchInputs = []
|
|
261
|
+
inputState = new Map()
|
|
216
262
|
return null
|
|
217
263
|
}
|
|
218
264
|
mapping = zcf.load(zcfPath())
|
|
219
265
|
mapping.commandDeviceId = selectCommandDeviceId(mapping)
|
|
220
266
|
publishedCircuitValues.clear()
|
|
221
267
|
monitoringRuntime = monitoring.createRuntime(mapping.meters || [])
|
|
268
|
+
switchInputs = inputs.switchInputs(mapping)
|
|
269
|
+
inputState = new Map(switchInputs.map(i => [i.path, { state: null, since: null }]))
|
|
222
270
|
runtimeState = new Map(mapping.circuits.map(c => [c.name, {
|
|
223
271
|
state: null,
|
|
224
272
|
percent: null,
|
|
@@ -892,6 +940,25 @@ module.exports = function (app) {
|
|
|
892
940
|
}
|
|
893
941
|
}
|
|
894
942
|
|
|
943
|
+
// Switch inputs: PGN 65284 (module status) and 65308 (input message), each
|
|
944
|
+
// input read only from the message its kind of module carries it in.
|
|
945
|
+
function decodeSwitchInputs (frame) {
|
|
946
|
+
if (!switchInputs.length) return
|
|
947
|
+
const decoded = inputs.decodeFrame(frame.data)
|
|
948
|
+
if (!decoded) return
|
|
949
|
+
for (const input of switchInputs) {
|
|
950
|
+
if (input.module !== decoded.module) continue
|
|
951
|
+
const on = inputs.inputState(input.moduleKind, decoded.bitmap, input.input, frame.pgn, input.wiring)
|
|
952
|
+
if (on === undefined) continue
|
|
953
|
+
const entry = inputState.get(input.path)
|
|
954
|
+
if (entry.state === on) continue
|
|
955
|
+
entry.state = on
|
|
956
|
+
entry.since = new Date().toISOString()
|
|
957
|
+
log(`CZone INPUT: ${input.name} ${on ? 'ON' : 'OFF'} (PGN ${frame.pgn} module=0x${decoded.module.toString(16).padStart(2, '0')} bitmap=0x${decoded.bitmap.toString(16).padStart(8, '0')})`)
|
|
958
|
+
publishDelta(input.path, on)
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
|
|
895
962
|
function handleRawFrame (line) {
|
|
896
963
|
const frame = nmea.parseRawLine(line)
|
|
897
964
|
if (!frame) return
|
|
@@ -901,7 +968,12 @@ module.exports = function (app) {
|
|
|
901
968
|
handleConfigFastPacket(frame)
|
|
902
969
|
return
|
|
903
970
|
}
|
|
971
|
+
if (frame.pgn === inputs.INPUT_PGN) {
|
|
972
|
+
decodeSwitchInputs(frame)
|
|
973
|
+
return
|
|
974
|
+
}
|
|
904
975
|
if (frame.pgn === 65284) {
|
|
976
|
+
decodeSwitchInputs(frame)
|
|
905
977
|
decodeCzoneCircuitStatus(frame)
|
|
906
978
|
return
|
|
907
979
|
}
|
|
@@ -1276,6 +1348,18 @@ module.exports = function (app) {
|
|
|
1276
1348
|
default: false,
|
|
1277
1349
|
description: 'Other Signal K apps switch with a PUT and cannot ask "are you sure?". Unticked, an off from them is refused with a message; the circuit can still be turned off from the webapp or a CZone keypad.'
|
|
1278
1350
|
},
|
|
1351
|
+
victronSwitches: {
|
|
1352
|
+
type: 'boolean',
|
|
1353
|
+
title: 'Show CZone circuits in the Victron switch pane',
|
|
1354
|
+
default: false,
|
|
1355
|
+
description: 'Victron GX only (Venus OS 3.60 or later). The circuits appear as switches on the GX touch screen, Remote Console and VRM, one card per CZone category. Switching there needs CZone read/write control enabled, and circuits set to confirm before turning off (on) are not turned off (on) from there unless other apps are allowed to.'
|
|
1356
|
+
},
|
|
1357
|
+
victronSwitchCurrent: {
|
|
1358
|
+
type: 'boolean',
|
|
1359
|
+
title: 'Show circuit current in the switch pane',
|
|
1360
|
+
default: true,
|
|
1361
|
+
description: 'Adds the current to a switch\'s label while the circuit is on, for example "Freezer (2.9 A)".'
|
|
1362
|
+
},
|
|
1279
1363
|
bridges: {
|
|
1280
1364
|
type: 'array',
|
|
1281
1365
|
title: 'Third-party switches',
|
|
@@ -1317,6 +1401,7 @@ module.exports = function (app) {
|
|
|
1317
1401
|
})
|
|
1318
1402
|
trends.start()
|
|
1319
1403
|
startBridge()
|
|
1404
|
+
switchPane.start(settings, mapping).catch(err => log(`Victron switch pane: ${err.message}`))
|
|
1320
1405
|
|
|
1321
1406
|
const ready = (reason = 'nmea2000OutAvailable event') => {
|
|
1322
1407
|
nmeaReady = true
|
|
@@ -1393,6 +1478,7 @@ module.exports = function (app) {
|
|
|
1393
1478
|
if (trends) trends.stop()
|
|
1394
1479
|
trends = null
|
|
1395
1480
|
bridge.stop()
|
|
1481
|
+
switchPane.stop()
|
|
1396
1482
|
closeBridgeBus()
|
|
1397
1483
|
reassembler = null
|
|
1398
1484
|
configFastPacket = null
|
|
@@ -1401,6 +1487,8 @@ module.exports = function (app) {
|
|
|
1401
1487
|
mapping = null
|
|
1402
1488
|
runtimeState.clear()
|
|
1403
1489
|
publishedCircuitValues.clear()
|
|
1490
|
+
switchInputs = []
|
|
1491
|
+
inputState = new Map()
|
|
1404
1492
|
// Signal K removes PUT handlers when the plugin stops. The plugin
|
|
1405
1493
|
// factory can be reused on a subsequent enable, so allow start() to
|
|
1406
1494
|
// register the handlers again instead of treating the stale paths as
|
|
@@ -1409,6 +1497,22 @@ module.exports = function (app) {
|
|
|
1409
1497
|
},
|
|
1410
1498
|
|
|
1411
1499
|
registerWithRouter: router => {
|
|
1500
|
+
router.get('/inputs', (_req, res) => {
|
|
1501
|
+
res.json({
|
|
1502
|
+
inputs: switchInputs.map(i => ({
|
|
1503
|
+
name: i.name,
|
|
1504
|
+
path: i.path,
|
|
1505
|
+
module: i.module,
|
|
1506
|
+
input: i.input,
|
|
1507
|
+
wiring: i.wiring || null,
|
|
1508
|
+
moduleType: i.decoded ? inputs.LAYOUT[i.moduleKind].name : null,
|
|
1509
|
+
decoded: i.decoded,
|
|
1510
|
+
state: inputState.get(i.path).state,
|
|
1511
|
+
since: inputState.get(i.path).since
|
|
1512
|
+
}))
|
|
1513
|
+
})
|
|
1514
|
+
})
|
|
1515
|
+
|
|
1412
1516
|
router.get('/status', (_req, res) => {
|
|
1413
1517
|
res.json({
|
|
1414
1518
|
sendingEnabled: settings.allowCzoneWrite === true,
|
|
@@ -1493,6 +1597,7 @@ module.exports = function (app) {
|
|
|
1493
1597
|
|
|
1494
1598
|
// Third-party switches: link status, the GX's outputs, a Shelly test.
|
|
1495
1599
|
router.get('/bridge/status', (_req, res) => res.json({ links: bridge.status() }))
|
|
1600
|
+
router.get('/victron/status', (_req, res) => res.json(switchPane.status()))
|
|
1496
1601
|
router.get('/bridge/outputs', (_req, res) => {
|
|
1497
1602
|
bridgeDrivers.victron.list()
|
|
1498
1603
|
.then(outputs => res.json({ available: true, outputs }))
|
|
@@ -1547,11 +1652,9 @@ module.exports = function (app) {
|
|
|
1547
1652
|
fs.copyFileSync(source, temp)
|
|
1548
1653
|
const next = zcf.load(temp)
|
|
1549
1654
|
try { fs.unlinkSync(temp) } catch (_) {}
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
networkConfigFile: filename
|
|
1554
|
-
}
|
|
1655
|
+
// Only the configuration source changes; every other setting (confirm
|
|
1656
|
+
// before on/off, trends, third-party switches, …) is kept.
|
|
1657
|
+
const newSettings = { ...settings, configurationSource: 'networkCache', networkConfigFile: filename }
|
|
1555
1658
|
const restartResult = await saveAndRestart(newSettings)
|
|
1556
1659
|
res.json({ ok: true, file: filename, bytes: next.fileSize, vesselName: next.vesselName || null, configuration: newSettings, restartRequested: restartResult.restartRequested })
|
|
1557
1660
|
} catch (err) { res.status(400).json({ ok: false, error: err.message }) }
|
|
@@ -1579,11 +1682,9 @@ module.exports = function (app) {
|
|
|
1579
1682
|
savedAt: new Date().toISOString()
|
|
1580
1683
|
}, null, 2))
|
|
1581
1684
|
} catch (_) {}
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
networkConfigFile: ''
|
|
1586
|
-
}
|
|
1685
|
+
// Only the configuration source changes; every other setting (confirm
|
|
1686
|
+
// before on/off, trends, third-party switches, …) is kept.
|
|
1687
|
+
const newSettings = { ...settings, configurationSource: 'installedZcf', networkConfigFile: '' }
|
|
1587
1688
|
const restartResult = await saveAndRestart(newSettings)
|
|
1588
1689
|
res.json({
|
|
1589
1690
|
ok: true,
|
|
@@ -1686,5 +1787,7 @@ module.exports = function (app) {
|
|
|
1686
1787
|
|
|
1687
1788
|
// For tests: run one bridge poll now instead of waiting for its timer.
|
|
1688
1789
|
Object.defineProperty(plugin, '_bridgePoll', { value: () => bridge.poll(), enumerable: false })
|
|
1790
|
+
// For tests: the Victron switch pane.
|
|
1791
|
+
Object.defineProperty(plugin, '_switchPane', { value: switchPane, enumerable: false })
|
|
1689
1792
|
return plugin
|
|
1690
1793
|
}
|
package/lib/inputs.js
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
// CZone switch inputs: ON / OFF of each switch wired to a CZone module, from
|
|
4
|
+
// the module's own messages. The inputs and their wiring come from the ZCF's
|
|
5
|
+
// signal-input table (signalk-czone-zcf parseSignalInputs).
|
|
6
|
+
//
|
|
7
|
+
// PGN 65284 (0xFF04), module status, about every two seconds and on change:
|
|
8
|
+
// 27 99 <module dipswitch> <module kind> <bitmap, 32 bits, low byte first>
|
|
9
|
+
// PGN 65308 (0xFF1C), input message, sent on change:
|
|
10
|
+
// 27 99 <module dipswitch> <variant> <bitmap, 32 bits, low byte first>
|
|
11
|
+
//
|
|
12
|
+
// Where an input sits depends on the kind of module (its type in the ZCF):
|
|
13
|
+
//
|
|
14
|
+
// Signal Interface (0x0D). Test bench, 6 Oct 2026: module 2, five switches on
|
|
15
|
+
// inputs 0-4 closed one after another gave 02 00, 0A 00, 2A 00, AA 00, AA 02:
|
|
16
|
+
// input n (from 0) is bit 2n+1, in 65284 and in 65308 (variant 00). The
|
|
17
|
+
// message comes on change too, so a tap on a momentary switch is caught.
|
|
18
|
+
//
|
|
19
|
+
// COI (0x1F). SugarShack capture, 27 Sep 2026: a COI's 65284 carries its
|
|
20
|
+
// outputs only; its inputs are in 65308 (variant 03), numbered from 16 in
|
|
21
|
+
// the ZCF, input i at bit 2(i-16)+1. Module 1A input 19 -> 80 00, 1A input
|
|
22
|
+
// 20 -> 00 02, 1C input 17 -> 08 00.
|
|
23
|
+
//
|
|
24
|
+
// Control X PLUS (0x36). Compass Rose, 15 Oct 2026, each input operated twice:
|
|
25
|
+
// its 65284 has the outputs in bits 0-15 and the inputs above, input i (from
|
|
26
|
+
// 16 in the ZCF) at bit i when it switches to positive, bit i+1 when it
|
|
27
|
+
// switches to negative. Anchor Up (input 17, to +) bit 17; Ignition (24, to +)
|
|
28
|
+
// bit 24; High Bilge Water Alarm (18, to -) bit 19. Only one input to
|
|
29
|
+
// negative was seen. Its 65308 messages come in four variants (byte 3: 00,
|
|
30
|
+
// 01, 03, 06) and are not read.
|
|
31
|
+
//
|
|
32
|
+
// Other kinds: not decoded (no state rather than a guess).
|
|
33
|
+
|
|
34
|
+
const STATUS_PGN = 65284
|
|
35
|
+
const INPUT_PGN = 65308
|
|
36
|
+
|
|
37
|
+
const oddBits = first => input => 2 * (input - first) + 1
|
|
38
|
+
const LAYOUT = {
|
|
39
|
+
0x0d: { name: 'Signal Interface', pgns: [STATUS_PGN, INPUT_PGN], first: 0, bit: oddBits(0) },
|
|
40
|
+
0x1f: { name: 'COI', pgns: [INPUT_PGN], first: 16, bit: oddBits(16) },
|
|
41
|
+
0x36: { name: 'Control X PLUS', pgns: [STATUS_PGN], first: 16, bit: (input, wiring) => input + (wiring === 'switchToNeg' ? 1 : 0) }
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// data: 8 bytes of a 65284 or 65308 frame -> { module, variant, bitmap } or null
|
|
45
|
+
function decodeFrame (data) {
|
|
46
|
+
if (!data || data.length !== 8 || data[0] !== 0x27 || data[1] !== 0x99) return null
|
|
47
|
+
return { module: data[2], variant: data[3], bitmap: data.readUInt32LE(4) }
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// true / false, or undefined when this kind of module or message does not
|
|
51
|
+
// carry the input.
|
|
52
|
+
function inputState (kind, bitmap, input, pgn, wiring) {
|
|
53
|
+
const layout = LAYOUT[kind]
|
|
54
|
+
if (!layout || !Number.isInteger(input) || !layout.pgns.includes(pgn)) return undefined
|
|
55
|
+
if (input < layout.first) return undefined
|
|
56
|
+
const bit = layout.bit(input, wiring)
|
|
57
|
+
if (bit > 31) return undefined
|
|
58
|
+
return ((bitmap >>> bit) & 1) === 1
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function slugify (name) {
|
|
62
|
+
const cleaned = String(name || 'Input').trim().replace(/[^A-Za-z0-9]+/g, '_').replace(/^_+|_+$/g, '')
|
|
63
|
+
return cleaned || 'Input'
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// The switch inputs of a loaded configuration, each with its Signal K path
|
|
67
|
+
// (electrical.czone.inputs.<name>.state) and its module's kind.
|
|
68
|
+
function switchInputs (mapping) {
|
|
69
|
+
const table = mapping && mapping.signalInputs
|
|
70
|
+
if (!table || !Array.isArray(table.inputs)) return []
|
|
71
|
+
const kinds = new Map((mapping.modules || []).filter(m => Number.isInteger(m.module)).map(m => [m.module, m.type]))
|
|
72
|
+
const used = new Map()
|
|
73
|
+
return table.inputs
|
|
74
|
+
.filter(i => i.type === 'switch' && Number.isInteger(i.module) && i.module !== 0)
|
|
75
|
+
.map(i => {
|
|
76
|
+
const base = slugify(i.name)
|
|
77
|
+
const count = (used.get(base) || 0) + 1
|
|
78
|
+
used.set(base, count)
|
|
79
|
+
const slug = count === 1 ? base : `${base}${count}`
|
|
80
|
+
const kind = kinds.get(i.module)
|
|
81
|
+
return {
|
|
82
|
+
name: String(i.name).trim(),
|
|
83
|
+
slug,
|
|
84
|
+
path: `electrical.czone.inputs.${slug}.state`,
|
|
85
|
+
module: i.module,
|
|
86
|
+
input: i.input,
|
|
87
|
+
wiring: i.wiring,
|
|
88
|
+
moduleKind: Number.isInteger(kind) ? kind : null,
|
|
89
|
+
decoded: Boolean(LAYOUT[kind])
|
|
90
|
+
}
|
|
91
|
+
})
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
module.exports = { STATUS_PGN, INPUT_PGN, LAYOUT, decodeFrame, inputState, switchInputs }
|