Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Finalize Pylontech US200 #14

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions battery_management_systems/pylontech_us2000_can/README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
# Pylontech US2000 (CAN bus)

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).
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).

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.
## Connect to Enapter

## CAN bus Communication Interface Parameters

- Baud rate: `500` kbps.
- Sign up to Enapter Cloud 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)).
- 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.
- [Add ENP-CAN to your site](https://handbook.enapter.com/software/mobile/android_mobile_app.html#adding-sites-and-devices) using the mobile app.
- [Upload](https://developers.enapter.com/docs/tutorial/uploading-blueprint/) this blueprint to ENP-CAN.

## References

Expand Down
156 changes: 119 additions & 37 deletions battery_management_systems/pylontech_us2000_can/firmware.lua
Original file line number Diff line number Diff line change
@@ -1,29 +1,121 @@
--- CAN bus interface baud rate
BAUD_RATE = 500 -- kbps

telemetry = {}

function main()
local err = can.init(500, can_handler)
local err = can.init(BAUD_RATE, can_handler)
if err and err ~= 0 then
enapter.log("CAN failed: "..err.." "..can.err_to_str(err), "error", true)
scheduler.add(5000, function()
enapter.send_telemetry({ status = 'error', alerts = {'can_init_failed'} })
end)
else
scheduler.add(30000, send_properties)
scheduler.add(1000, send_telemetry)
end

scheduler.add(30000, properties)
scheduler.add(1000, metrics)
end

function properties()
function send_properties()
enapter.send_properties({ vendor = "PylonTech", model = "US2000" })
end

function metrics()
if next(telemetry) ~= nil then
-- Holds the number of `send_telemetry` that had an empty `telemetry`
local missed_telemetry_count = 0

function send_telemetry()
if telemetry[1] ~= nil then
enapter.send_telemetry(telemetry)
-- Cleanup telemetry and let it be refilled by `can_handler`
telemetry = {}
missed_telemetry_count = 0
else
missed_telemetry_count = missed_telemetry_count + 1
if missed_telemetry_count > 5 then
enapter.send_telemetry({
status = 'read_error',
alerts = {'can_fail'}
})
end
end
end

function can_handler(msg_id, data)
local alerts = {}
local status = "ok"

if msg_id == 0x351 then
status, alerts = battery_metrics(data, status, alerts)
end

if msg_id == 0x355 then
status, alerts = soc_and_soh(data, status, alerts)
end

if msg_id == 0x356 then
status, alerts = module_or_system_metrics(data, status, alerts)
end

if msg_id == 0x359 then
status, alerts = alarms(data, status, alerts)
end

if msg_id == 0x35C then
status, alerts = charge_discharge_flags(data, status, alerts)
end

telemetry["status"] = status
telemetry["alerts"] = alerts
end

function battery_metrics(data, status, alerts)
local ok, err = pcall(function()
local byte_pair1, byte_pair2, byte_pair3, byte_pair4 = string.unpack("I2i2i2I2", data)
telemetry["battery_charge_voltage"] = byte_pair1 / 10.0
telemetry["charge_current_limit"] = byte_pair2 / 10.0
telemetry["discharge_current_limit"] = byte_pair3 / 10.0
telemetry["discharge_voltage"] = byte_pair4 / 10.0
end)
if not ok then
enapter.log("Reading message 0x351 failed: "..err, "error")
table.insert(alerts, "can_fail")
status = "read_error"
end
return status, alerts
end

function soc_and_soh(data, status, alerts)
local ok, err = pcall(function()
local soc, soh = string.unpack("I2I2", data)
telemetry["soh"] = soh
telemetry["soc"] = soc
end)
if not ok then
enapter.log("Reading message 0x355 failed: "..err, "error")
table.insert(alerts, "can_fail")
status = "read_error"
end
return status, alerts
end

function module_or_system_metrics(data, status, alerts)
local ok, err = pcall(function()
local voltage, current, temp = string.unpack("I2i2i2", data)
telemetry["voltage"] = voltage / 100.0
telemetry["total_current"] = current / 10.0
telemetry["average_cell_temperature"] = temp / 10.0
end)
if not ok then
enapter.log("Reading message 0x356 failed: "..err, "error")
table.insert(alerts, "can_fail")
status = "read_error"
end
return status, alerts
end

function alarms(data, status, alerts)
local ok, err = pcall(function()
local byte0, byte1, byte2, byte3 = string.unpack("<I1<I1<I1<I1", data)
local alerts = {}

if byte0 & 2 ~=0 then
table.insert(alerts, "cell_or_module_over_voltage")
Expand All @@ -44,7 +136,7 @@ function can_handler(msg_id, data)
telemetry["has_protection1"] = false
else
telemetry["has_protection1"] = true
telemetry["status"] = "warning"
status = "warning"
end

if byte1 & 1 ~=0 then
Expand All @@ -57,7 +149,7 @@ function can_handler(msg_id, data)
telemetry["has_protection2"] = false
else
telemetry["has_protection2"] = true
telemetry["status"] = "warning"
status = "warning"
end

if byte2 & 2 ~=0 then
Expand All @@ -79,7 +171,7 @@ function can_handler(msg_id, data)
telemetry["has_alarm1"] = false
else
telemetry["has_alarm1"] = true
telemetry["status"] = "warning"
status = "warning"
end

if byte3 & 1 ~=0 then
Expand All @@ -92,35 +184,19 @@ function can_handler(msg_id, data)
telemetry["has_alarm2"] = false
else
telemetry["has_alarm2"] = true
telemetry["status"] = "warning"
end

if #alerts == 0 then
telemetry["status"] = "ok"
status = "warning"
end

telemetry["alerts"] = alerts
end)
if not ok then
enapter.log("Reading message 0x351 failed: "..err, "error")
table.insert(alerts, "can_fail")
status = "read_error"
end
return status, alerts
end

if msg_id == 0x351 then
local byte_pair1, byte_pair2, byte_pair3, byte_pair4 = string.unpack("I2i2i2I2", data)
telemetry["battery_charge_voltage"] = byte_pair1 / 10.0
telemetry["charge_current_limit"] = byte_pair2 / 10.0
telemetry["discharge_current_limit"] = byte_pair3 / 10.0
telemetry["discharge_voltage"] = byte_pair4 / 10.0
end
if msg_id == 0x355 then
local soc, soh = string.unpack("I2I2", data)
telemetry["soh"] = soh
telemetry["soc"] = soc
end
if msg_id == 0x356 then
local voltage, current, temp = string.unpack("I2i2i2", data)
telemetry["voltage"] = voltage / 100.0
telemetry["total_current"] = current / 10.0
telemetry["average_cell_temperature"] = temp / 10.0
end
if msg_id == 0x35C then
function charge_discharge_flags(data, status, alerts)
local ok, err = pcall(function()
local byte = string.unpack("<I1", data)
if byte & 8 ~= 0 then
telemetry["request_force_charge"] = true
Expand All @@ -147,7 +223,13 @@ function can_handler(msg_id, data)
else
telemetry["charge_enable"] = false
end
end)
if not ok then
enapter.log("Reading message 0x35C failed: "..err, "error")
table.insert(alerts, "can_fail")
status = "read_error"
end
return status, alerts
end

main()
9 changes: 9 additions & 0 deletions battery_management_systems/pylontech_us2000_can/manifest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ telemetry:
enum:
- ok
- warning
- read_error
- error
has_protection1:
type: boolean
display_name: Has Protection 1
Expand Down Expand Up @@ -88,6 +90,13 @@ telemetry:
unit: celsius

alerts:
can_init_failed:
display_name: CAN Driver Initialization Failed
description: CAN driver could not initialize. Try to reboot device or contact Enapter support.
severity: error
can_fail:
display_name: CAN Communication Failed
severity: error
cell_or_module_over_voltage:
display_name: Protection 1 - Cell or Module Over Voltage
severity: warning
Expand Down
14 changes: 6 additions & 8 deletions battery_management_systems/pylontech_us2000_rs485/README.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
# Pylontech US200 (RS485)

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).
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).

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.
## Connect to Enapter

## RS-485 Communication Interface Parameters

- Baud rate: `9600` bps;
- Data bits: `8`;
- Parity: `N` (no parity);
- Stop bits: `1`.
- Sign up to Enapter Cloud 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)).
- 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.
- [Add ENP-RS485 your site](https://handbook.enapter.com/software/mobile/android_mobile_app.html#adding-sites-and-devices) using the mobile app.
- [Upload](https://developers.enapter.com/docs/tutorial/uploading-blueprint/) this blueprint to ENP-RS485.

## References

Expand Down
Loading