Skip to content

Commit

Permalink
switch to async modbus client
Browse files Browse the repository at this point in the history
  • Loading branch information
MadOne committed Oct 20, 2024
1 parent e123a84 commit e1b531c
Show file tree
Hide file tree
Showing 2 changed files with 90 additions and 54 deletions.
107 changes: 65 additions & 42 deletions custom_components/weishaupt_modbus/entities.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import warnings
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_PORT
from homeassistant.components.sensor import SensorDeviceClass, SensorEntity, SensorStateClass
from homeassistant.components.sensor import (
SensorDeviceClass,
SensorEntity,
SensorStateClass,
)
from homeassistant.components.select import SelectEntity
from homeassistant.components.number import NumberEntity
from homeassistant.const import UnitOfEnergy, UnitOfTemperature
Expand All @@ -11,14 +16,15 @@
from .hpconst import TEMPRANGE_STD, DEVICES
from .kennfeld import PowerMap


def BuildEntityList(entries, config_entry, modbusitems, type):
# this builds a list of entities that can be used as parameter by async_setup_entry()
# type of list is defined by the ModbusItem's type flag
# so the app only holds one list of entities that is build from a list of ModbusItem
# so the app only holds one list of entities that is build from a list of ModbusItem
# stored in hpconst.py so far, will be provided by an external file in future
for index, item in enumerate(modbusitems):
if item.type == type:
match type:
match type:
# here the entities are created with the parameters provided by the ModbusItem object
case TYPES.SENSOR | TYPES.NUMBER_RO:
entries.append(MySensorEntity(config_entry, modbusitems[index]))
Expand All @@ -31,7 +37,8 @@ def BuildEntityList(entries, config_entry, modbusitems, type):

return entries

class MyEntity():

class MyEntity:
# The base class for entities that hold general parameters
_config_entry = None
_modbus_item = None
Expand All @@ -57,15 +64,15 @@ def __init__(self, config_entry, modbus_item) -> None:
self._attr_state_class = SensorStateClass.MEASUREMENT
if self._modbus_item._format == FORMATS.POWER:
self._attr_state_class = SensorStateClass.MEASUREMENT

if self._modbus_item.resultlist != None:
self._attr_native_min_value = self._modbus_item.getNumberFromText("min")
self._attr_native_max_value = self._modbus_item.getNumberFromText("max")
self._attr_native_step = self._modbus_item.getNumberFromText("step")
self._divider = self._modbus_item.getNumberFromText("divider")
self._attr_device_class = self._modbus_item.getTextFromNumber(-1)

def calcTemperature(self,val: float):
def calcTemperature(self, val: float):
if val == None:
return None
if val == -32768:
Expand All @@ -76,20 +83,20 @@ def calcTemperature(self,val: float):
return None
return val / self._divider

def calcPercentage(self,val: float):
def calcPercentage(self, val: float):
if val == None:
return None
if val == 65535:
return None
return val / self._divider

@property
def translateVal(self):
async def translateVal(self):
# reads an translates a value from the modbus
mbo = ModbusObject(self._config_entry, self._modbus_item)
if mbo == None:
return None
val = mbo.value
val = await mbo.value
match self._modbus_item.format:
case FORMATS.TEMPERATUR:
return self.calcTemperature(val)
Expand All @@ -103,7 +110,7 @@ def translateVal(self):
return val / self._divider

@translateVal.setter
def translateVal(self,value):
async def translateVal(self, value):
# translates and writes a value to the modbus
mbo = ModbusObject(self._config_entry, self._modbus_item)
if mbo == None:
Expand All @@ -116,38 +123,39 @@ def translateVal(self,value):
case _:
val = value * self._divider
mbo.value = val

def my_device_info(self) -> DeviceInfo:
# helper to build the device info
# helper to build the device info
return {
"identifiers": {(CONST.DOMAIN, self._dev_device)},
"name": self._dev_device,
"sw_version": "Device_SW_Version",
"model": "Device_model",
"manufacturer": "Weishaupt",
"identifiers": {(CONST.DOMAIN, self._dev_device)},
"name": self._dev_device,
"sw_version": "Device_SW_Version",
"model": "Device_model",
"manufacturer": "Weishaupt",
}


class MySensorEntity(SensorEntity, MyEntity):
# class that represents a sensor entity derived from Sensorentity
# class that represents a sensor entity derived from Sensorentity
# and decorated with general parameters from MyEntity
_attr_native_unit_of_measurement = None
_attr_device_class = None
_attr_state_class = None
_attr_device_class = None
_attr_state_class = None

def __init__(self, config_entry, modbus_item) -> None:
MyEntity.__init__(self, config_entry, modbus_item)

async def async_update(self) -> None:
# the synching is done by the ModbusObject of the entity
self._attr_native_value = self.translateVal
self._attr_native_value = await self.translateVal

@property
def device_info(self) -> DeviceInfo:
return MyEntity.my_device_info(self)


class MyCalcSensorEntity(MySensorEntity):
# class that represents a sensor entity derived from Sensorentity
# class that represents a sensor entity derived from Sensorentity
# and decorated with general parameters from MyEntity
# calculates output from map
my_map = PowerMap()
Expand All @@ -157,51 +165,64 @@ def __init__(self, config_entry, modbus_item) -> None:

async def async_update(self) -> None:
# the synching is done by the ModbusObject of the entity
self._attr_native_value = self.translateVal
self._attr_native_value = await self.translateVal

def calcPower(self, val, x, y):
if val == None:
return val
return (val / 100) * self.my_map.map(x,y)
return (val / 100) * self.my_map.map(x, y)

@property
def translateVal(self):
async def translateVal(self):
# reads an translates a value from the modbus
mbo = ModbusObject(self._config_entry, self._modbus_item)
val = self.calcPercentage(mbo.value)

mb_x = ModbusItem(self._modbus_item.getNumberFromText("x"),"x",FORMATS.TEMPERATUR,TYPES.SENSOR_CALC,DEVICES.SYS, TEMPRANGE_STD)
val = self.calcPercentage(await mbo.value)

mb_x = ModbusItem(
self._modbus_item.getNumberFromText("x"),
"x",
FORMATS.TEMPERATUR,
TYPES.SENSOR_CALC,
DEVICES.SYS,
TEMPRANGE_STD,
)
mbo_x = ModbusObject(self._config_entry, mb_x)
if mbo_x == None:
return None
val_x = self.calcTemperature(mbo_x.value) / 10
mb_y = ModbusItem(self._modbus_item.getNumberFromText("y"),"y",FORMATS.TEMPERATUR,TYPES.SENSOR_CALC,DEVICES.WP, TEMPRANGE_STD)
val_x = self.calcTemperature(await mbo_x.value) / 10
mb_y = ModbusItem(
self._modbus_item.getNumberFromText("y"),
"y",
FORMATS.TEMPERATUR,
TYPES.SENSOR_CALC,
DEVICES.WP,
TEMPRANGE_STD,
)
mbo_y = ModbusObject(self._config_entry, mb_y)
if mbo_x == None:
return None
val_y = self.calcTemperature(mbo_y.value) / 10

match self._modbus_item.format:
case FORMATS.POWER:
return self.calcPower(val,val_x,val_y)
return self.calcPower(val, val_x, val_y)
case _:
return val / self._divider

@property
def device_info(self) -> DeviceInfo:
async def device_info(self) -> DeviceInfo:
return MySensorEntity.my_device_info(self)


class MyNumberEntity(NumberEntity, MyEntity):
# class that represents a sensor entity derived from Sensorentity
# class that represents a sensor entity derived from Sensorentity
# and decorated with general parameters from MyEntity
_attr_native_unit_of_measurement = None
_attr_device_class = None
_attr_state_class = None
_attr_device_class = None
_attr_state_class = None
_attr_native_min_value = 10
_attr_native_max_value = 60


def __init__(self, config_entry, modbus_item) -> None:
MyEntity.__init__(self, config_entry, modbus_item)

Expand All @@ -212,27 +233,29 @@ def __init__(self, config_entry, modbus_item) -> None:

async def async_set_native_value(self, value: float) -> None:
self.translateVal = value
self._attr_native_value = self.translateVal
self._attr_native_value = self.translateVal
self.async_write_ha_state()

async def async_update(self) -> None:
# the synching is done by the ModbusObject of the entity
self._attr_native_value = self.translateVal
self._attr_native_value = await self.translateVal

@property
def device_info(self) -> DeviceInfo:
return MyEntity.my_device_info(self)


class MySelectEntity(SelectEntity, MyEntity):
# class that represents a sensor entity derived from Sensorentity
# class that represents a sensor entity derived from Sensorentity
# and decorated with general parameters from MyEntity
options = []
_attr_current_option = "FEHLER"

def __init__(self, config_entry, modbus_item) -> None:
MyEntity.__init__(self, config_entry, modbus_item)
self.async_internal_will_remove_from_hass_port = self._config_entry.data[CONF_PORT]
self.async_internal_will_remove_from_hass_port = self._config_entry.data[
CONF_PORT
]
# option list build from the status list of the ModbusItem
self.options = []
for index, item in enumerate(self._modbus_item._resultlist):
Expand All @@ -241,13 +264,13 @@ def __init__(self, config_entry, modbus_item) -> None:
async def async_select_option(self, option: str) -> None:
# the synching is done by the ModbusObject of the entity
self.translateVal = option
self._attr_current_option = self.translateVal
self._attr_current_option = self.translateVal
self.async_write_ha_state()

async def async_update(self) -> None:
# the synching is done by the ModbusObject of the entity
# await self.coordinator.async_request_refresh()
self._attr_current_option = self.translateVal
self._attr_current_option = await self.translateVal

@property
def device_info(self) -> DeviceInfo:
Expand Down
37 changes: 25 additions & 12 deletions custom_components/weishaupt_modbus/modbusobject.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_HOST, CONF_PORT
from pymodbus.client import ModbusTcpClient as ModbusClient
from pymodbus.client import AsyncModbusTcpClient as AsyncModbusTcpClient
from .const import FORMATS, TYPES

# import logging
# logging.basicConfig()
# log = logging.getLogger()
# log.setLevel(logging.DEBUG)


# A Modbus object that contains a Modbus item and communicates with the Modbus
# it contains a ModbusClient for setting and getting Modbus register values
class ModbusObject():
class ModbusObject:
_ModbusItem = None
_DataFormat = None

Expand All @@ -20,41 +21,53 @@ class ModbusObject():

def __init__(self, config_entry, modbus_item):
self._ModbusItem = modbus_item
#self._HeatPump = heatpump
# self._HeatPump = heatpump

self._ip = config_entry.data[CONF_HOST]
self._port = config_entry.data[CONF_PORT]
self._ModbusClient = None

def connect(self):
async def connect(self):
try:
self._ModbusClient = ModbusClient(host=self._ip, port=self._port)
self._ModbusClient = AsyncModbusTcpClient(host=self._ip, port=self._port)
await self._ModbusClient.connect()
return self._ModbusClient.connected # noqa: TRY300
except: # noqa: E722
return None

@property
def value(self):
async def value(self):
try:
self.connect()
await self.connect()
match self._ModbusItem.type:
case TYPES.SENSOR | TYPES.SENSOR_CALC:
# Sensor entities are read-only
return self._ModbusClient.read_input_registers(self._ModbusItem.address, slave=1).registers[0]
return (
await self._ModbusClient.read_input_registers(
self._ModbusItem.address, slave=1
)
).registers[0]
case TYPES.SELECT | TYPES.NUMBER | TYPES.NUMBER_RO:
return self._ModbusClient.read_holding_registers(self._ModbusItem.address, slave=1).registers[0]
return (
await self._ModbusClient.read_holding_registers(
self._ModbusItem.address, slave=1
)
).registers[0]

except: # noqa: E722
return None

@value.setter
def value(self,value) -> None:
async def value(self, value) -> None:
try:
match self._ModbusItem.type:
case TYPES.SENSOR | TYPES.NUMBER_RO | TYPES.SENSOR_CALC:
# Sensor entities are read-only
# Sensor entities are read-only
return
case _:
self.connect()
self._ModbusClient.write_register(self._ModbusItem.address, int(value), slave=1)
await self._ModbusClient.write_register(
self._ModbusItem.address, int(value), slave=1
)
except: # noqua: E722
return None

0 comments on commit e1b531c

Please sign in to comment.