@rhizomatics/signalk-bluetti-plugin 1.4.1 → 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.
Files changed (43) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/README.md +40 -4
  3. package/cli.js +69 -9
  4. package/docs/assets/icon.svg +36 -0
  5. package/docs/assets/screenshots/bluetti_data.png +0 -0
  6. package/docs/examples/model_definition.yaml +112 -0
  7. package/index.js +78 -25
  8. package/lib/path-mapper.js +51 -9
  9. package/lib/register-loader.js +120 -0
  10. package/lib/scanner.js +1 -1
  11. package/package.json +12 -5
  12. package/registers/ac180.yaml +58 -0
  13. package/registers/ac180p.yaml +34 -0
  14. package/registers/ac180t.yaml +58 -0
  15. package/registers/ac200l.yaml +48 -0
  16. package/registers/ac200m.yaml +48 -0
  17. package/registers/ac200p.yaml +89 -0
  18. package/registers/ac200pl.yaml +48 -0
  19. package/registers/ac2a.yaml +34 -0
  20. package/registers/ac2p.yaml +34 -0
  21. package/registers/ac300.yaml +57 -0
  22. package/registers/ac500.yaml +56 -0
  23. package/registers/ac50b.yaml +31 -0
  24. package/registers/ac60.yaml +33 -0
  25. package/registers/ac60p.yaml +33 -0
  26. package/registers/ac70.yaml +62 -0
  27. package/registers/ac70p.yaml +58 -0
  28. package/registers/ap300.yaml +34 -0
  29. package/registers/eb3a.yaml +41 -0
  30. package/registers/el100v2.yaml +43 -0
  31. package/registers/el30v2.yaml +38 -0
  32. package/registers/ep2000.yaml +75 -0
  33. package/registers/ep500.yaml +57 -0
  34. package/registers/ep500p.yaml +57 -0
  35. package/registers/ep600.yaml +151 -0
  36. package/registers/ep760.yaml +72 -0
  37. package/registers/ep800.yaml +21 -0
  38. package/registers/handsfree1.yaml +62 -0
  39. package/registers/pr100v2.yaml +34 -0
  40. package/registers/pr30v2.yaml +34 -0
  41. package/lib/csv-loader.js +0 -160
  42. package/registers/ac200p.csv +0 -47
  43. package/registers/el100v2.csv +0 -27
@@ -1,9 +1,9 @@
1
1
  "use strict";
2
2
 
3
- const { decodeValue } = require("./csv-loader");
3
+ const { decodeValue } = require("./register-loader");
4
4
 
5
5
  // SignalK requires specific SI units. These conversions handle the most common
6
- // cases found in Bluetti register maps. The CSV's `unit` column drives the choice.
6
+ // cases found in Bluetti register maps. The YAML field's `unit` key drives the choice.
7
7
  const UNIT_CONVERSIONS = {
8
8
  // temperature: Bluetti reports in °C or 0.1°C (handled by scale), SignalK wants K
9
9
  c: (v) => v + 273.15,
@@ -30,9 +30,9 @@ function convertUnits(value, unit) {
30
30
  }
31
31
 
32
32
  // Canonical field_name → SignalK path for the register names Bluetti's own
33
- // documentation and the bundled CSVs use. A CSV's `signalk_path` column is an
33
+ // documentation and the bundled register maps use. A field's `path` key is an
34
34
  // override on top of this — only needed for a register the code doesn't know
35
- // about — so bundled/well-known CSVs can leave it blank and just list
35
+ // about — so bundled/well-known register maps can leave it out and just list
36
36
  // field_name + register info.
37
37
  //
38
38
  // ac_input_power, ac_input_voltage, and dc_output_power are intentionally
@@ -69,13 +69,55 @@ const STANDARD_FIELD_PATHS = {
69
69
  // registers for them; if not, current is derived (see buildDelta below).
70
70
  ac_input_current: "electrical.chargers.{name}.current",
71
71
  ac_input_frequency: "electrical.chargers.{name}.frequency",
72
+ grid_frequency: "electrical.chargers.{name}.frequency", // alias used by 3-phase/home-backup models
73
+
74
+ // Lifetime energy generation counter (some models only)
75
+ power_generation: "electrical.solar.{name}.energyGeneratedTotal",
76
+
77
+ // Second PV string (dual-MPPT models) — the primary string uses dc_input_* above
78
+ pv_s2_power: "electrical.solar.{name}.pv2.panelPower",
79
+ pv_s2_voltage: "electrical.solar.{name}.pv2.panelVoltage",
80
+ pv_s2_current: "electrical.solar.{name}.pv2.panelCurrent",
81
+
82
+ // Per-phase grid (mains) input — split-phase/3-phase home-backup models
83
+ grid_p1_power: "electrical.chargers.{name}.L1.power",
84
+ grid_p1_voltage: "electrical.chargers.{name}.L1.voltage",
85
+ grid_p1_current: "electrical.chargers.{name}.L1.current",
86
+ grid_p2_power: "electrical.chargers.{name}.L2.power",
87
+ grid_p2_voltage: "electrical.chargers.{name}.L2.voltage",
88
+ grid_p2_current: "electrical.chargers.{name}.L2.current",
89
+ grid_p3_power: "electrical.chargers.{name}.L3.power",
90
+ grid_p3_voltage: "electrical.chargers.{name}.L3.voltage",
91
+ grid_p3_current: "electrical.chargers.{name}.L3.current",
92
+
93
+ // Per-phase AC output — split-phase/3-phase home-backup models
94
+ ac_p1_power: "electrical.inverters.{name}.ac.L1.realPower",
95
+ ac_p1_voltage: "electrical.inverters.{name}.ac.L1.voltage",
96
+ ac_p1_current: "electrical.inverters.{name}.ac.L1.current",
97
+ ac_p2_power: "electrical.inverters.{name}.ac.L2.realPower",
98
+ ac_p2_voltage: "electrical.inverters.{name}.ac.L2.voltage",
99
+ ac_p2_current: "electrical.inverters.{name}.ac.L2.current",
100
+ ac_p3_power: "electrical.inverters.{name}.ac.L3.realPower",
101
+ ac_p3_voltage: "electrical.inverters.{name}.ac.L3.voltage",
102
+ ac_p3_current: "electrical.inverters.{name}.ac.L3.current",
103
+
104
+ // Smart meter (external CT clamp on the home panel) — home-backup models only
105
+ sm_p1_power: "electrical.inverters.{name}.smartMeter.L1.power",
106
+ sm_p1_voltage: "electrical.inverters.{name}.smartMeter.L1.voltage",
107
+ sm_p1_current: "electrical.inverters.{name}.smartMeter.L1.current",
108
+ sm_p2_power: "electrical.inverters.{name}.smartMeter.L2.power",
109
+ sm_p2_voltage: "electrical.inverters.{name}.smartMeter.L2.voltage",
110
+ sm_p2_current: "electrical.inverters.{name}.smartMeter.L2.current",
111
+ sm_p3_power: "electrical.inverters.{name}.smartMeter.L3.power",
112
+ sm_p3_voltage: "electrical.inverters.{name}.smartMeter.L3.voltage",
113
+ sm_p3_current: "electrical.inverters.{name}.smartMeter.L3.current",
72
114
  };
73
115
 
74
116
  // Fields that feed a derived computation in buildDelta() rather than being
75
117
  // published under their own path directly.
76
118
  const DERIVED_SOURCE_FIELDS = new Set(["ac_input_power", "ac_input_voltage", "dc_output_power", "dc_output_voltage"]);
77
119
 
78
- // Build a SignalK path for a field: explicit CSV override first, then the
120
+ // Build a SignalK path for a field: explicit `path` override first, then the
79
121
  // standard field_name registry above, then a best-effort keyword guess.
80
122
  function resolvePath(field, deviceName) {
81
123
  if (field.signalkPath) {
@@ -88,7 +130,7 @@ function resolvePath(field, deviceName) {
88
130
 
89
131
  // Best-effort automatic path generation for common Bluetti field names.
90
132
  // Only reached for fields not in STANDARD_FIELD_PATHS above — i.e. a custom
91
- // CSV using field names the code doesn't recognise and no explicit override.
133
+ // register map using field names the code doesn't recognise and no explicit override.
92
134
  function autoPath(fieldName, name) {
93
135
  const f = fieldName.toLowerCase();
94
136
  if (f.includes("battery") || f.includes("batt") || f.includes("soc")) {
@@ -163,8 +205,8 @@ function buildDelta(registers, fields, deviceName, source, opts = {}) {
163
205
  }
164
206
 
165
207
  // DC output port (e.g. a 12V accessory socket): Bluetti reports power only,
166
- // at a fixed nominal voltage that isn't itself a register — the CSV supplies
167
- // it as a constant (dc_output_voltage), and current is derived from that.
208
+ // at a fixed nominal voltage that isn't itself a register — the register map
209
+ // supplies it as a constant (dc_output_voltage), and current is derived from that.
168
210
  if (cache.has("dc_output_power") && cache.has("dc_output_voltage")) {
169
211
  const power = cache.get("dc_output_power");
170
212
  const voltage = cache.get("dc_output_voltage");
@@ -174,7 +216,7 @@ function buildDelta(registers, fields, deviceName, source, opts = {}) {
174
216
 
175
217
  // Remaining capacity = nominal capacity × state of charge. Bluetti's own
176
218
  // "remaining capacity" register drifts with cell aging and isn't available
177
- // on every model; deriving it from a fixed nominal spec (see registers/*.csv)
219
+ // on every model; deriving it from a fixed nominal spec (see registers/*.yaml)
178
220
  // plus the reported SoC is simpler and consistent across models.
179
221
  const soc = cache.has("battery_soc") ? cache.get("battery_soc") : cache.has("battery_percent") ? cache.get("battery_percent") : null;
180
222
  if (soc !== null && cache.has("total_capacity")) {
@@ -0,0 +1,120 @@
1
+ "use strict";
2
+
3
+ const fs = require("fs");
4
+ const YAML = require("yaml");
5
+
6
+ // Parse a register map YAML file and return an array of field descriptors.
7
+ // Each descriptor is either register-backed: { fieldName, register, count, dataType, scale, offset, unit, signalkPath }
8
+ // or a fixed constant (no register on the device): { fieldName, register: null, count: 0, dataType: "const", ..., constantValue }
9
+ //
10
+ // Expected shape:
11
+ //
12
+ // fields:
13
+ // battery_soc: { register: 100, type: uint16, unit: "%" }
14
+ // battery_voltage: { register: 101, type: uint16, scale: 0.1, unit: V }
15
+ // constants:
16
+ // battery_chemistry: LiFePO4
17
+ // total_capacity: { value: 2000, unit: Wh }
18
+ //
19
+ // `type` defaults to uint16, `scale` to 1, `offset` to 0, `count` is inferred
20
+ // from `type` when absent. `path` on a field/constant is an explicit SignalK
21
+ // path override (see lib/path-mapper.js). A constant may be a bare scalar
22
+ // (string or number) or an object with `value` (+ optional `unit`, `path`).
23
+ function loadRegisters(filePath) {
24
+ const raw = fs.readFileSync(filePath, "utf8");
25
+ const doc = YAML.parse(raw);
26
+ if (!doc || typeof doc !== "object") throw new Error(`${filePath}: empty or invalid YAML register map`);
27
+
28
+ const fields = [];
29
+
30
+ for (const [fieldName, spec] of Object.entries(doc.fields || {})) {
31
+ if (spec === null || typeof spec !== "object") {
32
+ throw new Error(`${filePath}: field "${fieldName}" must be a mapping with at least a "register" key`);
33
+ }
34
+ const register = parseInt(spec.register, 10);
35
+ if (isNaN(register)) throw new Error(`${filePath}: field "${fieldName}" is missing a numeric "register" address`);
36
+
37
+ const dataType = String(spec.type || "uint16").toLowerCase();
38
+ const count = Number.isInteger(spec.count) ? spec.count : registersForType(dataType);
39
+ const scale = spec.scale !== undefined ? Number(spec.scale) : 1;
40
+ const offset = spec.offset !== undefined ? Number(spec.offset) : 0;
41
+ const unit = spec.unit !== undefined ? String(spec.unit) : "";
42
+ const signalkPath = spec.path !== undefined ? String(spec.path) : "";
43
+
44
+ fields.push({ fieldName, register, count, dataType, scale, offset, unit, signalkPath });
45
+ }
46
+
47
+ for (const [fieldName, spec] of Object.entries(doc.constants || {})) {
48
+ const isExpanded = spec !== null && typeof spec === "object" && "value" in spec;
49
+ const value = isExpanded ? spec.value : spec;
50
+ const unit = isExpanded && spec.unit !== undefined ? String(spec.unit) : "";
51
+ const signalkPath = isExpanded && spec.path !== undefined ? String(spec.path) : "";
52
+ const constantValue = typeof value === "number" ? value : String(value);
53
+
54
+ fields.push({ fieldName, register: null, count: 0, dataType: "const", scale: 1, offset: 0, unit, signalkPath, constantValue });
55
+ }
56
+
57
+ if (fields.length === 0) throw new Error(`No fields or constants found in ${filePath}`);
58
+ return fields;
59
+ }
60
+
61
+ function registersForType(dataType) {
62
+ switch (dataType) {
63
+ case "int32":
64
+ case "uint32":
65
+ case "float32":
66
+ return 2;
67
+ case "int64":
68
+ case "uint64":
69
+ return 4;
70
+ default:
71
+ return 1; // uint16, int16, bool, enum
72
+ }
73
+ }
74
+
75
+ // Decode a raw register value (or pair of registers for 32-bit types) into a number.
76
+ // rawRegs: Map<addr, uint16>, startAddr: the register address for this field
77
+ function decodeValue(field, rawRegs) {
78
+ const { register, count, dataType, scale, offset } = field;
79
+
80
+ if (dataType === "const") return field.constantValue;
81
+
82
+ let raw;
83
+ if (count === 1) {
84
+ raw = rawRegs.get(register);
85
+ if (raw === undefined) return null;
86
+ } else if (count === 2) {
87
+ const hi = rawRegs.get(register);
88
+ const lo = rawRegs.get(register + 1);
89
+ if (hi === undefined || lo === undefined) return null;
90
+ raw = (hi << 16) | lo;
91
+ } else {
92
+ return null;
93
+ }
94
+
95
+ let value;
96
+ switch (dataType) {
97
+ case "int16":
98
+ value = raw > 0x7fff ? raw - 0x10000 : raw;
99
+ break;
100
+ case "int32": {
101
+ const signed = raw > 0x7fffffff ? raw - 0x100000000 : raw;
102
+ value = signed;
103
+ break;
104
+ }
105
+ case "float32": {
106
+ const tmpBuf = Buffer.alloc(4);
107
+ tmpBuf.writeUInt32BE(raw, 0);
108
+ value = tmpBuf.readFloatBE(0);
109
+ break;
110
+ }
111
+ case "bool":
112
+ return raw !== 0 ? 1 : 0;
113
+ default:
114
+ value = raw;
115
+ }
116
+
117
+ return value * scale + offset;
118
+ }
119
+
120
+ module.exports = { loadRegisters, decodeValue };
package/lib/scanner.js CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  const EventEmitter = require("events");
4
4
 
5
- const BLUETTI_NAME_PREFIXES = ["BT-TH-", "BLUETTI", "AC", "EP", "EB", "EL"];
5
+ const BLUETTI_NAME_PREFIXES = ["BT-TH-", "BLUETTI", "AC", "EP", "EB", "EL", "PR", "AP"];
6
6
 
7
7
  // Retry/backoff for BLE adapter init — covers the case where SignalK (and this
8
8
  // plugin) starts before bluetoothd/D-Bus is up, e.g. on boot when service
package/package.json CHANGED
@@ -1,16 +1,20 @@
1
1
  {
2
2
  "name": "@rhizomatics/signalk-bluetti-plugin",
3
- "version": "1.4.1",
4
- "description": "SignalK plugin for Bluetti power station monitoring via Bluetooth LE. Beta, use with caution.",
3
+ "version": "1.5.0",
4
+ "description": "SignalK plugin for Bluetti power station monitoring via Bluetooth LE. Comes with configuration for 20+ models.",
5
5
  "keywords": [
6
6
  "battery",
7
7
  "ble",
8
8
  "bluetooth",
9
9
  "bluetti",
10
10
  "electrical",
11
+ "generator",
11
12
  "marine",
13
+ "signalk",
14
+ "signalk-category-hardware",
12
15
  "signalk-node-plugin",
13
- "signalk-node-server-plugin"
16
+ "signalk-node-server-plugin",
17
+ "solar"
14
18
  ],
15
19
  "homepage": "https://github.com/rhizomatics/signalk-bluetti-plugin.git",
16
20
  "bugs": {
@@ -29,6 +33,8 @@
29
33
  "index.js",
30
34
  "cli.js",
31
35
  "lib/",
36
+ "docs/assets/",
37
+ "docs/examples/",
32
38
  "registers/",
33
39
  "README.md",
34
40
  "CHANGELOG.md"
@@ -45,11 +51,11 @@
45
51
  },
46
52
  "dependencies": {
47
53
  "@naugehyde/node-ble": "^1.13.5",
48
- "csv-parse": "^5.5.6"
54
+ "yaml": "^2.9.0"
49
55
  },
50
56
  "devDependencies": {
51
57
  "oxfmt": "^0.61.0",
52
- "oxlint": "^1.76.0",
58
+ "oxlint": "^1.75.0",
53
59
  "oxlint-tsgolint": "^7.0.2001"
54
60
  },
55
61
  "engines": {
@@ -58,6 +64,7 @@
58
64
  },
59
65
  "signalk": {
60
66
  "displayName": "Bluetti Monitoring",
67
+ "appIcon": "docs/assets/icon.svg",
61
68
  "recommends": [
62
69
  "@rhizomatics/signalk-einklabel-plugin"
63
70
  ],
@@ -0,0 +1,58 @@
1
+ # Bluetti AC180 register map
2
+ # Source: https://github.com/Patrick762/bluetti-bt-lib (bluetti_bt_lib/devices/ac180.py)
3
+ #
4
+ # field_name is a standard Bluetti register name — the code maps these to
5
+ # SignalK paths on its own (see lib/path-mapper.js). `path` on a field is an
6
+ # override, only needed if you're exposing a register the code doesn't
7
+ # recognise. `constants` are fixed, non-register facts about this model —
8
+ # a bare scalar (string/number) or { value, unit } for one that needs unit
9
+ # conversion (e.g. total_capacity in Wh -> J). No verified nominal capacity
10
+ # is bundled for most models — add a total_capacity constant yourself if you
11
+ # know it, to enable the derived remaining-capacity calculation.
12
+ #
13
+
14
+ fields:
15
+ dc_output_power:
16
+ register: 140
17
+ unit: W
18
+ ac_output_power:
19
+ register: 142
20
+ unit: W
21
+ dc_input_power:
22
+ register: 144
23
+ unit: W
24
+ ac_input_power:
25
+ register: 146
26
+ unit: W
27
+ dc_input_voltage:
28
+ register: 1213
29
+ scale: 0.1
30
+ unit: V
31
+ dc_input_current:
32
+ register: 1214
33
+ scale: 0.1
34
+ unit: A
35
+ ac_input_frequency:
36
+ register: 1300
37
+ scale: 0.1
38
+ unit: Hz
39
+ ac_input_voltage:
40
+ register: 1314
41
+ scale: 0.1
42
+ unit: V
43
+ ac_input_current:
44
+ register: 1315
45
+ scale: 0.1
46
+ unit: A
47
+ ac_output_frequency:
48
+ register: 1500
49
+ scale: 0.1
50
+ unit: Hz
51
+ ac_output_voltage:
52
+ register: 1511
53
+ scale: 0.1
54
+ unit: V
55
+ constants:
56
+ battery_chemistry: LiFePO4
57
+ battery_manufacturer_name: Bluetti
58
+ battery_manufacturer_model: AC180
@@ -0,0 +1,34 @@
1
+ # Bluetti AC180P register map
2
+ # Source: https://github.com/Patrick762/bluetti-bt-lib (bluetti_bt_lib/devices/ac180p.py)
3
+ #
4
+ # field_name is a standard Bluetti register name — the code maps these to
5
+ # SignalK paths on its own (see lib/path-mapper.js). `path` on a field is an
6
+ # override, only needed if you're exposing a register the code doesn't
7
+ # recognise. `constants` are fixed, non-register facts about this model —
8
+ # a bare scalar (string/number) or { value, unit } for one that needs unit
9
+ # conversion (e.g. total_capacity in Wh -> J). No verified nominal capacity
10
+ # is bundled for most models — add a total_capacity constant yourself if you
11
+ # know it, to enable the derived remaining-capacity calculation.
12
+ #
13
+
14
+ fields:
15
+ dc_output_power:
16
+ register: 140
17
+ unit: W
18
+ ac_output_power:
19
+ register: 142
20
+ unit: W
21
+ dc_input_power:
22
+ register: 144
23
+ unit: W
24
+ ac_input_power:
25
+ register: 146
26
+ unit: W
27
+ ac_input_voltage:
28
+ register: 1314
29
+ scale: 0.1
30
+ unit: V
31
+ constants:
32
+ battery_chemistry: LiFePO4
33
+ battery_manufacturer_name: Bluetti
34
+ battery_manufacturer_model: AC180P
@@ -0,0 +1,58 @@
1
+ # Bluetti AC180T register map
2
+ # Source: https://github.com/Patrick762/bluetti-bt-lib (bluetti_bt_lib/devices/ac180t.py)
3
+ #
4
+ # field_name is a standard Bluetti register name — the code maps these to
5
+ # SignalK paths on its own (see lib/path-mapper.js). `path` on a field is an
6
+ # override, only needed if you're exposing a register the code doesn't
7
+ # recognise. `constants` are fixed, non-register facts about this model —
8
+ # a bare scalar (string/number) or { value, unit } for one that needs unit
9
+ # conversion (e.g. total_capacity in Wh -> J). No verified nominal capacity
10
+ # is bundled for most models — add a total_capacity constant yourself if you
11
+ # know it, to enable the derived remaining-capacity calculation.
12
+ #
13
+
14
+ fields:
15
+ dc_output_power:
16
+ register: 140
17
+ unit: W
18
+ ac_output_power:
19
+ register: 142
20
+ unit: W
21
+ dc_input_power:
22
+ register: 144
23
+ unit: W
24
+ ac_input_power:
25
+ register: 146
26
+ unit: W
27
+ dc_input_voltage:
28
+ register: 1213
29
+ scale: 0.1
30
+ unit: V
31
+ dc_input_current:
32
+ register: 1214
33
+ scale: 0.1
34
+ unit: A
35
+ ac_input_frequency:
36
+ register: 1300
37
+ scale: 0.1
38
+ unit: Hz
39
+ ac_input_voltage:
40
+ register: 1314
41
+ scale: 0.1
42
+ unit: V
43
+ ac_input_current:
44
+ register: 1315
45
+ scale: 0.1
46
+ unit: A
47
+ ac_output_frequency:
48
+ register: 1500
49
+ scale: 0.1
50
+ unit: Hz
51
+ ac_output_voltage:
52
+ register: 1511
53
+ scale: 0.1
54
+ unit: V
55
+ constants:
56
+ battery_chemistry: LiFePO4
57
+ battery_manufacturer_name: Bluetti
58
+ battery_manufacturer_model: AC180T
@@ -0,0 +1,48 @@
1
+ # Bluetti AC200L register map
2
+ # Source: https://github.com/Patrick762/bluetti-bt-lib (bluetti_bt_lib/devices/ac200l.py)
3
+ #
4
+ # field_name is a standard Bluetti register name — the code maps these to
5
+ # SignalK paths on its own (see lib/path-mapper.js). `path` on a field is an
6
+ # override, only needed if you're exposing a register the code doesn't
7
+ # recognise. `constants` are fixed, non-register facts about this model —
8
+ # a bare scalar (string/number) or { value, unit } for one that needs unit
9
+ # conversion (e.g. total_capacity in Wh -> J). No verified nominal capacity
10
+ # is bundled for most models — add a total_capacity constant yourself if you
11
+ # know it, to enable the derived remaining-capacity calculation.
12
+ #
13
+
14
+ fields:
15
+ battery_soc:
16
+ register: 43
17
+ unit: "%"
18
+ dc_input_power:
19
+ register: 36
20
+ unit: W
21
+ ac_input_power:
22
+ register: 37
23
+ unit: W
24
+ ac_output_power:
25
+ register: 38
26
+ unit: W
27
+ dc_output_power:
28
+ register: 39
29
+ unit: W
30
+ ac_output_voltage:
31
+ register: 71
32
+ unit: V
33
+ ac_output_frequency:
34
+ register: 74
35
+ scale: 0.1
36
+ unit: Hz
37
+ dc_input_voltage:
38
+ register: 86
39
+ scale: 0.1
40
+ unit: V
41
+ dc_input_current:
42
+ register: 88
43
+ scale: 0.01
44
+ unit: A
45
+ constants:
46
+ battery_chemistry: LiFePO4
47
+ battery_manufacturer_name: Bluetti
48
+ battery_manufacturer_model: AC200L
@@ -0,0 +1,48 @@
1
+ # Bluetti AC200M register map
2
+ # Source: https://github.com/Patrick762/bluetti-bt-lib (bluetti_bt_lib/devices/ac200m.py)
3
+ #
4
+ # field_name is a standard Bluetti register name — the code maps these to
5
+ # SignalK paths on its own (see lib/path-mapper.js). `path` on a field is an
6
+ # override, only needed if you're exposing a register the code doesn't
7
+ # recognise. `constants` are fixed, non-register facts about this model —
8
+ # a bare scalar (string/number) or { value, unit } for one that needs unit
9
+ # conversion (e.g. total_capacity in Wh -> J). No verified nominal capacity
10
+ # is bundled for most models — add a total_capacity constant yourself if you
11
+ # know it, to enable the derived remaining-capacity calculation.
12
+ #
13
+
14
+ fields:
15
+ battery_soc:
16
+ register: 43
17
+ unit: "%"
18
+ dc_input_power:
19
+ register: 36
20
+ unit: W
21
+ ac_input_power:
22
+ register: 37
23
+ unit: W
24
+ ac_output_power:
25
+ register: 38
26
+ unit: W
27
+ dc_output_power:
28
+ register: 39
29
+ unit: W
30
+ ac_output_voltage:
31
+ register: 71
32
+ unit: V
33
+ ac_output_frequency:
34
+ register: 74
35
+ scale: 0.1
36
+ unit: Hz
37
+ dc_input_voltage:
38
+ register: 86
39
+ scale: 0.1
40
+ unit: V
41
+ dc_input_current:
42
+ register: 88
43
+ scale: 0.01
44
+ unit: A
45
+ constants:
46
+ battery_chemistry: LiFePO4
47
+ battery_manufacturer_name: Bluetti
48
+ battery_manufacturer_model: AC200M
@@ -0,0 +1,89 @@
1
+ # Bluetti register map example — based on AC200P / AC200MAX community reverse-engineering.
2
+ # Replace with your device's actual map if you have better data.
3
+ #
4
+ # field_name is a standard Bluetti register name — the code maps these to
5
+ # SignalK paths on its own (see lib/path-mapper.js). `path` on a field is an
6
+ # override, only needed if you're exposing a register the code doesn't
7
+ # recognise. `constants` are fixed, non-register facts about this model —
8
+ # a bare scalar (string/number) or { value, unit } for one that needs unit
9
+ # conversion (e.g. total_capacity in Wh -> J). No verified nominal capacity
10
+ # is bundled for most models — add a total_capacity constant yourself if you
11
+ # know it, to enable the derived remaining-capacity calculation.
12
+ #
13
+
14
+ fields:
15
+ battery_percent:
16
+ register: 100
17
+ unit: "%"
18
+ battery_voltage:
19
+ register: 101
20
+ scale: 0.1
21
+ unit: V
22
+ battery_current:
23
+ register: 102
24
+ type: int16
25
+ scale: 0.1
26
+ unit: A
27
+ battery_power:
28
+ register: 103
29
+ type: int16
30
+ unit: W
31
+ battery_temperature:
32
+ register: 104
33
+ scale: 0.1
34
+ unit: °C
35
+ dc_input_power:
36
+ register: 107
37
+ unit: W
38
+ dc_input_voltage:
39
+ register: 108
40
+ scale: 0.1
41
+ unit: V
42
+ dc_input_current:
43
+ register: 109
44
+ scale: 0.1
45
+ unit: A
46
+ ac_output_power:
47
+ register: 110
48
+ unit: W
49
+ ac_output_voltage:
50
+ register: 111
51
+ scale: 0.1
52
+ unit: V
53
+ ac_output_current:
54
+ register: 112
55
+ scale: 0.1
56
+ unit: A
57
+ ac_output_frequency:
58
+ register: 113
59
+ scale: 0.1
60
+ unit: Hz
61
+ ac_input_power:
62
+ register: 114
63
+ unit: W
64
+ ac_input_voltage:
65
+ register: 115
66
+ scale: 0.1
67
+ unit: V
68
+ ac_input_current:
69
+ register: 116
70
+ scale: 0.1
71
+ unit: A
72
+ ac_input_frequency:
73
+ register: 117
74
+ scale: 0.1
75
+ unit: Hz
76
+ internal_temperature:
77
+ register: 118
78
+ scale: 0.1
79
+ unit: °C
80
+ time_remaining_minutes:
81
+ register: 119
82
+ unit: min
83
+ constants:
84
+ battery_chemistry: LiFePO4
85
+ total_capacity:
86
+ value: 2000
87
+ unit: Wh
88
+ battery_manufacturer_name: Bluetti
89
+ battery_manufacturer_model: AC200P
@@ -0,0 +1,48 @@
1
+ # Bluetti AC200PL register map
2
+ # Source: https://github.com/Patrick762/bluetti-bt-lib (bluetti_bt_lib/devices/ac200pl.py)
3
+ #
4
+ # field_name is a standard Bluetti register name — the code maps these to
5
+ # SignalK paths on its own (see lib/path-mapper.js). `path` on a field is an
6
+ # override, only needed if you're exposing a register the code doesn't
7
+ # recognise. `constants` are fixed, non-register facts about this model —
8
+ # a bare scalar (string/number) or { value, unit } for one that needs unit
9
+ # conversion (e.g. total_capacity in Wh -> J). No verified nominal capacity
10
+ # is bundled for most models — add a total_capacity constant yourself if you
11
+ # know it, to enable the derived remaining-capacity calculation.
12
+ #
13
+
14
+ fields:
15
+ battery_soc:
16
+ register: 43
17
+ unit: "%"
18
+ dc_input_power:
19
+ register: 36
20
+ unit: W
21
+ ac_input_power:
22
+ register: 37
23
+ unit: W
24
+ ac_output_power:
25
+ register: 38
26
+ unit: W
27
+ dc_output_power:
28
+ register: 39
29
+ unit: W
30
+ ac_output_voltage:
31
+ register: 71
32
+ unit: V
33
+ ac_output_frequency:
34
+ register: 74
35
+ scale: 0.1
36
+ unit: Hz
37
+ dc_input_voltage:
38
+ register: 86
39
+ scale: 0.1
40
+ unit: V
41
+ dc_input_current:
42
+ register: 88
43
+ scale: 0.01
44
+ unit: A
45
+ constants:
46
+ battery_chemistry: LiFePO4
47
+ battery_manufacturer_name: Bluetti
48
+ battery_manufacturer_model: AC200PL