A service that exports power consumption and other metrics from Chinese solar inverters over MQTT. It can be used as a replacement for proprietary SmartESS mobile app.
In theory it can work with most inverters supported by SmartESS, but some inverters will probably require minor improvements/fixes in code -- see limitations.
Tested and working:
- PowMr VM PLUS 5.5KW (Aliexpress link)
Unsupported:
- Anenji 4kw/7.2kw (see this issue). If you happen to have one of these models, check out this repo by sabatex: NetDaemonApps.InverterAnenji-4kw-7.2kw.
Please let me know via issues if your inverter happen to work too (and more so if it doesn't), so I can update the list here.
The service periodically polls specified Modbus registers, interprets their values based on register space descriptors pulled from SmartESS and exports interpreted human-readable values over MQTT (e.g. to Home Assistant). In addition, it can configure the datalogger (SSID and password) and the inverter itself via CLI tool which is bundled into the service.
Register values are exported at openess/registers/{name} topics. Additionally, the datalogger connection status is exported at openess/status (online/offline).
Currently only WiFi dataloggers are supported (no BLE/serial). I've only tested it with a thing called Wi-Fi Plug Pro (Aliexpress link) that came with my inverter, but others will probably work too.
The service depends on original register space descriptor files pulled from SmartESS APK, see xxxx.json files in data/. The appropriate file is selected based on protocol string reported by datalogger during connection process.
Some other techinal limitations:
- Registers with
valueTypeother than1are not supported. It is not that hard to add other types, but we have to have an inverter for testing that supports them. - Some parts of descriptor files are not currently used, e.g.
OtherCodessections not related to enumerations.
Known issues:
- Automation protocol detection may not work correctly. You can override the protocol file via the
Protocolfield in the config.
Installation:
- Build the service with
make dist ARCH=arm64(omitARCH=arm64to build for host arch) - Copy
openess.tar.xzto target machine and extract it there. - Install the service with
cd data/ && sudo ./install.sh. You can uninstall later it withsudo ./install.sh uninstallif needed. - Run the service in CLI mode to check if it can properly connect to the datalogger:
openess -d 192.168.1.37:58899. Note what descriptor file is used (look forclient: loaded protocol descriptor: 0925.jsonlog entry). You can also change datalogger SSID/password here (see CLI description below). - Edit the config file at
/etc/openess/config.json. You must specify datalogger address (DeviceAddr), MQTT broker address (Export.Broker) and a set of exported registers for your inverter (Collector.Registers). You can lookup register names in yourxxxx.jsonfile found out at previous step. - Enable and start the service:
sudo systemctl enable openess --now
Configuration file:
Ensure that service is stopped before running the CLI. Type help to get a list of supported commands. Type exit or ^D to exit.
Setting datalogger SSID/password:
$ openess -d 192.168.1.37:58899
INFO 2024/01/24 13:52:04 client: connecting to device 192.168.1.37:58899
INFO 2024/01/24 13:52:04 client: connected to datalogger: manufacturer 37 device type 8 (protocol v1.2 props 0925,5,5,#0#)
% set-param ssid HomeWifi
% set-param password MyPassword
% set-param restart 1
% exit
Reading registers:
% read-named "Working State"
Line Mode
% read-named "Output apparent power "
4287VA
Writing registers:
% write-named "Remove all power history" 1
010c
% write-named "LCD backlight" 0
001d
When writing registers, enumeration variants are represented by numeric values, so you have to look up proper values in the xxxx.json descrptor file.
Complete configuration example for PowMr VM PLUS 5.5KW and similar inverters.
Note: These inverters fail to read cumulative energy registers for some reqson (today_energy, all_energy, or month_energy). Use the integration sensors below to calculate energy from power measurements instead.
mqtt:
sensor:
# Working State (enum: Power On / Stand By / Invert Mode / Line Mode / Bypass / Fault)
- name: "Inverter Working State"
unique_id: inverter_working_state
state_topic: "openess/register/working_state"
availability_topic: "openess/status"
icon: mdi:state-machine
# Battery
- name: "Inverter Battery"
unique_id: inverter_battery_capacity
state_topic: "openess/register/battery_capacity"
availability_topic: "openess/status"
unit_of_measurement: "%"
device_class: battery
state_class: measurement
value_template: "{{ value | int }}"
- name: "Inverter Battery Voltage"
unique_id: inverter_battery_voltage
state_topic: "openess/register/battery_voltage"
availability_topic: "openess/status"
unit_of_measurement: "V"
device_class: voltage
state_class: measurement
value_template: "{{ value | float | round(1) }}"
- name: "Inverter Battery Charging Current"
unique_id: inverter_charging_current
state_topic: "openess/register/charging_current"
availability_topic: "openess/status"
unit_of_measurement: "A"
device_class: current
state_class: measurement
value_template: "{{ value | float | round(1) }}"
- name: "Inverter Battery Discharge Current"
unique_id: inverter_discharge_current
state_topic: "openess/register/discharge_current"
availability_topic: "openess/status"
unit_of_measurement: "A"
device_class: current
state_class: measurement
value_template: "{{ value | float | round(1) }}"
# Solar (PV)
- name: "Inverter PV Voltage"
unique_id: inverter_pv_voltage
state_topic: "openess/register/pv_voltage"
availability_topic: "openess/status"
unit_of_measurement: "V"
device_class: voltage
state_class: measurement
value_template: "{{ value | float | round(1) }}"
- name: "Inverter PV Power"
unique_id: inverter_pv_power
state_topic: "openess/register/pv_power"
availability_topic: "openess/status"
unit_of_measurement: "W"
device_class: power
state_class: measurement
value_template: "{{ value | float | round(0) }}"
# Grid (AC Input)
- name: "Inverter AC Input Voltage"
unique_id: inverter_ac_input_voltage
state_topic: "openess/register/ac_input_voltage"
availability_topic: "openess/status"
unit_of_measurement: "V"
device_class: voltage
state_class: measurement
value_template: "{{ value | float | round(1) }}"
# Output (Load)
- name: "Inverter Output Voltage"
unique_id: inverter_output_voltage
state_topic: "openess/register/output_voltage"
availability_topic: "openess/status"
unit_of_measurement: "V"
device_class: voltage
state_class: measurement
value_template: "{{ value | float | round(1) }}"
- name: "Inverter Output Apparent Power"
unique_id: inverter_output_apparent_power
state_topic: "openess/register/output_power"
availability_topic: "openess/status"
unit_of_measurement: "VA"
device_class: apparent_power
state_class: measurement
value_template: "{{ value | float | round(0) }}"
- name: "Inverter Output Active Power"
unique_id: inverter_output_active_power
state_topic: "openess/register/output_active_power"
availability_topic: "openess/status"
unit_of_measurement: "W"
device_class: power
state_class: measurement
value_template: "{{ value | float | round(0) }}"
- name: "Inverter Output Load"
unique_id: inverter_output_load_pct
state_topic: "openess/register/output_power_percent"
availability_topic: "openess/status"
unit_of_measurement: "%"
state_class: measurement
value_template: "{{ value | float | round(0) }}"
template:
- sensor:
# Grid power: positive when consuming from grid (Line Mode)
- name: "Inverter Grid Power"
unique_id: inverter_grid_power
unit_of_measurement: "W"
device_class: power
state_class: measurement
state: >
{% if states('sensor.inverter_working_state') in ['Line Mode', 'Bypass'] %}
{{ states('sensor.inverter_output_power') | float(0) }}
{% else %}
0
{% endif %}
# Battery discharge power
- name: "Inverter Battery Discharge Power"
unique_id: inverter_battery_discharge_power
unit_of_measurement: "W"
device_class: power
state_class: measurement
state: >
{{ (states('sensor.inverter_battery_discharge_current') | float(0))
* (states('sensor.inverter_battery_voltage') | float(0)) }}
# Battery charge power
- name: "Inverter Battery Charge Power"
unique_id: inverter_battery_charge_power
unit_of_measurement: "W"
device_class: power
state_class: measurement
state: >
{{ (states('sensor.inverter_battery_charging_current') | float(0))
* (states('sensor.inverter_battery_voltage') | float(0)) }}
sensor:
# Convert power (W) to energy (kWh) via integration
# These sensors calculate energy totals from power measurements
- platform: integration
source: sensor.inverter_pv_power
name: "Solar Energy"
unique_id: inverter_solar_energy
unit_prefix: k
round: 2
method: left
- platform: integration
source: sensor.inverter_grid_power
name: "Grid Energy Consumed"
unique_id: inverter_grid_energy
unit_prefix: k
round: 2
method: left
- platform: integration
source: sensor.inverter_battery_discharge_power
name: "Battery Energy Discharged"
unique_id: inverter_battery_energy_discharged
unit_prefix: k
round: 2
method: left
- platform: integration
source: sensor.inverter_battery_charge_power
name: "Battery Energy Charged"
unique_id: inverter_battery_energy_charged
unit_prefix: k
round: 2
method: leftThis configuration provides:
- Real-time monitoring of battery, solar (PV), grid, and output metrics
- Calculated energy sensors that integrate power over time for use in Home Assistant's Energy Dashboard
- Template sensors for derived values (grid power, battery charge/discharge power)
To use this configuration, ensure your config.json includes the required registers as shown in the Configuration section above.
{ "BindPort": 8899, // local TCP port used for datalogger connection "DeviceAddr": "192.168.1.37:58899", // datalogger address "ProtoPath": "data/", // a path to descriptor files (xxxx.json) "Protocol": "0925", // overrides automatic protocol detection (optional field) "Export": { // MQTT export config "Broker": "tcp://127.0.0.1:1883", // broker address (required) "ClientId": "MyExporter", // client id (optional) "User": "user", // auth creds (optional) "Password": "password" // auth creds (optional) }, "Collector": { "Interval": "500ms", // polling interval "Enabled": true, // enable polling "Registers": { // A list of registers to poll. // Keys are MQTT register topic names: openess/registers/{name} // Values are register names from descriptor file "working_state": "Working State", "battery_capacity": "Battery capacity %", "battery_voltage": "Battery voltage", "charging_current": "Charging current", "discharge_current": "Battery discharge current", "pv_voltage": "PV input voltage", "pv_power": "PV input power", "ac_input_voltage": "AC input voltage", "output_voltage": "Output voltage", "output_power": "Output apparent power ", "output_active_power": "Output active power", "output_power_percent": "AC output Load %" } } }