Skip to content

Commit 1fa5d44

Browse files
author
Tatyana
committed
Finalize Pylontech
1 parent b34f189 commit 1fa5d44

3 files changed

Lines changed: 212 additions & 185 deletions

File tree

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

Lines changed: 195 additions & 153 deletions
Original file line numberDiff line numberDiff line change
@@ -7,187 +7,229 @@ function main()
77
local err = can.init(BAUD_RATE, can_handler)
88
if err and err ~= 0 then
99
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)
1016
end
11-
12-
scheduler.add(30000, properties)
13-
scheduler.add(1000, metrics)
1417
end
1518

16-
function properties()
19+
function send_properties()
1720
enapter.send_properties({ vendor = "PylonTech", model = "US2000" })
1821
end
1922

20-
function metrics()
21-
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
2228
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
2340
end
2441
end
2542

2643
function can_handler(msg_id, data)
2744
local alerts = {}
2845
local status = "ok"
2946

30-
if msg_id == 0x359 then
31-
local ok, err = pcall(function()
32-
local byte0, byte1, byte2, byte3 = string.unpack("<I1<I1<I1<I1", data)
33-
34-
if byte0 & 2 ~=0 then
35-
table.insert(alerts, "cell_or_module_over_voltage")
36-
end
37-
if byte0 & 4 ~= 0 then
38-
table.insert(alerts, "cell_or_module_under_voltage")
39-
end
40-
if byte0 & 8 ~= 0 then
41-
table.insert(alerts, "cell_over_temperature")
42-
end
43-
if byte0 & 16 ~= 0 then
44-
table.insert(alerts, "cell_under_temperature")
45-
end
46-
if byte0 & 16 ~= 0 then
47-
table.insert(alerts, "discharge_over_current")
48-
end
49-
if byte0 == 0 then
50-
telemetry["has_protection1"] = false
51-
else
52-
telemetry["has_protection1"] = true
53-
status = "warning"
54-
end
55-
56-
if byte1 & 1 ~=0 then
57-
table.insert(alerts, "charge_over_current")
58-
end
59-
if byte1 & 8 ~= 0 then
60-
table.insert(alerts, "system_error")
61-
end
62-
if byte1 == 0 then
63-
telemetry["has_protection2"] = false
64-
else
65-
telemetry["has_protection2"] = true
66-
status = "warning"
67-
end
68-
69-
if byte2 & 2 ~=0 then
70-
table.insert(alerts, "cell_or_module_high_voltage")
71-
end
72-
if byte2 & 4 ~= 0 then
73-
table.insert(alerts, "cell_or_module_low_voltage")
74-
end
75-
if byte2 & 8 ~= 0 then
76-
table.insert(alerts, "cell_high_temperature")
77-
end
78-
if byte2 & 16 ~= 0 then
79-
table.insert(alerts, "cell_low_temperature")
80-
end
81-
if byte2 & 16 ~= 0 then
82-
table.insert(alerts, "discharge_high_current")
83-
end
84-
if byte2 == 0 then
85-
telemetry["has_alarm1"] = false
86-
else
87-
telemetry["has_alarm1"] = true
88-
status = "warning"
89-
end
90-
91-
if byte3 & 1 ~=0 then
92-
table.insert(alerts, "charge_high_current")
93-
end
94-
if byte3 & 8 ~= 0 then
95-
table.insert(alerts, "internal_communication_fail")
96-
end
97-
if byte3 == 0 then
98-
telemetry["has_alarm2"] = false
99-
else
100-
telemetry["has_alarm2"] = true
101-
status = "warning"
102-
end
103-
end)
104-
if not ok then
105-
enapter.log("Reading message 0x351 failed: "..err, "error")
106-
table.insert(alerts, "can_fail")
107-
status = "read_error"
108-
end
109-
end
110-
11147
if msg_id == 0x351 then
112-
local ok, err = pcall(function()
113-
local byte_pair1, byte_pair2, byte_pair3, byte_pair4 = string.unpack("I2i2i2I2", data)
114-
telemetry["battery_charge_voltage"] = byte_pair1 / 10.0
115-
telemetry["charge_current_limit"] = byte_pair2 / 10.0
116-
telemetry["discharge_current_limit"] = byte_pair3 / 10.0
117-
telemetry["discharge_voltage"] = byte_pair4 / 10.0
118-
end)
119-
if not ok then
120-
enapter.log("Reading message 0x351 failed: "..err, "error")
121-
table.insert(alerts, "can_fail")
122-
status = "read_error"
123-
end
48+
status, alerts = handle_msg_0x351(data, status, alerts)
12449
end
12550

12651
if msg_id == 0x355 then
127-
local ok, err = pcall(function()
128-
local soc, soh = string.unpack("I2I2", data)
129-
telemetry["soh"] = soh
130-
telemetry["soc"] = soc
131-
end)
132-
if not ok then
133-
enapter.log("Reading message 0x355 failed: "..err, "error")
134-
table.insert(alerts, "can_fail")
135-
status = "read_error"
136-
end
52+
status, alerts = handle_msg_0x355(data, status, alerts)
13753
end
13854

13955
if msg_id == 0x356 then
140-
local ok, err = pcall(function()
141-
local voltage, current, temp = string.unpack("I2i2i2", data)
142-
telemetry["voltage"] = voltage / 100.0
143-
telemetry["total_current"] = current / 10.0
144-
telemetry["average_cell_temperature"] = temp / 10.0
145-
end)
146-
if not ok then
147-
enapter.log("Reading message 0x356 failed: "..err, "error")
148-
table.insert(alerts, "can_fail")
149-
status = "read_error"
150-
end
56+
status, alerts = handle_msg_0x356(data, status, alerts)
57+
end
58+
59+
if msg_id == 0x359 then
60+
status, alerts = handle_msg_0x359(data, status, alerts)
15161
end
15262

15363
if msg_id == 0x35C then
154-
local ok, err = pcall(function()
155-
local byte = string.unpack("<I1", data)
156-
if byte & 8 ~= 0 then
157-
telemetry["request_force_charge"] = true
158-
else
159-
telemetry["request_force_charge"] = false
160-
end
161-
if byte & 16 ~= 0 then
162-
telemetry["request_force_charge_2"] = true
163-
else
164-
telemetry["request_force_charge_2"] = false
165-
end
166-
if byte & 32 ~= 0 then
167-
telemetry["request_force_charge_1"] = true
168-
else
169-
telemetry["request_force_charge_1"] = false
170-
end
171-
if byte & 64 ~= 0 then
172-
telemetry["discharge_enable"] = true
173-
else
174-
telemetry["discharge_enable"] = false
175-
end
176-
if byte & 128 ~= 0 then
177-
telemetry["charge_enable"] = true
178-
else
179-
telemetry["charge_enable"] = false
180-
end
181-
end)
182-
if not ok then
183-
enapter.log("Reading message 0x35C failed: "..err, "error")
184-
table.insert(alerts, "can_fail")
185-
status = "read_error"
186-
end
64+
status, alerts = handle_msg_0x35C(data, status, alerts)
18765
end
18866

18967
telemetry["status"] = status
19068
telemetry["alerts"] = alerts
19169
end
19270

71+
function handle_msg_0x351(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 handle_msg_0x355(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 handle_msg_0x356(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 handle_msg_0x359(data, status, alerts)
117+
local ok, err = pcall(function()
118+
local byte0, byte1, byte2, byte3 = string.unpack("<I1<I1<I1<I1", data)
119+
120+
if byte0 & 2 ~=0 then
121+
table.insert(alerts, "cell_or_module_over_voltage")
122+
end
123+
if byte0 & 4 ~= 0 then
124+
table.insert(alerts, "cell_or_module_under_voltage")
125+
end
126+
if byte0 & 8 ~= 0 then
127+
table.insert(alerts, "cell_over_temperature")
128+
end
129+
if byte0 & 16 ~= 0 then
130+
table.insert(alerts, "cell_under_temperature")
131+
end
132+
if byte0 & 16 ~= 0 then
133+
table.insert(alerts, "discharge_over_current")
134+
end
135+
if byte0 == 0 then
136+
telemetry["has_protection1"] = false
137+
else
138+
telemetry["has_protection1"] = true
139+
status = "warning"
140+
end
141+
142+
if byte1 & 1 ~=0 then
143+
table.insert(alerts, "charge_over_current")
144+
end
145+
if byte1 & 8 ~= 0 then
146+
table.insert(alerts, "system_error")
147+
end
148+
if byte1 == 0 then
149+
telemetry["has_protection2"] = false
150+
else
151+
telemetry["has_protection2"] = true
152+
status = "warning"
153+
end
154+
155+
if byte2 & 2 ~=0 then
156+
table.insert(alerts, "cell_or_module_high_voltage")
157+
end
158+
if byte2 & 4 ~= 0 then
159+
table.insert(alerts, "cell_or_module_low_voltage")
160+
end
161+
if byte2 & 8 ~= 0 then
162+
table.insert(alerts, "cell_high_temperature")
163+
end
164+
if byte2 & 16 ~= 0 then
165+
table.insert(alerts, "cell_low_temperature")
166+
end
167+
if byte2 & 16 ~= 0 then
168+
table.insert(alerts, "discharge_high_current")
169+
end
170+
if byte2 == 0 then
171+
telemetry["has_alarm1"] = false
172+
else
173+
telemetry["has_alarm1"] = true
174+
status = "warning"
175+
end
176+
177+
if byte3 & 1 ~=0 then
178+
table.insert(alerts, "charge_high_current")
179+
end
180+
if byte3 & 8 ~= 0 then
181+
table.insert(alerts, "internal_communication_fail")
182+
end
183+
if byte3 == 0 then
184+
telemetry["has_alarm2"] = false
185+
else
186+
telemetry["has_alarm2"] = true
187+
status = "warning"
188+
end
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"
194+
end
195+
return status, alerts
196+
end
197+
198+
function handle_msg_0x35C(data, status, alerts)
199+
local ok, err = pcall(function()
200+
local byte = string.unpack("<I1", data)
201+
if byte & 8 ~= 0 then
202+
telemetry["request_force_charge"] = true
203+
else
204+
telemetry["request_force_charge"] = false
205+
end
206+
if byte & 16 ~= 0 then
207+
telemetry["request_force_charge_2"] = true
208+
else
209+
telemetry["request_force_charge_2"] = false
210+
end
211+
if byte & 32 ~= 0 then
212+
telemetry["request_force_charge_1"] = true
213+
else
214+
telemetry["request_force_charge_1"] = false
215+
end
216+
if byte & 64 ~= 0 then
217+
telemetry["discharge_enable"] = true
218+
else
219+
telemetry["discharge_enable"] = false
220+
end
221+
if byte & 128 ~= 0 then
222+
telemetry["charge_enable"] = true
223+
else
224+
telemetry["charge_enable"] = false
225+
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"
231+
end
232+
return status, alerts
233+
end
234+
193235
main()

0 commit comments

Comments
Β (0)