Skip to content

Commit

Permalink
Refactoring and introduction of number and select platforms. #9 #7
Browse files Browse the repository at this point in the history
  • Loading branch information
erikkastelec committed Apr 13, 2022
1 parent 5471aed commit cc34305
Show file tree
Hide file tree
Showing 11 changed files with 656 additions and 199 deletions.
12 changes: 7 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,10 @@ custom_components
│ ├── __init__.py
│ ├── const.py
│ ├── manifest.json
│ └── sensor.py
│ ├── coordinator.py
│ ├── select.py
│ ├── number.py
│ ├── sensor.py
│ └── wemportalapi.py
```

Expand All @@ -42,7 +45,7 @@ Configuration variables:
- `scan_interval (Optional)`: Defines update frequency of web scraping. Optional and in seconds (defaults to 30 min).
Setting update frequency bellow 15 min is not recommended.
- `api_scan_interval (Optional)`: Defines update frequency for API data fetching. Optional and in seconds (defaults to 5
min).
min, should not be lower than 3 min).
- `language (
Optional)`: Defines preferred language. Use `en` for English translation or `de` for German. (defaults to en (
English))
Expand All @@ -54,8 +57,7 @@ Add the following to your `configuration.yaml` file:

```yaml
# Example configuration.yaml entry
sensor:
- platform: wemportal
wemportal:
#scan_interval: 1800
#api_scan_interval: 300
#language: en
Expand All @@ -71,4 +73,4 @@ logger:
default: warn
logs:
custom_components.wemportal: debug
```
```
88 changes: 87 additions & 1 deletion custom_components/wemportal/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,87 @@
"""The WEM Portal component."""
"""
wemportal integration
Author: erikkastelec
https://github.com/erikkastelec/hass-WEM-Portal
"""
from datetime import timedelta

import voluptuous as vol

from homeassistant.const import CONF_PASSWORD, CONF_SCAN_INTERVAL, CONF_USERNAME
from homeassistant.core import HomeAssistant, callback
import homeassistant.helpers.config_validation as config_validation
from homeassistant.helpers.typing import ConfigType

from .const import CONF_LANGUAGE, CONF_MODE, CONF_SCAN_INTERVAL_API, DOMAIN, PLATFORMS
from .coordinator import WemPortalDataUpdateCoordinator
from .wemportalapi import WemPortalApi

CONFIG_SCHEMA = vol.Schema(
{
vol.Optional(DOMAIN, default=[]): vol.Schema(
{
vol.Optional(
CONF_SCAN_INTERVAL, default=timedelta(minutes=30)
): config_validation.time_period,
vol.Optional(
CONF_SCAN_INTERVAL_API, default=timedelta(minutes=5)
): config_validation.time_period,
vol.Optional(CONF_LANGUAGE, default="en"): config_validation.string,
vol.Optional(CONF_MODE, default="both"): config_validation.string,
vol.Required(CONF_USERNAME): config_validation.string,
vol.Required(CONF_PASSWORD): config_validation.string,
}
)
},
extra=vol.ALLOW_EXTRA,
)


async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up the wemportal component."""
# Set proper update_interval, based on selected mode
if config[DOMAIN].get(CONF_MODE) == "web":
update_interval = config[DOMAIN].get(CONF_SCAN_INTERVAL)

elif config[DOMAIN].get(CONF_MODE) == "api":
update_interval = config[DOMAIN].get(CONF_SCAN_INTERVAL_API)
else:
update_interval = min(
config[DOMAIN].get(CONF_SCAN_INTERVAL),
config[DOMAIN].get(CONF_SCAN_INTERVAL_API),
)
# Creatie API object
api = WemPortalApi(config[DOMAIN])
# Create custom coordinator
coordinator = WemPortalDataUpdateCoordinator(hass, api, update_interval)

hass.data[DOMAIN] = {
"api": api,
"config": config[DOMAIN],
"coordinator": coordinator,
}

await coordinator.async_config_entry_first_refresh()

# Initialize platforms
for platform in PLATFORMS:
hass.helpers.discovery.load_platform(platform, DOMAIN, {}, config)
return True

# """Remove obsolete entities from entity registry."""

# if hap.home.currentAPVersion < "2.2.12":
# return

# entity_registry = er.async_get(hass)
# er_entries = async_entries_for_config_entry(entity_registry, entry.entry_id)
# for er_entry in er_entries:
# if er_entry.unique_id.startswith("HomematicipAccesspointStatus"):
# entity_registry.async_remove(er_entry.entity_id)
# continue

# for hapid in hap.home.accessPointUpdateStates:
# if er_entry.unique_id == f"HomematicipBatterySensor_{hapid}":
# entity_registry.async_remove(er_entry.entity_id)
2 changes: 2 additions & 0 deletions custom_components/wemportal/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,5 @@
CONF_SCAN_INTERVAL_API: Final = "api_scan_interval"
CONF_LANGUAGE: Final = "language"
CONF_MODE: Final = "mode"
PLATFORMS = ["sensor", "number", "select"]
REFRESH_WAIT_TIME: int = 60
29 changes: 29 additions & 0 deletions custom_components/wemportal/coordinator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
""" WemPortal integration coordinator """
from __future__ import annotations

import async_timeout
from homeassistant.core import HomeAssistant
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator

from .const import _LOGGER, DEFAULT_TIMEOUT
from .wemportalapi import WemPortalApi


class WemPortalDataUpdateCoordinator(DataUpdateCoordinator):
"""DataUpdateCoordinator for wemportal component"""

def __init__(self, hass: HomeAssistant, api: WemPortalApi, update_interval):
"""Initialize DataUpdateCoordinator for the wemportal component"""
super().__init__(
hass,
_LOGGER,
name="WemPortal update",
update_interval=update_interval,
)
self.api = api
self.has = hass

async def _async_update_data(self):
"""Fetch data from the wemportal api"""
async with async_timeout.timeout(DEFAULT_TIMEOUT):
return await self.hass.async_add_executor_job(self.api.fetch_data)
4 changes: 2 additions & 2 deletions custom_components/wemportal/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"documentation": "https://github.com/erikkastelec/hass-WEM-Portal",
"issue_tracker": "https://github.com/erikkastelec/hass-WEM-Portal/issues",
"dependencies": [],
"version": "1.2.2",
"version": "1.3.0",
"codeowners": [
"@erikkastelec"
],
Expand All @@ -14,4 +14,4 @@
"fuzzywuzzy==0.18.0"
],
"iot_class": "cloud_polling"
}
}
137 changes: 137 additions & 0 deletions custom_components/wemportal/number.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
"""
Number platform for wemportal component
"""

from homeassistant.components.number import NumberEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity

from .const import _LOGGER, DOMAIN


async def async_setup_platform(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
discovery_info=None,
):
"""Setup the Wem Portal sensors."""

coordinator = hass.data[DOMAIN]["coordinator"]

entities: list[WemPortalNumber] = []
for unique_id, values in coordinator.data.items():
if values["platform"] == "number":
_LOGGER.warning(unique_id)
entities.append(WemPortalNumber(coordinator, unique_id, values))

async_add_entities(entities)


class WemPortalNumber(CoordinatorEntity, NumberEntity):
"""Representation of a WEM Portal number."""

def __init__(self, coordinator, _unique_id, entity_data):
"""Initialize the sensor."""
super().__init__(coordinator)
self._last_updated = None
self._name = _unique_id
self._parameter_id = entity_data["ParameterID"]
self._unique_id = _unique_id
self._icon = entity_data["icon"]
self._unit = entity_data["unit"]
self._state = self.state
self._attr_min_value = entity_data["min_value"]
self._attr_max_value = entity_data["max_value"]
self._attr_step = entity_data["step"]
self._module_index = entity_data["ModuleIndex"]
self._module_type = entity_data["ModuleType"]

async def async_set_value(self, value: float) -> None:
"""Update the current value."""
await self.hass.async_add_executor_job(
self.coordinator.api.change_value,
self._parameter_id,
self._module_index,
self._module_type,
value,
)
self._state = value
self.coordinator.data[self._unique_id]["value"] = value
self.async_write_ha_state()

@property
def should_poll(self):
"""No need to poll. Coordinator notifies entity of updates."""
return False

@property
def available(self):
"""Return if entity is available."""
return self.coordinator.last_update_success

async def async_added_to_hass(self):
"""When entity is added to hass."""
self.coordinator.async_add_listener(self.async_write_ha_state)

async def async_will_remove_from_hass(self):
"""When entity will be removed from hass."""
self.coordinator.async_remove_listener(self.async_write_ha_state)

@property
def name(self):
"""Return the name of the sensor."""
return self._name

@property
def unique_id(self):
"""Return the unique ID of the binary sensor."""
return self._unique_id

@property
def icon(self):
"""Icon to use in the frontend, if any."""
return self._icon

@property
def state(self):
"""Return the state of the sensor."""
try:
state = self.coordinator.data[self._unique_id]["value"]
if state:
return state
return 0
except KeyError:
_LOGGER.error("Can't find %s", self._unique_id)
_LOGGER.debug("Sensor data %s", self.coordinator.data)
return None

@property
def unit_of_measurement(self):
"""Return the unit of measurement of this entity, if any."""
return self._unit

# @property
# def state_class(self):
# """Return the state class of this entity, if any."""
# if self._unit in ("°C", "kW", "W", "%"):
# return STATE_CLASS_MEASUREMENT
# elif self._unit in ("kWh", "Wh"):
# return STATE_CLASS_TOTAL_INCREASING
# else:
# return None

@property
def extra_state_attributes(self):
"""Return the state attributes of this device."""
attr = {}
if self._last_updated is not None:
attr["Last Updated"] = self._last_updated
return attr

async def async_update(self):
"""Update Entity
Only used by the generic entity update service."""
await self.coordinator.async_request_refresh()
Loading

0 comments on commit cc34305

Please sign in to comment.