Skip to content

Commit 3617baa

Browse files
committed
Finalize Pylontech US200
1 parent 7db6cc2 commit 3617baa

File tree

5 files changed

+172
-90
lines changed

5 files changed

+172
-90
lines changed

β€Žbattery_management_systems/pylontech_us2000_can/README.mdβ€Ž

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
# Pylontech US2000 (CAN bus)
22

3-
This _Enapter Device Blueprint_ integrates **Pylontech US2000** - lithium battery control and monitoring via [CAN bus](https://developers.enapter.com/docs/reference/ucm/can). The device can also support RS-485 and RS-232 communication interfaces (configured by vendor).
3+
This [Enapter Device Blueprint](https://github.com/Enapter/marketplace#blue_book-enapter-device-blueprints) integrates **Pylontech US2000** - lithium battery control and monitoring via [CAN bus](https://developers.enapter.com/docs/reference/ucm/can). The device can also support RS-485 and RS-232 communication interfaces (configured by vendor).
44

5-
Use [Enapter ENP-CAN](https://handbook.enapter.com/modules/ENP-CAN/ENP-CAN.html) module for physical connection. See [connection instructions](https://handbook.enapter.com/modules/ENP-CAN/ENP-CAN.html#connection-examples) in the module manual.
5+
## Connect to Enapter
66

7-
## CAN bus Communication Interface Parameters
8-
9-
- Baud rate: `500` kbps.
7+
- Sign up Enapter Cloud to using [Web](https://cloud.enapter.com/) or mobile app ([iOS](https://apps.apple.com/app/id1388329910), [Android](https://play.google.com/store/apps/details?id=com.enapter&hl=en)).
8+
- Use [Enapter ENP-CAN](https://handbook.enapter.com/modules/ENP-CAN/ENP-CAN.html) module for physical connection. See [connection instructions](https://handbook.enapter.com/modules/ENP-CAN/ENP-CAN.html#connection-examples) in the module manual.
9+
- [Add ENP-CAN to your site](https://handbook.enapter.com/software/mobile/android_mobile_app.html#adding-sites-and-devices) using the mobile app.
10+
- [Upload](https://developers.enapter.com/docs/tutorial/uploading-blueprint/) this blueprint to ENP-CAN.
1011

1112
## References
1213

β€Žbattery_management_systems/pylontech_us2000_can/firmware.luaβ€Ž

Lines changed: 119 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,121 @@
1+
--- CAN bus interface baud rate
2+
BAUD_RATE = 500 -- kbps
3+
14
telemetry = {}
25

36
function main()
4-
local err = can.init(500, can_handler)
7+
local err = can.init(BAUD_RATE, can_handler)
58
if err and err ~= 0 then
69
enapter.log("CAN failed: "..err.." "..can.err_to_str(err), "error", true)
10+
scheduler.add(5000, function()
11+
enapter.send_telemetry({ status = 'error', alerts = {'can_init_failed'} })
12+
end)
13+
else
14+
scheduler.add(30000, send_properties)
15+
scheduler.add(1000, send_telemetry)
716
end
8-
9-
scheduler.add(30000, properties)
10-
scheduler.add(1000, metrics)
1117
end
1218

13-
function properties()
19+
function send_properties()
1420
enapter.send_properties({ vendor = "PylonTech", model = "US2000" })
1521
end
1622

17-
function metrics()
18-
if next(telemetry) ~= nil then
23+
-- Holds the number of `send_telemetry` that had an empty `telemetry`
24+
local missed_telemetry_count = 0
25+
26+
function send_telemetry()
27+
if telemetry[1] ~= nil then
1928
enapter.send_telemetry(telemetry)
29+
-- Cleanup telemetry and let it be refilled by `can_handler`
30+
telemetry = {}
31+
missed_telemetry_count = 0
32+
else
33+
missed_telemetry_count = missed_telemetry_count + 1
34+
if missed_telemetry_count > 5 then
35+
enapter.send_telemetry({
36+
status = 'read_error',
37+
alerts = {'can_fail'}
38+
})
39+
end
2040
end
2141
end
2242

2343
function can_handler(msg_id, data)
44+
local alerts = {}
45+
local status = "ok"
46+
47+
if msg_id == 0x351 then
48+
status, alerts = battery_metrics(data, status, alerts)
49+
end
50+
51+
if msg_id == 0x355 then
52+
status, alerts = soc_and_soh(data, status, alerts)
53+
end
54+
55+
if msg_id == 0x356 then
56+
status, alerts = module_or_system_metrics(data, status, alerts)
57+
end
58+
2459
if msg_id == 0x359 then
60+
status, alerts = alarms(data, status, alerts)
61+
end
62+
63+
if msg_id == 0x35C then
64+
status, alerts = charge_discharge_flags(data, status, alerts)
65+
end
66+
67+
telemetry["status"] = status
68+
telemetry["alerts"] = alerts
69+
end
70+
71+
function battery_metrics(data, status, alerts)
72+
local ok, err = pcall(function()
73+
local byte_pair1, byte_pair2, byte_pair3, byte_pair4 = string.unpack("I2i2i2I2", data)
74+
telemetry["battery_charge_voltage"] = byte_pair1 / 10.0
75+
telemetry["charge_current_limit"] = byte_pair2 / 10.0
76+
telemetry["discharge_current_limit"] = byte_pair3 / 10.0
77+
telemetry["discharge_voltage"] = byte_pair4 / 10.0
78+
end)
79+
if not ok then
80+
enapter.log("Reading message 0x351 failed: "..err, "error")
81+
table.insert(alerts, "can_fail")
82+
status = "read_error"
83+
end
84+
return status, alerts
85+
end
86+
87+
function soc_and_soh(data, status, alerts)
88+
local ok, err = pcall(function()
89+
local soc, soh = string.unpack("I2I2", data)
90+
telemetry["soh"] = soh
91+
telemetry["soc"] = soc
92+
end)
93+
if not ok then
94+
enapter.log("Reading message 0x355 failed: "..err, "error")
95+
table.insert(alerts, "can_fail")
96+
status = "read_error"
97+
end
98+
return status, alerts
99+
end
100+
101+
function module_or_system_metrics(data, status, alerts)
102+
local ok, err = pcall(function()
103+
local voltage, current, temp = string.unpack("I2i2i2", data)
104+
telemetry["voltage"] = voltage / 100.0
105+
telemetry["total_current"] = current / 10.0
106+
telemetry["average_cell_temperature"] = temp / 10.0
107+
end)
108+
if not ok then
109+
enapter.log("Reading message 0x356 failed: "..err, "error")
110+
table.insert(alerts, "can_fail")
111+
status = "read_error"
112+
end
113+
return status, alerts
114+
end
115+
116+
function alarms(data, status, alerts)
117+
local ok, err = pcall(function()
25118
local byte0, byte1, byte2, byte3 = string.unpack("<I1<I1<I1<I1", data)
26-
local alerts = {}
27119

28120
if byte0 & 2 ~=0 then
29121
table.insert(alerts, "cell_or_module_over_voltage")
@@ -44,7 +136,7 @@ function can_handler(msg_id, data)
44136
telemetry["has_protection1"] = false
45137
else
46138
telemetry["has_protection1"] = true
47-
telemetry["status"] = "warning"
139+
status = "warning"
48140
end
49141

50142
if byte1 & 1 ~=0 then
@@ -57,7 +149,7 @@ function can_handler(msg_id, data)
57149
telemetry["has_protection2"] = false
58150
else
59151
telemetry["has_protection2"] = true
60-
telemetry["status"] = "warning"
152+
status = "warning"
61153
end
62154

63155
if byte2 & 2 ~=0 then
@@ -79,7 +171,7 @@ function can_handler(msg_id, data)
79171
telemetry["has_alarm1"] = false
80172
else
81173
telemetry["has_alarm1"] = true
82-
telemetry["status"] = "warning"
174+
status = "warning"
83175
end
84176

85177
if byte3 & 1 ~=0 then
@@ -92,35 +184,19 @@ function can_handler(msg_id, data)
92184
telemetry["has_alarm2"] = false
93185
else
94186
telemetry["has_alarm2"] = true
95-
telemetry["status"] = "warning"
96-
end
97-
98-
if #alerts == 0 then
99-
telemetry["status"] = "ok"
187+
status = "warning"
100188
end
101-
102-
telemetry["alerts"] = alerts
189+
end)
190+
if not ok then
191+
enapter.log("Reading message 0x351 failed: "..err, "error")
192+
table.insert(alerts, "can_fail")
193+
status = "read_error"
103194
end
195+
return status, alerts
196+
end
104197

105-
if msg_id == 0x351 then
106-
local byte_pair1, byte_pair2, byte_pair3, byte_pair4 = string.unpack("I2i2i2I2", data)
107-
telemetry["battery_charge_voltage"] = byte_pair1 / 10.0
108-
telemetry["charge_current_limit"] = byte_pair2 / 10.0
109-
telemetry["discharge_current_limit"] = byte_pair3 / 10.0
110-
telemetry["discharge_voltage"] = byte_pair4 / 10.0
111-
end
112-
if msg_id == 0x355 then
113-
local soc, soh = string.unpack("I2I2", data)
114-
telemetry["soh"] = soh
115-
telemetry["soc"] = soc
116-
end
117-
if msg_id == 0x356 then
118-
local voltage, current, temp = string.unpack("I2i2i2", data)
119-
telemetry["voltage"] = voltage / 100.0
120-
telemetry["total_current"] = current / 10.0
121-
telemetry["average_cell_temperature"] = temp / 10.0
122-
end
123-
if msg_id == 0x35C then
198+
function charge_discharge_flags(data, status, alerts)
199+
local ok, err = pcall(function()
124200
local byte = string.unpack("<I1", data)
125201
if byte & 8 ~= 0 then
126202
telemetry["request_force_charge"] = true
@@ -147,7 +223,13 @@ function can_handler(msg_id, data)
147223
else
148224
telemetry["charge_enable"] = false
149225
end
226+
end)
227+
if not ok then
228+
enapter.log("Reading message 0x35C failed: "..err, "error")
229+
table.insert(alerts, "can_fail")
230+
status = "read_error"
150231
end
232+
return status, alerts
151233
end
152234

153235
main()

β€Žbattery_management_systems/pylontech_us2000_can/manifest.ymlβ€Ž

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ telemetry:
2222
enum:
2323
- ok
2424
- warning
25+
- read_error
26+
- error
2527
has_protection1:
2628
type: boolean
2729
display_name: Has Protection 1
@@ -88,6 +90,13 @@ telemetry:
8890
unit: celsius
8991

9092
alerts:
93+
can_init_failed:
94+
display_name: CAN Driver Initialization Failed
95+
description: CAN driver could not initialize. Try to reboot device or contact Enapter support.
96+
severity: error
97+
can_fail:
98+
display_name: CAN Communication Failed
99+
severity: error
91100
cell_or_module_over_voltage:
92101
display_name: Protection 1 - Cell or Module Over Voltage
93102
severity: warning

β€Žbattery_management_systems/pylontech_us2000_rs485/README.mdβ€Ž

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
11
# Pylontech US200 (RS485)
22

3-
This _Enapter Device Blueprint_ integrates **Pylontech US2000** - lithium battery control and monitoring via [RS-485 communication interface](https://developers.enapter.com/docs/reference/ucm/rs485). The device can also support CAN communication interface (configured by vendor).
3+
This [Enapter Device Blueprint](https://github.com/Enapter/marketplace#blue_book-enapter-device-blueprints) integrates **Pylontech US2000** - lithium battery control and monitoring via [RS-485 communication interface](https://developers.enapter.com/docs/reference/ucm/rs485). The device can also support CAN communication interface (configured by vendor).
44

5-
Use [Enapter ENP-RS485](https://handbook.enapter.com/modules/ENP-RS485/ENP-RS485.html) module for physical connection. See [connection instructions](https://handbook.enapter.com/modules/ENP-RS485/ENP-RS485.html#connection-examples) in the module manual.
5+
## Connect to Enapter
66

7-
## RS-485 Communication Interface Parameters
7+
- Sign up Enapter Cloud to using [Web](https://cloud.enapter.com/) or mobile app ([iOS](https://apps.apple.com/app/id1388329910), [Android](https://play.google.com/store/apps/details?id=com.enapter&hl=en)).
8+
- Use [Enapter ENP-RS485](https://handbook.enapter.com/modules/ENP-RS485/ENP-RS485.html) module for physical connection. See [connection instructions](https://handbook.enapter.com/modules/ENP-RS485/ENP-RS485.html#connection-examples) in the module manual.
9+
- [Add ENP-RS485 your site](https://handbook.enapter.com/software/mobile/android_mobile_app.html#adding-sites-and-devices) using the mobile app.
10+
- [Upload](https://developers.enapter.com/docs/tutorial/uploading-blueprint/) this blueprint to ENP-CAN.
811

9-
- Baud rate: `9600` bps;
10-
- Data bits: `8`;
11-
- Parity: `N` (no parity);
12-
- Stop bits: `1`.
1312

1413
## References
1514

0 commit comments

Comments
Β (0)