From af5974de223a286c951de1336d14c59ff4340ff6 Mon Sep 17 00:00:00 2001 From: Geert Meersman Date: Thu, 24 Aug 2023 11:25:36 +0200 Subject: [PATCH] fix: _LOGGER improvement and device_class fix --- custom_components/nexxtmove/__init__.py | 21 ++++------ custom_components/nexxtmove/client.py | 49 ++++++++++++---------- custom_components/nexxtmove/config_flow.py | 42 ++++++++----------- custom_components/nexxtmove/const.py | 5 +-- custom_components/nexxtmove/entity.py | 14 ++----- custom_components/nexxtmove/sensor.py | 28 +++++-------- custom_components/nexxtmove/switch.py | 18 ++++---- custom_components/nexxtmove/utils.py | 13 ------ 8 files changed, 76 insertions(+), 114 deletions(-) diff --git a/custom_components/nexxtmove/__init__.py b/custom_components/nexxtmove/__init__.py index bfaf832..855731f 100644 --- a/custom_components/nexxtmove/__init__.py +++ b/custom_components/nexxtmove/__init__.py @@ -2,23 +2,16 @@ from __future__ import annotations from homeassistant.config_entries import ConfigEntry -from homeassistant.const import CONF_PASSWORD -from homeassistant.const import CONF_USERNAME +from homeassistant.const import CONF_PASSWORD, CONF_USERNAME from homeassistant.core import HomeAssistant from homeassistant.helpers import device_registry as dr -from homeassistant.helpers.update_coordinator import DataUpdateCoordinator -from homeassistant.helpers.update_coordinator import UpdateFailed +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed from requests.exceptions import ConnectionError from .client import NexxtmoveClient -from .const import _LOGGER -from .const import COORDINATOR_UPDATE_INTERVAL -from .const import DOMAIN -from .const import PLATFORMS -from .exceptions import NexxtmoveException -from .exceptions import NexxtmoveServiceException +from .const import _LOGGER, COORDINATOR_UPDATE_INTERVAL, DOMAIN, PLATFORMS +from .exceptions import NexxtmoveException, NexxtmoveServiceException from .models import NexxtmoveItem -from .utils import log_debug async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: @@ -102,7 +95,7 @@ async def _async_update_data(self) -> dict | None: if len(items) > 0: fetched_items = {str(items[item].device_key) for item in items} - log_debug( + _LOGGER.debug( f"[init|NexxtmoveDataUpdateCoordinator|_async_update_data|fetched_items] {fetched_items}" ) if stale_items := current_items - fetched_items: @@ -110,7 +103,7 @@ async def _async_update_data(self) -> dict | None: if device := self._device_registry.async_get_device( {(DOMAIN, device_key)} ): - log_debug( + _LOGGER.debug( f"[init|NexxtmoveDataUpdateCoordinator|_async_update_data|async_remove_device] {device_key}", True, ) @@ -121,7 +114,7 @@ async def _async_update_data(self) -> dict | None: if self.data and fetched_items - { str(self.data[item].device_key) for item in self.data }: - # log_debug(f"[init|NexxtmoveDataUpdateCoordinator|_async_update_data|async_reload] {product.product_name}") + # _LOGGER.debug(f"[init|NexxtmoveDataUpdateCoordinator|_async_update_data|async_reload] {product.product_name}") self.hass.async_create_task( self.hass.config_entries.async_reload(self._config_entry_id) ) diff --git a/custom_components/nexxtmove/client.py b/custom_components/nexxtmove/client.py index 55eb3c9..e60203f 100644 --- a/custom_components/nexxtmove/client.py +++ b/custom_components/nexxtmove/client.py @@ -7,6 +7,7 @@ from requests import Session from .const import ( + _LOGGER, BASE_HEADERS, CONNECTION_RETRY, DEFAULT_NEXXTMOVE_ENVIRONMENT, @@ -15,7 +16,7 @@ ) from .exceptions import BadCredentialsException, NexxtmoveServiceException from .models import NexxtmoveEnvironment, NexxtmoveItem -from .utils import format_entity_name, log_debug +from .utils import format_entity_name class NexxtmoveClient: @@ -54,25 +55,25 @@ def request( ) -> dict: """Send a request to Nexxtmove.""" if data is None: - log_debug(f"{caller} Calling GET {url}") + _LOGGER.debug(f"{caller} Calling GET {url}") response = self.session.get( url, timeout=REQUEST_TIMEOUT, headers=self._headers | {"Authorize": f"Token {self.token}"}, ) else: - log_debug(f"{caller} Calling POST {url} with {data}") + _LOGGER.debug(f"{caller} Calling POST {url} with {data}") response = self.session.post( url, json=data, timeout=REQUEST_TIMEOUT, headers=self._headers | {"Authorize": f"Token {self.token}"}, ) - log_debug( + _LOGGER.debug( f"{caller} http status code = {response.status_code} (expecting {expected})" ) if log: - log_debug(f"{caller} Response:\n{response.text}") + _LOGGER.debug(f"{caller} Response:\n{response.text}") if expected is not None and response.status_code != expected: if response.status_code == 404: self.request_error = response.json() @@ -87,7 +88,7 @@ def request( raise NexxtmoveServiceException( f"[{caller}] Expecting HTTP {expected} | Response HTTP {response.status_code}, Response: {response.text}, Url: {response.url}" ) - log_debug( + _LOGGER.debug( f"[NexxtmoveClient|request] Received a HTTP {response.status_code}, nothing to worry about! We give it another try :-)" ) self.login() @@ -99,7 +100,7 @@ def request( def login(self) -> dict: """Start a new Nexxtmove session with a user & password.""" - log_debug("[NexxtmoveClient|login|start]") + _LOGGER.debug("[NexxtmoveClient|login|start]") """Login process""" if self.username is None or self.password is None: return False @@ -112,7 +113,7 @@ def login(self) -> dict: result = response.json() try: self.token = result.get("authToken") - log_debug(f"Setting Token {self.token}") + _LOGGER.debug(f"Setting Token {self.token}") self.profile = result.get("profile") except Exception as exception: raise BadCredentialsException( @@ -472,7 +473,7 @@ def fetch_data(self): """ tokens = self.charging_device_tokens(charging_device_id) - log_debug(f"Charging tokens: {tokens}", True) + _LOGGER.debug(f"Charging tokens: {tokens}", True) #Switch will be used when it becomes useful key = format_entity_name( @@ -586,7 +587,7 @@ def price_to_ISO4217(self, unit): def company(self): """Fetch Company info.""" - log_debug("[NexxtmoveClient|company] Fetching company info from Nexxtmove") + _LOGGER.debug("[NexxtmoveClient|company] Fetching company info from Nexxtmove") response = self.request( f"{self.environment.api_endpoint}/company", "[NexxtmoveClient|company]", @@ -599,7 +600,9 @@ def company(self): def device_list(self): """Fetch Device list.""" - log_debug("[NexxtmoveClient|device_list] Fetching device list from Nexxtmove") + _LOGGER.debug( + "[NexxtmoveClient|device_list] Fetching device list from Nexxtmove" + ) response = self.request( f"{self.environment.api_endpoint}/device/list", "[NexxtmoveClient|device_list]", @@ -612,7 +615,7 @@ def device_list(self): def device_events(self, device_id): """Fetch Device events.""" - log_debug( + _LOGGER.debug( "[NexxtmoveClient|device_events] Fetching device events from Nexxtmove" ) response = self.request( @@ -627,7 +630,7 @@ def device_events(self, device_id): def device_pin(self, device_id): """Fetch Device pin.""" - log_debug("[NexxtmoveClient|device_pin] Fetching device pin from Nexxtmove") + _LOGGER.debug("[NexxtmoveClient|device_pin] Fetching device pin from Nexxtmove") response = self.request( f"{self.environment.api_endpoint}/device/{device_id}/pin", "[NexxtmoveClient|device_pin]", @@ -640,7 +643,7 @@ def device_pin(self, device_id): def charging_device_tokens(self, device_id): """Fetch Device tokens.""" - log_debug( + _LOGGER.debug( "[NexxtmoveClient|charging_device_tokens] Fetching device tokens from Nexxtmove" ) response = self.request( @@ -655,7 +658,7 @@ def charging_device_tokens(self, device_id): def charging_device_graph(self, device_id, start_date, end_date): """Fetch Charging graph data.""" - log_debug( + _LOGGER.debug( "[NexxtmoveClient|charging_device_graph] Fetching charging graph data from Nexxtmove" ) response = self.request( @@ -670,7 +673,7 @@ def charging_device_graph(self, device_id, start_date, end_date): def charging_point(self, charging_point_id): """Fetch Charging point info.""" - log_debug( + _LOGGER.debug( "[NexxtmoveClient|charging_point] Fetching charging point info from Nexxtmove" ) response = self.request( @@ -685,7 +688,7 @@ def charging_point(self, charging_point_id): def charging_point_events(self, device_id): """Fetch charging point events.""" - log_debug( + _LOGGER.debug( "[NexxtmoveClient|charging_point_events] Fetching charging point events from Nexxtmove" ) response = self.request( @@ -700,7 +703,7 @@ def charging_point_events(self, device_id): def charge_latest(self): """Fetch charges.""" - log_debug("[NexxtmoveClient|charge_latest] Fetching charges from Nexxtmove") + _LOGGER.debug("[NexxtmoveClient|charge_latest] Fetching charges from Nexxtmove") response = self.request( f"{self.environment.api_endpoint}/charge/latest?maxRows=200&offset=0", "[NexxtmoveClient|charge_latest]", @@ -713,7 +716,9 @@ def charge_latest(self): def consumption(self): """Fetch consumption.""" - log_debug("[NexxtmoveClient|consumption] Fetching consumption from Nexxtmove") + _LOGGER.debug( + "[NexxtmoveClient|consumption] Fetching consumption from Nexxtmove" + ) response = self.request( f"{self.environment.api_endpoint}/charge/consumption", "[NexxtmoveClient|consumption]", @@ -726,7 +731,7 @@ def consumption(self): def charges(self): """Fetch charges.""" - log_debug("[NexxtmoveClient|charges] Fetching charges from Nexxtmove") + _LOGGER.debug("[NexxtmoveClient|charges] Fetching charges from Nexxtmove") response = self.request( f"{self.environment.api_endpoint}/charge/current?maxRows=100&offset=0", "[NexxtmoveClient|charges]", @@ -739,7 +744,7 @@ def charges(self): def residential_buildings(self): """Fetch residential buildings.""" - log_debug( + _LOGGER.debug( "[NexxtmoveClient|residential_buildings] Fetching residential buildings from Nexxtmove" ) response = self.request( @@ -754,7 +759,7 @@ def residential_buildings(self): def work_buildings(self): """Fetch work buildings.""" - log_debug( + _LOGGER.debug( "[NexxtmoveClient|work_buildings] Fetching work buildings from Nexxtmove" ) response = self.request( diff --git a/custom_components/nexxtmove/config_flow.py b/custom_components/nexxtmove/config_flow.py index 735156c..27e373a 100644 --- a/custom_components/nexxtmove/config_flow.py +++ b/custom_components/nexxtmove/config_flow.py @@ -1,30 +1,24 @@ """Config flow to configure the Nexxtmove integration.""" -from abc import ABC -from abc import abstractmethod +from abc import ABC, abstractmethod from typing import Any -import homeassistant.helpers.config_validation as cv -import voluptuous as vol -from homeassistant.config_entries import ConfigEntry -from homeassistant.config_entries import ConfigFlow -from homeassistant.config_entries import OptionsFlow -from homeassistant.const import CONF_PASSWORD -from homeassistant.const import CONF_USERNAME +from homeassistant.config_entries import ConfigEntry, ConfigFlow, OptionsFlow +from homeassistant.const import CONF_PASSWORD, CONF_USERNAME from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowHandler -from homeassistant.data_entry_flow import FlowResult -from homeassistant.helpers.selector import TextSelector -from homeassistant.helpers.selector import TextSelectorConfig -from homeassistant.helpers.selector import TextSelectorType +from homeassistant.data_entry_flow import FlowHandler, FlowResult +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.selector import ( + TextSelector, + TextSelectorConfig, + TextSelectorType, +) from homeassistant.helpers.typing import UNDEFINED +import voluptuous as vol from .client import NexxtmoveClient -from .const import DOMAIN -from .const import NAME -from .exceptions import BadCredentialsException -from .exceptions import NexxtmoveServiceException +from .const import _LOGGER, DOMAIN, NAME +from .exceptions import BadCredentialsException, NexxtmoveServiceException from .models import NexxtmoveConfigEntryData -from .utils import log_debug DEFAULT_ENTRY_DATA = NexxtmoveConfigEntryData( username=None, @@ -70,7 +64,7 @@ async def async_step_connection_init( if user_input is not None: user_input = self.new_data() | user_input test = await self.test_connection(user_input) - log_debug(f"Test result config flow: {test}") + _LOGGER.debug(f"Test result config flow: {test}") if not test["errors"]: self.new_title = test["profile"].get("username") self.new_entry_data |= user_input @@ -78,7 +72,7 @@ async def async_step_connection_init( f"{DOMAIN}_" + test["profile"].get("username") ) self._abort_if_unique_id_configured() - log_debug(f"New account {self.new_title} added") + _LOGGER.debug(f"New account {self.new_title} added") return self.finish_flow() errors = test["errors"] fields = { @@ -108,7 +102,7 @@ async def test_connection(self, user_input: dict | None = None) -> dict: profile = await self.async_validate_input(user_input) except AssertionError as exception: errors["base"] = "cannot_connect" - log_debug(f"[async_step_password|login] AssertionError {exception}") + _LOGGER.debug(f"[async_step_password|login] AssertionError {exception}") except ConnectionError: errors["base"] = "cannot_connect" except NexxtmoveServiceException: @@ -117,7 +111,7 @@ async def test_connection(self, user_input: dict | None = None) -> dict: errors["base"] = "invalid_auth" except Exception as exception: errors["base"] = "unknown" - log_debug(exception) + _LOGGER.debug(exception) return {"profile": profile, "errors": errors} async def async_step_password(self, user_input: dict | None = None) -> FlowResult: @@ -131,7 +125,7 @@ async def async_step_password(self, user_input: dict | None = None) -> FlowResul self.new_entry_data |= NexxtmoveConfigEntryData( password=user_input[CONF_PASSWORD], ) - log_debug(f"Password changed for {user_input[CONF_USERNAME]}") + _LOGGER.debug(f"Password changed for {user_input[CONF_USERNAME]}") return self.finish_flow() fields = { diff --git a/custom_components/nexxtmove/const.py b/custom_components/nexxtmove/const.py index 2e2a82e..80415ff 100644 --- a/custom_components/nexxtmove/const.py +++ b/custom_components/nexxtmove/const.py @@ -1,7 +1,7 @@ """Constants used by Nexxtmove.""" +from datetime import timedelta import json import logging -from datetime import timedelta from pathlib import Path from typing import Final @@ -9,8 +9,6 @@ from .models import NexxtmoveEnvironment -SHOW_DEBUG_AS_WARNING = False - _LOGGER = logging.getLogger(__name__) PLATFORMS: Final = [Platform.SENSOR, Platform.SWITCH] @@ -30,6 +28,7 @@ } GRAPH_START_DATE = "20220101" +_LOGGER = logging.getLogger(__name__) COORDINATOR_UPDATE_INTERVAL = timedelta(minutes=5) CONNECTION_RETRY = 5 diff --git a/custom_components/nexxtmove/entity.py b/custom_components/nexxtmove/entity.py index c7d5220..f787b88 100644 --- a/custom_components/nexxtmove/entity.py +++ b/custom_components/nexxtmove/entity.py @@ -5,18 +5,12 @@ from homeassistant.core import callback from homeassistant.helpers.device_registry import DeviceEntryType -from homeassistant.helpers.entity import DeviceInfo -from homeassistant.helpers.entity import EntityDescription +from homeassistant.helpers.entity import DeviceInfo, EntityDescription from homeassistant.helpers.update_coordinator import CoordinatorEntity from . import NexxtmoveDataUpdateCoordinator -from .const import ATTRIBUTION -from .const import DOMAIN -from .const import NAME -from .const import VERSION -from .const import WEBSITE +from .const import _LOGGER, ATTRIBUTION, DOMAIN, NAME, VERSION, WEBSITE from .models import NexxtmoveItem -from .utils import log_debug class NexxtmoveEntity(CoordinatorEntity[NexxtmoveDataUpdateCoordinator]): @@ -52,7 +46,7 @@ def __init__( self.last_synced = datetime.now() self._attr_name = f"{self.item.name}".capitalize() self._item = item - log_debug(f"[NexxtmoveEntity|init] {self._key}") + _LOGGER.debug(f"[NexxtmoveEntity|init] {self._key}") @callback def _handle_coordinator_update(self) -> None: @@ -65,7 +59,7 @@ def _handle_coordinator_update(self) -> None: self._item = item self.async_write_ha_state() return - log_debug( + _LOGGER.debug( f"[NexxtmoveEntity|_handle_coordinator_update] {self._attr_unique_id}: async_write_ha_state ignored since API fetch failed or not found", True, ) diff --git a/custom_components/nexxtmove/sensor.py b/custom_components/nexxtmove/sensor.py index 0184617..18ee29e 100644 --- a/custom_components/nexxtmove/sensor.py +++ b/custom_components/nexxtmove/sensor.py @@ -2,6 +2,7 @@ from __future__ import annotations from collections.abc import Callable +import copy from dataclasses import dataclass from typing import Any @@ -19,10 +20,9 @@ from homeassistant.helpers.typing import StateType from . import NexxtmoveDataUpdateCoordinator -from .const import DOMAIN +from .const import _LOGGER, DOMAIN from .entity import NexxtmoveEntity from .models import NexxtmoveItem -from .utils import log_debug @dataclass @@ -54,6 +54,7 @@ class NexxtmoveSensorDescription(SensorEntityDescription): NexxtmoveSensorDescription( key="totalEnergyWh", device_class=SensorDeviceClass.ENERGY, + state_class=SensorStateClass.TOTAL, native_unit_of_measurement=UnitOfEnergy.WATT_HOUR, suggested_display_precision=1, icon="mdi:gauge", @@ -78,7 +79,6 @@ class NexxtmoveSensorDescription(SensorEntityDescription): NexxtmoveSensorDescription( key="pricekwh", icon="mdi:currency-eur", - state_class=SensorStateClass.MEASUREMENT, device_class=SensorDeviceClass.MONETARY, ), NexxtmoveSensorDescription(key="residential_location", icon="mdi:home"), @@ -92,7 +92,7 @@ async def async_setup_entry( async_add_entities: AddEntitiesCallback, ) -> None: """Set up the Nexxtmove sensors.""" - log_debug("[sensor|async_setup_entry|async_add_entities|start]") + _LOGGER.debug("[sensor|async_setup_entry|async_add_entities|start]") coordinator: NexxtmoveDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] entities: list[NexxtmoveSensor] = [] @@ -100,28 +100,20 @@ async def async_setup_entry( description.key: description for description in SENSOR_DESCRIPTIONS } - # log_debug(f"[sensor|async_setup_entry|async_add_entities|SUPPORTED_KEYS] {SUPPORTED_KEYS}") + # _LOGGER.debug(f"[sensor|async_setup_entry|async_add_entities|SUPPORTED_KEYS] {SUPPORTED_KEYS}") if coordinator.data is not None: for item in coordinator.data: item = coordinator.data[item] if item.sensor_type == "sensor": if description := SUPPORTED_KEYS.get(item.type): + sensor_description = copy.deepcopy(description) if item.native_unit_of_measurement is not None: - native_unit_of_measurement = item.native_unit_of_measurement - else: - native_unit_of_measurement = ( - description.native_unit_of_measurement + sensor_description.native_unit_of_measurement = ( + item.native_unit_of_measurement ) - sensor_description = NexxtmoveSensorDescription( - key=str(item.key), - name=item.name, - value_fn=description.value_fn, - native_unit_of_measurement=native_unit_of_measurement, - icon=description.icon, - ) - log_debug(f"[sensor|async_setup_entry|adding] {item.name}") + _LOGGER.debug(f"[sensor|async_setup_entry|adding] {item.name}") entities.append( NexxtmoveSensor( coordinator=coordinator, @@ -130,7 +122,7 @@ async def async_setup_entry( ) ) else: - log_debug( + _LOGGER.debug( f"[sensor|async_setup_entry|no support type found] {item.name}, type: {item.type}, keys: {SUPPORTED_KEYS.get(item.type)}", True, ) diff --git a/custom_components/nexxtmove/switch.py b/custom_components/nexxtmove/switch.py index d3a6def..de0c476 100644 --- a/custom_components/nexxtmove/switch.py +++ b/custom_components/nexxtmove/switch.py @@ -5,8 +5,7 @@ from dataclasses import dataclass from typing import Any -from homeassistant.components.switch import SwitchEntity -from homeassistant.components.switch import SwitchEntityDescription +from homeassistant.components.switch import SwitchEntity, SwitchEntityDescription from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.helpers.entity import EntityDescription @@ -14,10 +13,9 @@ from homeassistant.helpers.typing import StateType from . import NexxtmoveDataUpdateCoordinator -from .const import DOMAIN +from .const import _LOGGER, DOMAIN from .entity import NexxtmoveEntity from .models import NexxtmoveItem -from .utils import log_debug @dataclass @@ -38,7 +36,7 @@ async def async_setup_entry( async_add_entities: AddEntitiesCallback, ) -> None: """Set up the Nexxtmove switches.""" - log_debug("[switch|async_setup_entry|async_add_entities|start]") + _LOGGER.debug("[switch|async_setup_entry|async_add_entities|start]") coordinator: NexxtmoveDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] entities: list[NexxtmoveSwitch] = [] @@ -46,7 +44,7 @@ async def async_setup_entry( description.key: description for description in SENSOR_DESCRIPTIONS } - # log_debug(f"[switch|async_setup_entry|async_add_entities|SUPPORTED_KEYS] {SUPPORTED_KEYS}") + # _LOGGER.debug(f"[switch|async_setup_entry|async_add_entities|SUPPORTED_KEYS] {SUPPORTED_KEYS}") if coordinator.data is not None: for item in coordinator.data: @@ -59,7 +57,7 @@ async def async_setup_entry( icon=description.icon, ) - log_debug(f"[switch|async_setup_entry|adding] {item.name}") + _LOGGER.debug(f"[switch|async_setup_entry|adding] {item.name}") entities.append( NexxtmoveSwitch( coordinator=coordinator, @@ -68,7 +66,7 @@ async def async_setup_entry( ) ) else: - log_debug( + _LOGGER.debug( f"[switch|async_setup_entry|no support type found] {item.name}, type: {item.type}, keys: {SUPPORTED_KEYS.get(item.type)}", True, ) @@ -124,8 +122,8 @@ def is_on(self) -> bool | None: async def async_turn_on(self, **kwargs: Any) -> None: """Turn the entity on.""" - log_debug(f"Turning {self.item.name} on") + _LOGGER.debug(f"Turning {self.item.name} on") async def async_turn_off(self, **kwargs: Any) -> None: """Turn the entity off.""" - log_debug(f"Turning {self.item.name} off") + _LOGGER.debug(f"Turning {self.item.name} off") diff --git a/custom_components/nexxtmove/utils.py b/custom_components/nexxtmove/utils.py index f41ffd9..cbf745c 100644 --- a/custom_components/nexxtmove/utils.py +++ b/custom_components/nexxtmove/utils.py @@ -1,23 +1,10 @@ """Nexxtmove utils.""" from __future__ import annotations -import logging import re from jsonpath import jsonpath -from .const import SHOW_DEBUG_AS_WARNING - -_LOGGER = logging.getLogger(__name__) - - -def log_debug(input, force=False) -> None: - """Log to logger as debug or force as warning.""" - if SHOW_DEBUG_AS_WARNING is True or force is True: - _LOGGER.warning(input) - else: - _LOGGER.debug(input) - def str_to_float(input) -> float: """Transform float to string."""