Hi all,
I have a Deye hybrid inverter with a LSW3 logger, I’d like to access through modbus of the logger but I cannot have success configuring it. I started from the HA custom plugin and in the HA system is working well (I use the deye_sg04lp3.yaml ), somebody had success configuring it?
Asuming solarman means a lsw logger and appropriate inverter like sofar.
Not modbus per se, but using logger api (which is a wrapped modbus) is here [new binding] Logger LSW for Sofar/Omnik/IE for SolarmanPV based on protocol v5 (iGEN tech) - #46 by Grzegorz_Golec.
Thanks for the reply,
I have a Deye inverter with LSW3 serial starts with 2389XXXXXX, which bind can I use?
Ouch, the future one mentioned in my link. However 23xxx support is being under development Handle loggers with SN 23xxxx · Issue #1 · ptrbojko/openhab-lsw4inverter-binding · GitHub
I tried to put the org.openhab.binding.lswlogger-3.3.0.jar in the addons but nothing happen
Meanwhile I solved in this way (I used the code from git repo ha plugin ) adding a main:
def main():
inverter = Inverter("/home/openhabian/solarman/inverter_definitions/", <your-loggger-sn>, <ip-of-the-logger>, 8899, 1, "deye_sg04lp3.yaml")
inverter.update()
val = inverter.get_current_val()
mqtt_client = paho.Client("deye_inverter")
mqtt_client.enable_logger()
mqtt_timeout = 3 # seconds
mqtt_client.connect(<mqtt-address>, 1883)
mqtt_client.loop_start()
mqtt_topic = "deye/measurements"
value = str(val)
json_object = json.dumps(val)
info = mqtt_client.publish(mqtt_topic, json_object, qos=1)
info.wait_for_publish(mqtt_timeout)
info = mqtt_client.publish("deye/last_update", inverter.status_lastUpdate, qos=1)
info.wait_for_publish(mqtt_timeout)
then a rule to update the mqtt queue:
rule "Deye update"
when
Time cron "0/20 * * ? * * *"
then
executeCommandLine(Duration.ofSeconds(10), "/usr/bin/python3.9","/home/openhabian/solarman/solarman.py")
end
and the channel of the thing:
UID: mqtt:topic:MQTTBroker:deye-mqtt
label: Deye inverter
thingTypeUID: mqtt:topic
configuration: {}
bridgeUID: mqtt:broker:MQTTBroker
channels:
- id: pv1_power
channelTypeUID: mqtt:number
label: PV1 Power
description: null
configuration:
stateTopic: deye/measurements
transformationPattern: JSONPATH:$.pv1_power
unit: W
- id: pv2_power
channelTypeUID: mqtt:number
label: PV2 Power
description: null
configuration:
stateTopic: deye/measurements
transformationPattern: JSONPATH:$.pv2_power
unit: W
- id: pv1_voltage
channelTypeUID: mqtt:number
label: PV1 Voltage
description: null
configuration:
stateTopic: deye/measurements
transformationPattern: JSONPATH:$.pv1_voltage
unit: V
- id: pv2_voltage
channelTypeUID: mqtt:number
label: PV2 Voltage
description: null
configuration:
stateTopic: deye/measurements
transformationPattern: JSONPATH:$.pv2_voltage
unit: V
- id: pv1_current
channelTypeUID: mqtt:number
label: PV1 Current
description: null
configuration:
stateTopic: deye/measurements
transformationPattern: JSONPATH:$.pv1_current
unit: A
- id: pv2_current
channelTypeUID: mqtt:number
label: PV2 Current
description: null
configuration:
stateTopic: deye/measurements
transformationPattern: JSONPATH:$.pv2_current
unit: A
- id: daily_production
channelTypeUID: mqtt:number
label: Daily Production
description: null
configuration:
stateTopic: deye/measurements
transformationPattern: JSONPATH:$.daily_production
unit: kWh
- id: total_production
channelTypeUID: mqtt:number
label: Total Production
description: null
configuration:
stateTopic: deye/measurements
transformationPattern: JSONPATH:$.total_production
unit: kWh
- id: daily_battery_charge
channelTypeUID: mqtt:number
label: Daily Battery Charge
description: null
configuration:
stateTopic: deye/measurements
transformationPattern: JSONPATH:$.daily_battery_charge
unit: kWh
- id: daily_battery_discharge
channelTypeUID: mqtt:number
label: Daily Battery Discharge
description: null
configuration:
stateTopic: deye/measurements
transformationPattern: JSONPATH:$.daily_battery_discharge
unit: kWh
- id: total_battery_charge
channelTypeUID: mqtt:number
label: Total Battery Charge
description: null
configuration:
stateTopic: deye/measurements
transformationPattern: JSONPATH:$.total_battery_charge
unit: kWh
- id: total_battery_discharge
channelTypeUID: mqtt:number
label: Total Battery Discharge
description: null
configuration:
stateTopic: deye/measurements
transformationPattern: JSONPATH:$.total_battery_discharge
unit: kWh
- id: battery_power
channelTypeUID: mqtt:number
label: Battery Power
description: null
configuration:
stateTopic: deye/measurements
transformationPattern: JSONPATH:$.battery_power
unit: W
- id: battery_voltage
channelTypeUID: mqtt:number
label: Battery Voltage
description: null
configuration:
stateTopic: deye/measurements
transformationPattern: JSONPATH:$.battery_voltage
unit: V
- id: battery_soc
channelTypeUID: mqtt:number
label: Battery SOC
description: null
configuration:
stateTopic: deye/measurements
transformationPattern: JSONPATH:$.battery_soc
- id: battery_current
channelTypeUID: mqtt:number
label: Battery Current
description: null
configuration:
stateTopic: deye/measurements
transformationPattern: JSONPATH:$.battery_current
unit: A
- id: battery_temperature
channelTypeUID: mqtt:number
label: Battery Temperature
description: null
configuration:
stateTopic: deye/measurements
transformationPattern: JSONPATH:$.battery_temperature
unit: °C
- id: total_grid_power
channelTypeUID: mqtt:number
label: Total Grid Power
description: null
configuration:
stateTopic: deye/measurements
transformationPattern: JSONPATH:$.total_grid_power
unit: W
- id: grid_voltage_l1
channelTypeUID: mqtt:number
label: Grid Voltage L1
description: null
configuration:
stateTopic: deye/measurements
transformationPattern: JSONPATH:$.grid_voltage_l1
unit: V
- id: grid_voltage_l2
channelTypeUID: mqtt:number
label: Grid Voltage L2
description: null
configuration:
stateTopic: deye/measurements
transformationPattern: JSONPATH:$.grid_voltage_l2
unit: V
- id: grid_voltage_l3
channelTypeUID: mqtt:number
label: Grid Voltage L3
description: null
configuration:
stateTopic: deye/measurements
transformationPattern: JSONPATH:$.grid_voltage_l3
unit: V
- id: internal_ct_l1_power
channelTypeUID: mqtt:number
label: Internal CT L1 Power
description: null
configuration:
stateTopic: deye/measurements
transformationPattern: JSONPATH:$.internal_ct_l1_power
unit: W
- id: internal_ct_l2_power
channelTypeUID: mqtt:number
label: Internal CT L2 Power
description: null
configuration:
stateTopic: deye/measurements
transformationPattern: JSONPATH:$.internal_ct_l2_power
unit: W
- id: internal_ct_l3_power
channelTypeUID: mqtt:number
label: Internal CT L3 Power
description: null
configuration:
stateTopic: deye/measurements
transformationPattern: JSONPATH:$.internal_ct_l3_power
unit: W
- id: external_ct_l1_power
channelTypeUID: mqtt:number
label: External CT L1 Power
description: null
configuration:
stateTopic: deye/measurements
transformationPattern: JSONPATH:$.external_ct_l1_power
unit: W
- id: external_ct_l2_power
channelTypeUID: mqtt:number
label: External CT L2 Power
description: null
configuration:
stateTopic: deye/measurements
transformationPattern: JSONPATH:$.external_ct_l2_power
unit: W
- id: external_ct_l3_power
channelTypeUID: mqtt:number
label: External CT L3 Power
description: null
configuration:
stateTopic: deye/measurements
transformationPattern: JSONPATH:$.external_ct_l3_power
unit: W
- id: daily_energy_bought
channelTypeUID: mqtt:number
label: Daily Energy Bought
description: null
configuration:
stateTopic: deye/measurements
transformationPattern: JSONPATH:$.daily_energy_bought
unit: kWh
- id: total_energy_bought
channelTypeUID: mqtt:number
label: Total Energy Bought
description: null
configuration:
stateTopic: deye/measurements
transformationPattern: JSONPATH:$.total_energy_bought
unit: kWh
- id: daily_energy_sold
channelTypeUID: mqtt:number
label: Daily Energy Sold
description: null
configuration:
stateTopic: deye/measurements
transformationPattern: JSONPATH:$.daily_energy_sold
unit: kWh
- id: total_energy_sold
channelTypeUID: mqtt:number
label: Total Energy Sold
description: null
configuration:
stateTopic: deye/measurements
transformationPattern: JSONPATH:$.total_energy_sold
unit: kWh
- id: total_grid_production
channelTypeUID: mqtt:number
label: Total Grid Production
description: null
configuration:
stateTopic: deye/measurements
transformationPattern: JSONPATH:$.total_grid_production
unit: kWh
- id: total_load_power
channelTypeUID: mqtt:number
label: Total Load Power
description: null
configuration:
stateTopic: deye/measurements
transformationPattern: JSONPATH:$.total_load_power
unit: W
- id: load_l1_power
channelTypeUID: mqtt:number
label: Load L1 Power
description: null
configuration:
stateTopic: deye/measurements
transformationPattern: JSONPATH:$.load_l1_power
unit: W
- id: load_l2_power
channelTypeUID: mqtt:number
label: Load L2 Power
description: null
configuration:
stateTopic: deye/measurements
transformationPattern: JSONPATH:$.load_l2_power
unit: W
- id: load_l3_power
channelTypeUID: mqtt:number
label: Load L3 Power
description: null
configuration:
stateTopic: deye/measurements
transformationPattern: JSONPATH:$.load_l3_power
unit: W
- id: load_voltage_l1
channelTypeUID: mqtt:number
label: Load Voltage L1
description: null
configuration:
stateTopic: deye/measurements
transformationPattern: JSONPATH:$.load_voltage_l1
unit: V
- id: load_voltage_l2
channelTypeUID: mqtt:number
label: Load Voltage L2
description: null
configuration:
stateTopic: deye/measurements
transformationPattern: JSONPATH:$.load_voltage_l2
unit: V
- id: load_voltage_l3
channelTypeUID: mqtt:number
label: Load Voltage L3
description: null
configuration:
stateTopic: deye/measurements
transformationPattern: JSONPATH:$.load_voltage_l3
unit: V
- id: daily_load_consumption
channelTypeUID: mqtt:number
label: Daily Load Consumption
description: null
configuration:
stateTopic: deye/measurements
transformationPattern: JSONPATH:$.daily_load_consumption
unit: kWh
- id: total_load_consumption
channelTypeUID: mqtt:number
label: Total Load Consumption
description: null
configuration:
stateTopic: deye/measurements
transformationPattern: JSONPATH:$.total_load_consumption
unit: kWh
- id: current_l1
channelTypeUID: mqtt:number
label: Current L1
description: null
configuration:
stateTopic: deye/measurements
transformationPattern: JSONPATH:$.current_l1
unit: A
- id: current_l2
channelTypeUID: mqtt:number
label: Current L2
description: null
configuration:
stateTopic: deye/measurements
transformationPattern: JSONPATH:$.current_l2
unit: A
- id: current_l3
channelTypeUID: mqtt:number
label: Current L3
description: null
configuration:
stateTopic: deye/measurements
transformationPattern: JSONPATH:$.current_l3
unit: A
- id: inverter_l1_power
channelTypeUID: mqtt:number
label: Inverter L1 Power
description: null
configuration:
stateTopic: deye/measurements
transformationPattern: JSONPATH:$.inverter_l1_power
unit: W
- id: inverter_l2_power
channelTypeUID: mqtt:number
label: Inverter L2 Power
description: null
configuration:
stateTopic: deye/measurements
transformationPattern: JSONPATH:$.inverter_l2_power
unit: W
- id: inverter_l3_power
channelTypeUID: mqtt:number
label: Inverter L3 Power
description: null
configuration:
stateTopic: deye/measurements
transformationPattern: JSONPATH:$.inverter_l3_power
unit: W
- id: dc_temperature
channelTypeUID: mqtt:number
label: DC Temperature
description: null
configuration:
stateTopic: deye/measurements
transformationPattern: JSONPATH:$.dc_temperature
unit: °C
- id: ac_temperature
channelTypeUID: mqtt:number
label: AC Temperature
description: null
configuration:
stateTopic: deye/measurements
transformationPattern: JSONPATH:$.ac_temperature
unit: °C
- id: communication_board_version_no
channelTypeUID: mqtt:number
label: Communication Board Version No.
description: null
configuration:
stateTopic: deye/measurements
transformationPattern: JSONPATH:$.communication_board_version_no
- id: control_board_version_no
channelTypeUID: mqtt:number
label: Control Board Version No.
description: null
configuration:
stateTopic: deye/measurements
transformationPattern: JSONPATH:$.control_board_version_no
all is working good without using the solarman api.
Hi @giorginus80 , that’s in interesting solution since this gives more real time data than the cloud api option.
However, I am not a coder and I am struggling to get this to work. I added the main to solarman.py right above the class definition. When calling “python3 solarman.py” from the shell directly, first problem are the IP addresses. I guess, they need to be with quotation marks?
Next problem is the missing homeassistant.util. I get an error “ModuleNotFoundError: No module named ‘homeassistant’”. I just copied the code from your link above to my server. Do I need to install the whole homeassistant system?
Thanks a lot for your support
Hello @Falk
yes you have to add const.py and parser.py and the directory /inverter_definitions, you have just to add the main (I attach a new ‘full’ version because I forgot to put retained prop to mqtt). It’s just a fast solution, working very well by the way.
Sorry I forgot to explain to remove all ha ref
import socket
import yaml
import logging
import struct
import paho.mqtt.client as paho
import json
from datetime import datetime
from parser import ParameterParser
from const import *
log = logging.getLogger(__name__)
START_OF_MESSAGE = 0xA5
END_OF_MESSAGE = 0x15
CONTROL_CODE = [0x10, 0x45]
SERIAL_NO = [0x00, 0x00]
SEND_DATA_FIELD = [0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
QUERY_RETRY_ATTEMPTS = 6
class Inverter:
def __init__(self, path, serial, host, port, mb_slaveid, lookup_file):
self._serial = serial
self.path = path
self._host = host
self._port = port
self._mb_slaveid = mb_slaveid
self._current_val = None
self.status_connection = "Disconnected"
self.status_lastUpdate = "N/A"
self.lookup_file = lookup_file
if not self.lookup_file or lookup_file == 'parameters.yaml':
self.lookup_file = 'deye_hybrid.yaml'
with open(self.path + self.lookup_file) as f:
self.parameter_definition = yaml.full_load(f)
def modbus(self, data):
POLY = 0xA001
crc = 0xFFFF
for byte in data:
crc ^= byte
for _ in range(8):
crc = ((crc >> 1) ^ POLY
if (crc & 0x0001)
else crc >> 1)
return crc
def get_serial_hex(self):
serial_hex = hex(self._serial)[2:]
serial_bytes = bytearray.fromhex(serial_hex)
serial_bytes.reverse()
return serial_bytes
def get_read_business_field(self, start, length, mb_fc):
request_data = bytearray([self._mb_slaveid, mb_fc]) # Function Code
request_data.extend(start.to_bytes(2, 'big'))
request_data.extend(length.to_bytes(2, 'big'))
crc = self.modbus(request_data)
request_data.extend(crc.to_bytes(2, 'little'))
return request_data
def generate_request(self, start, length, mb_fc):
packet = bytearray([START_OF_MESSAGE])
packet_data = []
packet_data.extend (SEND_DATA_FIELD)
buisiness_field = self.get_read_business_field(start, length, mb_fc)
packet_data.extend(buisiness_field)
length = packet_data.__len__()
packet.extend(length.to_bytes(2, "little"))
packet.extend(CONTROL_CODE)
packet.extend(SERIAL_NO)
packet.extend(self.get_serial_hex())
packet.extend(packet_data)
#Checksum
checksum = 0
for i in range(1,len(packet),1):
checksum += packet[i]
packet.append(checksum & 0xFF)
packet.append(END_OF_MESSAGE)
del packet_data
del buisiness_field
return packet
def validate_packet(self, packet):
# Perform some checks to ensure the received packet is correct
# Start with the outer V5 logger packet and work inwards towards the embedded modbus frame
# Does the v5 packet start and end with what we expect?
if packet[0] != 0xa5 or packet[len(packet) - 1] != 0x15:
log.debug("unexpected v5 packet start/stop")
return 0
# Does the v5 packet have the correct checksum?
elif self.validate_v5_checksum(packet) == 0:
log.debug("invalid v5 checksum")
return 0
# Is the control code what we expect? Note: We sometimes see keepalives appear (0x4710)
elif packet[3:5] != struct.pack("<H", 0x1510):
log.debug("unexpected v5 control code")
return 0
# Is the v5 packet of the expected type?
elif packet[11] != 0x02:
log.debug("unexpected v5 frame type")
return 0
# Move onto the encapsulated modbus frame
modbus_frame = packet[25:len(packet) - 2]
# Is the modbus CRC correct?
if self.validate_modbus_crc(modbus_frame) == 0:
log.debug("invalid modbus crc")
return 0
# Validation compelted successfully
return 1
def validate_modbus_crc(self, frame):
# Calculate crc with all but the last 2 bytes of the frame (they contain the crc)
calc_crc = 0xFFFF
for pos in frame[:-2]:
calc_crc ^= pos
for i in range(8):
if (calc_crc & 1) != 0:
calc_crc >>= 1
calc_crc ^= 0xA001 # bitwise 'or' with modbus magic number (0xa001 == bitwise reverse of 0x8005)
else:
calc_crc >>= 1
# Compare calculated crc with the one supplied in the frame....
frame_crc, = struct.unpack('<H', frame[-2:])
if calc_crc == frame_crc:
return 1
else:
return 0
def validate_v5_checksum(self, packet):
checksum = 0
length = len(packet)
# Don't include the checksum and END OF MESSAGE (-2)
for i in range(1, length - 2, 1):
checksum += packet[i]
checksum &= 0xFF
if checksum == packet[length - 2]:
return 1
else:
return 0
def send_request(self, params, start, end, mb_fc, sock):
result = 0
length = end - start + 1
request = self.generate_request(start, length, mb_fc)
try:
log.debug(request.hex())
sock.sendall(request)
raw_msg = sock.recv(1024)
log.debug(raw_msg.hex())
if self.validate_packet(raw_msg) == 1:
result = 1
params.parse(raw_msg, start, length)
else:
log.debug(f"Querying [{start} - {end}] failed, invalid response packet.")
del raw_msg
finally:
del request
return result
def update (self):
self.get_statistics()
return
def get_statistics(self):
result = 1
params = ParameterParser(self.parameter_definition)
requests = self.parameter_definition['requests']
log.debug(f"Starting to query for [{len(requests)}] ranges...")
def connect_to_server():
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.settimeout(6)
server.connect((self._host, self._port))
return server
sock = None
try:
sock = connect_to_server()
for request in requests:
start = request['start']
end = request['end']
mb_fc = request['mb_functioncode']
log.debug(f"Querying [{start} - {end}]...")
attempts_left = QUERY_RETRY_ATTEMPTS
while attempts_left > 0:
attempts_left -= 1
result = 0
try:
result = self.send_request(params, start, end, mb_fc, sock)
except ConnectionResetError:
log.debug(f"Querying [{start} - {end}] failed as client closed stream, trying to re-open.")
sock.close()
sock = connect_to_server()
except TimeoutError:
log.debug(f"Querying [{start} - {end}] failed with timeout")
except Exception as e:
log.debug(f"Querying [{start} - {end}] failed with exception [{type(e).__name__}]")
if result == 0:
log.debug(f"Querying [{start} - {end}] failed, [{attempts_left}] retry attempts left")
else:
log.debug(f"Querying [{start} - {end}] succeeded")
break
if result == 0:
log.warning(f"Querying registers [{start} - {end}] failed, aborting.")
break
if result == 1:
log.debug(f"All queries succeeded, exposing updated values.")
self.status_lastUpdate = datetime.now().strftime("%m/%d/%Y, %H:%M:%S")
self.status_connection = "Connected"
self._current_val = params.get_result()
else:
self.status_connection = "Disconnected"
except Exception as e:
log.warning(f"Querying inverter {self._serial} at {self._host}:{self._port} failed on connection start with exception [{type(e).__name__}]")
self.status_connection = "Disconnected"
finally:
if sock:
sock.close()
def get_current_val(self):
return self._current_val
def get_sensors(self):
params = ParameterParser(self.parameter_definition)
return params.get_sensors ()
def main():
inverter = Inverter("/home/openhabian/solarman/inverter_definitions/", <logger-sn>, <logger-ip>, 8899, 1, "deye_sg04lp3.yaml")
inverter.update()
val = inverter.get_current_val()
val["last_update"] = inverter.status_lastUpdate
val["status"] = "ON"
val["status_connection"] = inverter.status_connection
mqtt_client = paho.Client("deye_inverter")
mqtt_client.enable_logger()
mqtt_client.connect(<mqtt-address>, 1883)
#mqtt_client.loop_start()
mqtt_topic = "deye/measurements"
json_object = json.dumps(val)
mqtt_client.publish(mqtt_topic, json_object, qos=1, retain=True)
mqtt_client.disconnect()
if __name__ == "__main__":
main()
@giorginus80 , thanks a lot! That helped.
For my Deye SUN600G3-EU-230 I also needed to replace the inverter definition file. With “deye_4mppt.yaml” I can now see the data being posted to the MQTT broker.
Have a nice day!
I successfully installed home assistant with my Deye SG04LP3. I would also try openhab. Can you recommend me a plugin?
Actually there isnt a plugin but can you use the python script used in this post
If you have logger stick with with requirements as follow:
Supported sticks:
wifi stick with firmware - LSW3_15_FFFF_1.0.57, those have serial number starting with 17xxx
wifi stick with firmware - LSW3_15_FFFF_1.0.65, those have serial number starting with 17xxx
eth stick with firmware - ME_08_2701_2.06, those have serial number starting with 21xxx
Then you probably may use this [new binding] Logger LSW for Sofar/Omnik/IE for SolarmanPV based on protocol v5 (iGEN tech)
Have you tried this project? GitHub - kbialek/deye-inverter-mqtt: Reads Deye solar inverter metrics and posts them over mqtt
I’ve written it exactly because I couldn’t make any binding working with my inverter.
How fast are the values updated?
Every 60 seconds by default. This can be changed in the configuration
Anybody knows if using the modbus rj45 port of the Deye inverter, is it possible with a Serial ethernet, with modbus binding, read values and which addresses? Without using and keep busy with network traffic the serial logger
Working like a charm with a HF2211. So you can use a direct wired connection to the network
my params:
so you can use the modbus binding, list of params was sent to me from Deye just today, and they working perfect.
HF2211 works with ethernet or wireless. I used it for my control unit of swimming pool too and it’s working very well. I prefer to use this, just to don’t overload requests to the logger, and to use the modbus binding integration
I attach the deye addresses for who is interested
MODBUS RTU-V102_compressed.pdf (490.4 KB)