Skip to content

Commit

Permalink
1st version in DevContainer
Browse files Browse the repository at this point in the history
  • Loading branch information
OStrama committed Oct 21, 2024
1 parent 8fd24f7 commit 1edb124
Show file tree
Hide file tree
Showing 6 changed files with 861 additions and 335 deletions.
4 changes: 3 additions & 1 deletion custom_components/weishaupt_modbus/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@
"number",
"select",
"sensor",
# "switch",
# "switch",
]


# Return boolean to indicate that initialization was successful.
# return True
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
Expand All @@ -22,6 +23,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True


async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
# This is called when an entry/configured device is to be removed. The class
# needs to unload itself, and remove callbacks. See the classes for further
Expand Down
5 changes: 4 additions & 1 deletion custom_components/weishaupt_modbus/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
{vol.Required(CONF_HOST): str, vol.Optional(CONF_PORT, default="502"): cv.port}
)


async def validate_input(hass: HomeAssistant, data: dict) -> dict[str, Any]:
# Validate the data can be used to set up a connection.

Expand Down Expand Up @@ -65,13 +66,15 @@ async def async_step_user(self, user_input=None):
except Exception: # noqa: BLE001
errors["base"] = "unknown"

# If there is no user input or there were errors, show the form again, including any errors that were found with the input.
# If there is no user input or there were errors, show the form again, including any errors that were found with the input.
return self.async_show_form(
step_id="user", data_schema=DATA_SCHEMA, errors=errors
)


class InvalidHost(exceptions.HomeAssistantError):
"""Error to indicate there is an invalid hostname."""


class ConnectionFailed(exceptions.HomeAssistantError):
"""Error to indicate there is an invalid hostname."""
16 changes: 14 additions & 2 deletions custom_components/weishaupt_modbus/const.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
from dataclasses import dataclass
from datetime import timedelta
from homeassistant.const import UnitOfEnergy, UnitOfTemperature, UnitOfTime, UnitOfVolumeFlowRate, UnitOfPower, PERCENTAGE
from homeassistant.const import (
UnitOfEnergy,
UnitOfTemperature,
UnitOfTime,
UnitOfVolumeFlowRate,
UnitOfPower,
PERCENTAGE,
)


@dataclass(frozen=True)
class MainConstants:
Expand All @@ -10,8 +18,10 @@ class MainConstants:
APPID = 100
KENNFELDFILE = "weishaupt_wbb_kennfeld.json"


CONST = MainConstants()


@dataclass(frozen=True)
class FormatConstants:
TEMPERATUR = UnitOfTemperature.CELSIUS
Expand All @@ -25,8 +35,10 @@ class FormatConstants:
TIME_MIN = UnitOfTime.MINUTES
TIME_H = UnitOfTime.HOURS


FORMATS = FormatConstants()


@dataclass(frozen=True)
class TypeConstants:
SENSOR = "Sensor"
Expand All @@ -35,5 +47,5 @@ class TypeConstants:
NUMBER = "Number"
NUMBER_RO = "Number_RO"

TYPES = TypeConstants()

TYPES = TypeConstants()
86 changes: 54 additions & 32 deletions custom_components/weishaupt_modbus/entities.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
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 +15,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 +36,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 +63,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,7 +82,7 @@ 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:
Expand All @@ -103,7 +109,7 @@ def translateVal(self):
return val / self._divider

@translateVal.setter
def translateVal(self,value):
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,23 +122,24 @@ 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)
Expand All @@ -147,7 +154,7 @@ def device_info(self) -> DeviceInfo:


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 @@ -162,28 +169,42 @@ async def async_update(self) -> None:
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):
# 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)

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

Expand All @@ -193,15 +214,14 @@ def device_info(self) -> DeviceInfo:


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,7 +232,7 @@ 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:
Expand All @@ -225,14 +245,16 @@ def device_info(self) -> DeviceInfo:


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,7 +263,7 @@ 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:
Expand Down
Loading

0 comments on commit 1edb124

Please sign in to comment.