From 8640c6d24fe92b1493b8794e7d5a84ba21b47849 Mon Sep 17 00:00:00 2001 From: Johan Isacsson Date: Wed, 7 Dec 2022 22:46:47 +0100 Subject: [PATCH] Added support for setting holding registers --- custom_components/thermiagenesis/__init__.py | 6 +- .../thermiagenesis/binary_sensor.py | 1 - custom_components/thermiagenesis/climate.py | 1 - custom_components/thermiagenesis/const.py | 36 +++++ .../thermiagenesis/manifest.json | 2 +- custom_components/thermiagenesis/number.py | 148 ++++++++++++++++++ custom_components/thermiagenesis/sensor.py | 2 - custom_components/thermiagenesis/switch.py | 1 - hacs.json | 2 +- requirements_test.txt | 2 +- 10 files changed, 190 insertions(+), 11 deletions(-) create mode 100644 custom_components/thermiagenesis/number.py diff --git a/custom_components/thermiagenesis/__init__.py b/custom_components/thermiagenesis/__init__.py index f7acbb2..922e6d3 100644 --- a/custom_components/thermiagenesis/__init__.py +++ b/custom_components/thermiagenesis/__init__.py @@ -18,7 +18,7 @@ from .const import DOMAIN -PLATFORMS = ["sensor", "binary_sensor", "climate", "switch"] +PLATFORMS = ["sensor", "binary_sensor", "climate", "switch", "number"] SCAN_INTERVAL = timedelta(seconds=30) @@ -99,9 +99,9 @@ async def _async_update_data(self): # for reg in registers: # #await self.thermia.async_update(only_registers=[reg]) #registers) # print(f"Got {reg}: {self.thermia.data[reg]}") - print(data) + _LOGGER.debug(data) end_time = time.time() - print( + _LOGGER.debug( f"{datetime.now()} Fetching heatpump data took {end_time - start_time} s" ) diff --git a/custom_components/thermiagenesis/binary_sensor.py b/custom_components/thermiagenesis/binary_sensor.py index e2951b1..d0ad716 100644 --- a/custom_components/thermiagenesis/binary_sensor.py +++ b/custom_components/thermiagenesis/binary_sensor.py @@ -99,7 +99,6 @@ def entity_registry_enabled_default(self): return BINARY_SENSOR_TYPES[self.kind][ATTR_DEFAULT_ENABLED] def async_write_ha_state(self): - print(f"Writing state for {self.kind}: {self.state} ") super().async_write_ha_state() async def async_added_to_hass(self): diff --git a/custom_components/thermiagenesis/climate.py b/custom_components/thermiagenesis/climate.py index b120ed9..ab61d78 100644 --- a/custom_components/thermiagenesis/climate.py +++ b/custom_components/thermiagenesis/climate.py @@ -209,7 +209,6 @@ async def async_turn_off(self): await self.coordinator._async_set_data(self.meta[ATTR_ENABLED], False) def async_write_ha_state(self): - print(f"Writing state for {self.kind}: {self.state} ") super().async_write_ha_state() async def async_added_to_hass(self): diff --git a/custom_components/thermiagenesis/const.py b/custom_components/thermiagenesis/const.py index 7197ce3..46fcfbd 100644 --- a/custom_components/thermiagenesis/const.py +++ b/custom_components/thermiagenesis/const.py @@ -21,6 +21,8 @@ ATTR_DEFAULT_ENABLED = "default_enabled" ATTR_SCALE = "scale" ATTR_ADDR = "address" +ATTR_MAX_VALUE = "max_value" +ATTR_MIN_VALUE = "min_value" KEY_STATE_ATTRIBUTES = "state_attrs" KEY_STATUS_VALUE = "status_value" @@ -1670,11 +1672,17 @@ ATTR_UNIT: None, ATTR_DEFAULT_ENABLED: False, }, +} + +NUMBER_TYPES = { thermiaconst.ATTR_HOLDING_OPERATIONAL_MODE: { + # 1: OFF, 2: Standby, 3: ON/Auto ATTR_ICON: ICON_INPUT, ATTR_LABEL: "Operational Mode", ATTR_UNIT: None, ATTR_DEFAULT_ENABLED: False, + ATTR_MIN_VALUE: 1, + ATTR_MAX_VALUE: 3, }, thermiaconst.ATTR_HOLDING_MAX_LIMITATION: { ATTR_ICON: ICON_INPUT, @@ -1758,24 +1766,32 @@ ATTR_ICON: ICON_INPUT, ATTR_LABEL: "Minimum Allowed Gear In Heating", ATTR_UNIT: None, + ATTR_MIN_VALUE: 1, + ATTR_MAX_VALUE: 9, ATTR_DEFAULT_ENABLED: False, }, thermiaconst.ATTR_HOLDING_MAXIMUM_ALLOWED_GEAR_IN_HEATING: { ATTR_ICON: ICON_INPUT, ATTR_LABEL: "Maximum Allowed Gear In Heating", ATTR_UNIT: None, + ATTR_MIN_VALUE: 1, + ATTR_MAX_VALUE: 9, ATTR_DEFAULT_ENABLED: False, }, thermiaconst.ATTR_HOLDING_MAXIMUM_ALLOWED_GEAR_IN_TAP_WATER: { ATTR_ICON: ICON_INPUT, ATTR_LABEL: "Maximum Allowed Gear In Tap Water", ATTR_UNIT: None, + ATTR_MIN_VALUE: 1, + ATTR_MAX_VALUE: 9, ATTR_DEFAULT_ENABLED: False, }, thermiaconst.ATTR_HOLDING_MINIMUM_ALLOWED_GEAR_IN_TAP_WATER: { ATTR_ICON: ICON_INPUT, ATTR_LABEL: "Minimum Allowed Gear In Tap Water", ATTR_UNIT: None, + ATTR_MIN_VALUE: 1, + ATTR_MAX_VALUE: 9, ATTR_DEFAULT_ENABLED: False, }, thermiaconst.ATTR_HOLDING_COOLING_MIX_VALVE_SET_POINT: { @@ -2069,24 +2085,32 @@ thermiaconst.ATTR_HOLDING_MINIMUM_ALLOWED_GEAR_IN_POOL: { ATTR_ICON: ICON_INPUT, ATTR_LABEL: "Minimum Allowed Gear In Pool", + ATTR_MIN_VALUE: 1, + ATTR_MAX_VALUE: 9, ATTR_UNIT: None, ATTR_DEFAULT_ENABLED: False, }, thermiaconst.ATTR_HOLDING_MAXIMUM_ALLOWED_GEAR_IN_POOL: { ATTR_ICON: ICON_INPUT, ATTR_LABEL: "Maximum Allowed Gear In Pool", + ATTR_MIN_VALUE: 1, + ATTR_MAX_VALUE: 9, ATTR_UNIT: None, ATTR_DEFAULT_ENABLED: False, }, thermiaconst.ATTR_HOLDING_MINIMUM_ALLOWED_GEAR_IN_COOLING: { ATTR_ICON: ICON_INPUT, ATTR_LABEL: "Minimum Allowed Gear In Cooling", + ATTR_MIN_VALUE: 1, + ATTR_MAX_VALUE: 9, ATTR_UNIT: None, ATTR_DEFAULT_ENABLED: False, }, thermiaconst.ATTR_HOLDING_MAXIMUM_ALLOWED_GEAR_IN_COOLING: { ATTR_ICON: ICON_INPUT, ATTR_LABEL: "Maximum Allowed Gear In Cooling", + ATTR_MIN_VALUE: 1, + ATTR_MAX_VALUE: 9, ATTR_UNIT: None, ATTR_DEFAULT_ENABLED: False, }, @@ -2409,6 +2433,9 @@ ATTR_DEFAULT_ENABLED: False, }, thermiaconst.ATTR_HOLDING_SELECTED_MODE_FOR_MIXING_VALVE_2: { + # 0:Heat, 1:Cool, 2:Auto + ATTR_MIN_VALUE: 0, + ATTR_MAX_VALUE: 2, ATTR_ICON: ICON_INPUT, ATTR_LABEL: "Selected Mode For Mixing Valve 2", ATTR_UNIT: None, @@ -2433,6 +2460,9 @@ ATTR_DEFAULT_ENABLED: False, }, thermiaconst.ATTR_HOLDING_SELECTED_MODE_FOR_MIXING_VALVE_3: { + # 0:Heat, 1:Cool, 2:Auto + ATTR_MIN_VALUE: 0, + ATTR_MAX_VALUE: 2, ATTR_ICON: ICON_INPUT, ATTR_LABEL: "Selected Mode For Mixing Valve 3", ATTR_UNIT: None, @@ -2457,6 +2487,9 @@ ATTR_DEFAULT_ENABLED: False, }, thermiaconst.ATTR_HOLDING_SELECTED_MODE_FOR_MIXING_VALVE_4: { + # 0:Heat, 1:Cool, 2:Auto + ATTR_MIN_VALUE: 0, + ATTR_MAX_VALUE: 2, ATTR_ICON: ICON_INPUT, ATTR_LABEL: "Selected Mode For Mixing Valve 4", ATTR_UNIT: None, @@ -2481,6 +2514,9 @@ ATTR_DEFAULT_ENABLED: False, }, thermiaconst.ATTR_HOLDING_SELECTED_MODE_FOR_MIXING_VALVE_5: { + # 0:Heat, 1:Cool, 2:Auto + ATTR_MIN_VALUE: 0, + ATTR_MAX_VALUE: 2, ATTR_ICON: ICON_INPUT, ATTR_LABEL: "Selected Mode For Mixing Valve 5", ATTR_UNIT: None, diff --git a/custom_components/thermiagenesis/manifest.json b/custom_components/thermiagenesis/manifest.json index 2716c41..358f27d 100644 --- a/custom_components/thermiagenesis/manifest.json +++ b/custom_components/thermiagenesis/manifest.json @@ -8,5 +8,5 @@ "issue_tracker": "https://github.com/CJNE/thermiagenesis/issues", "documentation": "https://github.com/CJNE/thermiagenesis", "iot_class": "local_polling", - "version": "0.0.9" + "version": "0.0.10" } diff --git a/custom_components/thermiagenesis/number.py b/custom_components/thermiagenesis/number.py new file mode 100644 index 0000000..5e7892a --- /dev/null +++ b/custom_components/thermiagenesis/number.py @@ -0,0 +1,148 @@ +import logging + +from homeassistant.components.sensor import STATE_CLASS_MEASUREMENT +from homeassistant.components.number import NumberEntity +from homeassistant.const import PERCENTAGE +from homeassistant.const import TEMP_CELSIUS + +from homeassistant.helpers.entity import Entity +from pythermiagenesis.const import REGISTERS + +from .const import ATTR_CLASS +from .const import ATTR_DEFAULT_ENABLED +from .const import ATTR_ICON +from .const import ATTR_LABEL +from .const import ATTR_MANUFACTURER +from .const import ATTR_STATE_CLASS +from .const import ATTR_UNIT +from .const import ATTR_MIN_VALUE +from .const import ATTR_MAX_VALUE +from .const import DOMAIN +from .const import HEATPUMP_ALARMS +from .const import HEATPUMP_ATTRIBUTES +from .const import HEATPUMP_SENSOR +from .const import NUMBER_TYPES + +ATTR_COUNTER = "counter" +ATTR_FIRMWARE = "firmware" +ATTR_MODEL = "Diplomat Inverter Duo" + +_LOGGER = logging.getLogger(__name__) + + +async def async_setup_entry(hass, config_entry, async_add_entities): + """Add Thermia entities from a config_entry.""" + coordinator = hass.data[DOMAIN][config_entry.entry_id] + + numbers = [] + + device_info = { + "identifiers": {(DOMAIN, ATTR_MODEL)}, + "name": ATTR_MODEL, + "manufacturer": ATTR_MANUFACTURER, + "model": ATTR_MODEL, + "sw_version": coordinator.data.get(ATTR_FIRMWARE), + } + + for number in NUMBER_TYPES: + if REGISTERS[number][coordinator.kind]: + numbers.append(ThermiaGenericNumber(coordinator, number, device_info)) + async_add_entities(numbers, False) + + +def range_for_unit(unit): + if unit == PERCENTAGE: + return [0, 100] + if unit == TEMP_CELSIUS: + return [-40, 100] + return [0, 100] + + +class ThermiaGenericNumber(NumberEntity): + """Define a Thermia generic sensor.""" + + def __init__(self, coordinator, kind, device_info): + """Initialize.""" + self._name = f"{NUMBER_TYPES[kind][ATTR_LABEL]}" + # self._name = f"{coordinator.data[ATTR_MODEL]} {SENSOR_TYPES[kind][ATTR_LABEL]}" + self._unique_id = f"thermiagenesis_{kind}" + self._device_info = device_info + self.coordinator = coordinator + self.kind = kind + meta = NUMBER_TYPES[kind] + range = range_for_unit(meta.get(ATTR_UNIT, None)) + self.min = meta.get(ATTR_MIN_VALUE, range[0]) + self.max = meta.get(ATTR_MAX_VALUE, range[1]) + self._attrs = {} + + @property + def name(self): + """Return the name.""" + return self._name + + @property + def native_value(self): + """Return the state of the sensor.""" + return self.coordinator.data.get(self.kind) + + @property + def native_min_value(self): + """Return the state of the sensor.""" + return self.min + + @property + def native_max_value(self): + """Return the state of the sensor.""" + return self.max + + @property + def native_step(self): + """Return the state of the sensor.""" + return 1 + + async def async_set_native_value(self, value: float) -> None: + """Change the selected option.""" + _LOGGER.info("Writing holding register %s value %s", self.kind, value) + await self.coordinator._async_set_data(self.kind, value) + _LOGGER.debug("Done writing") + self.async_schedule_update_ha_state() + + @property + def native_unit_of_measurement(self): + """Return the unit the value is expressed in.""" + return NUMBER_TYPES[self.kind].get(ATTR_UNIT, None) + + @property + def icon(self): + """Return the icon.""" + return NUMBER_TYPES[self.kind][ATTR_ICON] + + @property + def unique_id(self): + """Return a unique_id for this entity.""" + return self._unique_id + + @property + def device_info(self): + """Return the device info.""" + return self._device_info + + def async_write_ha_state(self): + super().async_write_ha_state() + + @property + def entity_registry_enabled_default(self): + """Return if the entity should be enabled when first added to the entity registry.""" + return NUMBER_TYPES[self.kind][ATTR_DEFAULT_ENABLED] + + async def async_added_to_hass(self): + await super().async_added_to_hass() + """Connect to dispatcher listening for entity data notifications.""" + self.coordinator.registerAttribute(self.kind) + self.async_on_remove( + self.coordinator.async_add_listener(self.async_write_ha_state) + ) + + async def async_update(self): + """Update Thermia entity.""" + await self.coordinator.async_request_refresh() diff --git a/custom_components/thermiagenesis/sensor.py b/custom_components/thermiagenesis/sensor.py index 4d94d68..cd363d8 100644 --- a/custom_components/thermiagenesis/sensor.py +++ b/custom_components/thermiagenesis/sensor.py @@ -126,7 +126,6 @@ def has_alarm(self): return False def async_write_ha_state(self): - print(f"Writing state for {self.kind}: {self.state} ") super().async_write_ha_state() async def async_added_to_hass(self): @@ -214,7 +213,6 @@ def device_info(self): return self._device_info def async_write_ha_state(self): - print(f"Writing state for {self.kind}: {self.state} ") super().async_write_ha_state() @property diff --git a/custom_components/thermiagenesis/switch.py b/custom_components/thermiagenesis/switch.py index 366a78e..2bfeae4 100644 --- a/custom_components/thermiagenesis/switch.py +++ b/custom_components/thermiagenesis/switch.py @@ -99,7 +99,6 @@ def entity_registry_enabled_default(self): return SWITCH_TYPES[self.kind][ATTR_DEFAULT_ENABLED] def async_write_ha_state(self): - print(f"Writing state for {self.kind}: {self.state} ") super().async_write_ha_state() async def async_added_to_hass(self): diff --git a/hacs.json b/hacs.json index 860e113..f04d5e3 100644 --- a/hacs.json +++ b/hacs.json @@ -1,7 +1,7 @@ { "name": "Thermia Genesis", "hacs": "1.6.0", - "domains": ["binary_sensor", "sensor", "switch"], + "domains": ["binary_sensor", "sensor", "switch", "number", "climate"], "iot_class": "Local Polling", "render_readme": true, "homeassistant": "0.118.0" diff --git a/requirements_test.txt b/requirements_test.txt index f29a079..8bfe4cd 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,2 +1,2 @@ -r requirements_dev.txt -pytest-homeassistant-custom-component==0.12.28 +pytest-homeassistant-custom-component