Solarman modbus integration

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

1 Like

@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!

1 Like

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)

1 Like

Could you please tell me the connection to the modbus port of the deye (1 (485B) ,2 (485A),3 (GND)).
Is there something to setup in the software of the inverter?

Thx

Hello,
sorry for the late response, there is nothing to configure modbus is a serial port so it’s working without any configuration. I post you my actual .thing and .items, you can see from deye documentation that pins of the rj45 is configured like 1=B and 2=A but no worry if you don’t put in the right way nothing happen, you just don’t get the values (you can try with AV reporter modbus application)

Things:

Bridge modbus:tcp:deye "Deye modbus" [ host="192.168.0.93", port=8899, id=1] {

    Bridge poller batteryValues "Deye polling battery" [ start=586, length=3, refresh=10000, type="holding"] {
		Thing data holding586 "Deye battery temperature" [ readStart="586", readValueType="uint16", readTransform="JS(deye-temperature.js)"]
        Thing data holding588 "Deye battery soc" [ readStart="588", readValueType="uint16"]
    }
	
    Bridge poller productionValues "Deye polling production" [ start=520, length=22, refresh=10000, type="holding"] {
		Thing data holding520 "Deye day buy" [ readStart="520", readValueType="uint16", readTransform="JS(divideBy10.js)"]
		Thing data holding521 "Deye day sell" [ readStart="521", readValueType="uint16", readTransform="JS(divideBy10.js)"]
		Thing data holding526 "Deye day load" [ readStart="526", readValueType="uint16", readTransform="JS(divideBy10.js)"]
        Thing data holding529 "Deye day production" [ readStart="529", readValueType="uint16", readTransform="JS(divideBy10.js)"]
		Thing data holding534 "Deye total production" [ readStart="534", readValueType="uint16", readTransform="JS(divideBy10.js)"]
		Thing data holding540 "Deye AC temperature" [ readStart="540", readValueType="uint16", readTransform="JS(deye-temperature.js)"]
		Thing data holding541 "Deye DC temperature" [ readStart="541", readValueType="uint16", readTransform="JS(deye-temperature.js)"]
    }
	
    Bridge poller pvValues "Deye polling pv power" [ start=672, length=2, refresh=10000, type="holding"] {
		Thing data holding672 "Deye pv1 power" [ readStart="672", readValueType="uint16"]
        Thing data holding673 "Deye pv2 power" [ readStart="673", readValueType="uint16"]
    }
	
    Bridge poller loadValues "Deye polling load total power" [ start=653, length=1, refresh=10000, type="holding"] {
		Thing data holding653 "Deye load tot power" [ readStart="653", readValueType="uint16"]
    }

    Bridge poller gridPowerValues "Deye polling grid total power" [ start=619, length=1, refresh=10000, type="holding"] {
		Thing data holding619 "Deye grid tot power" [ readStart="619", readValueType="int16"]
    }	
	
}

Items:

Number DeyeModbusBatterySoc		"Carica batteria [%.0f %%]"			<batterylevel>					{ channel="modbus:data:deye:batteryValues:holding588:number" }
Number DeyeModbusBatteryTemp	"Temperatura batteria [%.1f °C]"	<temperature>					{ channel="modbus:data:deye:batteryValues:holding586:number" }
Number DeyeModbusAcTemp			"Temperatura AC [%.1f °C]"			<temperature>					{ channel="modbus:data:deye:productionValues:holding540:number" }
Number DeyeModbusDcTemp			"Temperatura DC [%.1f °C]"			<temperature>					{ channel="modbus:data:deye:productionValues:holding541:number" }
Number DeyeModbusLoadDaily		"Consumo giornaliero [%.2f kWh]"	<energy>						{ channel="modbus:data:deye:productionValues:holding526:number" }
Number DeyeModbusProdDaily		"Produzione giornaliera [%.2f kWh]"	<energy>						{ channel="modbus:data:deye:productionValues:holding529:number" }
Number DeyeModbusProdTotal		"Produzione totale [%.2f kWh]"		<energy>						{ channel="modbus:data:deye:productionValues:holding534:number" }
Group  DeyePvGroup
Number DeyeModbusPv1Power		"PV1 Power"							<energy>	(DeyePvGroup)		{ channel="modbus:data:deye:pvValues:holding672:number" }
Number DeyeModbusPv2Power		"PV2 Power"							<energy>	(DeyePvGroup)		{ channel="modbus:data:deye:pvValues:holding673:number" }
Number DeyeModbusPvPower		"Produzione attuale [%.2f W]"
Number DeyeModbusLoadTotal		"Consumo attuale in casa [%.2f W]"	<energy>						{ channel="modbus:data:deye:loadValues:holding653:number" }
Number DeyeModbusGridTotal		"Consumo contatore [%.2f W]"		<energy>						{ channel="modbus:data:deye:gridPowerValues:holding619:number" }
Group  DeyeBuyGroup
Number DeyeModbusBuyDaily		"Comprata giornaliera [%.2f kWh]"	<energy>	(DeyeBuyGroup)		{ channel="modbus:data:deye:productionValues:holding520:number" }
String DeyeModbusBuyDailyTxt	"Comprata giornaliera [%s]"						(DeyeBuyGroup)
Dimmer DeyeModbusBuyDimmer		"Prezzo al kWh [%.2f €]"						(DeyeBuyGroup)
Number DeyeModbusSellDaily		"Venduta giornaliera [%.2f kWh]"	<energy>						{ channel="modbus:data:deye:productionValues:holding521:number" }

Rules:

rule "Deye pv total"
when
   Member of DeyePvGroup changed
then
	val sum = (DeyeModbusPv1Power.state as Number) + (DeyeModbusPv2Power.state as Number)
	DeyeModbusPvPower.postUpdate(sum as Number)
end

rule "Deye buy update"
when
   Member of DeyeBuyGroup changed
then
	val buyVal = (DeyeModbusBuyDaily.state as Number) * (DeyeModbusBuyDimmer.state as Number)
	DeyeModbusBuyDailyTxt.postUpdate(DeyeModbusBuyDaily.state + " kWh (" + String.format("%.2f", buyVal) + "€)")
end

and the final result on my sitemap is:

Produzione attuale (actual production) is the sum of my 2 strings panels because on the deye modbus we don’t have the sum but only the production of the single string
For me this is the best strategy because you dont need to use the wifi logger (the modbus tcp is wireless or wired but I prefer wired so I bring a 2 wires ethernet cable to the inverter and the modbus tcp in my rack but you can use it near the inverter in wifi, its better for me than using the logger)

Thanks Giorgio, this works wonderfully.
In your Things file you have references to two Transform scripts (deye-temperature.js and divideBy10.js), it would be nice if you could post them as well. Unfortunately I don’t know Java.

Yes sorry, it’s just to format better the result:

deye-temperature.js

(function(data) {
  return (data - 1000) / 10
})(input)

divideBy10.js

(function(data) {
    return data/10;
})(input)