Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support dynamic fan behavior coupled to the modes #89

Merged
merged 1 commit into from
Jan 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 24 additions & 32 deletions custom_components/maestro_mcz/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import os
import async_timeout
from .config_flow import CONF_POLLING_INTERVAL
from .maestro.responses.model import Configuration, ModelConfiguration, SensorConfiguration
from .maestro.responses.model import Configuration, ModelConfiguration, SensorConfiguration, SensorConfigurationMultipleModes
from .models import MczConfigItem

from homeassistant.config_entries import ConfigEntry
Expand Down Expand Up @@ -122,61 +122,53 @@ def get_device_info(self) -> DeviceInfo:
+ f", DB:{self._maestroapi.Status.nome_banca_dati_sel}",
)

def get_model_configuration_by_model_configuration_id(self, model_configuration_id:str) -> ModelConfiguration | None:
return next((x for x in self._maestroapi.Model.model_configurations if x.configuration_id is not None and x.configuration_id.lower() == model_configuration_id.lower()), None)

def get_model_configuration_by_model_configuration_name(self, model_configuration_name:str) -> ModelConfiguration | None:
return next((x for x in self._maestroapi.Model.model_configurations if x.configuration_name is not None and x.configuration_name.lower() == model_configuration_name.lower()), None)
return next((x for x in self._maestroapi.Model.model_configurations if x.configuration_name is not None and model_configuration_name is not None and x.configuration_name.lower() == model_configuration_name.lower()), None)

def get_sensor_configuration_by_model_configuration_id_and_sensor_id(self, model_configuration_id:str, sensor_id:str) -> SensorConfiguration | None:
model_configuration = self.get_model_configuration_by_model_configuration_id(model_configuration_id)
if(model_configuration is None):
return None
else:
sensor_configuration = next((x for x in model_configuration.configurations if x.sensor_id is not None and x.sensor_id.lower() == sensor_id.lower()), None)
if(sensor_configuration is not None):
return SensorConfiguration(sensor_configuration, model_configuration.configuration_id)

def get_sensor_configuration_by_model_configuration_name_and_sensor_name(self, model_configuration_name:str, sensor_name:str) -> SensorConfiguration | None:
model_configuration = self.get_model_configuration_by_model_configuration_name(model_configuration_name)
if(model_configuration is None):
return None
else:
sensor_configuration = next((x for x in model_configuration.configurations if x.sensor_name is not None and x.sensor_name.lower() == sensor_name.lower()), None)
sensor_configuration = next((x for x in model_configuration.configurations if x.sensor_name is not None and sensor_name is not None and x.sensor_name.lower() == sensor_name.lower()), None)
if(sensor_configuration is not None):
return SensorConfiguration(sensor_configuration, model_configuration.configuration_id)

def get_first_matching_sensor_configuration_by_model_configuration_id_and_sensor_id(self, mcz_config_items_list_to_match: list[MczConfigItem]) -> tuple[MczConfigItem,SensorConfiguration] | None:
for x in mcz_config_items_list_to_match:
matching_configuration = self.get_sensor_configuration_by_model_configuration_id_and_sensor_id(x.sensor_set_config_id, x.sensor_set_id)
if(matching_configuration is not None):
return (x, matching_configuration)
return None

def get_first_matching_sensor_configuration_by_model_configuration_name_and_sensor_name(self, mcz_config_items_list_to_match: list[MczConfigItem]) -> tuple[MczConfigItem,SensorConfiguration] | None:
for x in mcz_config_items_list_to_match:
matching_configuration = self.get_sensor_configuration_by_model_configuration_name_and_sensor_name(x.sensor_set_config_name, x.sensor_set_name)
if(matching_configuration is not None):
return (x, matching_configuration)
if x.sensor_set_config_name is not None:
matching_configuration = self.get_sensor_configuration_by_model_configuration_name_and_sensor_name(x.sensor_set_config_name, x.sensor_set_name)
if(matching_configuration is not None):
return (x, matching_configuration)
return None

def get_all_matching_sensor_configurations_by_model_configuration_id_and_sensor_id(self, mcz_config_items_list_to_match: list[MczConfigItem]) -> list(tuple[MczConfigItem,SensorConfiguration]) | None:
def get_all_matching_sensor_configurations_by_model_configuration_name_and_sensor_name(self, mcz_config_items_list_to_match: list[MczConfigItem]) -> list(tuple[MczConfigItem,SensorConfiguration]) | None:
temp_list = []
for x in mcz_config_items_list_to_match:
matching_configuration = self.get_sensor_configuration_by_model_configuration_id_and_sensor_id(x.sensor_set_config_id, x.sensor_set_id)
if(matching_configuration is not None):
temp_list.append((x, matching_configuration))
if x.sensor_set_config_name is not None:
matching_configuration = self.get_sensor_configuration_by_model_configuration_name_and_sensor_name(x.sensor_set_config_name, x.sensor_set_name)
if(matching_configuration is not None):
temp_list.append((x, matching_configuration))
if temp_list:
return temp_list
else:
return None

def get_all_matching_sensor_configurations_by_model_configuration_name_and_sensor_name(self, mcz_config_items_list_to_match: list[MczConfigItem]) -> list(tuple[MczConfigItem,SensorConfiguration]) | None:

def get_all_matching_sensor_for_all_configurations_by_model_mode_and_sensor_name(self, mcz_config_items_list_to_match: list[MczConfigItem]) -> list(tuple[MczConfigItem,SensorConfigurationMultipleModes]) | None:
temp_list = []
for x in mcz_config_items_list_to_match:
matching_configuration = self.get_sensor_configuration_by_model_configuration_name_and_sensor_name(x.sensor_set_config_name, x.sensor_set_name)
if(matching_configuration is not None):
temp_list.append((x, matching_configuration))
if x.mode_to_configuration_name_mapping is not None:
temp_mode_configurations: dict[str,SensorConfiguration] = {}
for mode in x.mode_to_configuration_name_mapping:
matching_configuration = self.get_sensor_configuration_by_model_configuration_name_and_sensor_name(x.mode_to_configuration_name_mapping[mode], x.sensor_set_name)
if(matching_configuration is not None):
temp_mode_configurations[mode] = matching_configuration

if(temp_mode_configurations is not None and len(temp_mode_configurations) > 0):
temp_list.append((x,SensorConfigurationMultipleModes(temp_mode_configurations)))
if temp_list:
return temp_list
else:
Expand Down
53 changes: 39 additions & 14 deletions custom_components/maestro_mcz/fan.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""Platform for Fan integration."""
import logging

from ..maestro_mcz.maestro.responses.model import SensorConfiguration
from ..maestro_mcz.maestro.responses.model import SensorConfiguration, SensorConfigurationMultipleModes
from ..maestro_mcz.maestro.types.enums import TypeEnum
from . import MczCoordinator, models

Expand All @@ -23,7 +23,9 @@ async def async_setup_entry(hass, entry, async_add_entities):
entities = []
for stove in stove_list:
stove:MczCoordinator = stove
supported_fans = stove.get_all_matching_sensor_configurations_by_model_configuration_name_and_sensor_name(models.supported_fans)

supported_fans = stove.get_all_matching_sensor_for_all_configurations_by_model_mode_and_sensor_name(models.supported_fans)

if(supported_fans is not None):
for supported_fan in supported_fans:
if(supported_fan[0] is not None and supported_fan[1] is not None):
Expand All @@ -37,10 +39,11 @@ class MczFanEntity(CoordinatorEntity, FanEntity):
_attr_preset_mode = None
_attr_is_on = None
#
_fan_configuration: SensorConfiguration | None = None
_fan_configuration: SensorConfigurationMultipleModes | None = None
_current_fan_configuration: SensorConfiguration | None = None
_presets: list | None = None

def __init__(self, coordinator, supported_fan: models.FanMczConfigItem, matching_fan_configuration: SensorConfiguration):
def __init__(self, coordinator, supported_fan: models.FanMczConfigItem, matching_fan_configuration: SensorConfigurationMultipleModes):
super().__init__(coordinator)
self.coordinator:MczCoordinator = coordinator
self._attr_name = supported_fan.user_friendly_name
Expand All @@ -50,9 +53,9 @@ def __init__(self, coordinator, supported_fan: models.FanMczConfigItem, matching
self._enabled_default = supported_fan.enabled_by_default
self._category = supported_fan.category
self._fan_configuration = matching_fan_configuration
if(matching_fan_configuration.configuration.type == TypeEnum.INT.value):
self._presets = self._attr_preset_modes = list(map(str,range(int(matching_fan_configuration.configuration.min), int(matching_fan_configuration.configuration.max) + 1 , 1)))
self._attr_supported_features = (FanEntityFeature.PRESET_MODE)

self.update_features_based_on_current_stove_state()

self.handle_coordinator_update_internal() #getting the initial update directly without delay

@property
Expand All @@ -78,28 +81,50 @@ def entity_category(self):

async def async_set_preset_mode(self, preset_mode: str) -> None:
"""Set the preset mode of the fan."""
if(self._fan_configuration is not None):
await self.coordinator._maestroapi.ActivateProgram(self._fan_configuration.configuration.sensor_id, self._fan_configuration.configuration_id, int(preset_mode))
if(self._current_fan_configuration is not None):
await self.coordinator._maestroapi.ActivateProgram(self._current_fan_configuration.configuration.sensor_id, self._current_fan_configuration.configuration_id, int(preset_mode))
await self.coordinator.async_refresh()

async def async_turn_on(self) -> None:
"""Turn on the fan."""
if(self._fan_configuration is not None and self._presets is not None and len(self._presets) > 0):
await self.coordinator._maestroapi.ActivateProgram(self._fan_configuration.configuration.sensor_id, self._fan_configuration.configuration_id, int(self._presets[-1]))
if(self._current_fan_configuration is not None and self._presets is not None and len(self._presets) > 0):
await self.coordinator._maestroapi.ActivateProgram(self._current_fan_configuration.configuration.sensor_id, self._current_fan_configuration.configuration_id, int(self._presets[-1]))
await self.coordinator.async_refresh()

async def async_turn_off(self) -> None:
"""Turn off the fan."""
if(self._fan_configuration is not None and self._presets is not None and len(self._presets) > 0):
await self.coordinator._maestroapi.ActivateProgram(self._fan_configuration.configuration.sensor_id, self._fan_configuration.configuration_id, int(self._presets[0]))
if(self._current_fan_configuration is not None and self._presets is not None and len(self._presets) > 0):
await self.coordinator._maestroapi.ActivateProgram(self._current_fan_configuration.configuration.sensor_id, self._current_fan_configuration.configuration_id, int(self._presets[0]))
await self.coordinator.async_refresh()

def get_configuration_for_current_stove_mode(self) -> SensorConfiguration | None:
"""Get the correct sensor configuration for the current mode that the stove is in"""
if(self.coordinator._maestroapi.State is not None and self.coordinator._maestroapi.State.mode is not None
and self._fan_configuration is not None and self.coordinator._maestroapi.State.mode in self._fan_configuration.mode_configurations):
return self._fan_configuration.mode_configurations[self.coordinator._maestroapi.State.mode]
return None

def update_features_based_on_current_stove_state(self) -> None:
"""Refresh all fan features that are applicable for the current mode that the stove is in"""
self._current_fan_configuration = self.get_configuration_for_current_stove_mode()

self._attr_supported_features = FanEntityFeature(0) # resetting the features
if(self._current_fan_configuration is not None and
self._current_fan_configuration.configuration is not None and
self._current_fan_configuration.configuration.enabled == True):
if(self._current_fan_configuration.configuration.type == TypeEnum.INT.value):
self._presets = self._attr_preset_modes = list(map(str,range(int(self._current_fan_configuration.configuration.min), int(self._current_fan_configuration.configuration.max) + 1 , 1)))
self._attr_supported_features = (FanEntityFeature.PRESET_MODE)

@callback
def _handle_coordinator_update(self) -> None:
"""handle coordinator updates"""
self.update_features_based_on_current_stove_state()
self.handle_coordinator_update_internal()
self.async_write_ha_state()

def handle_coordinator_update_internal(self) -> None:
"""handle coordinator updates for this fan"""
#presets
if(hasattr(self.coordinator._maestroapi.State, self._prop)):
self._attr_preset_mode = str(getattr(self.coordinator._maestroapi.State, self._prop))
Expand All @@ -109,7 +134,7 @@ def handle_coordinator_update_internal(self) -> None:
self._attr_preset_mode = None

# on/off
if(self._fan_configuration is not None and self._presets is not None and len(self._presets) > 0):
if(self._current_fan_configuration is not None and self._presets is not None and len(self._presets) > 0):
self._attr_is_on = self.preset_mode != self._presets[0]
else:
self._attr_is_on = False
11 changes: 11 additions & 0 deletions custom_components/maestro_mcz/maestro/responses/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,3 +83,14 @@ class SensorConfiguration:
def __init__(self, configuration: Configuration, configuration_id: str) -> None:
self.configuration = configuration
self.configuration_id = configuration_id


@dataclass
class SensorConfigurationMultipleModes():
mode_configurations: dict[str,SensorConfiguration] | None = None # key => Mode | value => SensorConfiguration for that mode

def __init__(self, mode_configurations: dict[str,SensorConfiguration]) -> None:
self.mode_configurations = mode_configurations



25 changes: 11 additions & 14 deletions custom_components/maestro_mcz/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,7 @@ class MczConfigItem:
sensor_set_config_name: str | None = None

#optional
sensor_set_id: str | None = None #id used for setting data trough the API (resolved in configs first)
sensor_set_config_id: str | None = None


mode_to_configuration_name_mapping: dict[str,str] | None = None #key => Mode | value => Configuration Name
user_friendly_name: str | None = None
icon: str | None = None
enabled_by_default: bool = True
Expand Down Expand Up @@ -66,13 +63,13 @@ def __init__(self, user_friendly_name:str, sensor_get_name:str, sensor_set_name:

@dataclass
class FanMczConfigItem(MczConfigItem):
def __init__(self, user_friendly_name:str, sensor_get_name:str, sensor_set_name:str, sensor_set_config_name:str, enabled_by_default: bool):

def __init__(self, user_friendly_name:str, sensor_get_name:str, sensor_set_name:str, mode_to_configuration_name_mapping: dict[str,str], enabled_by_default: bool):
super().__init__(user_friendly_name)
self.sensor_get_name = sensor_get_name
self.icon = "mdi:fan"
self.sensor_set_name = sensor_set_name
self.sensor_set_config_name = sensor_set_config_name
self.mode_to_configuration_name_mapping = mode_to_configuration_name_mapping # means we want to find the sensor name in the different mode configs
self.enabled_by_default = enabled_by_default

@dataclass
Expand Down Expand Up @@ -184,7 +181,7 @@ def __init__(self, user_friendly_name:str, sensor_get_name:str, icon:str, unit:s

supported_thermostats = [
ThermostatMczConfigItem("Ambient Temperature", "set_amb1", "set_amb1", "Set_amb_temp", True),
ThermostatMczConfigItem("Ambient Temperature", "set_amb1", "m1_set_amb1", "Set_amb_temp", True), #for first generation M1+
ThermostatMczConfigItem("Ambient Temperature", "set_amb1", "m1_set_amb1", "Set_amb_temp", True), #for first generation M1+
ThermostatMczConfigItem("Ambient Temperature 2", "set_amb2", "set_amb2", "Set_amb_temp", True),
ThermostatMczConfigItem("Ambient Temperature 2", "set_amb2", "m1_set_amb2", "Set_amb_temp", True), #for first generation M1+
ThermostatMczConfigItem("Ambient Temperature 3", "set_amb3", "set_amb3", "Set_amb_temp", True),
Expand All @@ -197,12 +194,12 @@ def __init__(self, user_friendly_name:str, sensor_get_name:str, icon:str, unit:s
]

supported_fans = [
FanMczConfigItem("Fan 1", "set_vent_v1", "set_vent_v1", "set_v1", True),
FanMczConfigItem("Fan 1", "set_vent_v1", "m1_set_vent_v1", "set_v1", True), #for first generation M1+
FanMczConfigItem("Fan 2", "set_vent_v2", "set_vent_v2", "set_v2", True),
FanMczConfigItem("Fan 2", "set_vent_v2", "m1_set_vent_v2", "set_v2", True), #for first generation M1+
FanMczConfigItem("Fan 3", "set_vent_v3", "set_vent_v3", "set_v3", True),
FanMczConfigItem("Fan 3", "set_vent_v3", "m1_set_vent_v3", "set_v3", True), #for first generation M1+
FanMczConfigItem("Fan 1", "set_vent_v1", "set_vent_v1", {"manual":"Manuale", "auto":"Auto", "overnight":"Overnight", "comfort":"Comfort", "turbo":"Turbo"}, True),
FanMczConfigItem("Fan 1", "set_vent_v1", "m1_set_vent_v1", {"manual":"Manuale","dynamic":"Dynamic", "overnight":"Overnight", "comfort":"Comfort", "power":"Power"}, True), #for first generation M1+
FanMczConfigItem("Fan 2", "set_vent_v2", "set_vent_v2", {"manual":"Manuale", "auto":"Auto", "overnight":"Overnight", "comfort":"Comfort", "turbo":"Turbo"}, True),
FanMczConfigItem("Fan 2", "set_vent_v2", "m1_set_vent_v2", {"manual":"Manuale","dynamic":"Dynamic", "overnight":"Overnight", "comfort":"Comfort", "power":"Power"}, True), #for first generation M1+
FanMczConfigItem("Fan 3", "set_vent_v3", "set_vent_v3", {"manual":"Manuale", "auto":"Auto", "overnight":"Overnight", "comfort":"Comfort", "turbo":"Turbo"}, True),
FanMczConfigItem("Fan 3", "set_vent_v3", "m1_set_vent_v3", {"manual":"Manuale","dynamic":"Dynamic", "overnight":"Overnight", "comfort":"Comfort", "power":"Power"}, True), #for first generation M1+
]

supported_switches = [
Expand Down