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

Main PR #1

Merged
merged 5 commits into from
May 12, 2022
Merged
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
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
################################################################################
# This .gitignore file was automatically created by Microsoft(R) Visual Studio.
################################################################################

/.vs/marketplace.fork/v17/.suo
/.vs
11 changes: 11 additions & 0 deletions battery_management_systems/lg_resu_prime_http/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# LG RESU (10|16)h Prime (http)

This _Enapter Device Blueprint_ integrates **LG RESU 10h and 16h Prime** lithium battery control and monitoring via an undocumented http interface on the battery

Use an Enapter Virtual UCM to load this blueprint and set the IP address of the LG Battery.

Please ensure that your installer is connecting the LAN connection of the battery to your network.

## References

- [LG RESU Prime Battery product page](https://www.lgessbattery.com/us/home-battery/product-info.lg)
252 changes: 252 additions & 0 deletions battery_management_systems/lg_resu_prime_http/firmware.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,252 @@
json = require 'json'

-- Configuration variables must be also defined
-- in `write_configuration` command arguments in manifest.yml
IP_ADDRESS_CONFIG = 'ip_address'

MYModel=''

function main()
scheduler.add(30000, sendmyproperties)
scheduler.add(15000, sendmytelemetry)

config.init({
[IP_ADDRESS_CONFIG] = { type = 'string', required = true }
})

end

function registration()
end

function sendmyproperties()
local properties = {}
local values, err = config.read_all()
if err then
enapter.log('cannot read config: '..tostring(err), 'error')
else
for name, val in pairs(values) do
properties[name] = val
end
end
if (MYModel~='') then
properties['model']=MYModel
end
enapter.send_properties(properties)
end

function sendmytelemetry()
local json = require('json')

local values, err = config.read_all()
if err then
enapter.log('cannot read config: '..tostring(err), 'error')
return nil, 'cannot_read_config'
else
local ip_address = values[IP_ADDRESS_CONFIG]
local telemetry = {}

local response, err = http.get('http://'..ip_address..'/getbmsdata')
if err then
enapter.log('Cannot do request: '..err, 'error')
return
elseif response.code ~= 200 then
enapter.log('Request returned non-OK code: '..response.code, 'error')
return
end

local tres=response.body
-- http results in a html block that needs to be converted to json
tres=string.gsub(tres, '<caption>BMS Data</caption><tr><th class=\"text%-center\">Item</th><th class=\"text%-center\">Value</th></tr>','{')
tres=string.gsub(tres, '</td></tr><tr><td>', '", "')
tres=string.gsub(tres, '<tr><td>',' "')
tres=string.gsub(tres, '</td><td>','": "')
tres=string.gsub(tres, '</td></tr>', '" }')
--enapter.log('Request succeeded: '..tres, 'info')

local deco=json.decode(tres)
telemetry["battery_soc"] = tonumber(deco.SOC)/100
telemetry["battery_power"]= tonumber(deco.Current)
local cur=tonumber(deco.Current)
telemetry["battery_temp"]= tonumber(deco.Temperature)/10
telemetry["lastresponse"]='All Good'
-- this should have the correct battery status but shows only 0x0001 no matter of state
local tcur=tonumber(deco.OperationModeStatus);
telemetry["battery_energy"]= tonumber(deco.SOH)
telemetry["battery_voltage"]= tonumber(deco.AvgCellVoltage)/100

if (MYModel=='') then
if (tonumber(deco.SOH)==10000) then
MYModel="LG RESU10 Prime"
else
MYModel="LG RESU16 Prime"
end
end

if (tcur==0) then
telemetry["status"]='Off'
elseif (tcur==1) then
telemetry["status"]='Standby'
elseif (tcur==2) then
telemetry["status"]='Initializing'
elseif (tcur==3) then
telemetry["status"]='Charging'
elseif (tcur==4) then
telemetry["status"]='Discharging'
elseif (tcur==5) then
telemetry["status"]='Fault'
elseif (tcur==7) then
telemetry["status"]='Idle'
end

-- fallback since OperationModeStatus does not work correctly
if (cur==0) then
telemetry["status"]='Standby'
elseif (cur>0) then
telemetry["status"]='Charging'
else
telemetry["status"]='Discharging'
end

enapter.send_telemetry(telemetry)
end
end

---------------------------------
-- Stored Configuration API
---------------------------------

config = {}

-- Initializes config options. Registers required UCM commands.
-- @param options: key-value pairs with option name and option params
-- @example
-- config.init({
-- address = { type = 'string', required = true },
-- unit_id = { type = 'number', default = 1 },
-- reconnect = { type = 'boolean', required = true }
-- })
function config.init(options)
assert(next(options) ~= nil, 'at least one config option should be provided')
assert(not config.initialized, 'config can be initialized only once')
for name, params in pairs(options) do
local type_ok = params.type == 'string' or params.type == 'number' or params.type == 'boolean'
assert(type_ok, 'type of `'..name..'` option should be either string or number or boolean')
end

enapter.register_command_handler('write_configuration', config.build_write_configuration_command(options))
enapter.register_command_handler('read_configuration', config.build_read_configuration_command(options))

config.options = options
config.initialized = true
end

-- Reads all initialized config options
-- @return table: key-value pairs
-- @return nil|error
function config.read_all()
local result = {}

for name, _ in pairs(config.options) do
local value, err = config.read(name)
if err then
return nil, 'cannot read `'..name..'`: '..err
else
result[name] = value
end
end

return result, nil
end

-- @param name string: option name to read
-- @return string
-- @return nil|error
function config.read(name)
local params = config.options[name]
assert(params, 'undeclared config option: `'..name..'`, declare with config.init')

local ok, value, ret = pcall(function()
return storage.read(name)
end)

if not ok then
return nil, 'error reading from storage: '..tostring(value)
elseif ret and ret ~= 0 then
return nil, 'error reading from storage: '..storage.err_to_str(ret)
elseif value then
return config.deserialize(name, value), nil
else
return params.default, nil
end
end

-- @param name string: option name to write
-- @param val string: value to write
-- @return nil|error
function config.write(name, val)
local ok, ret = pcall(function()
return storage.write(name, config.serialize(name, val))
end)

if not ok then
return 'error writing to storage: '..tostring(ret)
elseif ret and ret ~= 0 then
return 'error writing to storage: '..storage.err_to_str(ret)
end
end

-- Serializes value into string for storage
function config.serialize(_, value)
if value then
return tostring(value)
else
return nil
end
end

-- Deserializes value from stored string
function config.deserialize(name, value)
local params = config.options[name]
assert(params, 'undeclared config option: `'..name..'`, declare with config.init')

if params.type == 'number' then
return tonumber(value)
elseif params.type == 'string' then
return value
elseif params.type == 'boolean' then
if value == 'true' then
return true
elseif value == 'false' then
return false
else
return nil
end
end
end

function config.build_write_configuration_command(options)
return function(ctx, args)
for name, params in pairs(options) do
if params.required then
assert(args[name], '`'..name..'` argument required')
end

local err = config.write(name, args[name])
if err then ctx.error('cannot write `'..name..'`: '..err) end
end
end
end

function config.build_read_configuration_command(_config_options)
return function(ctx)
local result, err = config.read_all()
if err then
ctx.error(err)
else
return result
end
end
end

main()
107 changes: 107 additions & 0 deletions battery_management_systems/lg_resu_prime_http/manifest.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
blueprint_spec: "device/1.0"
display_name: LG RESU Prime
icon: enapter-battery-storage

communication_modules:
eth:
product: ENP-VIRTUAL
lua_file: firmware.lua

properties:
model:
display_name: Device Model
type: string
enum:
- LG RESU10 Prime
- LG RESU16 Prime
ip_address:
display_name: IP Address
type: string

telemetry:
battery_soc:
display_name: State of Charge
description: Current State of Charge
type: float
unit: precentage
battery_energy:
display_name: Battery Energy
description: Amount Energy of the Battery
type: float
unit: watts
battery_voltage:
display_name: Battery Voltage
type: float
unit: volt
battery_power:
display_name: Power Flow
description: Amount of power flowing in or out of the battery
type: float
unit: watts
battery_temp:
display_name: Battery Temperature
description: Current temperature of the battery
type: float
unit: celsius
status:
display_name: Battery status
type: string
enum:
- 'Off'
- Standby
- Initializing
- Charging
- Discharging
- Fault
- Idle
lastresponse:
display_name: Last Response
description: Last Response from battery
type: string

alerts:
lowbattery:
severity: error
code: E001
display_name: Low battery
description: Battery has less then 10% load

command_groups:
connection:
display_name: Connection

commands:
# Connection Config
write_configuration:
populate_values_command: read_configuration
display_name: Configure Connection
group: connection
ui:
icon: wrench-outline
arguments:
ip_address:
display_name: IP Address
description: IP address of the LG Prime Battery
type: string
required: true
read_configuration:
display_name: Read Connection Config
group: connection
ui:
icon: wrench-outline

.cloud:
category: batteries
mobile_main_chart: battery_soc
mobile_telemetry:
- battery_soc
- battery_power
- battery_temp
- battery_energy
- battery_voltage
- operation_mode
mobile_charts:
- battery_soc
- battery_power
- battery_temp
- battery_energy