@rhizomatics/signalk-bluetti-plugin 1.4.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/CHANGELOG.md +11 -0
- package/README.md +40 -4
- package/cli.js +69 -9
- package/docs/assets/icon.svg +36 -0
- package/docs/assets/screenshots/bluetti_data.png +0 -0
- package/docs/examples/model_definition.yaml +112 -0
- package/index.js +78 -25
- package/lib/path-mapper.js +51 -9
- package/lib/register-loader.js +120 -0
- package/lib/scanner.js +1 -1
- package/package.json +15 -6
- package/registers/ac180.yaml +58 -0
- package/registers/ac180p.yaml +34 -0
- package/registers/ac180t.yaml +58 -0
- package/registers/ac200l.yaml +48 -0
- package/registers/ac200m.yaml +48 -0
- package/registers/ac200p.yaml +89 -0
- package/registers/ac200pl.yaml +48 -0
- package/registers/ac2a.yaml +34 -0
- package/registers/ac2p.yaml +34 -0
- package/registers/ac300.yaml +57 -0
- package/registers/ac500.yaml +56 -0
- package/registers/ac50b.yaml +31 -0
- package/registers/ac60.yaml +33 -0
- package/registers/ac60p.yaml +33 -0
- package/registers/ac70.yaml +62 -0
- package/registers/ac70p.yaml +58 -0
- package/registers/ap300.yaml +34 -0
- package/registers/eb3a.yaml +41 -0
- package/registers/el100v2.yaml +43 -0
- package/registers/el30v2.yaml +38 -0
- package/registers/ep2000.yaml +75 -0
- package/registers/ep500.yaml +57 -0
- package/registers/ep500p.yaml +57 -0
- package/registers/ep600.yaml +151 -0
- package/registers/ep760.yaml +72 -0
- package/registers/ep800.yaml +21 -0
- package/registers/handsfree1.yaml +62 -0
- package/registers/pr100v2.yaml +34 -0
- package/registers/pr30v2.yaml +34 -0
- package/lib/csv-loader.js +0 -160
- package/registers/ac200p.csv +0 -47
- package/registers/el100v2.csv +0 -27
package/lib/path-mapper.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
const { decodeValue } = require("./
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
//
|
|
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
|
|
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/*.
|
|
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
|
-
"description": "SignalK plugin for Bluetti power station monitoring via Bluetooth LE.
|
|
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"
|
|
@@ -39,15 +45,17 @@
|
|
|
39
45
|
"cli": "node cli.js",
|
|
40
46
|
"fmt": "oxfmt",
|
|
41
47
|
"fmt:check": "oxfmt --check",
|
|
42
|
-
"lint": "oxlint"
|
|
48
|
+
"lint": "oxlint",
|
|
49
|
+
"test": "node --test",
|
|
50
|
+
"test:coverage": "node --experimental-test-coverage --test-coverage-include=\"lib/**/*.js\" --test-coverage-exclude=\"lib/device.js\" --test-coverage-exclude=\"lib/scanner.js\" --test-coverage-lines=80 --test-coverage-branches=80 --test-coverage-functions=80 --test"
|
|
43
51
|
},
|
|
44
52
|
"dependencies": {
|
|
45
53
|
"@naugehyde/node-ble": "^1.13.5",
|
|
46
|
-
"
|
|
54
|
+
"yaml": "^2.9.0"
|
|
47
55
|
},
|
|
48
56
|
"devDependencies": {
|
|
49
57
|
"oxfmt": "^0.61.0",
|
|
50
|
-
"oxlint": "^1.
|
|
58
|
+
"oxlint": "^1.75.0",
|
|
51
59
|
"oxlint-tsgolint": "^7.0.2001"
|
|
52
60
|
},
|
|
53
61
|
"engines": {
|
|
@@ -56,6 +64,7 @@
|
|
|
56
64
|
},
|
|
57
65
|
"signalk": {
|
|
58
66
|
"displayName": "Bluetti Monitoring",
|
|
67
|
+
"appIcon": "docs/assets/icon.svg",
|
|
59
68
|
"recommends": [
|
|
60
69
|
"@rhizomatics/signalk-einklabel-plugin"
|
|
61
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
|