Skip to content

Commit

Permalink
unify modbus sleeps (currently used by solaredge and huawei) (#1446)
Browse files Browse the repository at this point in the history
* unify modbus sleeps (currently used by solaredge and huawei)

* use reconnect delay

* review

* flake8
  • Loading branch information
LKuemmel committed Mar 25, 2024
1 parent e6c5dc8 commit c85d351
Show file tree
Hide file tree
Showing 7 changed files with 68 additions and 28 deletions.
6 changes: 3 additions & 3 deletions packages/modules/common/evse.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ def activate_precise_current(self) -> None:
else:
with ModifyLoglevelContext(log, logging.DEBUG):
log.debug("Bit zur Angabe der Ströme in 0,1A-Schritten wird gesetzt.")
self.client.delegate.write_registers(2005, value ^ self.PRECISE_CURRENT_BIT, unit=self.id)
self.client.write_registers(2005, value ^ self.PRECISE_CURRENT_BIT, unit=self.id)
# Zeit zum Verarbeiten geben
time.sleep(1)

Expand All @@ -83,10 +83,10 @@ def deactivate_precise_current(self) -> None:
if value & self.PRECISE_CURRENT_BIT:
with ModifyLoglevelContext(log, logging.DEBUG):
log.debug("Bit zur Angabe der Ströme in 0,1A-Schritten wird zurueckgesetzt.")
self.client.delegate.write_registers(2005, value ^ self.PRECISE_CURRENT_BIT, unit=self.id)
self.client.write_registers(2005, value ^ self.PRECISE_CURRENT_BIT, unit=self.id)
else:
return

def set_current(self, current: int) -> None:
time.sleep(0.1)
self.client.delegate.write_registers(1000, current, unit=self.id)
self.client.write_registers(1000, current, unit=self.id)
65 changes: 50 additions & 15 deletions packages/modules/common/modbus.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
import logging
import struct
from enum import Enum
from typing import Callable, Iterable, Union, overload, List
import time
from typing import Any, Callable, Iterable, Optional, Union, overload, List

import pymodbus
from pymodbus.client.sync import ModbusTcpClient, ModbusSerialClient
Expand Down Expand Up @@ -46,29 +47,41 @@ def __init__(self, bits: int, decoding_method: str):


class ModbusClient:
def __init__(self, delegate: Union[ModbusSerialClient, ModbusTcpClient], address: str, port: int = 502):
self.delegate = delegate
def __init__(self,
delegate: Union[ModbusSerialClient, ModbusTcpClient],
address: str, port: int = 502,
sleep_after_connect: Optional[int] = 0):
self._delegate = delegate
self.address = address
self.port = port
self.sleep_after_connect = sleep_after_connect

def __enter__(self):
try:
self.delegate.__enter__()
self._delegate.__enter__()
time.sleep(self.sleep_after_connect)
except pymodbus.exceptions.ConnectionException as e:
e.args += (NO_CONNECTION.format(self.address, self.port),)
raise e
return self

def __exit__(self, exc_type, exc_value, exc_traceback):
self.delegate.__exit__(exc_type, exc_value, exc_traceback)
self._delegate.__exit__(exc_type, exc_value, exc_traceback)

def close_connection(self) -> None:
def connect(self) -> None:
self._delegate.connect()
time.sleep(self.sleep_after_connect)

def close(self) -> None:
try:
log.debug("Close Modbus TCP connection")
self.delegate.close()
self._delegate.close()
except Exception as e:
raise Exception(__name__+" "+str(type(e))+" " + str(e)) from e

def is_socket_open(self) -> bool:
return self._delegate.is_socket_open()

def __read_registers(self, read_register_method: Callable,
address: int,
types: Union[Iterable[ModbusDataType], ModbusDataType],
Expand Down Expand Up @@ -118,7 +131,7 @@ def read_holding_registers(self, address: int,
wordorder: Endian = Endian.Big,
**kwargs):
return self.__read_registers(
self.delegate.read_holding_registers, address, types, byteorder, wordorder, **kwargs
self._delegate.read_holding_registers, address, types, byteorder, wordorder, **kwargs
)

@overload
Expand All @@ -137,7 +150,12 @@ def read_input_registers(self, address: int,
byteorder: Endian = Endian.Big,
wordorder: Endian = Endian.Big,
**kwargs):
return self.__read_registers(self.delegate.read_input_registers, address, types, byteorder, wordorder, **kwargs)
return self.__read_registers(self._delegate.read_input_registers,
address,
types,
byteorder,
wordorder,
**kwargs)

@overload
def read_coils(self, address: int, types: Iterable[ModbusDataType], byteorder: Endian = Endian.Big,
Expand All @@ -151,7 +169,7 @@ def read_coils(self, address: int, count: int, **kwargs) -> bool:

def read_coils(self, address: int, count: int, **kwargs):
try:
response = self.delegate.read_coils(address, count, **kwargs)
response = self._delegate.read_coils(address, count, **kwargs)
if response.isError():
raise Exception(__name__+" "+str(response))
return response.bits[0] if count == 1 else response.bits[:count]
Expand All @@ -162,18 +180,35 @@ def read_coils(self, address: int, count: int, **kwargs):
e.args += (NO_VALUES.format(self.address, self.port),)
raise e

def write_registers(self, address: int, value: Any, types: ModbusDataType):
self._delegate.write_registers(address, value, types)


class ModbusTcpClient_(ModbusClient):
def __init__(self, address: str, port: int = 502):
def __init__(self,
address: str,
port: int = 502,
sleep_after_connect: Optional[int] = None,
**kwargs):
parsed_url = parse_url(address)
host = parsed_url.host
if parsed_url.port is not None:
port = parsed_url.port
super().__init__(ModbusTcpClient(host, port), address, port)
super().__init__(ModbusTcpClient(host, port, **kwargs), address, port, sleep_after_connect)


class ModbusSerialClient_(ModbusClient):
def __init__(self, port: int):
super().__init__(ModbusSerialClient(method="rtu", port=port, baudrate=9600, stopbits=1, bytesize=8, timeout=1),
def __init__(self,
port: int,
sleep_after_connect: Optional[int] = None,
**kwargs):
super().__init__(ModbusSerialClient(method="rtu",
port=port,
baudrate=9600,
stopbits=1,
bytesize=8,
timeout=1,
**kwargs),
"Serial",
port)
port,
sleep_after_connect)
2 changes: 1 addition & 1 deletion packages/modules/devices/good_we/device.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ def read_legacy(component_type: str, ip_address: str, id: int, num: Optional[int
log.debug('GoodWe ID: ' + str(id))

dev.update()
dev.client.close_connection()
dev.client.close()


def main(argv: List[str]):
Expand Down
16 changes: 10 additions & 6 deletions packages/modules/devices/huawei/device.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,20 @@ def create_inverter_component(component_config: HuaweiInverterSetup):
return HuaweiInverter(device_config.id, component_config)

def update_components(components: Iterable[Union[HuaweiBat, HuaweiCounter, HuaweiInverter]]):
with client as c:
if client.is_socket_open() is False:
client.connect()
try:
modbus_id = device_config.configuration.modbus_id
regs = c.read_holding_registers(32064, [ModbusDataType.INT_32]*5701, unit=modbus_id)
regs = client.read_holding_registers(32064, [ModbusDataType.INT_32]*5701, unit=modbus_id)
counter_currents_reg = regs[5043:5045] # INT 32, 37107-9
counter_power_reg = regs[5049] # INT32, 37113
bat_power_reg = regs[-1] # INT32, 37765
inverter_power_reg = regs[0] # INT32 32064
# Huawei darf nur mit Regelintervall sehr langsam betrieben werden, daher kann hier eine längere Pause
# eingelegt werden. Ob auch eine kürzere ausreichend ist, ist nicht getestet.
time.sleep(5)
bat_soc_reg = c.read_holding_registers(37760, ModbusDataType.INT_16, unit=modbus_id) # Int 16 37760
bat_soc_reg = client.read_holding_registers(
37760, ModbusDataType.INT_16, unit=modbus_id) # Int 16 37760

for component in components:
with SingleComponentUpdateContext(component.fault_state):
Expand All @@ -46,11 +49,12 @@ def update_components(components: Iterable[Union[HuaweiBat, HuaweiCounter, Huawe
component.update(counter_currents_reg, counter_power_reg)
elif isinstance(component, HuaweiInverter):
component.update(inverter_power_reg)
except Exception as e:
client.close()
raise e

try:
client = ModbusTcpClient_(device_config.configuration.ip_address, 502)
client.delegate.connect()
time.sleep(7)
client = ModbusTcpClient_(device_config.configuration.ip_address, 502, sleep_after_connect=7)
except Exception:
log.exception("Fehler in create_device")
return ConfigurableDevice(
Expand Down
2 changes: 1 addition & 1 deletion packages/modules/devices/huawei_smartlogger/device.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ def __init__(self, device_config: Union[Dict, Huawei_Smartlogger]) -> None:
ip_address = self.device_config.configuration.ip_address
self.port = 502
self.client = modbus.ModbusTcpClient_(ip_address, 502)
self.client.delegate.connect()
self.client.connect()
except Exception:
log.exception("Fehler im Modul "+self.device_config.name)

Expand Down
3 changes: 2 additions & 1 deletion packages/modules/devices/solaredge/device.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ def __init__(self, device_config: Union[Dict, Solaredge]) -> None:
try:
self.device_config = dataclass_from_dict(Solaredge, device_config)
self.client = modbus.ModbusTcpClient_(self.device_config.configuration.ip_address,
self.device_config.configuration.port)
self.device_config.configuration.port,
reconnect_delay=reconnect_delay)
self.inverter_counter = 0
self.synergy_units = 1
except Exception:
Expand Down
2 changes: 1 addition & 1 deletion packages/modules/devices/sungrow/bat.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ def __init__(self,
def update(self) -> None:
unit = self.__device_modbus_id
soc = int(self.__tcp_client.read_input_registers(13022, ModbusDataType.INT_16, unit=unit) / 10)
resp = self.__tcp_client.delegate.read_input_registers(13000, 1, unit=unit)
resp = self.__tcp_client._delegate.read_input_registers(13000, 1, unit=unit)
binary = bin(resp.registers[0])[2:].zfill(8)
power = self.__tcp_client.read_input_registers(13021, ModbusDataType.INT_16, unit=unit)
if binary[5] == "1":
Expand Down

0 comments on commit c85d351

Please sign in to comment.