Skip to content

Commit

Permalink
Merge pull request #142 from jontofront/1.1.15-dev
Browse files Browse the repository at this point in the history
1.1.15 dev
  • Loading branch information
jontofront authored Jan 13, 2025
2 parents 23063a4 + c8fd834 commit fa6ca9e
Show file tree
Hide file tree
Showing 10 changed files with 238 additions and 37 deletions.
14 changes: 10 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -187,17 +187,23 @@ Updated the names of the lighter, boilerPower, and feeder sensors in custom_comp

## # Changelog

## [1.1.14] - 2025-01-07
## [1.1.0] - 2025-01-07
### Added
- Introduced `should_skip_params_edits` function in `custom_components/econet300/common.py` to determine if parameter edits should be skipped based on `controllerID` (controller_id == "ecoMAX360i":).
- Introduced `skip_params_edits` function in `custom_components/econet300/common.py` to determine if parameter edits should be skipped based on `controllerID` (controller_id == "ecoMAX360i").
- Added `async_gather_entities` function in `custom_components/econet300/sensor.py` to collect sensor entities.
- Added annotations and type hints across various functions.
- Support for ecoMAX360i controller.

### Changed
- Updated `EconetDataCoordinator` class in `custom_components/econet300/common.py` to use type hints and new `should_skip_params_edits` function.
- Updated `EconetDataCoordinator` class in `custom_components/econet300/common.py` to use type hints and new `skip_params_edits` function.
- Modified entity setup in `custom_components/econet300/number.py` to skip for `controllerID: ecoMAX360i`.
- Refactored sensor entity gathering logic in `async_setup_entry`.
- Improved error handling in `Econet300Api` by adding specific exception logging.
- Changed `homeassistant` version to `2024.12.2` and `ruff` version to `0.8.4` in `requirements.txt`.
- Removed `colorlog` dependency from `requirements.txt`.

### Fixed
- Addressed type hinting issues in `custom_components/econet300/common.py` and `custom_components/econet300/number.py`.
- Addressed type hinting issues in `custom_components/econet300/common.py` and `custom_components/econet300/number.py`.
- Improved data attribute checks in `EconetEntity` for better error handling.

For more details, you can view the commits.
65 changes: 55 additions & 10 deletions custom_components/econet300/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -270,8 +270,22 @@ async def fetch_reg_params_data(self) -> dict[str, Any]:
regParamsData = await self._fetch_api_data_by_key(
API_REG_PARAMS_DATA_URI, API_REG_PARAMS_DATA_PARAM_DATA
)
except aiohttp.ClientError as e:
_LOGGER.error("Client error occurred while fetching regParamsData: %s", e)
return {}
except asyncio.TimeoutError as e:
_LOGGER.error("Timeout error occurred while fetching regParamsData: %s", e)
return {}
except ValueError as e:
_LOGGER.error("Value error occurred while fetching regParamsData: %s", e)
return {}
except DataError as e:
_LOGGER.error("Error fetching regParamsData: %s", e)
_LOGGER.error("Data error occurred while fetching regParamsData: %s", e)
return {}
except Exception as e:
_LOGGER.error(
"Unexpected error occurred while fetching regParamsData: %s", e
)
return {}
else:
_LOGGER.debug("Fetched regParamsData: %s", regParamsData)
Expand Down Expand Up @@ -309,19 +323,50 @@ async def fetch_sys_params(self) -> dict[str, Any]:

async def _fetch_api_data_by_key(self, endpoint: str, data_key: str | None = None):
"""Fetch a key from the json-encoded data returned by the API for a given registry If key is None, then return whole data."""
data = await self._client.get(f"{self.host}/econet/{endpoint}")
try:
data = await self._client.get(f"{self.host}/econet/{endpoint}")

if data is None:
raise DataError(f"Data fetched by API for endpoint: {endpoint} is None")
if data is None:
_LOGGER.error("Data fetched by API for endpoint: %s is None", endpoint)
return None

if data_key is None:
return data
if data_key is None:
return data

if data_key not in data:
_LOGGER.debug(data)
raise DataError(f"Data for key: {data_key} does not exist")
if data_key not in data:
_LOGGER.error(
"Data for key: %s does not exist in endpoint: %s",
data_key,
endpoint,
)
return None

return data[data_key]
return data[data_key]
except aiohttp.ClientError as e:
_LOGGER.error(
"lient error occurred while fetching data from endpoint: %s, error: %s",
endpoint,
e,
)
except asyncio.TimeoutError as e:
_LOGGER.error(
"A timeout error occurred while fetching data from endpoint: %s, error: %s",
endpoint,
e,
)
except ValueError as e:
_LOGGER.error(
"A value error occurred while processing data from endpoint: %s, error: %s",
endpoint,
e,
)
except Exception as e:
_LOGGER.error(
"An unexpected error occurred while fetching data from endpoint: %s, error: %s",
endpoint,
e,
)
return None


async def make_api(hass: HomeAssistant, cache: MemCache, data: dict):
Expand Down
4 changes: 2 additions & 2 deletions custom_components/econet300/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
_LOGGER = logging.getLogger(__name__)


def should_skip_params_edits(sys_params: dict[str, Any]) -> bool:
def skip_params_edits(sys_params: dict[str, Any]) -> bool:
"""Determine whether paramsEdits should be skipped based on controllerID."""
controller_id = sys_params.get("controllerID")
if controller_id == "ecoMAX360i":
Expand Down Expand Up @@ -71,7 +71,7 @@ async def _async_update_data(self) -> dict[str, Any]:
sys_params = await self._api.fetch_sys_params()

# Determine whether to fetch paramsEdits from ../econet/rmCurrentDataParamsEdits
if should_skip_params_edits(sys_params):
if skip_params_edits(sys_params):
params_edits = {}
else:
params_edits = await self._api.fetch_param_edit_data()
Expand Down
52 changes: 49 additions & 3 deletions custom_components/econet300/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,9 +87,20 @@
#######################

SENSOR_MAP_KEY = {
"ecoster": {
"ecoSterTemp1",
"ecoSterTemp2",
"ecoMAX360i": {
"PS",
"Circuit2thermostatTemp",
"TempClutch",
"Circuit3thermostatTemp",
"TempWthr",
"TempCircuit3",
"TempCircuit2",
"TempBuforUp",
"TempCWU",
"TempBuforDown",
"heatingUpperTemp",
"Circuit1thermostat",
"heating_work_state_pump4",
},
"lambda": {
"lambdaStatus",
Expand Down Expand Up @@ -175,6 +186,17 @@
"burnerOutput": PERCENTAGE,
"mixerTemp": UnitOfTemperature.CELSIUS,
"mixerSetTemp": UnitOfTemperature.CELSIUS,
# ecoMAX360i
"Circuit2thermostatTemp": UnitOfTemperature.CELSIUS,
"TempClutch": UnitOfTemperature.CELSIUS,
"Circuit3thermostatTemp": UnitOfTemperature.CELSIUS,
"TempWthr": UnitOfTemperature.CELSIUS,
"TempCircuit3": UnitOfTemperature.CELSIUS,
"TempCircuit2": UnitOfTemperature.CELSIUS,
"TempBuforUp": UnitOfTemperature.CELSIUS,
"TempBuforDown": UnitOfTemperature.CELSIUS,
"heatingUpperTemp": UnitOfTemperature.CELSIUS,
"Circuit1thermostat": UnitOfTemperature.CELSIUS,
}

# By default all sensors state_class are MEASUREMENT
Expand All @@ -190,6 +212,9 @@
"moduleCSoftVer": None,
"moduleLambdaSoftVer": None,
"modulePanelSoftVer": None,
# ecoMAX360i
"PS": None,
"heating_work_state_pump4": None,
}

# By default all sensors device_class are None
Expand All @@ -215,6 +240,17 @@
"tempLowerBuffer": SensorDeviceClass.TEMPERATURE,
"signal": SensorDeviceClass.SIGNAL_STRENGTH,
"servoMixer1": SensorDeviceClass.ENUM,
# ecoMAX360i
"Circuit2thermostatTemp": SensorDeviceClass.TEMPERATURE,
"TempClutch": SensorDeviceClass.TEMPERATURE,
"Circuit3thermostatTemp": SensorDeviceClass.TEMPERATURE,
"TempWthr": SensorDeviceClass.TEMPERATURE,
"TempCircuit3": SensorDeviceClass.TEMPERATURE,
"TempCircuit2": SensorDeviceClass.TEMPERATURE,
"TempBuforUp": SensorDeviceClass.TEMPERATURE,
"TempBuforDown": SensorDeviceClass.TEMPERATURE,
"heatingUpperTemp": SensorDeviceClass.TEMPERATURE,
"Circuit1thermostat": SensorDeviceClass.TEMPERATURE,
}

ENTITY_NUMBER_SENSOR_DEVICE_CLASS_MAP = {
Expand Down Expand Up @@ -261,6 +297,12 @@
"moduleCSoftVer": None,
"moduleLambdaSoftVer": None,
"modulePanelSoftVer": None,
# ecoMAX360i
"PS": None,
"TempBuforDown": 1,
"heatingUpperTemp": 1,
"Circuit1thermostat": 1,
"heating_work_state_pump4": None,
}

ENTITY_ICON = {
Expand Down Expand Up @@ -303,6 +345,10 @@
"moduleCSoftVer": "mdi:raspberry-pi",
"moduleLambdaSoftVer": "mdi:raspberry-pi",
"modulePanelSoftVer": "mdi:alarm-panel-outline",
# ecoMAX360i
"TempBuforDown": "mdi:thermometer",
"heatingUpperTemp": "mdi:thermometer",
"heating_work_state_pump4": "mdi:sync",
}

ENTITY_ICON_OFF = {
Expand Down
18 changes: 9 additions & 9 deletions custom_components/econet300/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,11 @@ async def async_added_to_hass(self):
_LOGGER.debug("Added to HASS: %s", self.entity_description)
_LOGGER.debug("Coordinator: %s", self.coordinator)

# Check if the coordinator has a 'data' attributes
if "data" not in dir(self.coordinator):
_LOGGER.error("Coordinator object does not have a 'data' attribute")
return

# Retrieve sysParams and regParams paramsEdits data
sys_params = self.coordinator.data.get("sysParams", {})
reg_params = self.coordinator.data.get("regParams", {})
Expand All @@ -92,15 +97,10 @@ async def async_added_to_hass(self):
_LOGGER.debug("async_regParams: %s", reg_params)
_LOGGER.debug("async_paramsEdits: %s", params_edits)

# Check if the coordinator has a 'data' attributes
if "data" not in dir(self.coordinator):
_LOGGER.error("Coordinator object does not have a 'data' attribute")
return

# Check the available keys in all sources
sys_keys = sys_params.keys()
reg_keys = reg_params.keys()
edit_keys = params_edits.keys()
sys_keys = sys_params.keys() if sys_params is not None else []
reg_keys = reg_params.keys() if reg_params is not None else []
edit_keys = params_edits.keys() if params_edits is not None else []
_LOGGER.debug("Available keys in sysParams: %s", sys_keys)
_LOGGER.debug("Available keys in regParams: %s", reg_keys)
_LOGGER.debug("Available keys in paramsEdits: %s", edit_keys)
Expand Down Expand Up @@ -167,7 +167,7 @@ def device_info(self) -> DeviceInfo | None:


class LambdaEntity(EconetEntity):
"""Represents EcosterEntity."""
"""Initialize the LambdaEntity."""

def __init__(
self,
Expand Down
2 changes: 1 addition & 1 deletion custom_components/econet300/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,6 @@
"issue_tracker": "https://github.com/jontofront/ecoNET-300-Home-Assistant-Integration/issues",
"requirements": [],
"ssdp": [],
"version": "v1.0.14",
"version": "v1.1.0",
"zeroconf": []
}
4 changes: 2 additions & 2 deletions custom_components/econet300/number.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from homeassistant.helpers.entity_platform import AddEntitiesCallback

from .api import Limits
from .common import Econet300Api, EconetDataCoordinator, should_skip_params_edits
from .common import Econet300Api, EconetDataCoordinator, skip_params_edits
from .common_functions import camel_to_snake
from .const import (
DOMAIN,
Expand Down Expand Up @@ -175,7 +175,7 @@ async def async_setup_entry(

for key in NUMBER_MAP:
sys_params = coordinator.data.get("sysParams", {})
if should_skip_params_edits(sys_params):
if skip_params_edits(sys_params):
_LOGGER.info("Skipping number entity setup for controllerID: ecoMAX360i")
continue

Expand Down
44 changes: 38 additions & 6 deletions custom_components/econet300/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,10 +116,23 @@ def create_controller_sensors(
"""Create controller sensor entities."""
entities: list[EconetSensor] = []

# Get the system and regular parameters from the coordinator
data_regParams = coordinator.data.get("regParams", {})
data_sysParams = coordinator.data.get("sysParams", {})

for data_key in SENSOR_MAP_KEY["_default"]:
# Extract the controllerID from sysParams
controller_id = data_sysParams.get("controllerID", None)

# Determine the keys to use based on the controllerID
sensor_keys = SENSOR_MAP_KEY.get(controller_id, SENSOR_MAP_KEY["_default"])
_LOGGER.info(
"Using sensor keys for controllerID '%s': %s",
controller_id if controller_id else "None (default)",
sensor_keys,
)

# Iterate through the selected keys and create sensors if valid data is found
for data_key in sensor_keys:
_LOGGER.debug(
"Processing entity sensor data_key: %s from regParams & sysParams", data_key
)
Expand Down Expand Up @@ -266,19 +279,38 @@ async def async_setup_entry(
) -> bool:
"""Set up the sensor platform."""

def async_gather_entities(
def gather_entities(
coordinator: EconetDataCoordinator, api: Econet300Api
) -> list[EconetSensor]:
"""Collect all sensor entities."""
entities = []
entities.extend(create_controller_sensors(coordinator, api))
entities.extend(create_mixer_sensors(coordinator, api))
entities.extend(create_lambda_sensors(coordinator, api))
_LOGGER.info("Starting entity collection for sensors...")

# Gather sensors dynamically based on the controller
controller_sensors = create_controller_sensors(coordinator, api)
_LOGGER.info("Collected %d controller sensors", len(controller_sensors))
entities.extend(controller_sensors)

# Gather mixer sensors
mixer_sensors = create_mixer_sensors(coordinator, api)
_LOGGER.info("Collected %d mixer sensors", len(mixer_sensors))
entities.extend(mixer_sensors)

# Gather lambda sensors
lambda_sensors = create_lambda_sensors(coordinator, api)
_LOGGER.info("Collected %d lambda sensors", len(lambda_sensors))
entities.extend(lambda_sensors)

_LOGGER.info("Total entities collected: %d", len(entities))
return entities

coordinator = hass.data[DOMAIN][entry.entry_id][SERVICE_COORDINATOR]
api = hass.data[DOMAIN][entry.entry_id][SERVICE_API]

entities = async_gather_entities(coordinator, api)
# Collect entities synchronously
entities = await hass.async_add_executor_job(gather_entities, coordinator, api)

# Add entities to Home Assistant
async_add_entities(entities)
_LOGGER.info("Entities successfully added to Home Assistant")
return True
Loading

0 comments on commit fa6ca9e

Please sign in to comment.