@rhizomatics/signalk-bluetti-plugin 1.5.0 → 1.5.1

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.
Files changed (35) hide show
  1. package/CHANGELOG.md +7 -1
  2. package/README.md +4 -4
  3. package/cli.js +10 -10
  4. package/index.js +16 -26
  5. package/lib/path-mapper.js +1 -1
  6. package/package.json +2 -2
  7. /package/{registers → devices}/ac180.yaml +0 -0
  8. /package/{registers → devices}/ac180p.yaml +0 -0
  9. /package/{registers → devices}/ac180t.yaml +0 -0
  10. /package/{registers → devices}/ac200l.yaml +0 -0
  11. /package/{registers → devices}/ac200m.yaml +0 -0
  12. /package/{registers → devices}/ac200p.yaml +0 -0
  13. /package/{registers → devices}/ac200pl.yaml +0 -0
  14. /package/{registers → devices}/ac2a.yaml +0 -0
  15. /package/{registers → devices}/ac2p.yaml +0 -0
  16. /package/{registers → devices}/ac300.yaml +0 -0
  17. /package/{registers → devices}/ac500.yaml +0 -0
  18. /package/{registers → devices}/ac50b.yaml +0 -0
  19. /package/{registers → devices}/ac60.yaml +0 -0
  20. /package/{registers → devices}/ac60p.yaml +0 -0
  21. /package/{registers → devices}/ac70.yaml +0 -0
  22. /package/{registers → devices}/ac70p.yaml +0 -0
  23. /package/{registers → devices}/ap300.yaml +0 -0
  24. /package/{registers → devices}/eb3a.yaml +0 -0
  25. /package/{registers → devices}/el100v2.yaml +0 -0
  26. /package/{registers → devices}/el30v2.yaml +0 -0
  27. /package/{registers → devices}/ep2000.yaml +0 -0
  28. /package/{registers → devices}/ep500.yaml +0 -0
  29. /package/{registers → devices}/ep500p.yaml +0 -0
  30. /package/{registers → devices}/ep600.yaml +0 -0
  31. /package/{registers → devices}/ep760.yaml +0 -0
  32. /package/{registers → devices}/ep800.yaml +0 -0
  33. /package/{registers → devices}/handsfree1.yaml +0 -0
  34. /package/{registers → devices}/pr100v2.yaml +0 -0
  35. /package/{registers → devices}/pr30v2.yaml +0 -0
package/CHANGELOG.md CHANGED
@@ -2,9 +2,15 @@
2
2
 
3
3
  All notable changes to this plugin are documented here. Format loosely follows [Keep a Changelog](https://keepachangelog.com/).
4
4
 
5
+ ## [1.5.1]
6
+
7
+ - Simplify the custom device configuration override
8
+ - 'Register Map' is now 'Device Configuration'
9
+ - Bundled register maps moved from `registers/` to `devices/`
10
+
5
11
  ## [1.5.0]
6
12
 
7
- - Expanded Bluetti model support
13
+ - Expanded Bluetti model support, over 20 now with register mappings
8
14
  - Configuration moved from CSV to YAML
9
15
  - Configurable directory to add YAML files for unsupported Bluetti models
10
16
  - CLI `models` command lists supported register maps with field/constant counts
package/README.md CHANGED
@@ -73,7 +73,7 @@ node cli.js dump aa:bb:cc:dd:ee:ff --timeout 30 # discovery timeout if device
73
73
  Connect to a device and decode its live register values the same way the plugin itself does — same GATT UUIDs, same encryption handshake (including the AES/ECDH handshake used by "V2"-protocol models like the EL100V2), same register parsing. Requires `--registers`. If `mac` is omitted, scans and connects to the first Bluetti-matching device found.
74
74
 
75
75
  ```bash
76
- # Decode live registers using a bundled register map (see registers/*.yaml for available models)
76
+ # Decode live registers using a bundled device configuration (see devices/*.yaml for available models)
77
77
  node cli.js info aa:bb:cc:dd:ee:ff --registers ac200p
78
78
  node cli.js info --registers ac200p # scan and use the first Bluetti device found
79
79
 
@@ -86,7 +86,7 @@ node cli.js info aa:bb:cc:dd:ee:ff --registers ac200p --timeout 30 # discovery
86
86
 
87
87
  ### `models`
88
88
 
89
- List every register map the CLI (and the plugin) can find — bundled ones plus anything dropped into the custom directory (`~/.signalk/bluetti` by default) — with a field and constant count per model. No device connection needed.
89
+ List every device configuration the CLI (and the plugin) can find — bundled ones plus anything dropped into the custom directory (`~/.signalk/bluetti` by default) — with a field and constant count per model. No device connection needed.
90
90
 
91
91
  ```bash
92
92
  node cli.js models
@@ -140,9 +140,9 @@ This tells systemd to start `bluetoothd` first and wait for it before starting S
140
140
 
141
141
  ### My station isn't supported
142
142
 
143
- Additional products can be added using a YAML file mapping modbus registers and providing static values — see [`docs/examples/model_definition.yaml`](docs/examples/model_definition.yaml) in the repo for an annotated template, and `registers/*.yaml` for real examples.
143
+ Additional products can be added using a YAML file mapping modbus registers and providing static values — see [`docs/examples/model_definition.yaml`](docs/examples/model_definition.yaml) in the repo for an annotated template, and `devices/*.yaml` for real examples.
144
144
 
145
- Drop your file into the plugin's **Custom register maps directory** (a plugin setting, defaulting to `~/.signalk/bluetti`) and it'll appear in the **Register map** dropdown for a device, named after the file (without `.yaml`). A file there takes priority over a bundled one of the same name, so it also doubles as a way to override a built-in map. If you'd rather not touch plugin settings, point a device's **Custom register map YAML path** at the file directly instead — either way works, use whichever is more convenient.
145
+ Drop your file into the plugin's **Custom Device Configuration Directory** (a plugin setting, defaulting to `~/.signalk/bluetti`) and it'll appear in the **Device Configuration** dropdown for a device, named after the file (without `.yaml`). A file there takes priority over a bundled one of the same name, so it also doubles as a way to override a built-in map.
146
146
 
147
147
  Check one of these projects to see if some of the mapping work has been done:
148
148
 
package/cli.js CHANGED
@@ -5,11 +5,11 @@ const fs = require("fs");
5
5
  const os = require("os");
6
6
  const path = require("path");
7
7
 
8
- const REGISTERS_DIR = path.join(__dirname, "registers");
8
+ const DEVICES_DIR = path.join(__dirname, "devices");
9
9
  // Same default the plugin itself uses when running under a real SignalK
10
10
  // server (see index.js) — lets a user's custom register maps be found by the
11
11
  // CLI too, without needing to pass --registers-dir.
12
- const DEFAULT_USER_REGISTERS_DIR = path.join(os.homedir(), ".signalk", "bluetti");
12
+ const DEFAULT_USER_DEVICES_DIR = path.join(os.homedir(), ".signalk", "bluetti");
13
13
 
14
14
  function listModels(dir) {
15
15
  try {
@@ -24,7 +24,7 @@ function listModels(dir) {
24
24
  }
25
25
 
26
26
  function listBuiltinModels() {
27
- return [...new Set([...listModels(REGISTERS_DIR), ...listModels(DEFAULT_USER_REGISTERS_DIR)])].sort((a, b) => a.localeCompare(b));
27
+ return [...new Set([...listModels(DEVICES_DIR), ...listModels(DEFAULT_USER_DEVICES_DIR)])].sort((a, b) => a.localeCompare(b));
28
28
  }
29
29
 
30
30
  // One row per model, checking the custom directory before the bundled one so
@@ -35,8 +35,8 @@ function listModelDetails() {
35
35
 
36
36
  return listBuiltinModels().map((model) => {
37
37
  for (const [dir, source] of [
38
- [DEFAULT_USER_REGISTERS_DIR, "custom"],
39
- [REGISTERS_DIR, "built-in"],
38
+ [DEFAULT_USER_DEVICES_DIR, "custom"],
39
+ [DEVICES_DIR, "built-in"],
40
40
  ]) {
41
41
  for (const ext of [".yaml", ".yml"]) {
42
42
  const p = path.join(dir, `${model}${ext}`);
@@ -79,12 +79,12 @@ Commands:
79
79
  Bluetti device found.
80
80
  --registers <model|path> Built-in model (${builtins.join(", ") || "none bundled"}) or a register map
81
81
  YAML path (required). Also checks
82
- ${DEFAULT_USER_REGISTERS_DIR} for custom models.
82
+ ${DEFAULT_USER_DEVICES_DIR} for custom models.
83
83
  --encryption-key <path> Path to the Bluetti-provided encryption CSV
84
84
  (only needed for legacy XOR-scrambled models)
85
85
  --timeout <seconds> Discovery/scan timeout if not already known (default: 20)
86
86
 
87
- models List supported register maps, with a field and
87
+ models List supported device configurations, with a field and
88
88
  constant count per model
89
89
 
90
90
  help Show this help
@@ -180,7 +180,7 @@ async function findDevice(adapter, mac, timeoutMs) {
180
180
  }
181
181
 
182
182
  function resolveRegistersArg(value) {
183
- for (const dir of [DEFAULT_USER_REGISTERS_DIR, REGISTERS_DIR]) {
183
+ for (const dir of [DEFAULT_USER_DEVICES_DIR, DEVICES_DIR]) {
184
184
  for (const ext of [".yaml", ".yml"]) {
185
185
  const p = path.join(dir, `${value}${ext}`);
186
186
  if (fs.existsSync(p)) return p;
@@ -188,7 +188,7 @@ function resolveRegistersArg(value) {
188
188
  }
189
189
  if (fs.existsSync(value)) return value;
190
190
  const builtins = listBuiltinModels();
191
- throw new Error(`Register map not found: "${value}" (not a built-in model [${builtins.join(", ")}] or an existing file path)`);
191
+ throw new Error(`Device configuration not found: "${value}" (not a built-in model [${builtins.join(", ")}] or an existing file path)`);
192
192
  }
193
193
 
194
194
  async function printDeviceSummary(bleDevice, mac) {
@@ -406,7 +406,7 @@ async function cmdInfo(args) {
406
406
  function cmdModels() {
407
407
  const rows = listModelDetails();
408
408
  if (rows.length === 0) {
409
- console.log(`No register maps found (checked ${REGISTERS_DIR} and ${DEFAULT_USER_REGISTERS_DIR}).`);
409
+ console.log(`No device configurations found (checked ${DEVICES_DIR} and ${DEFAULT_USER_DEVICES_DIR}).`);
410
410
  return;
411
411
  }
412
412
  printTable(rows, ["model", "fields", "constants", "source"]);
package/index.js CHANGED
@@ -5,7 +5,7 @@ const path = require("path");
5
5
  const fs = require("fs");
6
6
 
7
7
  const PLUGIN_ID = "signalk-bluetti-plugin";
8
- const REGISTERS_DIR = path.join(__dirname, "registers");
8
+ const DEVICES_DIR = path.join(__dirname, "devices");
9
9
 
10
10
  // Bluetti device BLE name prefixes (mirrors scanner.js).
11
11
  const BLUETTI_PREFIXES = ["BT-TH-", "BLUETTI", "AC", "EP", "EB", "EL", "PR", "AP"];
@@ -38,7 +38,7 @@ function yamlModelNames(dir) {
38
38
  }
39
39
  }
40
40
 
41
- // The directory a user can drop their own register map YAML files into,
41
+ // The directory a user can drop their own device configuration YAML files into,
42
42
  // without needing to fork/publish the plugin — defaults to a `bluetti`
43
43
  // subdirectory of the SignalK home directory (e.g. ~/.signalk/bluetti).
44
44
  // `app.config.configPath` isn't part of the documented plugin API but is the
@@ -57,7 +57,7 @@ module.exports = function (app) {
57
57
  let waitingStatusTimer = null;
58
58
 
59
59
  const defaultUserDir = defaultUserRegistersDir(app);
60
- const builtins = yamlModelNames(REGISTERS_DIR);
60
+ const builtins = yamlModelNames(DEVICES_DIR);
61
61
  // Models available for the dropdown at schema-render time — bundled plus
62
62
  // whatever's already sitting in the default user directory. If `registersDir`
63
63
  // is overridden away from the default, models unique to that directory won't
@@ -83,13 +83,13 @@ module.exports = function (app) {
83
83
  scanOnStart: {
84
84
  type: "boolean",
85
85
  title: "Scan for new devices on plugin start",
86
- description: "Runs a 15-second BLE scan and logs discovered Bluetti devices. Useful for finding device addresses.",
86
+ description: "Runs a short BLE scan and logs discovered Bluetti devices. Useful for finding device addresses.",
87
87
  default: true,
88
88
  },
89
89
  registersDir: {
90
90
  type: "string",
91
- title: "Custom register maps directory",
92
- description: `Directory to scan for your own register map YAML files (e.g. for a model this plugin doesn't bundle yet), in addition to the ones built in. Leave blank to use the default: ${defaultUserDir || "<SignalK home>/bluetti"}.`,
91
+ title: "Custom Device Configuration Directory",
92
+ description: `Directory to scan for your own device configuration YAML files (e.g. for a model this plugin doesn't bundle yet), in addition to the ones built in. Leave blank to use the default: ${defaultUserDir || "<SignalK home>/bluetti"}.`,
93
93
  default: "",
94
94
  },
95
95
  devices: {
@@ -117,17 +117,11 @@ module.exports = function (app) {
117
117
  },
118
118
  builtinModel: {
119
119
  type: "string",
120
- title: "Register map",
120
+ title: "Device Configuration",
121
121
  description:
122
- 'Select a register map for your device model — bundled with the plugin, or dropped into the custom register maps directory above — or "custom" to supply an explicit YAML file path below.',
123
- enum: ["custom", ...allModels],
124
- default: allModels.length > 0 ? allModels[0] : "custom",
125
- },
126
- registerMapPath: {
127
- type: "string",
128
- title: "Custom register map YAML path",
129
- description: 'Absolute path to a register map YAML file. Only used when "custom" is selected above.',
130
- default: "",
122
+ "Select a device configuration for your model — bundled with the plugin, or dropped into the custom device configuration directory above.",
123
+ enum: allModels,
124
+ default: allModels.length > 0 ? allModels[0] : "",
131
125
  },
132
126
  encryptionCsvPath: {
133
127
  type: "string",
@@ -154,7 +148,6 @@ module.exports = function (app) {
154
148
  items: {
155
149
  address: { "ui:placeholder": "aa:bb:cc:dd:ee:ff" },
156
150
  name: { "ui:placeholder": "house" },
157
- registerMapPath: { "ui:placeholder": "e.g. /path/to/my-device-registers.yaml" },
158
151
  encryptionCsvPath: { "ui:placeholder": "e.g. /path/to/19e1646709e0421b755fa9dda74.csv" },
159
152
  },
160
153
  },
@@ -180,7 +173,7 @@ module.exports = function (app) {
180
173
  try {
181
174
  fs.mkdirSync(userRegistersDir, { recursive: true });
182
175
  } catch (err) {
183
- log(`Could not create custom register maps directory "${userRegistersDir}": ${err.message}`);
176
+ log(`Could not create custom device configuration directory "${userRegistersDir}": ${err.message}`);
184
177
  }
185
178
  }
186
179
 
@@ -298,7 +291,7 @@ module.exports = function (app) {
298
291
  // Finds <model>.yaml (or .yml), checking the custom directory before the
299
292
  // bundled one so a user's own file can override a built-in of the same name.
300
293
  function findModelFile(model, userRegistersDir) {
301
- const dirs = [userRegistersDir, REGISTERS_DIR].filter(Boolean);
294
+ const dirs = [userRegistersDir, DEVICES_DIR].filter(Boolean);
302
295
  for (const dir of dirs) {
303
296
  for (const ext of [".yaml", ".yml"]) {
304
297
  const p = path.join(dir, `${model}${ext}`);
@@ -309,15 +302,12 @@ module.exports = function (app) {
309
302
  }
310
303
 
311
304
  function resolveRegisterMapPath(cfg, userRegistersDir) {
312
- const { builtinModel, registerMapPath } = cfg;
313
- if (!builtinModel || builtinModel === "custom") {
314
- if (!registerMapPath) throw new Error("No register map: select a model or provide a custom YAML path");
315
- return registerMapPath;
316
- }
305
+ const { builtinModel } = cfg;
306
+ if (!builtinModel) throw new Error("No device configuration: select a model");
317
307
  const found = findModelFile(builtinModel, userRegistersDir);
318
308
  if (!found)
319
309
  throw new Error(
320
- `Register map "${builtinModel}" not found (checked ${userRegistersDir ? `${userRegistersDir} and ` : ""}${REGISTERS_DIR})`,
310
+ `Device configuration "${builtinModel}" not found (checked ${userRegistersDir ? `${userRegistersDir} and ` : ""}${DEVICES_DIR})`,
321
311
  );
322
312
  return found;
323
313
  }
@@ -339,7 +329,7 @@ module.exports = function (app) {
339
329
  fields = loadRegisters(registerPath);
340
330
  log(`[${name}] Loaded ${fields.length} registers from ${registerPath}`);
341
331
  } catch (err) {
342
- app.setPluginError(`[${name}] Failed to load register map "${registerPath}": ${err.message}`);
332
+ app.setPluginError(`[${name}] Failed to load device configuration "${registerPath}": ${err.message}`);
343
333
  return;
344
334
  }
345
335
 
@@ -216,7 +216,7 @@ function buildDelta(registers, fields, deviceName, source, opts = {}) {
216
216
 
217
217
  // Remaining capacity = nominal capacity × state of charge. Bluetti's own
218
218
  // "remaining capacity" register drifts with cell aging and isn't available
219
- // on every model; deriving it from a fixed nominal spec (see registers/*.yaml)
219
+ // on every model; deriving it from a fixed nominal spec (see devices/*.yaml)
220
220
  // plus the reported SoC is simpler and consistent across models.
221
221
  const soc = cache.has("battery_soc") ? cache.get("battery_soc") : cache.has("battery_percent") ? cache.get("battery_percent") : null;
222
222
  if (soc !== null && cache.has("total_capacity")) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rhizomatics/signalk-bluetti-plugin",
3
- "version": "1.5.0",
3
+ "version": "1.5.1",
4
4
  "description": "SignalK plugin for Bluetti power station monitoring via Bluetooth LE. Comes with configuration for 20+ models.",
5
5
  "keywords": [
6
6
  "battery",
@@ -35,7 +35,7 @@
35
35
  "lib/",
36
36
  "docs/assets/",
37
37
  "docs/examples/",
38
- "registers/",
38
+ "devices/",
39
39
  "README.md",
40
40
  "CHANGELOG.md"
41
41
  ],
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes