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

Code clean up, warnings fix #40

Open
wants to merge 32 commits into
base: dev
Choose a base branch
from
Open
Changes from 1 commit
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
ad16b9f
Updated overloads of device_state_attributes to extra_state_attributes
gadulowaty Oct 9, 2022
6c66b79
Updated get_mac_address to correctly handle host name by DNS entry
gadulowaty Oct 9, 2022
c0ed06c
Merge branch 'dev' into dev
dgtal1 Oct 11, 2022
79f48b9
Merge branch 'dgtal1:dev' into dev
gadulowaty Oct 12, 2022
4117075
Merge branch 'dgtal1:dev' into dev
gadulowaty Oct 12, 2022
e91ec86
Added missing DOMAIN_VIRTUAL_BINARY_SENSOR_SENSOR in DOMAIN_VIRTUAL_S…
gadulowaty Oct 12, 2022
1bf31af
Merge branch 'dgtal1:dev' into dev
gadulowaty May 24, 2024
9431cc5
Added changes for compability with latest 2024.5.4 update of Homeassi…
gadulowaty May 24, 2024
75936fd
Fix HomeAssistantType deprecated warning
gadulowaty May 24, 2024
4296d2f
Replaced deprecated constants for unit of measurements
gadulowaty May 24, 2024
541dd36
ExtaLifeLight: deprecated supported_features result value
gadulowaty May 24, 2024
81c8ad0
Fix blocking import module inside event loop
gadulowaty May 28, 2024
f81acf6
Added issue tracker address to integration manifest
gadulowaty May 28, 2024
2197c91
Code clean up - part I
gadulowaty May 29, 2024
6b5c66f
Code clean up - part I
gadulowaty May 29, 2024
51692c5
Merge remote-tracking branch 'origin/dev' into dev
gadulowaty May 29, 2024
769d60a
Code clean up - part II
gadulowaty May 30, 2024
26d2029
ImportLib code refactoring durnig async_setup_custom_platform
gadulowaty May 30, 2024
950672e
Replaced deprecated calls to 'async_add_job'
gadulowaty May 30, 2024
c929fea
Extalife API rework
gadulowaty May 31, 2024
7aeb776
Fixed error in computing native_value for sensor when using defined f…
gadulowaty May 31, 2024
462d2b2
* Added more restrictive constraint for native_value recomputing when…
gadulowaty Jun 2, 2024
a9896e7
Code clean up - part III
gadulowaty Jun 2, 2024
a80fc4c
Merge remote-tracking branch 'origin/dev' into dev
gadulowaty Jun 2, 2024
1a5caa2
Fixed integration reload from HA UI
gadulowaty Jun 2, 2024
be3a4b0
Added initial support for new sensor RCW-21
gadulowaty Jun 2, 2024
33ff212
RCW-21: Changed unit of measurement for wind speed
gadulowaty Jun 3, 2024
d47c3d0
RCW-21: Fixed recursive call in suggested_unit_of_measurement property
gadulowaty Jun 3, 2024
c24a8f1
ExtalifeAPI: Replaced controller commands for name. version with NEW …
gadulowaty Jun 3, 2024
9bfad6e
ExtalifeAPI: Rewrote of ExtaLife connector, changed logic in API and …
gadulowaty Jun 13, 2024
14bdb79
Minor changes to services configuration
gadulowaty Jun 14, 2024
b1621c7
Code cleanup
gadulowaty Jun 14, 2024
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
Prev Previous commit
Next Next commit
Code clean up - part I
This commit includes many changes:
* removing of unused imports
* grammar correction in comments
* source files reformating (wrapping code to 120 chars, spaces, new lines etc.)
* removing of duplicate const values
* marking unused callback params for valid code analysis
* correction of some imports to match changes in HA core

pyextalife.py:
* added parameter timeout for async_connect for changing default timeout to 10 sec while reconnecting controller
* better handling of raised exception when data sync with Ha occure while in disconnected state and reconnecting
* added identificatiors for some new devices (SLN-2?, RCW-21)
* replaced int values in DEVICE_ARR_*** with idents (in near feature will move to IntEnum type)
gadulowaty committed May 29, 2024
commit 2197c91489c6a7eb4a9b408b9bb18f5289e7911c
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -10,18 +10,18 @@ ZAMEL Exta Life integration with Home Assistant based on custom component.
* Smart sockets: ROG-21
* Roller blind controllers: SRP-22, SRM-22
* Heating controller: RGT-01, GKN-01
* Sensors: RCT-21, RCT-22, RNK-21&RNK-22 built-in temperature sensor, flood sensor RCZ-21, motion sensor RCR-21, window sensor RCK-21, multisensor RCM-21, 3-phase energy meter MEM-21
* Sensors: RCT-21, RCT-22, RNK-21&RNK-22 built-in temperature sensor, flood sensor RCZ-21, motion sensor RCR-21, window sensor RCK-21, multi-sensor RCM-21, 3-phase energy meter MEM-21

### Exta Free supported devices (via EFC-01 controller):
* Switches: ROP-01, ROP-02, ROP-05, ROP-06, ROP-07, ROM-01, ROM-10
* Dimmers: RDP-01, RDP-02, RDP-11
* Smart sockets: RWG-01
* Roller blind controllers: SRP-02, SRP-03, ROB-01

**Note:** Certain switches are mapped into Home Asistant light entities depending on icon assigned to them. This is to support voice control by Google Assistant and others and because switches are mostly used for light control.
**Note:** Certain switches are mapped into Home Assistant light entities depending on icon assigned to them. This is to support voice control by Google Assistant and others and because switches are mostly used for light control.
### Setup
Make sure you copied the integration to `custom_components` folder in your `config` directory.
The integration is setup from Home Assistant GUI (Integrations screen). Search for "Exta Life" on the list of possible integrations. If it's not visible - clear your browser chache and refresh page.
The integration is set up from Home Assistant GUI (Integrations screen). Search for "Exta Life" on the list of possible integrations. If it's not visible - clear your browser cache and refresh page.
The integration supports Integration Options - search for it on the Exta Life integration badge in Integrations GUI.

Discussion, news and many more on https://www.forumextalife.pl/
115 changes: 56 additions & 59 deletions extalife/__init__.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,13 @@
"""Support for ExtaLife devices."""
import asyncio
from datetime import timedelta
import importlib
import logging
from typing import Optional
from typing import Any
import voluptuous as vol

from homeassistant.const import CONF_ACCESS_TOKEN
import homeassistant.helpers.config_validation as cv
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers.discovery import load_platform
from homeassistant.helpers.entity import Entity
from homeassistant.helpers import entity_component
from homeassistant.helpers import entity_platform
from homeassistant.helpers.event import async_track_time_interval
from homeassistant.helpers import device_registry as dr
from homeassistant.helpers.typing import ConfigType
from homeassistant.core import HomeAssistant
@@ -66,10 +60,10 @@
OPTIONS_GENERAL,
OPTIONS_GENERAL_POLL_INTERVAL,
OPTIONS_GENERAL_DISABLE_NOT_RESPONDING,
VIRT_SENSOR_CHN_FIELD,
VIRT_SENSOR_DEV_CLS,
VIRT_SENSOR_PATH,
VIRT_SENSOR_ALLOWED_CHANNELS
VIRTUAL_SENSOR_CHN_FIELD,
VIRTUAL_SENSOR_DEV_CLS,
VIRTUAL_SENSOR_PATH,
VIRTUAL_SENSOR_ALLOWED_CHANNELS
)

from .helpers.services import ExtaLifeServices
@@ -123,6 +117,7 @@
)


# noinspection PyUnusedLocal
async def async_migrate_entry(hass, config_entry: ConfigEntry):
"""Migrate old entry."""
_LOGGER.debug("Migrating from version %s", config_entry.version)
@@ -144,10 +139,9 @@ async def async_migrate_entry(hass, config_entry: ConfigEntry):
new = {**config_entry.data}
try:
new.pop(CONF_POLL_INTERVAL)
new.pop(
CONF_OPTIONS
) # get rid of errorneously migrated options from integration 1.0
except: # pylint: disable=bare-except
# get rid of erroneously migrated options from integration 1.0
new.pop(CONF_OPTIONS)
except KeyError: # pylint: disable=bare-except
pass
config_entry.data = {**new}

@@ -158,6 +152,7 @@ async def async_migrate_entry(hass, config_entry: ConfigEntry):
return True


# noinspection PyUnusedLocal
async def async_setup(hass: HomeAssistant, hass_config: ConfigType):
"""Set up Exta Life component from configuration.yaml. This will basically
forward the config to a Config Flow and will migrate to Config Entry"""
@@ -190,10 +185,11 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry):
return await initialize(hass, config_entry)


# noinspection PyUnusedLocal
async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry):
"""Unload a config entry: unload platform entities, stored data, deregister signal listeners"""
core = Core.get(config_entry.entry_id)

core = Core.get(config_entry.entry_id)
await core.unload_entry_from_hass()

return True
@@ -202,8 +198,9 @@ async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry):
async def initialize(hass: HomeAssistant, config_entry: ConfigEntry):
"""Initialize Exta Life integration based on a Config Entry"""

def init_options(hass: HomeAssistant, config_entry: ConfigEntry):
def init_options():
"""Populate default options for Exta Life."""

default = get_default_options()
options = {**config_entry.options}
# migrate options after creation of ConfigEntry
@@ -222,16 +219,16 @@ def init_options(hass: HomeAssistant, config_entry: ConfigEntry):
for k, v in default.items():
options_def.setdefault(k, v)

# check for changes and if options should be peristed
# check for changes and if options should be persisted
if options_def != options or not config_entry.options:
hass.config_entries.async_update_entry(config_entry, options=options_def)

async def api_connect(user, password, host):
controller = Core.get(config_entry.entry_id).api
await controller.async_connect(user, password, host=host)
return controller
async def api_connect(conn_user, conn_password, conn_host):
result = Core.get(config_entry.entry_id).api
await result.async_connect(conn_user, conn_password, host=conn_host)
return result

init_options(hass, config_entry)
init_options()

controller = None

@@ -258,13 +255,15 @@ async def api_connect(user, password, host):
_LOGGER.debug(
"Connection exception: %s, class: %s", e.previous, e.previous.__class__
)
# invalid IP / IP changed? - try autodetection
# invalid IP / IP changed? - try auto-detection
if isinstance(e.previous, OSError) and e.previous.errno == 113:
_LOGGER.warning(
"Could not connect to EFC-01 on IP stored in configuration: %s. Trying to discover controller IP in the network",
"Could not connect to EFC-01 on IP stored in configuration: %s. "
"Trying to discover controller IP in the network",
controller_ip,
)
# controller = await hass.async_add_executor_job(api_connect, el_conf[CONF_USER], el_conf[CONF_PASSWORD], None)
# controller = await hass.async_add_executor_job(api_connect, el_conf[CONF_USER],
# el_conf[CONF_PASSWORD], None)
controller = await api_connect(
el_conf[CONF_USER], el_conf[CONF_PASSWORD], None
)
@@ -276,7 +275,7 @@ async def api_connect(user, password, host):
_LOGGER.info("Controller IP updated to: %s", controller.host)
else:
raise e
_LOGGER.debug("Connected to controller on IP: %s", controller.host)
_LOGGER.info("Connected to controller on IP: %s", controller.host)

sw_version = controller.sw_version

@@ -290,12 +289,12 @@ async def api_connect(user, password, host):

return False

except TCPConnError as e: # pylint: disable=invalid-name, unused-variable
except TCPConnError:
host = controller.host if (controller and controller.host) else "unknown"
_LOGGER.error("Could not connect to EFC-01 on IP: %s", host)

await core.unload_entry_from_hass()
raise ConfigEntryNotReady # pylint: disable=raise-missing-from
raise ConfigEntryNotReady from None

await core.register_controller()

@@ -328,8 +327,6 @@ def __init__(self, hass: HomeAssistant, config_entry: ConfigEntry) -> None:
self._poller_callback_remove = None
self._ping_callback_remove = None

return None

@property
def core(self):
return Core.get(self._config_entry.entry_id)
@@ -343,19 +340,19 @@ def on_notify(self, msg):
_LOGGER.debug("Received status change notification from controller: %s", msg)
data = msg.get("data")
channel = data.get("channel", "#")
chan_id = str(data.get("id")) + "-" + str(channel)
channel_id = str(data.get("id")) + "-" + str(channel)

# inform HA entity of state change via notification
signal = ExtaLifeChannel.get_notif_upd_signal(chan_id)
signal = ExtaLifeChannel.get_notif_upd_signal(channel_id)
if channel != "#":
self.core.async_signal_send(signal, data)
else:
self.core.async_signal_send_sync(signal, data)

def update_channel(self, id: str, data: dict): # pylint: disable=redefined-builtin
def update_channel(self, channel_id: str, channel_data: dict):
"""Update data of a channel e.g. after notification data received and processed
by an entity"""
self.channels_indx.update({id: data})
self.channels_indx.update({channel_id: channel_data})

async def async_start_polling(self, poll_now: bool):
"""Start cyclic status polling
@@ -434,7 +431,7 @@ async def async_discover_devices(self):
Fetch / refresh device data & discover devices and register them in Home Assistant.
"""

component_configs = {}
component_configs: dict = {}
other_configs = {}

# get data from the ChannelDataManager object stored in HA object data
@@ -503,8 +500,8 @@ async def async_discover_devices(self):
# Load discovered devices

if component_configs:
# can happen we don't have any sensors, so we need to put an empty list to trigger creation of virtual sensors (if any)
# for
# can happen we don't have any sensors, so we need to put an empty list to trigger
# creation of virtual sensors (if any) for
component_configs.setdefault(DOMAIN_SENSOR, [])

# sensors must be last as platforms will delegate their attributes to virtual sensors
@@ -572,7 +569,7 @@ async def async_will_remove_from_hass(self) -> None:

async def async_update_callback(self):
"""Inform HA of state update from status poller"""
_LOGGER.debug("Update callback for entty id: %s", self.entity_id)
_LOGGER.debug("Update callback for entity id: %s", self.entity_id)
self.async_schedule_update_ha_state(True)

async def async_state_notif_update_callback(self, *args):
@@ -587,7 +584,7 @@ async def async_state_notif_update_callback(self, *args):
self.on_state_notification(data)

def on_state_notification(self, data):
"""must be overriden in entity subclasses"""
"""must be overridden in entity subclasses"""

def get_unique_id(self):
"""Provide unique id for HA entity registry"""
@@ -651,7 +648,7 @@ def device_info(self):
}

@property
def name(self) -> Optional[str]:
def name(self) -> str | None:
"""Return name of the entity"""
return self.channel_data["alias"]

@@ -670,11 +667,10 @@ async def async_action(self, action, **add_pars):
)

try:
resp = await self.controller.async_execute_action(
action, self.channel_id, **add_pars
)
resp = await self.controller.async_execute_action(action, self.channel_id, **add_pars)
except TCPConnError as err:
_LOGGER.error(err.data)
return None

return resp

@@ -692,7 +688,7 @@ def available(self):
is_timeout,
)

return self.data_available == True and is_timeout == False
return self.data_available is True and is_timeout is False

async def async_update(self):
"""Call to update state."""
@@ -725,8 +721,8 @@ def sync_data_update_ha(self):
self.async_schedule_update_ha_state(True)

@property
def extra_state_attributes(self):
""" " Return state atributes"""
def extra_state_attributes(self) -> dict[str, Any]:
""" " Return state attributes"""
return {
"channel_id": self.channel_id,
"not_responding": self.channel_data.get("is_timeout"),
@@ -739,21 +735,21 @@ def virtual_sensors(self) -> list:
return []

def _get_virtual_sensors(self) -> list:
"""By default check all entity attributes and return virtual sensor config"""
"""By default, check all entity attributes and return virtual sensor config"""
from .sensor import MAP_EXTA_ATTRIBUTE_TO_DEV_CLASS

attr = []
for k, v in self.channel_data.items(): # pylint: disable=unused-variable
dev_class = MAP_EXTA_ATTRIBUTE_TO_DEV_CLASS.get(k)
if dev_class:

if not self.is_virt_sensor_allowed(k):
if not self.is_virtual_sensor_allowed(k):
continue

attr.append(
{
VIRT_SENSOR_DEV_CLS: dev_class,
VIRT_SENSOR_PATH: k
VIRTUAL_SENSOR_DEV_CLS: dev_class,
VIRTUAL_SENSOR_PATH: k
}
)

@@ -764,15 +760,15 @@ def _get_virtual_sensors(self) -> list:

return attr

def is_virt_sensor_allowed(self, attr_name: str):
def is_virtual_sensor_allowed(self, attr_name: str):
"""Check if virtual sensor should be created for an attribute based on settings"""
from .sensor import VIRTUAL_SENSOR_RESTRICTIONS

channel = self.channel_data.get("channel")
restr = VIRTUAL_SENSOR_RESTRICTIONS.get(attr_name)

if restr:
if not (channel in restr.get(VIRT_SENSOR_ALLOWED_CHANNELS)):
if not (channel in restr.get(VIRTUAL_SENSOR_ALLOWED_CHANNELS)):
return False

return True
@@ -786,14 +782,15 @@ def push_virtual_sensor_channels(self, virtual_sensor_domain: str, channel_data:
_LOGGER.debug("Virtual sensors: %s", virtual_sensors)
for virtual in virtual_sensors:
v_channel_data = channel_data.copy()
v_channel_data.update({VIRT_SENSOR_CHN_FIELD: virtual})
v_channel_data.update({VIRTUAL_SENSOR_CHN_FIELD: virtual})
self.core.push_channels(
virtual_sensor_domain, v_channel_data, append=True, custom=True
)

def format_state_attr(self, attr: dict):
"""Format state atteibutes based on name and other criteria.
Can be overriden in dedicated subclasses to refine formatiing"""
@staticmethod
def format_state_attr(attr: dict):
"""Format state attributes based on name and other criteria.
Can be overridden in dedicated subclasses to refine formatting"""
from re import search

for k, v in attr.items():
@@ -889,7 +886,7 @@ def device_info(self):
}

@property
def name(self) -> Optional[str]:
def name(self) -> str | None:
"""Return name of the entity"""
return self.api.name

@@ -918,7 +915,7 @@ def extra_state_attributes(self):
{
"type": "gateway",
"mac_address": self.mac,
"ipv4_addres:": self.api.host,
"ipv4_address:": self.api.host,
"software_version": self.api.sw_version,
"name": self.api.name,
}
Loading