diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..618347a --- /dev/null +++ b/.gitignore @@ -0,0 +1,236 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + + +IntelliJ IDEA +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# AWS User-specific +.idea/**/aws.xml + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# SonarLint plugin +.idea/sonarlint/ + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +# Home Assistant local development folder +hass/ \ No newline at end of file diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/hass-tns-energo.iml b/.idea/hass-tns-energo.iml new file mode 100644 index 0000000..febadb2 --- /dev/null +++ b/.idea/hass-tns-energo.iml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..05773f6 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..cd666f9 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..1f89c1a --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,13 @@ + + + + + + + + + \ No newline at end of file diff --git a/custom_components/tns_energo/_base.py b/custom_components/tns_energo/_base.py index 4d536f3..f9b5e94 100644 --- a/custom_components/tns_energo/_base.py +++ b/custom_components/tns_energo/_base.py @@ -301,7 +301,6 @@ def __missing__(self, key: str): _TData = TypeVar("_TData") _TAccount = TypeVar("_TAccount", bound="Account") - SupportedServicesType = Mapping[ Optional[Tuple[type, SupportsInt]], Mapping[str, Union[dict, Callable[[dict], dict]]], @@ -313,6 +312,8 @@ class TNSEnergoEntity(Entity, Generic[_TAccount]): _supported_services: ClassVar[SupportedServicesType] = {} + _attr_should_poll = False + @property def entity_id_prefix(self) -> str: return f"tns_{self._account.api.region}_{self._account.code}" @@ -391,14 +392,6 @@ def name_format(self) -> str: # Base overrides ################################################################################# - @property - def should_poll(self) -> bool: - """Return True if entity has to be polled for state. - - False if entity pushes its state to HA. - """ - return False - @property def device_state_attributes(self): """Return the attribute(s) of the sensor""" @@ -561,11 +554,6 @@ def code(self) -> str: def state(self) -> StateType: raise NotImplementedError - @property - @abstractmethod - def icon(self) -> str: - raise NotImplementedError - @property @abstractmethod def sensor_related_attributes(self) -> Optional[Mapping[str, Any]]: @@ -581,11 +569,6 @@ def name_format_values(self) -> Mapping[str, Any]: def unique_id(self) -> str: raise NotImplementedError - @property - @abstractmethod - def device_class(self) -> Optional[str]: - raise NotImplementedError - def register_supported_services(self, for_object: Optional[Any] = None) -> None: for type_feature, services in self._supported_services.items(): result, features = ( diff --git a/custom_components/tns_energo/sensor.py b/custom_components/tns_energo/sensor.py index a071d4f..3c83bce 100644 --- a/custom_components/tns_energo/sensor.py +++ b/custom_components/tns_energo/sensor.py @@ -4,6 +4,7 @@ """ import logging import re +from abc import ABC from datetime import datetime from typing import ( Any, @@ -22,6 +23,7 @@ import homeassistant.helpers.config_validation as cv import voluptuous as vol +from homeassistant.components.sensor import SensorEntity from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( ATTR_ENTITY_ID, @@ -79,7 +81,6 @@ RE_HTML_TAGS = re.compile(r"<[^<]+?>") RE_MULTI_SPACES = re.compile(r"\s{2,}") - INDICATIONS_MAPPING_SCHEMA = vol.Schema( { vol.Required(vol.Match(r"t\d+")): cv.positive_float, @@ -91,7 +92,6 @@ lambda x: dict(map(lambda y: ("t" + str(y[0]), y[1]), enumerate(x, start=1))), ) - CALCULATE_PUSH_INDICATIONS_SCHEMA = vol.All( cv.make_entity_service_schema( { @@ -145,11 +145,19 @@ def get_supported_features(from_services: SupportedServicesType, for_object: Any ATTR_METER_CODES: Final = "meter_codes" -class TNSEnergoAccount(TNSEnergoEntity): +class TNSEnergoSensor(TNSEnergoEntity, SensorEntity, ABC): + pass + + +class TNSEnergoAccount(TNSEnergoSensor): """The class for this sensor""" config_key: ClassVar[str] = CONF_ACCOUNTS + _attr_unit_of_measurement = "руб." + _attr_icon = "mdi:lightning-bolt-circle" + _attr_device_class = DOMAIN + "_account" + _supported_services: ClassVar[SupportedServicesType] = { None: { SERVICE_GET_PAYMENTS: _SERVICE_SCHEMA_BASE_DATED, @@ -165,10 +173,6 @@ def __init__(self, *args, **kwargs) -> None: def code(self) -> str: return self._account.code - @property - def device_class(self) -> Optional[str]: - return DOMAIN + "_account" - @property def unique_id(self) -> str: """Return the unique ID of the sensor""" @@ -184,14 +188,6 @@ def state(self) -> Union[float, str]: return 0.0 return balance - @property - def icon(self) -> str: - return "mdi:lightning-bolt-circle" - - @property - def unit_of_measurement(self) -> Optional[str]: - return "руб." - @property def sensor_related_attributes(self) -> Optional[Mapping[str, Any]]: account = self._account @@ -322,11 +318,14 @@ async def async_service_get_payments(self, **call_data): _LOGGER.info(self.log_prefix + "Finish handling payments retrieval") -class TNSEnergoMeter(TNSEnergoEntity): +class TNSEnergoMeter(TNSEnergoSensor): """The class for this sensor""" config_key: ClassVar[str] = CONF_METERS + _attr_icon = "mdi:counter" + _attr_device_class = DOMAIN + "_meter" + _supported_services: ClassVar[SupportedServicesType] = { None: { SERVICE_PUSH_INDICATIONS: SERVICE_PUSH_INDICATIONS_SCHEMA, @@ -405,14 +404,6 @@ def unique_id(self) -> str: def state(self) -> str: return self._meter.status or STATE_OK - @property - def icon(self): - return "mdi:counter" - - @property - def device_class(self) -> Optional[str]: - return DOMAIN + "_meter" - @property def sensor_related_attributes(self) -> Optional[Mapping[str, Any]]: meter = self._meter @@ -569,9 +560,13 @@ async def async_service_get_indications(self, **call_data): _LOGGER.info(self.log_prefix + "Finish handling indications retrieval") -class TNSEnergoLastPayment(TNSEnergoEntity): +class TNSEnergoLastPayment(TNSEnergoSensor): config_key: ClassVar[str] = CONF_LAST_PAYMENT + _attr_unit_of_measurement = "руб." + _attr_icon = "mdi:cash-multiple" + _attr_device_class = DOMAIN + "_payment" + def __init__(self, *args, last_payment: Optional[Payment] = None, **kwargs) -> None: super().__init__(*args, **kwargs) self._last_payment = last_payment @@ -623,14 +618,6 @@ def state(self) -> StateType: return self._last_payment.amount - @property - def unit_of_measurement(self) -> str: - return "руб." - - @property - def icon(self) -> str: - return "mdi:cash-multiple" - @property def sensor_related_attributes(self) -> Optional[Mapping[str, Any]]: payment = self._last_payment @@ -664,10 +651,6 @@ def unique_id(self) -> str: acc = self._account return f"{acc.api.__class__.__name__}_lastpayment_{acc.code}" - @property - def device_class(self) -> Optional[str]: - return DOMAIN + "_payment" - async_setup_entry = make_common_async_setup_entry( TNSEnergoAccount, diff --git a/hacs.json b/hacs.json index 9b99733..1fc464a 100644 --- a/hacs.json +++ b/hacs.json @@ -7,6 +7,6 @@ "sensor" ], "country": "ru", - "homeassistant": "2021.4.6", + "homeassistant": "2022.3.0", "iot_class": "Cloud Polling" } \ No newline at end of file