@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.
- package/CHANGELOG.md +7 -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 +12 -5
- 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
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# Bluetti EP500P register map
|
|
2
|
+
# Source: https://github.com/Patrick762/bluetti-bt-lib (bluetti_bt_lib/devices/ep500p.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
|
+
scale: 0.1
|
|
33
|
+
unit: V
|
|
34
|
+
ac_output_frequency:
|
|
35
|
+
register: 74
|
|
36
|
+
scale: 0.01
|
|
37
|
+
unit: Hz
|
|
38
|
+
ac_input_voltage:
|
|
39
|
+
register: 77
|
|
40
|
+
scale: 0.1
|
|
41
|
+
unit: V
|
|
42
|
+
ac_input_frequency:
|
|
43
|
+
register: 80
|
|
44
|
+
scale: 0.01
|
|
45
|
+
unit: Hz
|
|
46
|
+
dc_input_voltage:
|
|
47
|
+
register: 86
|
|
48
|
+
scale: 0.1
|
|
49
|
+
unit: V
|
|
50
|
+
dc_input_current:
|
|
51
|
+
register: 88
|
|
52
|
+
scale: 0.1
|
|
53
|
+
unit: A
|
|
54
|
+
constants:
|
|
55
|
+
battery_chemistry: LiFePO4
|
|
56
|
+
battery_manufacturer_name: Bluetti
|
|
57
|
+
battery_manufacturer_model: EP500P
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# Bluetti EP600 register map
|
|
2
|
+
# Source: https://github.com/Patrick762/bluetti-bt-lib (bluetti_bt_lib/devices/ep600.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: 102
|
|
17
|
+
unit: "%"
|
|
18
|
+
power_generation:
|
|
19
|
+
register: 1202
|
|
20
|
+
scale: 0.1
|
|
21
|
+
unit: kWh
|
|
22
|
+
dc_input_power:
|
|
23
|
+
register: 1212
|
|
24
|
+
unit: W
|
|
25
|
+
dc_input_voltage:
|
|
26
|
+
register: 1213
|
|
27
|
+
scale: 0.1
|
|
28
|
+
unit: V
|
|
29
|
+
dc_input_current:
|
|
30
|
+
register: 1214
|
|
31
|
+
scale: 0.1
|
|
32
|
+
unit: A
|
|
33
|
+
pv_s2_power:
|
|
34
|
+
register: 1220
|
|
35
|
+
unit: W
|
|
36
|
+
pv_s2_voltage:
|
|
37
|
+
register: 1221
|
|
38
|
+
scale: 0.1
|
|
39
|
+
unit: V
|
|
40
|
+
pv_s2_current:
|
|
41
|
+
register: 1222
|
|
42
|
+
scale: 0.1
|
|
43
|
+
unit: A
|
|
44
|
+
sm_p1_power:
|
|
45
|
+
register: 1228
|
|
46
|
+
unit: W
|
|
47
|
+
sm_p1_voltage:
|
|
48
|
+
register: 1229
|
|
49
|
+
scale: 0.1
|
|
50
|
+
unit: V
|
|
51
|
+
sm_p1_current:
|
|
52
|
+
register: 1230
|
|
53
|
+
unit: A
|
|
54
|
+
sm_p2_power:
|
|
55
|
+
register: 1236
|
|
56
|
+
unit: W
|
|
57
|
+
sm_p2_voltage:
|
|
58
|
+
register: 1237
|
|
59
|
+
scale: 0.1
|
|
60
|
+
unit: V
|
|
61
|
+
sm_p2_current:
|
|
62
|
+
register: 1238
|
|
63
|
+
unit: A
|
|
64
|
+
sm_p3_power:
|
|
65
|
+
register: 1244
|
|
66
|
+
unit: W
|
|
67
|
+
sm_p3_voltage:
|
|
68
|
+
register: 1245
|
|
69
|
+
scale: 0.1
|
|
70
|
+
unit: V
|
|
71
|
+
sm_p3_current:
|
|
72
|
+
register: 1246
|
|
73
|
+
unit: A
|
|
74
|
+
grid_frequency:
|
|
75
|
+
register: 1300
|
|
76
|
+
scale: 0.1
|
|
77
|
+
unit: Hz
|
|
78
|
+
grid_p1_power:
|
|
79
|
+
register: 1313
|
|
80
|
+
unit: W
|
|
81
|
+
grid_p1_voltage:
|
|
82
|
+
register: 1314
|
|
83
|
+
scale: 0.1
|
|
84
|
+
unit: V
|
|
85
|
+
grid_p1_current:
|
|
86
|
+
register: 1315
|
|
87
|
+
scale: 0.1
|
|
88
|
+
unit: A
|
|
89
|
+
grid_p2_power:
|
|
90
|
+
register: 1319
|
|
91
|
+
unit: W
|
|
92
|
+
grid_p2_voltage:
|
|
93
|
+
register: 1320
|
|
94
|
+
scale: 0.1
|
|
95
|
+
unit: V
|
|
96
|
+
grid_p2_current:
|
|
97
|
+
register: 1321
|
|
98
|
+
scale: 0.1
|
|
99
|
+
unit: A
|
|
100
|
+
grid_p3_power:
|
|
101
|
+
register: 1325
|
|
102
|
+
unit: W
|
|
103
|
+
grid_p3_voltage:
|
|
104
|
+
register: 1326
|
|
105
|
+
scale: 0.1
|
|
106
|
+
unit: V
|
|
107
|
+
grid_p3_current:
|
|
108
|
+
register: 1327
|
|
109
|
+
scale: 0.1
|
|
110
|
+
unit: A
|
|
111
|
+
ac_output_frequency:
|
|
112
|
+
register: 1500
|
|
113
|
+
scale: 0.1
|
|
114
|
+
unit: Hz
|
|
115
|
+
ac_p1_power:
|
|
116
|
+
register: 1510
|
|
117
|
+
unit: W
|
|
118
|
+
ac_p1_voltage:
|
|
119
|
+
register: 1511
|
|
120
|
+
scale: 0.1
|
|
121
|
+
unit: V
|
|
122
|
+
ac_p1_current:
|
|
123
|
+
register: 1512
|
|
124
|
+
scale: 0.1
|
|
125
|
+
unit: A
|
|
126
|
+
ac_p2_power:
|
|
127
|
+
register: 1517
|
|
128
|
+
unit: W
|
|
129
|
+
ac_p2_voltage:
|
|
130
|
+
register: 1518
|
|
131
|
+
scale: 0.1
|
|
132
|
+
unit: V
|
|
133
|
+
ac_p2_current:
|
|
134
|
+
register: 1519
|
|
135
|
+
scale: 0.1
|
|
136
|
+
unit: A
|
|
137
|
+
ac_p3_power:
|
|
138
|
+
register: 1524
|
|
139
|
+
unit: W
|
|
140
|
+
ac_p3_voltage:
|
|
141
|
+
register: 1525
|
|
142
|
+
scale: 0.1
|
|
143
|
+
unit: V
|
|
144
|
+
ac_p3_current:
|
|
145
|
+
register: 1526
|
|
146
|
+
scale: 0.1
|
|
147
|
+
unit: A
|
|
148
|
+
constants:
|
|
149
|
+
battery_chemistry: LiFePO4
|
|
150
|
+
battery_manufacturer_name: Bluetti
|
|
151
|
+
battery_manufacturer_model: EP600
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# Bluetti EP760 register map
|
|
2
|
+
# Source: https://github.com/Patrick762/bluetti-bt-lib (bluetti_bt_lib/devices/ep760.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: 102
|
|
17
|
+
unit: "%"
|
|
18
|
+
dc_input_power:
|
|
19
|
+
register: 1212
|
|
20
|
+
unit: W
|
|
21
|
+
dc_input_voltage:
|
|
22
|
+
register: 1213
|
|
23
|
+
scale: 0.1
|
|
24
|
+
unit: V
|
|
25
|
+
dc_input_current:
|
|
26
|
+
register: 1214
|
|
27
|
+
scale: 0.1
|
|
28
|
+
unit: A
|
|
29
|
+
sm_p1_power:
|
|
30
|
+
register: 1228
|
|
31
|
+
unit: W
|
|
32
|
+
sm_p1_voltage:
|
|
33
|
+
register: 1229
|
|
34
|
+
scale: 0.1
|
|
35
|
+
unit: V
|
|
36
|
+
sm_p1_current:
|
|
37
|
+
register: 1230
|
|
38
|
+
unit: A
|
|
39
|
+
ac_input_frequency:
|
|
40
|
+
register: 1300
|
|
41
|
+
scale: 0.1
|
|
42
|
+
unit: Hz
|
|
43
|
+
ac_input_power:
|
|
44
|
+
register: 1313
|
|
45
|
+
unit: W
|
|
46
|
+
ac_input_voltage:
|
|
47
|
+
register: 1314
|
|
48
|
+
scale: 0.1
|
|
49
|
+
unit: V
|
|
50
|
+
ac_input_current:
|
|
51
|
+
register: 1315
|
|
52
|
+
scale: 0.1
|
|
53
|
+
unit: A
|
|
54
|
+
ac_output_frequency:
|
|
55
|
+
register: 1500
|
|
56
|
+
scale: 0.1
|
|
57
|
+
unit: Hz
|
|
58
|
+
ac_output_power:
|
|
59
|
+
register: 1510
|
|
60
|
+
unit: W
|
|
61
|
+
ac_output_voltage:
|
|
62
|
+
register: 1511
|
|
63
|
+
scale: 0.1
|
|
64
|
+
unit: V
|
|
65
|
+
ac_output_current:
|
|
66
|
+
register: 1512
|
|
67
|
+
scale: 0.1
|
|
68
|
+
unit: A
|
|
69
|
+
constants:
|
|
70
|
+
battery_chemistry: LiFePO4
|
|
71
|
+
battery_manufacturer_name: Bluetti
|
|
72
|
+
battery_manufacturer_model: EP760
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Bluetti EP800 register map
|
|
2
|
+
# Source: https://github.com/Patrick762/bluetti-bt-lib (bluetti_bt_lib/devices/ep800.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: 102
|
|
17
|
+
unit: "%"
|
|
18
|
+
constants:
|
|
19
|
+
battery_chemistry: LiFePO4
|
|
20
|
+
battery_manufacturer_name: Bluetti
|
|
21
|
+
battery_manufacturer_model: EP800
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# Bluetti HANDSFREE1 register map
|
|
2
|
+
# Source: https://github.com/Patrick762/bluetti-bt-lib (bluetti_bt_lib/devices/handsfree1.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
|
+
time_remaining_minutes:
|
|
28
|
+
register: 104
|
|
29
|
+
scale: 6
|
|
30
|
+
unit: min
|
|
31
|
+
dc_input_voltage:
|
|
32
|
+
register: 1213
|
|
33
|
+
scale: 0.1
|
|
34
|
+
unit: V
|
|
35
|
+
dc_input_current:
|
|
36
|
+
register: 1214
|
|
37
|
+
scale: 0.1
|
|
38
|
+
unit: A
|
|
39
|
+
ac_input_frequency:
|
|
40
|
+
register: 1300
|
|
41
|
+
scale: 0.1
|
|
42
|
+
unit: Hz
|
|
43
|
+
ac_input_voltage:
|
|
44
|
+
register: 1314
|
|
45
|
+
scale: 0.1
|
|
46
|
+
unit: V
|
|
47
|
+
ac_input_current:
|
|
48
|
+
register: 1315
|
|
49
|
+
scale: 0.1
|
|
50
|
+
unit: A
|
|
51
|
+
ac_output_frequency:
|
|
52
|
+
register: 1500
|
|
53
|
+
scale: 0.1
|
|
54
|
+
unit: Hz
|
|
55
|
+
ac_output_voltage:
|
|
56
|
+
register: 1511
|
|
57
|
+
scale: 0.1
|
|
58
|
+
unit: V
|
|
59
|
+
constants:
|
|
60
|
+
battery_chemistry: LiFePO4
|
|
61
|
+
battery_manufacturer_name: Bluetti
|
|
62
|
+
battery_manufacturer_model: Handsfree 1
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Bluetti PR100V2 register map
|
|
2
|
+
# Source: https://github.com/Patrick762/bluetti-bt-lib (bluetti_bt_lib/devices/pr100v2.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: PR100 V2
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Bluetti PR30V2 register map
|
|
2
|
+
# Source: https://github.com/Patrick762/bluetti-bt-lib (bluetti_bt_lib/devices/pr30v2.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: PR30 V2
|
package/lib/csv-loader.js
DELETED
|
@@ -1,160 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
const fs = require("fs");
|
|
4
|
-
const { parse } = require("csv-parse/sync");
|
|
5
|
-
|
|
6
|
-
// Column name aliases — Bluetti CSVs have been seen in English and Chinese headers.
|
|
7
|
-
// Maps our internal key → list of possible header strings (case-insensitive match).
|
|
8
|
-
const COL_ALIASES = {
|
|
9
|
-
field_name: ["field_name", "fieldname", "name", "字段名", "field"],
|
|
10
|
-
register_address: ["register_address", "register", "address", "addr", "reg", "no", "no.", "寄存器", "地址"],
|
|
11
|
-
data_type: ["data_type", "datatype", "type", "数据type", "数据类型"],
|
|
12
|
-
scale: ["scale", "倍率", "factor", "multiplier"],
|
|
13
|
-
offset: ["offset", "偏移"],
|
|
14
|
-
unit: ["unit", "units", "单位"],
|
|
15
|
-
signalk_path: ["signalk_path", "signalk", "sk_path", "path"],
|
|
16
|
-
register_count: ["register_count", "count", "length", "bytes", "数据长度"],
|
|
17
|
-
constant_value: ["constant_value", "constant", "const", "fixed_value", "static_value"],
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
function resolveColumn(headers, key) {
|
|
21
|
-
const aliases = COL_ALIASES[key];
|
|
22
|
-
for (const alias of aliases) {
|
|
23
|
-
const found = headers.find((h) => h.trim().toLowerCase() === alias.toLowerCase());
|
|
24
|
-
if (found) return found;
|
|
25
|
-
}
|
|
26
|
-
return null;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// Parse the CSV and return an array of field descriptors.
|
|
30
|
-
// Each descriptor is either register-backed: { fieldName, register, count, dataType, scale, offset, unit, signalkPath }
|
|
31
|
-
// or a fixed constant (register_address left blank): { fieldName, register: null, count: 0, dataType: "const", ..., constantValue }
|
|
32
|
-
function loadCsv(filePath) {
|
|
33
|
-
const raw = fs.readFileSync(filePath, "utf8");
|
|
34
|
-
|
|
35
|
-
// Strip BOM if present
|
|
36
|
-
const content = raw.charCodeAt(0) === 0xfeff ? raw.slice(1) : raw;
|
|
37
|
-
|
|
38
|
-
const records = parse(content, {
|
|
39
|
-
columns: true,
|
|
40
|
-
skip_empty_lines: true,
|
|
41
|
-
trim: true,
|
|
42
|
-
comment: "#",
|
|
43
|
-
relax_column_count: true,
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
if (records.length === 0) throw new Error(`CSV file ${filePath} has no data rows`);
|
|
47
|
-
|
|
48
|
-
const headers = Object.keys(records[0]);
|
|
49
|
-
|
|
50
|
-
const col = {};
|
|
51
|
-
for (const key of Object.keys(COL_ALIASES)) {
|
|
52
|
-
col[key] = resolveColumn(headers, key);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
if (!col.register_address) throw new Error("CSV must have a register address column");
|
|
56
|
-
if (!col.field_name) throw new Error("CSV must have a field name column");
|
|
57
|
-
|
|
58
|
-
const fields = [];
|
|
59
|
-
for (const row of records) {
|
|
60
|
-
const fieldName = (row[col.field_name] || "").trim();
|
|
61
|
-
if (!fieldName) continue;
|
|
62
|
-
|
|
63
|
-
const signalkPath = col.signalk_path ? (row[col.signalk_path] || "").trim() : "";
|
|
64
|
-
const constantRaw = col.constant_value ? (row[col.constant_value] || "").trim() : "";
|
|
65
|
-
|
|
66
|
-
const registerRaw = row[col.register_address];
|
|
67
|
-
const register = parseInt(registerRaw, 10);
|
|
68
|
-
|
|
69
|
-
if (isNaN(register)) {
|
|
70
|
-
// No register address — only valid if this row instead supplies a fixed
|
|
71
|
-
// constant (e.g. a model's nominal battery capacity or chemistry, which
|
|
72
|
-
// isn't a live register on every device). Otherwise it's a header-like
|
|
73
|
-
// row embedded in the data and gets skipped.
|
|
74
|
-
if (!constantRaw) continue;
|
|
75
|
-
// Numeric constants (e.g. a nominal capacity in Wh) need to go through
|
|
76
|
-
// the same unit conversion as register-backed fields; text constants
|
|
77
|
-
// (chemistry, manufacturer name) are left as-is.
|
|
78
|
-
const asNumber = Number(constantRaw);
|
|
79
|
-
const constantValue = Number.isNaN(asNumber) ? constantRaw : asNumber;
|
|
80
|
-
const unit = col.unit ? (row[col.unit] || "").trim() : "";
|
|
81
|
-
fields.push({ fieldName, register: null, count: 0, dataType: "const", scale: 1, offset: 0, unit, signalkPath, constantValue });
|
|
82
|
-
continue;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
const dataType = col.data_type ? (row[col.data_type] || "uint16").toLowerCase().trim() : "uint16";
|
|
86
|
-
const scale = col.scale ? parseFloat(row[col.scale]) || 1 : 1;
|
|
87
|
-
const offset = col.offset ? parseFloat(row[col.offset]) || 0 : 0;
|
|
88
|
-
const unit = col.unit ? (row[col.unit] || "").trim() : "";
|
|
89
|
-
|
|
90
|
-
// register_count: for multi-register types (int32, uint32, float32 = 2 regs; int64 = 4 regs)
|
|
91
|
-
let count = col.register_count ? parseInt(row[col.register_count], 10) : NaN;
|
|
92
|
-
if (isNaN(count)) count = registersForType(dataType);
|
|
93
|
-
|
|
94
|
-
fields.push({ fieldName, register, count, dataType, scale, offset, unit, signalkPath });
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
if (fields.length === 0) throw new Error(`No valid register rows found in ${filePath}`);
|
|
98
|
-
return fields;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
function registersForType(dataType) {
|
|
102
|
-
switch (dataType) {
|
|
103
|
-
case "int32":
|
|
104
|
-
case "uint32":
|
|
105
|
-
case "float32":
|
|
106
|
-
return 2;
|
|
107
|
-
case "int64":
|
|
108
|
-
case "uint64":
|
|
109
|
-
return 4;
|
|
110
|
-
default:
|
|
111
|
-
return 1; // uint16, int16, bool, enum
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
// Decode a raw register value (or pair of registers for 32-bit types) into a number.
|
|
116
|
-
// rawRegs: Map<addr, uint16>, startAddr: the register address for this field
|
|
117
|
-
function decodeValue(field, rawRegs) {
|
|
118
|
-
const { register, count, dataType, scale, offset } = field;
|
|
119
|
-
|
|
120
|
-
if (dataType === "const") return field.constantValue;
|
|
121
|
-
|
|
122
|
-
let raw;
|
|
123
|
-
if (count === 1) {
|
|
124
|
-
raw = rawRegs.get(register);
|
|
125
|
-
if (raw === undefined) return null;
|
|
126
|
-
} else if (count === 2) {
|
|
127
|
-
const hi = rawRegs.get(register);
|
|
128
|
-
const lo = rawRegs.get(register + 1);
|
|
129
|
-
if (hi === undefined || lo === undefined) return null;
|
|
130
|
-
raw = (hi << 16) | lo;
|
|
131
|
-
} else {
|
|
132
|
-
return null;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
let value;
|
|
136
|
-
switch (dataType) {
|
|
137
|
-
case "int16":
|
|
138
|
-
value = raw > 0x7fff ? raw - 0x10000 : raw;
|
|
139
|
-
break;
|
|
140
|
-
case "int32": {
|
|
141
|
-
const signed = raw > 0x7fffffff ? raw - 0x100000000 : raw;
|
|
142
|
-
value = signed;
|
|
143
|
-
break;
|
|
144
|
-
}
|
|
145
|
-
case "float32": {
|
|
146
|
-
const tmpBuf = Buffer.alloc(4);
|
|
147
|
-
tmpBuf.writeUInt32BE(raw, 0);
|
|
148
|
-
value = tmpBuf.readFloatBE(0);
|
|
149
|
-
break;
|
|
150
|
-
}
|
|
151
|
-
case "bool":
|
|
152
|
-
return raw !== 0 ? 1 : 0;
|
|
153
|
-
default:
|
|
154
|
-
value = raw;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
return value * scale + offset;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
module.exports = { loadCsv, decodeValue };
|
package/registers/ac200p.csv
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
# Bluetti register map example — based on AC200P / AC200MAX community reverse-engineering.
|
|
2
|
-
# Replace with your device's actual CSV. Columns the plugin recognises:
|
|
3
|
-
#
|
|
4
|
-
# field_name — a standard Bluetti register name (see lib/path-mapper.js
|
|
5
|
-
# for the ones the code already maps to a SignalK path),
|
|
6
|
-
# or any identifier you like if you're supplying signalk_path
|
|
7
|
-
# register_address — Modbus holding register number (decimal); leave blank
|
|
8
|
-
# for a fixed constant_value row instead (e.g. model specs)
|
|
9
|
-
# register_count — number of 16-bit registers this field spans (1 for uint16/int16, 2 for int32/float32)
|
|
10
|
-
# data_type — uint16 | int16 | uint32 | int32 | float32 | bool
|
|
11
|
-
# scale — multiply raw value by this to get physical value
|
|
12
|
-
# offset — add after scale (usually 0)
|
|
13
|
-
# unit — V | A | W | Wh | % | °C | Hz | min — drives SignalK unit conversion
|
|
14
|
-
# signalk_path — override; only needed for a register the code doesn't
|
|
15
|
-
# recognise by field_name. Use {name} where the device name goes
|
|
16
|
-
# constant_value — fixed value for a non-register row (register_address blank)
|
|
17
|
-
#
|
|
18
|
-
field_name,register_address,register_count,data_type,scale,offset,unit,signalk_path,constant_value
|
|
19
|
-
# ── Battery ────────────────────────────────────────────────────────────────
|
|
20
|
-
battery_percent,100,1,uint16,1,0,%,,
|
|
21
|
-
battery_voltage,101,1,uint16,0.1,0,V,,
|
|
22
|
-
battery_current,102,1,int16,0.1,0,A,,
|
|
23
|
-
battery_power,103,1,int16,1,0,W,,
|
|
24
|
-
battery_temperature,104,1,uint16,0.1,0,°C,,
|
|
25
|
-
# ── DC / Solar input ────────────────────────────────────────────────────────
|
|
26
|
-
dc_input_power,107,1,uint16,1,0,W,,
|
|
27
|
-
dc_input_voltage,108,1,uint16,0.1,0,V,,
|
|
28
|
-
dc_input_current,109,1,uint16,0.1,0,A,,
|
|
29
|
-
# ── AC output (inverter) ────────────────────────────────────────────────────
|
|
30
|
-
ac_output_power,110,1,uint16,1,0,W,,
|
|
31
|
-
ac_output_voltage,111,1,uint16,0.1,0,V,,
|
|
32
|
-
ac_output_current,112,1,uint16,0.1,0,A,,
|
|
33
|
-
ac_output_frequency,113,1,uint16,0.1,0,Hz,,
|
|
34
|
-
# ── AC input (charger / mains) ──────────────────────────────────────────────
|
|
35
|
-
ac_input_power,114,1,uint16,1,0,W,,
|
|
36
|
-
ac_input_voltage,115,1,uint16,0.1,0,V,,
|
|
37
|
-
ac_input_current,116,1,uint16,0.1,0,A,,
|
|
38
|
-
ac_input_frequency,117,1,uint16,0.1,0,Hz,,
|
|
39
|
-
# ── Internal temperature ────────────────────────────────────────────────────
|
|
40
|
-
internal_temperature,118,1,uint16,0.1,0,°C,,
|
|
41
|
-
# ── Estimated time remaining ────────────────────────────────────────────────
|
|
42
|
-
time_remaining_minutes,119,1,uint16,1,0,min,,
|
|
43
|
-
# ── Fixed model facts ────────────────────────────────────────────────────────
|
|
44
|
-
battery_chemistry,,,,,,,,LiFePO4
|
|
45
|
-
total_capacity,,,,,,Wh,,2000
|
|
46
|
-
battery_manufacturer_name,,,,,,,,Bluetti
|
|
47
|
-
battery_manufacturer_model,,,,,,,,AC200P
|
package/registers/el100v2.csv
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
# Bluetti Elite 100 V2 (EL100V2) register map
|
|
2
|
-
# Source: https://github.com/Patrick762/bluetti-bt-lib
|
|
3
|
-
# BaseDeviceV2 fields: BATTERY_SOC (102)
|
|
4
|
-
# EL100V2-specific fields: 140, 142, 144, 146, 1314
|
|
5
|
-
# Note: DEVICE_TYPE (110) and DEVICE_SN (116) are string/serial fields — omitted.
|
|
6
|
-
#
|
|
7
|
-
# field_name is a standard Bluetti register name — the code maps these to
|
|
8
|
-
# SignalK paths on its own (see lib/path-mapper.js). signalk_path is an
|
|
9
|
-
# override, only needed if you're exposing a register the code doesn't
|
|
10
|
-
# recognise. constant_value is for fixed, non-register facts about this model
|
|
11
|
-
# (register_address left blank for those rows).
|
|
12
|
-
#
|
|
13
|
-
field_name,register_address,register_count,data_type,scale,offset,unit,signalk_path,constant_value
|
|
14
|
-
battery_soc,102,1,uint16,1,0,%,,
|
|
15
|
-
dc_output_power,140,1,uint16,1,0,W,,
|
|
16
|
-
ac_output_power,142,1,uint16,1,0,W,,
|
|
17
|
-
dc_input_power,144,1,uint16,1,0,W,,
|
|
18
|
-
ac_input_power,146,1,uint16,1,0,W,,
|
|
19
|
-
ac_input_voltage,1314,1,uint16,0.1,0,V,,
|
|
20
|
-
# ── Fixed model facts ────────────────────────────────────────────────────────
|
|
21
|
-
battery_chemistry,,,,,,,,LiFePO4
|
|
22
|
-
total_capacity,,,,,,Wh,,1024
|
|
23
|
-
battery_manufacturer_name,,,,,,,,Bluetti
|
|
24
|
-
battery_manufacturer_model,,,,,,,,Elite 100 V2
|
|
25
|
-
# dc_output_power (140) only reports power, not current — this fixed nominal
|
|
26
|
-
# voltage (12V accessory port) is what lib/path-mapper.js derives current from.
|
|
27
|
-
dc_output_voltage,,,,,,V,,12
|