From 194a43aafe31048c86a3d4c8958acc583ff42b7e Mon Sep 17 00:00:00 2001 From: Elad Bar Date: Tue, 13 Feb 2024 13:12:47 +0200 Subject: [PATCH 01/25] Initial commit for v2 --- custom_components/hpprinter/__init__.py | 77 +-- .../hpprinter/api/HPPrinterAPI.py | 286 ----------- custom_components/hpprinter/api/__init__.py | 7 - custom_components/hpprinter/binary_sensor.py | 86 ++-- .../hpprinter/common/__init__.py | 0 .../hpprinter/common/base_entity.py | 111 +++++ custom_components/hpprinter/common/consts.py | 41 ++ .../hpprinter/common/entity_descriptions.py | 277 +++++++++++ custom_components/hpprinter/config_flow.py | 110 +---- .../hpprinter/data/data_points.json | 140 ++++++ .../hpprinter/data/endpoint_validations.json | 18 + custom_components/hpprinter/diagnostics.py | 108 +++++ .../hpprinter/helpers/__init__.py | 53 --- custom_components/hpprinter/helpers/const.py | 151 ------ .../hpprinter/managers/HPDeviceData.py | 445 ------------------ .../hpprinter/managers/config_flow_manager.py | 189 -------- .../managers/configuration_manager.py | 41 -- .../hpprinter/managers/device_manager.py | 87 ---- .../hpprinter/managers/entity_manager.py | 361 -------------- .../hpprinter/managers/flow_manager.py | 104 ++++ .../hpprinter/managers/ha_config_manager.py | 270 +++++++++++ .../hpprinter/managers/ha_coordinator.py | 311 ++++++++++++ .../hpprinter/managers/home_assistant.py | 220 --------- .../hpprinter/managers/rest_api.py | 354 ++++++++++++++ .../hpprinter/managers/storage_manager.py | 49 -- custom_components/hpprinter/manifest.json | 4 +- .../hpprinter/models/base_entity.py | 153 ------ .../hpprinter/models/config_data.py | 78 +-- .../hpprinter/models/entity_data.py | 52 -- .../hpprinter/models/exceptions.py | 10 + custom_components/hpprinter/sensor.py | 90 ++-- custom_components/hpprinter/strings.json | 12 +- requirements.txt | 11 +- tests/api_test.py | 66 +++ 34 files changed, 2003 insertions(+), 2369 deletions(-) delete mode 100644 custom_components/hpprinter/api/HPPrinterAPI.py delete mode 100644 custom_components/hpprinter/api/__init__.py create mode 100644 custom_components/hpprinter/common/__init__.py create mode 100644 custom_components/hpprinter/common/base_entity.py create mode 100644 custom_components/hpprinter/common/consts.py create mode 100644 custom_components/hpprinter/common/entity_descriptions.py create mode 100644 custom_components/hpprinter/data/data_points.json create mode 100644 custom_components/hpprinter/data/endpoint_validations.json create mode 100644 custom_components/hpprinter/diagnostics.py delete mode 100644 custom_components/hpprinter/helpers/__init__.py delete mode 100644 custom_components/hpprinter/helpers/const.py delete mode 100644 custom_components/hpprinter/managers/HPDeviceData.py delete mode 100644 custom_components/hpprinter/managers/config_flow_manager.py delete mode 100644 custom_components/hpprinter/managers/configuration_manager.py delete mode 100644 custom_components/hpprinter/managers/device_manager.py delete mode 100644 custom_components/hpprinter/managers/entity_manager.py create mode 100644 custom_components/hpprinter/managers/flow_manager.py create mode 100644 custom_components/hpprinter/managers/ha_config_manager.py create mode 100644 custom_components/hpprinter/managers/ha_coordinator.py delete mode 100644 custom_components/hpprinter/managers/home_assistant.py create mode 100644 custom_components/hpprinter/managers/rest_api.py delete mode 100644 custom_components/hpprinter/managers/storage_manager.py delete mode 100644 custom_components/hpprinter/models/base_entity.py delete mode 100644 custom_components/hpprinter/models/entity_data.py create mode 100644 custom_components/hpprinter/models/exceptions.py create mode 100644 tests/api_test.py diff --git a/custom_components/hpprinter/__init__.py b/custom_components/hpprinter/__init__.py index 7c6730a..64533ea 100644 --- a/custom_components/hpprinter/__init__.py +++ b/custom_components/hpprinter/__init__.py @@ -1,20 +1,13 @@ -""" -This component provides support for HP Printers. -For more details about this component, please refer to the documentation at -https://home-assistant.io/components/hpprinter/ -""" -from custom_components.hpprinter.helpers import ( - async_set_ha, - clear_ha, - get_ha, - handle_log_level, -) +import logging +import sys + from homeassistant.config_entries import ConfigEntry -from homeassistant.const import CONF_NAME +from homeassistant.const import EVENT_HOMEASSISTANT_START from homeassistant.core import HomeAssistant -from .helpers.const import * -from .managers.HPDeviceData import * +from .common.consts import DEFAULT_NAME, DOMAIN +from .managers.ha_config_manager import HAConfigManager +from .managers.ha_coordinator import HACoordinator _LOGGER = logging.getLogger(__name__) @@ -24,50 +17,58 @@ async def async_setup(_hass, _config): async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: - """Set up a HP Printer component.""" + """Set up a Shinobi Video component.""" initialized = False try: - await handle_log_level(hass, entry) + entry_config = {key: entry.data[key] for key in entry.data} + + config_manager = HAConfigManager(hass, entry) + await config_manager.initialize(entry_config) + + is_initialized = config_manager.is_initialized + + if is_initialized: + coordinator = HACoordinator(hass, config_manager) + + hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator - _LOGGER.debug(f"Starting async_setup_entry of {DOMAIN}") - entry.add_update_listener(async_options_updated) - name = entry.data.get(CONF_NAME) + if hass.is_running: + await coordinator.initialize() - await async_set_ha(hass, name, entry) + else: + hass.bus.async_listen_once( + EVENT_HOMEASSISTANT_START, coordinator.on_home_assistant_start + ) - initialized = True + _LOGGER.info("Finished loading integration") + + initialized = is_initialized except Exception as ex: exc_type, exc_obj, tb = sys.exc_info() line_number = tb.tb_lineno - _LOGGER.error(f"Failed to load HP Printer, error: {ex}, line: {line_number}") + _LOGGER.error( + f"Failed to load {DEFAULT_NAME}, error: {ex}, line: {line_number}" + ) return initialized async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): """Unload a config entry.""" - name = entry.data.get(CONF_NAME) - ha = get_ha(hass, name) - - if ha is not None: - await ha.async_remove() + _LOGGER.info(f"Unloading {DOMAIN} integration, Entry ID: {entry.entry_id}") - clear_ha(hass, name) + coordinator: HACoordinator = hass.data[DOMAIN][entry.entry_id] - return True + await coordinator.config_manager.remove(entry.entry_id) + platforms = coordinator.config_manager.platforms -async def async_options_updated(hass: HomeAssistant, entry: ConfigEntry): - """Triggered by config entry options updates.""" - await handle_log_level(hass, entry) + for platform in platforms: + await hass.config_entries.async_forward_entry_unload(entry, platform) - _LOGGER.info(f"async_options_updated, Entry: {entry.as_dict()} ") + del hass.data[DOMAIN][entry.entry_id] - name = entry.data.get(CONF_NAME) - ha = get_ha(hass, name) - - if ha is not None: - await ha.async_update_entry(entry) + return True diff --git a/custom_components/hpprinter/api/HPPrinterAPI.py b/custom_components/hpprinter/api/HPPrinterAPI.py deleted file mode 100644 index 460da0e..0000000 --- a/custom_components/hpprinter/api/HPPrinterAPI.py +++ /dev/null @@ -1,286 +0,0 @@ -from asyncio import sleep -import json -import logging -import sys -from typing import Optional - -import aiohttp -import xmltodict - -from homeassistant.helpers.aiohttp_client import async_create_clientsession - -from . import LoginError -from ..helpers.const import * -from ..managers.configuration_manager import ConfigManager -from ..models.config_data import ConfigData - -_LOGGER = logging.getLogger(__name__) - - -class HPPrinterAPI: - def __init__(self, hass, config_manager: ConfigManager, data_type=None): - self._config_manager = config_manager - - self._hass = hass - self._data_type = data_type - self._data = None - self._session = None - - self.initialize() - - @property - def data(self): - return self._data - - @property - def config_data(self) -> Optional[ConfigData]: - if self._config_manager is not None: - return self._config_manager.data - - return None - - @property - def url(self): - config_data = self.config_data - - url = f"{config_data.protocol}://{config_data.host}:{config_data.port}/DevMgmt/{self._data_type}.xml" - - return url - - def initialize(self): - try: - self._session = async_create_clientsession( - hass=self._hass, auto_cleanup=True - ) - - except Exception as ex: - exc_type, exc_obj, tb = sys.exc_info() - line_number = tb.tb_lineno - - _LOGGER.error( - f"Failed to initialize Printer API, error: {ex}, line: {line_number}" - ) - - async def terminate(self): - try: - if self._session is not None and not self._session.closed: - await self._session.close() - - await sleep(3) - - self._session = None - - except Exception as ex: - exc_type, exc_obj, tb = sys.exc_info() - line_number = tb.tb_lineno - - _LOGGER.error( - f"Failed to terminate Printer API, error: {ex}, line: {line_number}" - ) - - async def get_data(self): - try: - self._data = None - - _LOGGER.debug(f"Updating {self._data_type} from {self.config_data.host}") - - file_reader = self.config_data.file_reader - - if file_reader is None: - printer_data = await self.async_get() - else: - printer_data = file_reader(self._data_type) - - result = {} - - if printer_data is not None: - for root_key in printer_data: - root_item = printer_data[root_key] - - item = self.extract_data(root_item, root_key) - - if item is not None: - result[root_key] = item - - self._data = result - - json_data = json.dumps(self._data) - - self.save_file("json", json_data) - - except Exception as ex: - exc_type, exc_obj, tb = sys.exc_info() - line_number = tb.tb_lineno - - _LOGGER.error( - f"Failed to update data ({self._data_type}) and parse it, Error: {ex}, Line: {line_number}" - ) - - return self._data - - def save_file(self, extension, content, file_name: Optional[str] = None): - if self.config_data.should_store: - if file_name is None: - file_name = self._data_type - - with open(f"{self.config_data.name}-{file_name}.{extension}", "w") as file: - file.write(content) - - async def async_get(self, throw_exception: bool = False): - result = None - status_code = 400 - - try: - _LOGGER.debug(f"Retrieving {self._data_type} from {self.config_data.host}") - - async with self._session.get( - self.url, ssl=False, timeout=aiohttp.ClientTimeout(total=10) - ) as response: - status_code = response.status - response.raise_for_status() - - content = await response.text() - - self.save_file("xml", content) - - for ns in NAMESPACES_TO_REMOVE: - content = content.replace(f"{ns}:", "") - - json_data = xmltodict.parse(content) - - result = json_data - - except Exception as ex: - exc_type, exc_obj, tb = sys.exc_info() - line_number = tb.tb_lineno - - _LOGGER.info( - f"Cannot retrieve data ({self._data_type}) from printer, Error: {ex}, Line: {line_number}" - ) - - if throw_exception and status_code > 399: - raise LoginError(status_code) - - return result - - def extract_data(self, data_item, data_item_key): - try: - ignore = data_item_key in IGNORE_ITEMS - is_default_array = data_item_key in ARRAY_AS_DEFAULT - - if ignore: - return None - - elif isinstance(data_item, dict): - return self.extract_ordered_dictionary(data_item, data_item_key) - - elif isinstance(data_item, list) and not is_default_array: - return self.extract_array(data_item, data_item_key) - - else: - return data_item - except Exception as ex: - exc_type, exc_obj, tb = sys.exc_info() - line_number = tb.tb_lineno - - _LOGGER.error( - f"Failed to extract {data_item_key} of {data_item}, Error: {ex}, Line: {line_number}" - ) - - def extract_ordered_dictionary(self, data_item, item_key): - try: - result = {} - - for data_item_key in data_item: - next_item = data_item[data_item_key] - - item = self.extract_data(next_item, data_item_key) - - if item is not None: - result[data_item_key] = item - - return result - except Exception as ex: - exc_type, exc_obj, tb = sys.exc_info() - line_number = tb.tb_lineno - error_details = f"Error: {ex}, Line: {line_number}" - - _LOGGER.error( - f"Failed to extract from dictionary {item_key} of {data_item}, {error_details}" - ) - - def extract_array(self, data_item, item_key): - try: - result = {} - keys = ARRAY_KEYS.get(item_key, []) - index = 0 - - for current_item in data_item: - next_item_key = item_key - item = {} - for key in current_item: - next_item = current_item[key] - - item_data = self.extract_data(next_item, key) - - if item_data is not None: - item[key] = item_data - - if key in keys: - next_item_key = f"{next_item_key}_{item[key]}" - - if len(keys) == 0: - next_item_key = f"{next_item_key}_{index}" - - result[next_item_key] = item - - index += 1 - - return result - except Exception as ex: - exc_type, exc_obj, tb = sys.exc_info() - line_number = tb.tb_lineno - - _LOGGER.error( - f"Failed to extract from array {item_key} of {data_item}, Error: {ex}, Line: {line_number}" - ) - - @staticmethod - def clean_parameter(data_item, data_key, default_value="N/A"): - result = data_item.get(data_key, {}) - - if not isinstance(result, str): - result = result.get("#text", 0) - - if not isinstance(result, str): - result = default_value - - return result - - -class ConsumableConfigDynPrinterDataAPI(HPPrinterAPI): - def __init__(self, hass, config_manager: ConfigManager): - data_type = "ConsumableConfigDyn" - - super().__init__(hass, config_manager, data_type) - - -class ProductUsageDynPrinterDataAPI(HPPrinterAPI): - def __init__(self, hass, config_manager: ConfigManager): - data_type = "ProductUsageDyn" - - super().__init__(hass, config_manager, data_type) - - -class ProductStatusDynDataAPI(HPPrinterAPI): - def __init__(self, hass, config_manager: ConfigManager): - data_type = "ProductStatusDyn" - - super().__init__(hass, config_manager, data_type) - - -class ProductConfigDynDataAPI(HPPrinterAPI): - def __init__(self, hass, config_manager: ConfigManager): - data_type = "ProductConfigDyn" - - super().__init__(hass, config_manager, data_type) diff --git a/custom_components/hpprinter/api/__init__.py b/custom_components/hpprinter/api/__init__.py deleted file mode 100644 index d46a402..0000000 --- a/custom_components/hpprinter/api/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -class LoginError(Exception): - def __init__(self, status_code): - self._status_code = status_code - - @property - def status_code(self): - return self._status_code diff --git a/custom_components/hpprinter/binary_sensor.py b/custom_components/hpprinter/binary_sensor.py index d2f0c4d..3104d75 100644 --- a/custom_components/hpprinter/binary_sensor.py +++ b/custom_components/hpprinter/binary_sensor.py @@ -1,68 +1,58 @@ -""" -Support for HP Printer binary sensors. -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/binary_sensor.hp_printer/ -""" -from __future__ import annotations - import logging -from homeassistant.components.binary_sensor import ( - BinarySensorDeviceClass, - BinarySensorEntity, -) +from homeassistant.components.binary_sensor import BinarySensorEntity +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import Platform from homeassistant.core import HomeAssistant -from .helpers.const import * -from .models.base_entity import HPPrinterEntity, async_setup_base_entry -from .models.entity_data import EntityData +from .common.base_entity import BaseEntity, async_setup_base_entry +from .common.entity_descriptions import IntegrationBinarySensorEntityDescription +from .managers.ha_coordinator import HACoordinator _LOGGER = logging.getLogger(__name__) -CURRENT_DOMAIN = DOMAIN_BINARY_SENSOR - - -def get_binary_sensor(hass: HomeAssistant, integration_name: str, entity: EntityData): - binary_sensor = HPPrinterBinarySensor() - binary_sensor.initialize(hass, integration_name, entity, CURRENT_DOMAIN) - - return binary_sensor - - -async def async_setup_entry(hass: HomeAssistant, entry, async_add_entities): - """Set up HP Printer based off an entry.""" +async def async_setup_entry( + hass: HomeAssistant, entry: ConfigEntry, async_add_entities +): await async_setup_base_entry( - hass, entry, async_add_entities, CURRENT_DOMAIN, get_binary_sensor + hass, + entry, + Platform.BINARY_SENSOR, + HABinarySensorEntity, + async_add_entities, ) -async def async_unload_entry(_hass, config_entry): - _LOGGER.info(f"async_unload_entry {CURRENT_DOMAIN}: {config_entry}") +class HABinarySensorEntity(BaseEntity, BinarySensorEntity): + """Representation of a sensor.""" - return True + def __init__( + self, + entity_description: IntegrationBinarySensorEntityDescription, + coordinator: HACoordinator, + ): + super().__init__(entity_description, coordinator) + self._attr_device_class = entity_description.device_class + self._entity_attributes = entity_description.attributes + self._entity_on_value = entity_description.on_value -class HPPrinterBinarySensor(BinarySensorEntity, HPPrinterEntity): - """Representation a binary sensor that is updated by HP Printer.""" + def _handle_coordinator_update(self) -> None: + """Fetch new state parameters for the sensor.""" + device_data = self.get_data() + state = device_data.get(self._data_key) - @property - def is_on(self): - """Return true if the binary sensor is on.""" - return bool(self.entity.state) + is_on = str(state).lower() == str(self._entity_on_value).lower() - @property - def device_class(self) -> BinarySensorDeviceClass | str | None: - """Return the class of this sensor.""" - return self.entity.binary_sensor_device_class + attributes = {} + if self._entity_attributes is not None: + for attribute_key in self._entity_attributes: + value = device_data.get(attribute_key) - async def async_added_to_hass_local(self): - _LOGGER.info(f"Added new {self.name}") + attributes[attribute_key] = value - def _immediate_update(self, previous_state: bool): - if previous_state != self.entity.state: - _LOGGER.debug( - f"{self.name} updated from {previous_state} to {self.entity.state}" - ) + self._attr_is_on = is_on + self._attr_extra_state_attributes = attributes - super()._immediate_update(previous_state) + self.async_write_ha_state() diff --git a/custom_components/hpprinter/common/__init__.py b/custom_components/hpprinter/common/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/custom_components/hpprinter/common/base_entity.py b/custom_components/hpprinter/common/base_entity.py new file mode 100644 index 0000000..0f52365 --- /dev/null +++ b/custom_components/hpprinter/common/base_entity.py @@ -0,0 +1,111 @@ +import logging +import sys + +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import Platform +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers.dispatcher import async_dispatcher_connect +from homeassistant.helpers.update_coordinator import CoordinatorEntity +from homeassistant.util import slugify + +from ..managers.ha_coordinator import HACoordinator +from .consts import ADD_COMPONENT_SIGNALS, DOMAIN +from .entity_descriptions import IntegrationEntityDescription + +_LOGGER = logging.getLogger(__name__) + + +async def async_setup_base_entry( + hass: HomeAssistant, + entry: ConfigEntry, + platform: Platform, + entity_type: type, + async_add_entities, +): + @callback + def _async_handle_device(entry_id: str): + if entry.entry_id != entry_id: + return + + try: + coordinator: HACoordinator = hass.data[DOMAIN][entry.entry_id] + + entity_descriptions = coordinator.get_entity_descriptions(platform) + + entities = [ + entity_type(entity_description, coordinator) + for entity_description in entity_descriptions + if entity_description.platform == platform + ] + + _LOGGER.debug(f"Setting up {platform} entities: {entities}") + + async_add_entities(entities, True) + + except Exception as ex: + exc_type, exc_obj, tb = sys.exc_info() + line_number = tb.tb_lineno + + _LOGGER.error( + f"Failed to initialize {platform}, Error: {ex}, Line: {line_number}" + ) + + for add_component_signal in ADD_COMPONENT_SIGNALS: + entry.async_on_unload( + async_dispatcher_connect(hass, add_component_signal, _async_handle_device) + ) + + +class BaseEntity(CoordinatorEntity): + _device_code: str + _entity_description: IntegrationEntityDescription + _translations: dict + + def __init__( + self, + entity_description: IntegrationEntityDescription, + coordinator: HACoordinator, + item_id: str | None, + ): + super().__init__(coordinator) + + self.entity_description = entity_description + + self._item_id = item_id + self._data_key = self._entity_description.data_point_key + + device_info = coordinator.get_device( + entity_description.device_type, self._item_id + ) + + entity_name = coordinator.config_manager.get_entity_name( + entity_description, device_info + ) + + unique_id_parts = [DOMAIN, entity_description.platform, entity_description.key] + + unique_id = slugify("_".join(unique_id_parts)) + + self._attr_device_info = device_info + self._attr_name = entity_name + self._attr_unique_id = unique_id + + self._data = {} + + @property + def local_coordinator(self) -> HACoordinator: + return self.coordinator + + @property + def data(self) -> dict | None: + return self._data + + def get_data(self) -> dict: + section = self._entity_description.data_point_section + array_key = self._entity_description.data_point_item_key + + data = self.local_coordinator.get_device_data( + section, array_key=array_key, item_id=self._item_id + ) + + return data diff --git a/custom_components/hpprinter/common/consts.py b/custom_components/hpprinter/common/consts.py new file mode 100644 index 0000000..78c4e6e --- /dev/null +++ b/custom_components/hpprinter/common/consts.py @@ -0,0 +1,41 @@ +from datetime import timedelta + +from homeassistant.const import CONF_HOST, CONF_PORT, CONF_SSL + +MANUFACTURER = "HP" +DEFAULT_NAME = "HP Printer" +DOMAIN = "hpprinter" +DATA_HP_PRINTER = f"data_{DOMAIN}" + +INK_ICON = "mdi:cup-water" +PAGES_ICON = "mdi:book-open-page-variant" +SCANNER_ICON = "mdi:scanner" + +PROTOCOLS = {True: "https", False: "http"} + +NOT_AVAILABLE = "N/A" + +PRINTER_STATUS = { + "ready": "On", + "scanProcessing": "Scanning", + "copying": "Copying", + "processing": "Printing", + "cancelJob": "Cancelling Job", + "inPowerSave": "Idle", + "": "Off", +} + +IGNORED_KEYS = ["@schemaLocation", "Version"] + +SIGNAL_HA_DEVICE_NEW = f"signal_{DOMAIN}_device_new" +CONFIGURATION_FILE = f"{DOMAIN}.config.json" +LEGACY_KEY_FILE = f"{DOMAIN}.key" + +UPDATE_API_INTERVAL = timedelta(minutes=5) + +ADD_COMPONENT_SIGNALS = [SIGNAL_HA_DEVICE_NEW] +DEFAULT_ENTRY_ID = "config" +CONF_UPDATE_INTERVAL = "update_interval" +CONF_TITLE = "title" + +DATA_KEYS = [CONF_HOST, CONF_PORT, CONF_SSL] diff --git a/custom_components/hpprinter/common/entity_descriptions.py b/custom_components/hpprinter/common/entity_descriptions.py new file mode 100644 index 0000000..c1fa90c --- /dev/null +++ b/custom_components/hpprinter/common/entity_descriptions.py @@ -0,0 +1,277 @@ +from dataclasses import dataclass +from typing import Callable + +from homeassistant.components.binary_sensor import BinarySensorEntityDescription +from homeassistant.components.number import NumberEntityDescription +from homeassistant.components.sensor import SensorEntityDescription +from homeassistant.const import Platform +from homeassistant.helpers.entity import EntityDescription + + +@dataclass(frozen=True, kw_only=True) +class IntegrationEntityDescription(EntityDescription): + platform: Platform | None = None + device_type: str | None = None + data_point_section: str | None = None + data_point_key: str | None = None + data_point_item_key: str | None = None + filter: Callable[[dict | None], bool] | None = lambda d: True + + +@dataclass(frozen=True, kw_only=True) +class IntegrationBinarySensorEntityDescription( + BinarySensorEntityDescription, IntegrationEntityDescription +): + platform: Platform | None = Platform.BINARY_SENSOR + on_value: str | bool | None = None + attributes: list[str] | None = None + + +@dataclass(frozen=True, kw_only=True) +class IntegrationSensorEntityDescription( + SensorEntityDescription, IntegrationEntityDescription +): + platform: Platform | None = Platform.SENSOR + + +@dataclass(frozen=True, kw_only=True) +class IntegrationNumberEntityDescription( + NumberEntityDescription, IntegrationEntityDescription +): + platform: Platform | None = Platform.NUMBER + + +DEFAULT_ENTITY_DESCRIPTIONS: list[IntegrationEntityDescription] = [ + IntegrationSensorEntityDescription( + key="printer_total_pages_printed", + device_type="Printer", + data_point_section="Printer", + data_point_key="Total pages printed", + native_unit_of_measurement="pages", + ), + IntegrationSensorEntityDescription( + key="printer_total_black_and_white_pages_printed", + device_type="Printer", + data_point_section="Printer", + data_point_key="Total black-and-white pages printed", + native_unit_of_measurement="pages", + ), + IntegrationSensorEntityDescription( + key="printer_total_color_pages_printed", + device_type="Printer", + data_point_section="Printer", + data_point_key="Total color pages printed", + native_unit_of_measurement="pages", + ), + IntegrationSensorEntityDescription( + key="printer_total_single_sided_pages_printed", + device_type="Printer", + data_point_section="Printer", + data_point_key="Total single-sided pages printed", + native_unit_of_measurement="pages", + ), + IntegrationSensorEntityDescription( + key="printer_total_double_sided_pages_printed", + device_type="Printer", + data_point_section="Printer", + data_point_key="Total double-sided pages printed", + native_unit_of_measurement="pages", + ), + IntegrationSensorEntityDescription( + key="printer_total_jams", + device_type="Printer", + data_point_section="Printer", + data_point_key="Total jams", + native_unit_of_measurement="pages", + ), + IntegrationSensorEntityDescription( + key="printer_miss_picks", + device_type="Printer", + data_point_section="Printer", + data_point_key="Total miss picks", + native_unit_of_measurement="pages", + ), + IntegrationSensorEntityDescription( + key="scanner_total_scanned_pages", + device_type="Scanner", + data_point_section="Scanner", + data_point_key="Total scanned pages", + native_unit_of_measurement="pages", + ), + IntegrationSensorEntityDescription( + key="scanner_total_scanned_pages_from_adf", + device_type="Scanner", + data_point_section="Scanner", + data_point_key="Total scanned pages from ADF", + native_unit_of_measurement="pages", + ), + IntegrationSensorEntityDescription( + key="scanner_total_pages_from_scanner_glass", + device_type="Scanner", + data_point_section="Scanner", + data_point_key="Total pages from scanner glass", + native_unit_of_measurement="pages", + ), + IntegrationSensorEntityDescription( + key="scanner_total_double_sided_pages_scanned", + device_type="Scanner", + data_point_section="Scanner", + data_point_key="Total double-sided pages scanned", + native_unit_of_measurement="pages", + ), + IntegrationSensorEntityDescription( + key="scanner_total_jams", + device_type="Scanner", + data_point_section="Scanner", + data_point_key="Total jams", + native_unit_of_measurement="pages", + ), + IntegrationSensorEntityDescription( + key="scanner_miss_picks", + device_type="Scanner", + data_point_section="Scanner", + data_point_key="Total miss picks", + native_unit_of_measurement="pages", + ), + IntegrationSensorEntityDescription( + key="copy_total_copies", + device_type="Copy", + data_point_section="Copy", + data_point_key="Total copies", + native_unit_of_measurement="pages", + ), + IntegrationSensorEntityDescription( + key="copy_total_copies_from_adf", + device_type="Copy", + data_point_section="Copy", + data_point_key="Total copies from ADF", + native_unit_of_measurement="pages", + ), + IntegrationSensorEntityDescription( + key="copy_total_pages_from_scanner_glass", + device_type="Copy", + data_point_section="Copy", + data_point_key="Total pages from scanner glass", + native_unit_of_measurement="pages", + ), + IntegrationSensorEntityDescription( + key="copy_total_black_and_white_copies", + device_type="Copy", + data_point_section="Copy", + data_point_key="Total black-and-white copies", + native_unit_of_measurement="pages", + ), + IntegrationSensorEntityDescription( + key="copy_total_color_copies", + device_type="Copy", + data_point_section="Copy", + data_point_key="Total color copies", + native_unit_of_measurement="pages", + ), + IntegrationSensorEntityDescription( + key="fax_total_faxed", + device_type="Fax", + data_point_section="Fax", + data_point_key="Total faxed", + native_unit_of_measurement="pages", + ), + IntegrationBinarySensorEntityDescription( + key="eprint_registration_state", + device_type="Main", + data_point_section="ePrint", + data_point_key="Registration State", + on_value="registered", + ), + IntegrationBinarySensorEntityDescription( + key="eprint_status", + device_type="Main", + data_point_section="ePrint", + data_point_key="Status", + on_value="enabled", + ), +] + +CARTRIDGE_ENTITY_DESCRIPTIONS: list[IntegrationEntityDescription] = [ + IntegrationSensorEntityDescription( + key="cartridges_consumable_type_enum", + device_type="Cartridge", + data_point_section="Cartridges", + data_point_item_key="Label Code", + data_point_key="Consumable Type Enum", + ), + IntegrationSensorEntityDescription( + key="cartridges_installation_date", + device_type="Cartridge", + data_point_section="Cartridges", + data_point_item_key="Label Code", + data_point_key="Installation Date", + ), + IntegrationSensorEntityDescription( + key="cartridges_warranty_expiration_date", + device_type="Cartridge", + data_point_section="Cartridges", + data_point_item_key="Label Code", + data_point_key="Warranty Expiration Date", + filter=lambda d: d.get("Consumable Type Enum") != "printhead", + ), + IntegrationSensorEntityDescription( + key="cartridges_consumable_percentage_level_remaining", + device_type="Cartridge", + data_point_section="Cartridges", + data_point_item_key="Label Code", + data_point_key="Consumable Percentage Level Remaining", + native_unit_of_measurement="%", + filter=lambda d: d.get("Consumable Type Enum") != "printhead", + ), + IntegrationSensorEntityDescription( + key="cartridges_estimated_pages_remaining", + device_type="Cartridge", + data_point_section="Cartridges", + data_point_item_key="Label Code", + data_point_key="Estimated Pages Remaining", + native_unit_of_measurement="pages", + filter=lambda d: d.get("Consumable Type Enum") != "printhead", + ), + IntegrationSensorEntityDescription( + key="cartridges_counterfeit_refilled_count", + device_type="Cartridge", + data_point_section="Cartridges", + data_point_item_key="Label Code", + data_point_key="Counterfeit Refilled Count", + native_unit_of_measurement="refill", + ), + IntegrationSensorEntityDescription( + key="cartridges_genuine_refilled_count", + device_type="Cartridge", + data_point_section="Cartridges", + data_point_item_key="Label Code", + data_point_key="Genuine Refilled Count", + native_unit_of_measurement="refill", + ), + IntegrationBinarySensorEntityDescription( + key="cartridges_consumable_state", + device_type="Cartridge", + data_point_section="Cartridges", + data_point_item_key="Label Code", + data_point_key="ConsumableState", + on_value="ok", + ), +] + +ADAPTER_ENTITY_DESCRIPTIONS: list[IntegrationEntityDescription] = [ + IntegrationSensorEntityDescription( + key="adapters_device_connectivity_port_type", + device_type="Main", + data_point_section="Adapters", + data_point_item_key="Name", + data_point_key="DeviceConnectivityPortType", + ), + IntegrationBinarySensorEntityDescription( + key="adapters_is_connected", + device_type="Main", + data_point_section="Adapters", + data_point_item_key="Name", + data_point_key="IsConnected", + on_value="true", + ), +] diff --git a/custom_components/hpprinter/config_flow.py b/custom_components/hpprinter/config_flow.py index d1badfc..07d7c21 100644 --- a/custom_components/hpprinter/config_flow.py +++ b/custom_components/hpprinter/config_flow.py @@ -1,22 +1,21 @@ -"""Config flow to configure HPPrinter.""" +"""Config flow to configure.""" +from __future__ import annotations + import logging from homeassistant import config_entries from homeassistant.config_entries import ConfigEntry -from homeassistant.const import CONF_NAME from homeassistant.core import callback -from .helpers import get_ha -from .helpers.const import * -from .managers.config_flow_manager import ConfigFlowManager -from .models import AlreadyExistsError, LoginError +from .common.consts import DOMAIN +from .managers.flow_manager import IntegrationFlowManager _LOGGER = logging.getLogger(__name__) @config_entries.HANDLERS.register(DOMAIN) -class HPPrinterFlowHandler(config_entries.ConfigFlow): - """Handle a HPPrinter config flow.""" +class DomainFlowHandler(config_entries.ConfigFlow): + """Handle a domain config flow.""" VERSION = 1 CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_POLL @@ -24,105 +23,32 @@ class HPPrinterFlowHandler(config_entries.ConfigFlow): def __init__(self): super().__init__() - self._config_flow = ConfigFlowManager() - @staticmethod @callback def async_get_options_flow(config_entry): """Get the options flow for this handler.""" - return HPPrinterOptionsFlowHandler(config_entry) + return DomainOptionsFlowHandler(config_entry) async def async_step_user(self, user_input=None): """Handle a flow start.""" - _LOGGER.debug(f"Starting async_step_user of {DOMAIN}") - - errors = None - - self._config_flow.initialize(self.hass) - - if user_input is not None: - self._config_flow.update_data(user_input, True) - - name = self._config_flow.config_data.name - - ha = get_ha(self.hass, name) - - if ha is None: - errors = await self._config_flow.valid_login() - else: - _LOGGER.warning(f"{DEFAULT_NAME} ({name}) already configured") - - return self.async_abort( - reason="already_configured", description_placeholders=user_input - ) - - if errors is None: - _LOGGER.info(f"Storing configuration data: {user_input}") - - return self.async_create_entry(title=name, data=user_input) - - data_schema = self._config_flow.get_default_data() + flow_manager = IntegrationFlowManager(self.hass, self) - return self.async_show_form( - step_id="user", data_schema=data_schema, errors=errors - ) + return await flow_manager.async_step(user_input) - async def async_step_import(self, info): - """Import existing configuration from Z-Wave.""" - _LOGGER.debug(f"Starting async_step_import of {DOMAIN}") - return self.async_create_entry( - title="HPPrinter (import from configuration.yaml)", - data=info, - ) +class DomainOptionsFlowHandler(config_entries.OptionsFlow): + """Handle domain options.""" - -class HPPrinterOptionsFlowHandler(config_entries.OptionsFlow): - """Handle HP Printer options.""" + _config_entry: ConfigEntry def __init__(self, config_entry: ConfigEntry): - """Initialize HP Printer options flow.""" + """Initialize domain options flow.""" super().__init__() - self._config_flow = ConfigFlowManager(config_entry) + self._config_entry = config_entry async def async_step_init(self, user_input=None): - """Manage the HP Printer options.""" - return await self.async_step_hp_printer_additional_settings(user_input) - - async def async_step_hp_printer_additional_settings(self, user_input=None): - errors = None - - self._config_flow.initialize(self.hass) - - if user_input is not None: - new_user_input = None - - try: - new_user_input = await self._config_flow.update_options( - user_input, True - ) - - except LoginError as lex: - _LOGGER.warning("Cannot complete login") - - errors = lex.errors - - except AlreadyExistsError as aeex: - new_name = aeex.entry.data.get(CONF_NAME) - - _LOGGER.warning(f"Cannot update host to: {new_name}") - - errors = {"base": "already_configured"} - - if errors is None: - return self.async_create_entry(title="", data=new_user_input) - - data_schema = self._config_flow.get_default_options() + """Manage the domain options.""" + flow_manager = IntegrationFlowManager(self.hass, self, self._config_entry) - return self.async_show_form( - step_id="hp_printer_additional_settings", - data_schema=data_schema, - errors=errors, - description_placeholders=self._config_flow.data, - ) + return await flow_manager.async_step(user_input) diff --git a/custom_components/hpprinter/data/data_points.json b/custom_components/hpprinter/data/data_points.json new file mode 100644 index 0000000..c4e8d90 --- /dev/null +++ b/custom_components/hpprinter/data/data_points.json @@ -0,0 +1,140 @@ +{ + "objects": [ + { + "name": "Adapters", + "endpoint": "/IoMgmt/Adapters", + "path": "Adapters.Adapter", + "subPath": { + "Name": "HardwareConfig.Name", + "DeviceConnectivityPortType": "HardwareConfig.DeviceConnectivityPortType", + "IsConnected": "HardwareConfig.IsConnected" + } + }, + { + "name": "ePrint", + "endpoint": "/ePrint/ePrintConfigDyn.xml", + "path": "ePrintConfigDyn", + "subPath": { + "Printer ID": "PrinterID", + "Registration State": "RegistrationState", + "Status": "CloudServicesSwitch.Status" + } + }, + { + "name": "Wifi Direct Current Configuration", + "endpoint": "/DevMgmt/NetAppsSecureDyn.xml", + "path": "NetAppsSecureDyn.WirelessDirectConfig", + "subPath": { + "SSID Prefix": "SSIDPrefix", + "Connection Method": "ConnectionMethod" + } + }, + { + "name": "Product", + "endpoint": "/DevMgmt/ProductConfigDyn.xml", + "path": "ProductConfigDyn.ProductInformation", + "subPath": { + "Make And Model": "MakeAndModel", + "Make And Model Family": "MakeAndModelFamily", + "SKU Identifier": "SKUIdentifier", + "Serial Number": "SerialNumber", + "Product Number": "ProductNumber", + "Manufacturer Name": "Manufacturer.Name", + "Manufactured At": "Manufacturer.Date" + } + }, + { + "name": "Cartridges", + "endpoint": "/DevMgmt/ConsumableConfigDyn.xml", + "path": "ConsumableConfigDyn.ConsumableInfo", + "subPath": { + "Label Code": "ConsumableLabelCode", + "Consumable State": "ConsumableLifeState.ConsumableState", + "Brand": "ConsumableLifeState.Brand", + "Consumable Station": "ConsumableStation", + "Consumable Type Enum": "ConsumableTypeEnum", + "Installation Date": "Installation.Date", + "Max Capacity": "Capacity.MaxCapacity", + "Consumable Percentage Level Remaining": "ConsumablePercentageLevelRemaining", + "Consumable Selectibility Number": "ConsumableSelectibilityNumber", + "Manufacturer Name": "Manufacturer.Name", + "Manufactured At": "Manufacturer.Date", + "Serial Number": "SerialNumber", + "Product Number": "ProductNumber", + "Warranty Expiration Date": "Warranty.ExpirationDate", + "Consumable Unique ID": "ConsumableUniqueID" + } + }, + { + "name": "Cartridges Usage", + "endpoint": "/DevMgmt/ProductUsageDyn.xml", + "path": "ProductUsageDyn.ConsumableSubunit.Consumable", + "subPath": { + "Consumable Station": "ConsumableStation", + "Marker Color": "MarkerColor", + "Consumable Type Enum": "ConsumableTypeEnum", + "Estimated Pages Remaining": "EstimatedPagesRemaining", + "ConsumableState": "ConsumableState", + "Consumable Raw Percentage Level Remaining": "ConsumableRawPercentageLevelRemaining", + "Serial Number": "SupplySerialNumber.#text", + "Counterfeit Refilled Count": "RefilledCount.CounterfeitRefilledCount.#text", + "Genuine Refilled Count": "RefilledCount.GenuineRefilledCount" + } + }, + { + "name": "Printer", + "endpoint": "/DevMgmt/ProductUsageDyn.xml", + "path": "ProductUsageDyn.PrinterSubunit", + "subPath": { + "Total pages printed": "TotalImpressions.#text", + "Total black-and-white pages printed": "MonochromeImpressions", + "Total color pages printed": "ColorImpressions", + "Total single-sided pages printed": "SimplexSheets", + "Total double-sided pages printed": "DuplexSheets.#text", + "Total jams": "JamEvents.#text", + "Total miss picks": "MispickEvents" + } + }, + { + "name": "Scanner", + "endpoint": "/DevMgmt/ProductUsageDyn.xml", + "path": "ProductUsageDyn.ScannerEngineSubunit", + "subPath": { + "Total scanned pages": "ScanImages.#text", + "Total scanned pages from ADF": "AdfImages.#text", + "Total double-sided pages scanned": "DuplexSheets.#text", + "Total pages from scanner glass": "FlatbedImages", + "Total jams": "JamEvents", + "Total miss picks": "MispickEvents" + } + }, + { + "name": "Copy", + "endpoint": "/DevMgmt/ProductUsageDyn.xml", + "path": "ProductUsageDyn.CopyApplicationSubunit", + "subPath": { + "Total copies": "TotalImpressions.#text", + "Total copies from ADF": "AdfImages", + "Total double-sided pages scanned": "DuplexSheets.#text", + "Total pages from scanner glass": "FlatbedImages", + "Total black-and-white copies": "MonochromeImpressions", + "Total color copies": "ColorImpressions" + } + }, + { + "name": "Fax", + "endpoint": "/DevMgmt/ProductUsageDyn.xml", + "path": "ProductUsageDyn.FaxApplicationSubunit", + "subPath": { + "Total faxes": "TotalImpressions.#text" + } + } + ], + "merge": [ + { + "from": "Cartridges Usage", + "to": "Cartridges", + "key": "Consumable Station" + } + ] +} diff --git a/custom_components/hpprinter/data/endpoint_validations.json b/custom_components/hpprinter/data/endpoint_validations.json new file mode 100644 index 0000000..dba3741 --- /dev/null +++ b/custom_components/hpprinter/data/endpoint_validations.json @@ -0,0 +1,18 @@ +{ + "exclude_type": ["ns", "feature", "manifest"], + "exclude_uri": [ + "/DevMgmt/InternalPrintDyn.xml", + "/Scan/SPF", + "/Jobs/JobList", + "/CachedData/Info", + "/CachedData/Files", + "/ePrint/EmailAddress", + "/ePrint/PrinterSignature", + "/ePrint/XMPPConfiguration", + "/ePrint/ClaimInfo", + "/IoMgmt/Adapters/Wifi1/ClientList", + "/WalkupScanToComp/WalkupScanToCompEvent", + "/FirmwareUpdate/FirmwareUpdateDyn.xml", + "/FirmwareUpdate/WebFWUpdate/State" + ] +} diff --git a/custom_components/hpprinter/diagnostics.py b/custom_components/hpprinter/diagnostics.py new file mode 100644 index 0000000..fc0b9a1 --- /dev/null +++ b/custom_components/hpprinter/diagnostics.py @@ -0,0 +1,108 @@ +"""Diagnostics support for Tuya.""" +from __future__ import annotations + +import logging +from typing import Any + +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers import device_registry as dr, entity_registry as er +from homeassistant.helpers.device_registry import DeviceEntry + +from .common.consts import DOMAIN +from .managers.ha_coordinator import HACoordinator +from .managers.rest_api import RestAPIv2 + +_LOGGER = logging.getLogger(__name__) + + +async def async_get_config_entry_diagnostics( + hass: HomeAssistant, entry: ConfigEntry +) -> dict[str, Any]: + """Return diagnostics for a config entry.""" + _LOGGER.debug("Starting diagnostic tool") + + return await _async_get_diagnostics(hass, entry) + + +async def async_get_device_diagnostics( + hass: HomeAssistant, entry: ConfigEntry, device: DeviceEntry +) -> dict[str, Any]: + """Return diagnostics for a device entry.""" + return await _async_get_diagnostics(hass, entry, device) + + +async def _async_get_diagnostics( + hass: HomeAssistant, + entry: ConfigEntry, + _device: DeviceEntry | None = None, +) -> dict[str, Any]: + """Return diagnostics for a config entry.""" + _LOGGER.debug("Getting diagnostic information") + + coordinator: HACoordinator = hass.data[DOMAIN][entry.entry_id] + + rest_api = RestAPIv2(hass, coordinator.config_manager) + + await rest_api.initialize() + + data = { + "disabled_by": entry.disabled_by, + "disabled_polling": entry.pref_disable_polling, + "debug": await coordinator.get_debug_data(), + } + + return data + + +@callback +def _async_device_as_dict( + hass: HomeAssistant, identifiers, additional_data: dict +) -> dict[str, Any]: + """Represent a Shinobi monitor as a dictionary.""" + device_registry = dr.async_get(hass) + entity_registry = er.async_get(hass) + + ha_device = device_registry.async_get_device(identifiers=identifiers) + data = {} + + if ha_device: + data["device"] = { + "name": ha_device.name, + "name_by_user": ha_device.name_by_user, + "disabled": ha_device.disabled, + "disabled_by": ha_device.disabled_by, + "data": additional_data, + "entities": [], + } + + ha_entities = er.async_entries_for_device( + entity_registry, + device_id=ha_device.id, + include_disabled_entities=True, + ) + + for entity_entry in ha_entities: + state = hass.states.get(entity_entry.entity_id) + state_dict = None + if state: + state_dict = dict(state.as_dict()) + + # The context doesn't provide useful information in this case. + state_dict.pop("context", None) + + data["device"]["entities"].append( + { + "disabled": entity_entry.disabled, + "disabled_by": entity_entry.disabled_by, + "entity_category": entity_entry.entity_category, + "device_class": entity_entry.device_class, + "original_device_class": entity_entry.original_device_class, + "icon": entity_entry.icon, + "original_icon": entity_entry.original_icon, + "unit_of_measurement": entity_entry.unit_of_measurement, + "state": state_dict, + } + ) + + return data diff --git a/custom_components/hpprinter/helpers/__init__.py b/custom_components/hpprinter/helpers/__init__.py deleted file mode 100644 index bcffd4e..0000000 --- a/custom_components/hpprinter/helpers/__init__.py +++ /dev/null @@ -1,53 +0,0 @@ -import logging -import sys - -from homeassistant.components.logger import DOMAIN as DOMAIN_LOGGER, SERVICE_SET_LEVEL -from homeassistant.config_entries import ConfigEntry -from homeassistant.core import HomeAssistant - -from ..managers.home_assistant import HPPrinterHomeAssistant -from .const import * - -_LOGGER = logging.getLogger(__name__) - - -def clear_ha(hass: HomeAssistant, name): - if DATA_HP_PRINTER not in hass.data: - hass.data[DATA_HP_PRINTER] = {} - - del hass.data[DATA_HP_PRINTER][name] - - -def get_ha(hass: HomeAssistant, host): - ha_data = hass.data.get(DATA_HP_PRINTER, {}) - ha = ha_data.get(host) - - return ha - - -async def async_set_ha(hass: HomeAssistant, name, entry: ConfigEntry): - try: - if DATA_HP_PRINTER not in hass.data: - hass.data[DATA_HP_PRINTER] = {} - - instance = HPPrinterHomeAssistant(hass) - - await instance.async_init(entry) - - hass.data[DATA_HP_PRINTER][name] = instance - except Exception as ex: - exc_type, exc_obj, tb = sys.exc_info() - line_number = tb.tb_lineno - - _LOGGER.error(f"Failed to async_set_ha, error: {ex}, line: {line_number}") - - -async def handle_log_level(hass: HomeAssistant, entry: ConfigEntry): - log_level = entry.options.get(CONF_LOG_LEVEL, LOG_LEVEL_DEFAULT) - - if log_level == LOG_LEVEL_DEFAULT: - return - - log_level_data = {f"custom_components.{DOMAIN}": log_level.lower()} - - await hass.services.async_call(DOMAIN_LOGGER, SERVICE_SET_LEVEL, log_level_data) diff --git a/custom_components/hpprinter/helpers/const.py b/custom_components/hpprinter/helpers/const.py deleted file mode 100644 index 89d55f6..0000000 --- a/custom_components/hpprinter/helpers/const.py +++ /dev/null @@ -1,151 +0,0 @@ -from homeassistant.components.binary_sensor import DOMAIN as DOMAIN_BINARY_SENSOR -from homeassistant.components.sensor import DOMAIN as DOMAIN_SENSOR - -MANUFACTURER = "HP" -DEFAULT_NAME = "HP Printer" -DOMAIN = "hpprinter" -DATA_HP_PRINTER = f"data_{DOMAIN}" -SIGNAL_UPDATE_HP_PRINTER = f"updates_{DOMAIN}" -NOTIFICATION_ID = f"{DOMAIN}_notification" -NOTIFICATION_TITLE = f"{DEFAULT_NAME} Setup" - -SENSOR_ENTITY_ID = "sensor.{}_{}" -BINARY_SENSOR_ENTITY_ID = "binary_sensor.{}_{}" - -NAMESPACES_TO_REMOVE = [ - "ccdyn", - "ad", - "dd", - "dd2", - "pudyn", - "psdyn", - "xsd", - "pscat", - "locid", - "prdcfgdyn2", - "prdcfgdyn", -] - -CONF_STORE_DATA = "store_data" -CONF_UPDATE_INTERVAL = "update_interval" -CONF_LOG_LEVEL = "log_level" - -ENTITY_ICON = "icon" -ENTITY_STATE = "state" -ENTITY_ATTRIBUTES = "attributes" -ENTITY_NAME = "name" -ENTITY_MODEL = "model" -ENTITY_MODEL_FAMILY = "model-family" -ENTITY_DEVICE_NAME = "device-name" -ENTITY_UNIQUE_ID = "unique-id" -ENTITY_BINARY_SENSOR_DEVICE_CLASS = "binary-sensor-device-class" -ENTITY_SENSOR_DEVICE_CLASS = "sensor-device-class" -ENTITY_SENSOR_STATE_CLASS = "sensor-state-class" - -ENTITY_STATUS = "entity-status" -ENTITY_STATUS_EMPTY = None -ENTITY_STATUS_READY = f"{ENTITY_STATUS}-ready" -ENTITY_STATUS_CREATED = f"{ENTITY_STATUS}-created" -ENTITY_STATUS_MODIFIED = f"{ENTITY_STATUS}-modified" -ENTITY_STATUS_IGNORE = f"{ENTITY_STATUS}-ignore" -ENTITY_STATUS_CANCELLED = f"{ENTITY_STATUS}-cancelled" - -ENTITY_DISABLED = "disabled" - -PRINTER_CURRENT_STATUS = "status" -PRINTER_SENSOR = "Printer" - -INK_ICON = "mdi:cup-water" -PAGES_ICON = "mdi:book-open-page-variant" -SCANNER_ICON = "mdi:scanner" - -PROTOCOLS = {True: "https", False: "http"} - -IGNORE_ITEMS = [ - "@xsi:schemaLocation", - "@xmlns:xsd", - "@xmlns:dd", - "@xmlns:dd2", - "@xmlns:ccdyn", - "@xmlns:xsi", - "@xmlns:pudyn", - "@xmlns:ad", - "@xmlns:psdyn", - "@xmlns:pscat", - "@xmlns:locid", - "@xmlns:locid", - "@xmlns:prdcfgdyn", - "@xmlns:prdcfgdyn2", - "@xmlns:pudyn", - "PECounter", -] - -ARRAY_KEYS = { - "UsageByMedia": [], - "SupportedConsumable": ["ConsumableTypeEnum", "ConsumableLabelCode"], - "SupportedConsumableInfo": ["ConsumableUsageType"], - "EmailAlertCategories": ["AlertCategory"], -} - -ARRAY_AS_DEFAULT = [ - "AlertDetailsUserAction", - "ConsumableStateAction", - "AlertCategory", - "ResourceURI", - "Language", - "AutoOnEvent", - "DaysOfWeek", -] - -HP_DEVICE_CONNECTIVITY = "Connectivity" -HP_DEVICE_STATUS = "Status" -HP_DEVICE_PRINTER = "Printer" -HP_DEVICE_SCANNER = "Scanner" -HP_DEVICE_CARTRIDGES = "Cartridges" - -HP_DEVICE_PRINTER_STATE = "Total" -HP_DEVICE_SCANNER_STATE = "Total" -HP_DEVICE_CARTRIDGE_STATE = "Remaining" - -HP_DEVICE_IS_ONLINE = "IsOnline" - -HP_HEAD_TYPE_INK = "ink" -HP_HEAD_TYPE_PRINT_HEAD = "printhead" -HP_ORGANIC_PHOTO_CONDUCTOR = "OPC" -HP_ORGANIC_PHOTO_CONDUCTOR_NAME = "Organic Photo Conductor" - -NOT_AVAILABLE = "N/A" - -HP_INK_MAPPING = {"C": "Cyan", "Y": "Yellow", "M": "Magenta", "K": "Black"} - -SIGNAL_UPDATE_BINARY_SENSOR = f"{DEFAULT_NAME}_{DOMAIN_BINARY_SENSOR}_SIGNAL_UPDATE" -SIGNAL_UPDATE_SENSOR = f"{DEFAULT_NAME}_{DOMAIN_SENSOR}_SIGNAL_UPDATE" - -SIGNALS = { - DOMAIN_BINARY_SENSOR: SIGNAL_UPDATE_BINARY_SENSOR, - DOMAIN_SENSOR: SIGNAL_UPDATE_SENSOR, -} - -LOG_LEVEL_DEFAULT = "Default" -LOG_LEVEL_DEBUG = "Debug" -LOG_LEVEL_INFO = "Info" -LOG_LEVEL_WARNING = "Warning" -LOG_LEVEL_ERROR = "Error" - -LOG_LEVELS = [ - LOG_LEVEL_DEFAULT, - LOG_LEVEL_DEBUG, - LOG_LEVEL_INFO, - LOG_LEVEL_WARNING, - LOG_LEVEL_ERROR, -] - -PRINTER_STATUS = { - "ready": "On", - "scanProcessing": "Scanning", - "copying": "Copying", - "processing": "Printing", - "cancelJob": "Cancelling Job", - "inPowerSave": "Idle", - "": "Off", -} diff --git a/custom_components/hpprinter/managers/HPDeviceData.py b/custom_components/hpprinter/managers/HPDeviceData.py deleted file mode 100644 index 780eced..0000000 --- a/custom_components/hpprinter/managers/HPDeviceData.py +++ /dev/null @@ -1,445 +0,0 @@ -from custom_components.hpprinter.api.HPPrinterAPI import * - -from ..models.config_data import ConfigData -from .storage_manager import StorageManager - -_LOGGER = logging.getLogger(__name__) - - -class HPDeviceData: - device_data: dict - - def __init__(self, hass, config_manager: ConfigManager): - self._hass = hass - self._config_manager = config_manager - - self._storage_manager = StorageManager(self._hass, self._config_manager) - - self._usage_data_manager = ProductUsageDynPrinterDataAPI( - hass, self._config_manager - ) - self._consumable_data_manager = ConsumableConfigDynPrinterDataAPI( - hass, self._config_manager - ) - self._product_config_manager = ProductConfigDynDataAPI( - hass, self._config_manager - ) - self._product_status_manager = ProductStatusDynDataAPI( - hass, self._config_manager - ) - - self._usage_data = None - self._consumable_data = None - self._product_config_data = None - self._product_status_data = None - self.device_data = {} - - @property - def config_data(self) -> ConfigData: - return self._config_manager.data - - @property - def name(self): - return self.config_data.name - - @property - def host(self): - return self.config_data.host - - async def initialize(self): - _LOGGER.debug("Initialize") - - self.device_data = await self._storage_manager.async_load_from_store() - - if self.device_data is None: - self.device_data = {} - - self.device_data[PRINTER_CURRENT_STATUS] = PRINTER_STATUS[""] - self.device_data[HP_DEVICE_IS_ONLINE] = False - - async def terminate(self): - await self._usage_data_manager.terminate() - await self._consumable_data_manager.terminate() - await self._product_config_manager.terminate() - await self._product_status_manager.terminate() - - async def update(self): - try: - self.device_data["Name"] = self.config_data.name - - self._usage_data = await self._usage_data_manager.get_data() - self._consumable_data = await self._consumable_data_manager.get_data() - self._product_config_data = await self._product_config_manager.get_data() - self._product_status_data = await self._product_status_manager.get_data() - - data_list = [ - self._usage_data, - self._consumable_data, - self._product_config_data, - self._product_status_data, - ] - - is_online = True - - for item in data_list: - if item is None: - is_online = False - break - - if is_online: - self.set_usage_data() - self.set_consumable_data() - self.set_product_config_data() - self.set_product_status_data() - else: - self.device_data[PRINTER_CURRENT_STATUS] = PRINTER_STATUS[""] - - self.device_data[HP_DEVICE_IS_ONLINE] = is_online - - if is_online: - await self._storage_manager.async_save_to_store(self.device_data) - - except Exception as ex: - exc_type, exc_obj, tb = sys.exc_info() - line_number = tb.tb_lineno - error_details = f"Error: {ex}, Line: {line_number}" - - _LOGGER.error( - f"Failed to update data ({self.name} @{self.host}) and parse it, {error_details}" - ) - - def set_consumable_data(self): - try: - if self._consumable_data is not None: - root = self._consumable_data.get("ConsumableConfigDyn", {}) - consumables_info = root.get("ConsumableInfo", []) - - if "ConsumableLabelCode" in consumables_info: - self.set_printer_consumable_data(consumables_info) - else: - for consumable_key in consumables_info: - consumable = consumables_info[consumable_key] - - self.set_printer_consumable_data(consumable) - - except Exception as ex: - exc_type, exc_obj, tb = sys.exc_info() - line_number = tb.tb_lineno - error_details = f"Error: {ex}, Line: {line_number}" - - _LOGGER.error( - f"Failed to parse consumable data ({self.name} @{self.host}), {error_details}" - ) - - def set_product_config_data(self): - try: - if self._product_config_data is not None: - root = self._product_config_data.get("ProductConfigDyn", {}) - product_information = root.get("ProductInformation", {}) - self.device_data[ENTITY_MODEL] = product_information.get("MakeAndModel") - - except Exception as ex: - exc_type, exc_obj, tb = sys.exc_info() - line_number = tb.tb_lineno - - _LOGGER.error( - f"Failed to parse usage data ({self.name} @{self.host}), Error: {ex}, Line: {line_number}" - ) - - def set_product_status_data(self): - try: - if self._product_status_data is not None: - root = self._product_status_data.get("ProductStatusDyn", {}) - status = root.get("Status", []) - printer_status = "" - - if "StatusCategory" in status: - printer_status = self.clean_parameter(status, "StatusCategory") - else: - for item in status: - status_item = status[item] - if "LocString" not in status_item: - printer_status = self.clean_parameter( - status_item, "StatusCategory" - ) - - self.device_data[PRINTER_CURRENT_STATUS] = PRINTER_STATUS.get( - printer_status, printer_status - ) - - except Exception as ex: - exc_type, exc_obj, tb = sys.exc_info() - line_number = tb.tb_lineno - - _LOGGER.error( - f"Failed to parse usage data ({self.name} @{self.host}), Error: {ex}, Line: {line_number}" - ) - - def set_usage_data(self): - try: - if self._usage_data is not None: - root = self._usage_data.get("ProductUsageDyn", {}) - printer_data = root.get("PrinterSubunit") - scanner_data = root.get("ScannerEngineSubunit") - consumables_data = root.get("ConsumableSubunit") - - if printer_data is not None: - self.set_printer_usage_data(printer_data) - - if scanner_data is not None: - self.set_scanner_usage_data(scanner_data) - - if consumables_data is not None: - printer_consumables = consumables_data.get("Consumable") - - if printer_consumables is not None: - if "ConsumableStation" in printer_consumables: - self.set_printer_consumable_usage_data(printer_consumables) - else: - for key in printer_consumables: - consumable = printer_consumables.get(key) - - if consumable is not None: - self.set_printer_consumable_usage_data(consumable) - - except Exception as ex: - exc_type, exc_obj, tb = sys.exc_info() - line_number = tb.tb_lineno - - _LOGGER.error( - f"Failed to parse usage data ({self.name} @{self.host}), Error: {ex}, Line: {line_number}" - ) - - def set_printer_usage_data(self, printer_data): - try: - total_printed_pages = self.clean_parameter( - printer_data, "TotalImpressions", "0" - ) - - color_printed_pages = self.clean_parameter(printer_data, "ColorImpressions") - monochrome_printed_pages = self.clean_parameter( - printer_data, "MonochromeImpressions" - ) - - printer_jams = self.clean_parameter(printer_data, "Jams") - if printer_jams == NOT_AVAILABLE: - printer_jams = self.clean_parameter(printer_data, "JamEvents", "0") - - cancelled_print_jobs_number = self.clean_parameter( - printer_data, "TotalFrontPanelCancelPresses" - ) - - self.device_data[HP_DEVICE_PRINTER] = { - HP_DEVICE_PRINTER_STATE: total_printed_pages, - "Color": color_printed_pages, - "Monochrome": monochrome_printed_pages, - "Jams": printer_jams, - "Cancelled": cancelled_print_jobs_number, - } - - except Exception as ex: - exc_type, exc_obj, tb = sys.exc_info() - line_number = tb.tb_lineno - - _LOGGER.error( - f"Failed to set printer data ({self.name} @{self.host}), Error: {ex}, Line: {line_number}" - ) - - def set_scanner_usage_data(self, scanner_data): - try: - scan_images_count = self.clean_parameter(scanner_data, "ScanImages") - adf_images_count = self.clean_parameter(scanner_data, "AdfImages") - duplex_sheets_count = self.clean_parameter(scanner_data, "DuplexSheets") - flatbed_images = self.clean_parameter(scanner_data, "FlatbedImages") - scanner_jams = self.clean_parameter(scanner_data, "JamEvents", "0") - scanner_mispick = self.clean_parameter(scanner_data, "MispickEvents", "0") - - if scan_images_count == NOT_AVAILABLE: - new_scan_images_count = 0 - - if adf_images_count != NOT_AVAILABLE and int(adf_images_count) > 0: - new_scan_images_count = int(adf_images_count) - - if flatbed_images != NOT_AVAILABLE and int(flatbed_images) > 0: - new_scan_images_count = new_scan_images_count + int(flatbed_images) - - scan_images_count = new_scan_images_count - - self.device_data[HP_DEVICE_SCANNER] = { - HP_DEVICE_SCANNER_STATE: scan_images_count, - "ADF": adf_images_count, - "Duplex": duplex_sheets_count, - "Flatbed": flatbed_images, - "Jams": scanner_jams, - "Mispick": scanner_mispick, - } - - except Exception as ex: - exc_type, exc_obj, tb = sys.exc_info() - line_number = tb.tb_lineno - - _LOGGER.error( - f"Failed to set scanner data ({self.name} @{self.host}), Error: {ex}, Line: {line_number}" - ) - - def set_printer_consumable_usage_data(self, printer_consumable_data): - try: - color = self.clean_parameter(printer_consumable_data, "MarkerColor") - head_type = self.clean_parameter( - printer_consumable_data, "ConsumableTypeEnum" - ).capitalize() - station = self.clean_parameter(printer_consumable_data, "ConsumableStation") - - if NOT_AVAILABLE in head_type.upper() or NOT_AVAILABLE in color: - _LOGGER.info(f"Skipped setting using data for {head_type} {color}") - - return - - cartridge_key = f"{head_type} {color}" - - should_create_cartridges = False - should_create_cartridge = False - - cartridges = self.device_data.get(HP_DEVICE_CARTRIDGES) - if cartridges is None: - cartridges = {} - should_create_cartridges = True - - cartridge = cartridges.get(cartridge_key) - - if cartridge is None: - cartridge = {} - should_create_cartridge = True - - cartridge["Color"] = color - cartridge["Type"] = head_type - cartridge["Station"] = station - - if should_create_cartridge: - cartridges[cartridge_key] = cartridge - - if should_create_cartridges: - self.device_data[HP_DEVICE_CARTRIDGES] = cartridges - - except Exception as ex: - exc_type, exc_obj, tb = sys.exc_info() - line_number = tb.tb_lineno - error_details = f"Error: {ex}, Line: {line_number}" - - _LOGGER.error( - f"Failed to set printer consumable usage data ({self.name} @{self.host}), {error_details}" - ) - - def set_printer_consumable_data(self, printer_consumable_data): - try: - consumable_label_code = self.clean_parameter( - printer_consumable_data, "ConsumableLabelCode" - ) - head_type = self.clean_parameter( - printer_consumable_data, "ConsumableTypeEnum" - ).capitalize() - product_number = self.clean_parameter( - printer_consumable_data, "ProductNumber" - ) - serial_number = self.clean_parameter( - printer_consumable_data, "SerialNumber" - ) - remaining = self.clean_parameter( - printer_consumable_data, "ConsumablePercentageLevelRemaining", "0" - ) - - installation = printer_consumable_data.get("Installation", {}) - installation_data = self.clean_parameter(installation, "Date") - - manufacturer = printer_consumable_data.get("Manufacturer", {}) - manufactured_by = self.clean_parameter(manufacturer, "Name").rstrip() - manufactured_at = self.clean_parameter(manufacturer, "Date") - - warranty = printer_consumable_data.get("Warranty", {}) - expiration_date = self.clean_parameter(warranty, "ExpirationDate") - - if head_type == HP_HEAD_TYPE_PRINT_HEAD: - color = consumable_label_code - else: - color_map = [] - - if consumable_label_code == HP_ORGANIC_PHOTO_CONDUCTOR: - color = HP_ORGANIC_PHOTO_CONDUCTOR_NAME - else: - for color_letter in consumable_label_code: - mapped_color = HP_INK_MAPPING.get(color_letter, color_letter) - - color_map.append(mapped_color) - - color = "".join(color_map) - - if color == consumable_label_code: - _LOGGER.warning( - f"Head type {head_type} color mapping for {consumable_label_code} not available" - ) - - if NOT_AVAILABLE in head_type.upper() or NOT_AVAILABLE in color: - _LOGGER.info(f"Skipped setting {head_type} {color}") - - return - - cartridge_key = f"{head_type} {color}" - - should_create_cartridges = False - should_create_cartridge = False - - cartridges = self.device_data.get(HP_DEVICE_CARTRIDGES) - if cartridges is None: - cartridges = {} - should_create_cartridges = True - - cartridge = cartridges.get(cartridge_key) - - if cartridge is None: - cartridge = {} - should_create_cartridge = True - - if head_type == HP_HEAD_TYPE_PRINT_HEAD: - cartridge["Color"] = color - cartridge["Type"] = head_type - - else: - cartridge["Product Number"] = product_number - cartridge["Serial Number"] = serial_number - cartridge["Manufactured By"] = manufactured_by - cartridge["Manufactured At"] = manufactured_at - cartridge["Warranty Expiration Date"] = expiration_date - - cartridge["Installed At"] = installation_data - cartridge[HP_DEVICE_CARTRIDGE_STATE] = remaining - - if should_create_cartridge: - cartridges[cartridge_key] = cartridge - - if should_create_cartridges: - self.device_data[HP_DEVICE_CARTRIDGES] = cartridges - - except Exception as ex: - exc_type, exc_obj, tb = sys.exc_info() - line_number = tb.tb_lineno - - error_details = f"Error: {str(ex)}, Line: {line_number}" - - _LOGGER.error( - f"Failed to set printer consumable data ({self.name} @{self.host}), {error_details}" - ) - - @staticmethod - def clean_parameter(data_item, data_key, default_value=NOT_AVAILABLE): - if data_item is None: - result = default_value - else: - result = data_item.get(data_key, {}) - - if not isinstance(result, str): - result = result.get("#text", 0) - - if not isinstance(result, str): - result = default_value - - return result diff --git a/custom_components/hpprinter/managers/config_flow_manager.py b/custom_components/hpprinter/managers/config_flow_manager.py deleted file mode 100644 index ef47963..0000000 --- a/custom_components/hpprinter/managers/config_flow_manager.py +++ /dev/null @@ -1,189 +0,0 @@ -import logging -from typing import Optional - -import voluptuous as vol - -from homeassistant.config_entries import ConfigEntry -from homeassistant.const import CONF_HOST, CONF_NAME -from homeassistant.helpers import config_validation as cv - -from .. import LoginError -from ..api.HPPrinterAPI import ProductConfigDynDataAPI -from ..helpers.const import * -from ..managers.configuration_manager import ConfigManager -from ..models import AlreadyExistsError -from ..models.config_data import ConfigData - -_LOGGER = logging.getLogger(__name__) -_CONF_ARR = [CONF_NAME, CONF_HOST] - - -class ConfigFlowManager: - config_manager: ConfigManager - options: Optional[dict] - data: Optional[dict] - config_entry: ConfigEntry - - def __init__(self, config_entry: Optional[ConfigEntry] = None): - self.config_entry = config_entry - - self.options = None - self.data = None - self._pre_config = False - - if config_entry is not None: - self._pre_config = True - - self.update_data(self.config_entry.data) - - self._is_initialized = True - self._auth_error = False - self._hass = None - - def initialize(self, hass): - self._hass = hass - - if not self._pre_config: - self.options = {} - self.data = {} - - self.config_manager = ConfigManager() - - self._update_entry() - - @property - def config_data(self) -> ConfigData: - return self.config_manager.data - - async def update_options(self, options: dict, update_entry: bool = False): - new_options = {} - validate_login = False - config_entries = None - - if update_entry: - config_entries = self._hass.config_entries - - data = self.config_entry.data - name_changed = False - - for conf in _CONF_ARR: - if data.get(conf) != options.get(conf): - validate_login = True - - if conf == CONF_NAME: - name_changed = True - - if name_changed: - entries = config_entries.async_entries(DOMAIN) - - for entry in entries: - entry_item: ConfigEntry = entry - - if entry_item.unique_id == self.config_entry.unique_id: - continue - - if options.get(CONF_NAME) == entry_item.data.get(CONF_NAME): - raise AlreadyExistsError(entry_item) - - new_options = {} - for key in options: - new_options[key] = options[key] - - if update_entry: - for conf in _CONF_ARR: - if conf in new_options: - self.data[conf] = new_options[conf] - - del new_options[conf] - - self.options = new_options - - self._update_entry() - - if validate_login: - errors = await self.valid_login() - - if errors is None: - config_entries.async_update_entry(self.config_entry, data=self.data) - else: - raise LoginError(errors) - - return new_options - - def update_data(self, data: dict, update_entry: bool = False): - new_data = None - - if data is not None: - new_data = {} - for key in data: - new_data[key] = data[key] - - self.data = new_data - - if update_entry: - self._update_entry() - - def _update_entry(self): - entry = ConfigEntry( - version=0, - minor_version=0, - domain="", - title="", - data=self.data, - source="", - options=self.options, - ) - - self.config_manager.update(entry) - - @staticmethod - def get_default_data(): - fields = { - vol.Required(CONF_NAME, default=DEFAULT_NAME): str, - vol.Required(CONF_HOST): str, - } - - data_schema = vol.Schema(fields) - - return data_schema - - def get_default_options(self): - config_data = self.config_data - - fields = { - vol.Required(CONF_NAME, default=config_data.name): str, - vol.Required(CONF_HOST, default=config_data.host): str, - vol.Optional(CONF_STORE_DATA, default=config_data.should_store): bool, - vol.Required( - CONF_UPDATE_INTERVAL, default=config_data.update_interval - ): cv.positive_int, - vol.Required(CONF_LOG_LEVEL, default=config_data.log_level): vol.In( - LOG_LEVELS - ), - } - - data_schema = vol.Schema(fields) - - return data_schema - - async def valid_login(self): - errors = None - - config_data = self.config_manager.data - - api = ProductConfigDynDataAPI(self._hass, self.config_manager) - - try: - await api.async_get(True) - except LoginError as ex: - _LOGGER.info( - f"Unable to access {DEFAULT_NAME} ({config_data.host}), HTTP Status Code {ex.status_code}" - ) - - status_code = ex.status_code - if status_code not in [400, 404]: - status_code = 400 - - errors = {"base": f"error_{status_code}"} - - return errors diff --git a/custom_components/hpprinter/managers/configuration_manager.py b/custom_components/hpprinter/managers/configuration_manager.py deleted file mode 100644 index d170075..0000000 --- a/custom_components/hpprinter/managers/configuration_manager.py +++ /dev/null @@ -1,41 +0,0 @@ -from homeassistant.config_entries import ConfigEntry -from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PORT, CONF_SSL - -from ..helpers.const import * -from ..models.config_data import ConfigData - - -class ConfigManager: - data: ConfigData - config_entry: ConfigEntry - - def update(self, config_entry: ConfigEntry): - data = config_entry.data - options = config_entry.options - - result = ConfigData() - - result.name = data.get(CONF_NAME) - result.host = data.get(CONF_HOST) - result.port = data.get(CONF_PORT, 80) - result.ssl = data.get(CONF_SSL, False) - result.should_store = self._get_config_data_item( - CONF_STORE_DATA, options, data, False - ) - result.update_interval = self._get_config_data_item( - CONF_UPDATE_INTERVAL, options, data, 60 - ) - result.log_level = self._get_config_data_item( - CONF_LOG_LEVEL, options, data, LOG_LEVEL_DEFAULT - ) - - self.config_entry = config_entry - self.data = result - - @staticmethod - def _get_config_data_item(key, options, data, default_value=None): - data_result = data.get(key, default_value) - - result = options.get(key, data_result) - - return result diff --git a/custom_components/hpprinter/managers/device_manager.py b/custom_components/hpprinter/managers/device_manager.py deleted file mode 100644 index 276b7a1..0000000 --- a/custom_components/hpprinter/managers/device_manager.py +++ /dev/null @@ -1,87 +0,0 @@ -import logging - -from homeassistant.const import ( - ATTR_CONFIGURATION_URL, - ATTR_IDENTIFIERS, - ATTR_MANUFACTURER, - ATTR_MODEL, - ATTR_NAME, -) -from homeassistant.helpers.device_registry import async_get - -from ..helpers.const import * -from ..managers.HPDeviceData import HPDeviceData - -_LOGGER = logging.getLogger(__name__) - - -class DeviceManager: - def __init__(self, hass, ha): - self._hass = hass - self._ha = ha - - self._devices = {} - - @property - def data_manager(self) -> HPDeviceData: - return self._ha.data_manager - - @property - def data(self): - return self.data_manager.device_data - - @property - def name(self): - return self.data_manager.name - - async def async_remove_entry(self, entry_id): - dr = async_get(self._hass) - dr.async_clear_config_entry(entry_id) - - async def delete_device(self, name): - _LOGGER.info(f"Deleting device {name}") - - device = self._devices[name] - - device_identifiers = device.get("identifiers") - device_connections = device.get("connections", {}) - - dr = async_get(self._hass) - - device = dr.async_get_device(device_identifiers, device_connections) - - if device is not None: - dr.async_remove_device(device.id) - - async def async_remove(self): - for device_name in self._devices: - await self.delete_device(device_name) - - def get(self, name): - return self._devices.get(name, {}) - - def set(self, name, device_info): - self._devices[name] = device_info - - def update(self): - self.generate_device_info() - - def generate_device_info(self): - device_model = self.data.get(ENTITY_MODEL, self.name) - device_model_family = self.data.get(ENTITY_MODEL_FAMILY, self.name) - - device_id = f"{DEFAULT_NAME}-{self.name}-{device_model_family}" - - device_info = { - ATTR_IDENTIFIERS: {(DOMAIN, device_id)}, - ATTR_NAME: device_model_family, - ATTR_MANUFACTURER: MANUFACTURER, - ATTR_MODEL: device_model, - } - - if self.data_manager.device_data[HP_DEVICE_IS_ONLINE]: - device_info[ - ATTR_CONFIGURATION_URL - ] = f"{PROTOCOLS[self.data_manager.config_data.ssl]}://{self.data_manager.config_data.host}" - - self.set(DEFAULT_NAME, device_info) diff --git a/custom_components/hpprinter/managers/entity_manager.py b/custom_components/hpprinter/managers/entity_manager.py deleted file mode 100644 index 57960f2..0000000 --- a/custom_components/hpprinter/managers/entity_manager.py +++ /dev/null @@ -1,361 +0,0 @@ -import logging -import sys -from typing import Optional - -from homeassistant.components.binary_sensor import BinarySensorDeviceClass -from homeassistant.components.sensor import SensorStateClass -from homeassistant.core import HomeAssistant -from homeassistant.helpers.entity_registry import EntityRegistry - -from ..helpers.const import * -from ..managers.HPDeviceData import HPDeviceData -from ..models.config_data import ConfigData -from ..models.entity_data import EntityData - -_LOGGER = logging.getLogger(__name__) - - -def _get_camera_binary_sensor_key(topic, event_type): - key = f"{topic}_{event_type}".lower() - - return key - - -class EntityManager: - hass: HomeAssistant - ha = None - entities: dict - domain_component_manager: dict - - def __init__(self, hass, ha): - self.hass = hass - self.ha = ha - self.domain_component_manager = {} - self.entities = {} - - @property - def data_manager(self) -> HPDeviceData: - return self.ha.data_manager - - @property - def data(self): - return self.data_manager.device_data - - @property - def entity_registry(self) -> EntityRegistry: - return self.ha.entity_registry - - @property - def config_data(self) -> ConfigData: - return self.ha.config_data - - def set_domain_component(self, domain, async_add_entities, component): - self.domain_component_manager[domain] = { - "async_add_entities": async_add_entities, - "component": component, - } - - def is_device_name_in_use(self, device_name): - result = False - - for entity in self.get_all_entities(): - if entity.device_name == device_name: - result = True - break - - return result - - def get_all_entities(self) -> list[EntityData]: - entities = [] - for domain in self.entities: - for name in self.entities[domain]: - entity = self.entities[domain][name] - - entities.append(entity) - - return entities - - def check_domain(self, domain): - if domain not in self.entities: - self.entities[domain] = {} - - def get_entities(self, domain) -> dict[str, EntityData]: - self.check_domain(domain) - - return self.entities[domain] - - def get_entity(self, domain, name) -> Optional[EntityData]: - entities = self.get_entities(domain) - entity = entities.get(name) - - return entity - - def get_entity_status(self, domain, name): - entity = self.get_entity(domain, name) - - status = ENTITY_STATUS_EMPTY if entity is None else entity.status - - return status - - def set_entity_status(self, domain, name, status): - if domain in self.entities and name in self.entities[domain]: - self.entities[domain][name].status = status - - def delete_entity(self, domain, name): - if domain in self.entities and name in self.entities[domain]: - del self.entities[domain][name] - - def set_entity(self, domain, name, data: EntityData): - try: - self.check_domain(domain) - - self.entities[domain][name] = data - except Exception as ex: - self.log_exception( - ex, f"Failed to set_entity, domain: {domain}, name: {name}" - ) - - async def create_components(self): - cartridges_data = self.data.get(HP_DEVICE_CARTRIDGES) - - self.create_status_binary_sensor() - self.create_status_sensor() - self.create_printer_sensor() - self.create_scanner_sensor() - - if cartridges_data is not None: - for key in cartridges_data: - cartridge = cartridges_data.get(key) - - if cartridge is not None: - self.create_cartridge_sensor(cartridge, key) - - def update(self): - self.hass.async_create_task(self._async_update()) - - async def _async_update(self): - step = "Mark as ignore" - try: - step = "Create components" - - await self.create_components() - - step = "Start updating" - - for domain in SIGNALS: - step = f"Start updating domain {domain}" - - entities_to_add = [] - domain_component_manager = self.domain_component_manager[domain] - domain_component = domain_component_manager["component"] - async_add_entities = domain_component_manager["async_add_entities"] - - entities = dict(self.get_entities(domain)) - - for entity_key in entities: - step = f"Start updating {domain} -> {entity_key}" - - entity = entities[entity_key] - - entity_id = self.entity_registry.async_get_entity_id( - domain, DOMAIN, entity.unique_id - ) - - if entity.status == ENTITY_STATUS_CREATED: - entity_item = self.entity_registry.async_get(entity_id) - - step = f"Mark as created - {domain} -> {entity_key}" - - entity_component = domain_component( - self.hass, self.config_data.name, entity - ) - - if entity_id is not None: - entity_component.entity_id = entity_id - - state = self.hass.states.get(entity_id) - - if state is None: - restored = True - else: - restored = state.attributes.get("restored", False) - - if restored: - _LOGGER.info( - f"Entity {entity.name} restored | {entity_id}" - ) - - if restored: - if entity_item is None or not entity_item.disabled: - entities_to_add.append(entity_component) - else: - entities_to_add.append(entity_component) - - entity.status = ENTITY_STATUS_READY - - if entity_item is not None: - entity.disabled = entity_item.disabled - - step = f"Add entities to {domain}" - - if len(entities_to_add) > 0: - async_add_entities(entities_to_add, True) - - except Exception as ex: - self.log_exception(ex, f"Failed to update, step: {step}") - - def create_status_sensor(self): - status = self.data.get(PRINTER_CURRENT_STATUS, "Off") - - name = self.data.get("Name", DEFAULT_NAME) - entity_name = f"{name} {HP_DEVICE_STATUS}" - - device_name = DEFAULT_NAME - unique_id = entity_name - - icon = self.get_printer_icon() - - attributes = {"friendly_name": entity_name} - - entity = EntityData() - - entity.unique_id = unique_id - entity.name = entity_name - entity.attributes = attributes - entity.icon = icon - entity.device_name = device_name - entity.binary_sensor_device_class = BinarySensorDeviceClass.CONNECTIVITY - entity.state = status - - self.set_entity(DOMAIN_SENSOR, entity_name, entity) - - def get_printer_name(self): - printer_name = self.data.get("Name", DEFAULT_NAME) - - return printer_name - - def is_online(self): - is_online = self.data.get(HP_DEVICE_IS_ONLINE, False) - - return is_online - - def get_printer_icon(self): - is_online = self.is_online() - - icon = "mdi:printer" if is_online else "mdi:printer-off" - - return icon - - def create_status_binary_sensor(self): - is_online = self.is_online() - - name = self.data.get("Name", DEFAULT_NAME) - entity_name = f"{name} {HP_DEVICE_CONNECTIVITY}" - unique_id = f"{DEFAULT_NAME}-{DOMAIN_BINARY_SENSOR}-{entity_name}" - device_name = DEFAULT_NAME - icon = self.get_printer_icon() - - attributes = {"friendly_name": entity_name} - - entity = EntityData() - - entity.unique_id = unique_id - entity.name = entity_name - entity.attributes = attributes - entity.icon = icon - entity.device_name = device_name - entity.state = is_online - entity.binary_sensor_device_class = BinarySensorDeviceClass.CONNECTIVITY - - self.set_entity(DOMAIN_BINARY_SENSOR, entity_name, entity) - - def create_printer_sensor(self): - printer_data = self.data.get(HP_DEVICE_PRINTER) - - if printer_data is not None: - name = self.get_printer_name() - entity_name = f"{name} {HP_DEVICE_PRINTER}" - unique_id = f"{DEFAULT_NAME}-{DOMAIN_SENSOR}-{entity_name}" - device_name = DEFAULT_NAME - - state = printer_data.get(HP_DEVICE_PRINTER_STATE) - - attributes = {"unit_of_measurement": "Pages", "friendly_name": entity_name} - - for key in printer_data: - if key != HP_DEVICE_PRINTER_STATE: - attributes[key] = printer_data[key] - - entity = EntityData() - - entity.unique_id = unique_id - entity.name = entity_name - entity.attributes = attributes - entity.icon = PAGES_ICON - entity.device_name = device_name - entity.state = state - entity.sensor_state_class = SensorStateClass.TOTAL_INCREASING - - self.set_entity(DOMAIN_SENSOR, entity_name, entity) - - def create_scanner_sensor(self): - scanner_data = self.data.get(HP_DEVICE_SCANNER) - - if scanner_data is not None: - name = self.get_printer_name() - entity_name = f"{name} {HP_DEVICE_SCANNER}" - unique_id = f"{DEFAULT_NAME}-{DOMAIN_SENSOR}-{entity_name}" - device_name = DEFAULT_NAME - - state = scanner_data.get(HP_DEVICE_SCANNER_STATE) - - attributes = {"unit_of_measurement": "Pages", "friendly_name": entity_name} - - for key in scanner_data: - if key != HP_DEVICE_SCANNER_STATE: - attributes[key] = scanner_data[key] - - entity = EntityData() - - entity.unique_id = unique_id - entity.name = entity_name - entity.attributes = attributes - entity.icon = SCANNER_ICON - entity.device_name = device_name - entity.state = state - entity.sensor_state_class = SensorStateClass.TOTAL_INCREASING - - self.set_entity(DOMAIN_SENSOR, entity_name, entity) - - def create_cartridge_sensor(self, cartridge, key): - name = self.get_printer_name() - entity_name = f"{name} {key}" - unique_id = f"{DEFAULT_NAME}-{DOMAIN_SENSOR}-{entity_name}" - device_name = DEFAULT_NAME - - state = cartridge.get(HP_DEVICE_CARTRIDGE_STATE, 0) - - attributes = {"unit_of_measurement": "%", "friendly_name": entity_name} - - for key in cartridge: - if key != HP_DEVICE_CARTRIDGE_STATE: - attributes[key] = cartridge[key] - - entity = EntityData() - - entity.unique_id = unique_id - entity.name = entity_name - entity.attributes = attributes - entity.icon = INK_ICON - entity.device_name = device_name - entity.state = state - entity.sensor_state_class = SensorStateClass.MEASUREMENT - - self.set_entity(DOMAIN_SENSOR, entity_name, entity) - - @staticmethod - def log_exception(ex, message): - exc_type, exc_obj, tb = sys.exc_info() - line_number = tb.tb_lineno - - _LOGGER.error(f"{message}, Error: {str(ex)}, Line: {line_number}") diff --git a/custom_components/hpprinter/managers/flow_manager.py b/custom_components/hpprinter/managers/flow_manager.py new file mode 100644 index 0000000..4a2291c --- /dev/null +++ b/custom_components/hpprinter/managers/flow_manager.py @@ -0,0 +1,104 @@ +"""Config flow to configure.""" +from __future__ import annotations + +from copy import copy +import logging +from typing import Any + +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.data_entry_flow import FlowHandler + +from ..common.consts import DATA_KEYS, DEFAULT_NAME +from ..models.config_data import ConfigData +from ..models.exceptions import IntegrationAPIError +from .ha_config_manager import HAConfigManager +from .rest_api import RestAPIv2 + +_LOGGER = logging.getLogger(__name__) + + +class IntegrationFlowManager: + _hass: HomeAssistant + _entry: ConfigEntry | None + + _flow_handler: FlowHandler + _flow_id: str + + _config_manager: HAConfigManager + + def __init__( + self, + hass: HomeAssistant, + flow_handler: FlowHandler, + entry: ConfigEntry | None = None, + ): + self._hass = hass + self._flow_handler = flow_handler + self._entry = entry + self._flow_id = "user" if entry is None else "init" + self._config_manager = HAConfigManager(self._hass, None) + + async def async_step(self, user_input: dict | None = None): + """Manage the domain options.""" + _LOGGER.info(f"Config flow started, Step: {self._flow_id}, Input: {user_input}") + + form_errors = None + + if user_input is None: + if self._entry is None: + user_input = {} + + else: + user_input = {key: self._entry.data[key] for key in self._entry.data} + + else: + try: + await self._config_manager.initialize(user_input) + + api = RestAPIv2(self._hass, self._config_manager) + + await api.initialize(True) + + _LOGGER.debug("User inputs are valid") + title = DEFAULT_NAME + + if self._entry is None: + data = copy(user_input) + + else: + data = await self.remap_entry_data(user_input) + title = self._entry.title + + return self._flow_handler.async_create_entry(title=title, data=data) + + except IntegrationAPIError as iapiex: + form_errors = {"base": "error_404"} + + _LOGGER.warning(f"Failed to setup integration, Error: {iapiex}") + + schema = ConfigData.default_schema(user_input) + + return self._flow_handler.async_show_form( + step_id=self._flow_id, data_schema=schema, errors=form_errors + ) + + async def remap_entry_data(self, options: dict[str, Any]) -> dict[str, Any]: + config_options = {} + config_data = {} + + entry = self._entry + entry_data = entry.data + + for key in options: + if key in DATA_KEYS: + config_data[key] = options.get(key, entry_data.get(key)) + + else: + config_options[key] = options.get(key) + + self._hass.config_entries.async_update_entry( + entry, data=config_data, title=entry.title + ) + + return config_options diff --git a/custom_components/hpprinter/managers/ha_config_manager.py b/custom_components/hpprinter/managers/ha_config_manager.py new file mode 100644 index 0000000..fc3f6e5 --- /dev/null +++ b/custom_components/hpprinter/managers/ha_config_manager.py @@ -0,0 +1,270 @@ +from copy import copy +import json +import logging + +from homeassistant.config_entries import STORAGE_VERSION, ConfigEntry +from homeassistant.const import CONF_PASSWORD, CONF_TEMPERATURE_UNIT, CONF_USERNAME +from homeassistant.core import HomeAssistant +from homeassistant.helpers import translation +from homeassistant.helpers.entity import DeviceInfo +from homeassistant.helpers.json import JSONEncoder +from homeassistant.helpers.storage import Store + +from ..common.consts import ( + CONF_UPDATE_INTERVAL, + CONFIGURATION_FILE, + DEFAULT_ENTRY_ID, + DEFAULT_NAME, + DOMAIN, +) +from ..common.entity_descriptions import ( + DEFAULT_ENTITY_DESCRIPTIONS, + IntegrationEntityDescription, +) +from ..models.config_data import ConfigData + +_LOGGER = logging.getLogger(__name__) + + +class HAConfigManager: + _api_config: dict | None + _translations: dict | None + _entry: ConfigEntry | None + _entry_id: str + _entry_title: str + _config_data: ConfigData + _store: Store | None + + def __init__(self, hass: HomeAssistant | None, entry: ConfigEntry | None): + self._hass = hass + + self._data = None + self.platforms = [] + + self._entity_descriptions = {} + self._protocol_codes = {} + self._protocol_codes_configuration = {} + + self._hvac_modes = {} + self._hvac_modes_reverse = {} + + self._fan_modes = {} + self._fan_modes_reverse = {} + + self._devices = {} + self._api_config = None + self._translations = None + + self._entry = entry + self._entry_id = DEFAULT_ENTRY_ID if entry is None else entry.entry_id + self._entry_title = DEFAULT_NAME if entry is None else entry.title + + self._config_data = ConfigData() + + self._is_initialized = False + self._store = None + + if hass is not None: + self._store = Store( + hass, STORAGE_VERSION, CONFIGURATION_FILE, encoder=JSONEncoder + ) + + @property + def is_initialized(self) -> bool: + is_initialized = self._is_initialized + + return is_initialized + + @property + def entry_id(self) -> str: + entry_id = self._entry_id + + return entry_id + + @property + def entry_title(self) -> str: + entry_title = self._entry_title + + return entry_title + + @property + def entry(self) -> ConfigEntry: + entry = self._entry + + return entry + + @property + def config_data(self) -> ConfigData: + config_data = self._config_data + + return config_data + + @property + def update_interval(self) -> int: + interval = self._data.get(CONF_UPDATE_INTERVAL, 60) + + return interval + + async def initialize(self, entry_config: dict): + await self._load() + + self._config_data.update(entry_config) + + if self._hass: + self._translations = await translation.async_get_translations( + self._hass, self._hass.config.language, "entity", {DOMAIN} + ) + + self._is_initialized = True + + async def remove(self, entry_id: str): + if self._store is None: + return + + store_data = await self._store.async_load() + + entries = [DEFAULT_ENTRY_ID, entry_id] + + if store_data is not None: + should_save = False + data = {key: store_data[key] for key in store_data} + + for rm_entry_id in entries: + if rm_entry_id in store_data: + data.pop(rm_entry_id) + + should_save = True + + if should_save: + await self._store.async_save(data) + + def get_entity_name( + self, entity_description: IntegrationEntityDescription, device_info: DeviceInfo + ) -> str: + entity_key = entity_description.key + platform = entity_description.platform + + device_name = device_info.get("name") + + translation_key = f"component.{DOMAIN}.entity.{platform}.{entity_key}.name" + + translated_name = self._translations.get( + translation_key, entity_description.name + ) + + _LOGGER.debug( + f"Translations requested, Key: {translation_key}, " + f"Entity: {entity_description.name}, Value: {translated_name}" + ) + + entity_name = ( + device_name + if translated_name is None or translated_name == "" + else f"{device_name} {translated_name}" + ) + + return entity_name + + async def set_update_interval(self, value: int): + _LOGGER.debug(f"Set update interval in seconds to to {value}") + + self._data[CONF_UPDATE_INTERVAL] = value + + await self._save() + + def get_debug_data(self) -> dict: + data = self._config_data.to_dict() + + for key in self._data: + data[key] = self._data[key] + + return data + + async def _load(self): + self._data = None + + await self._load_config_from_file() + + if self._data is None: + self._data = {} + + default_configuration = self._get_defaults() + + for key in default_configuration: + value = default_configuration[key] + + if key not in self._data: + self._data[key] = value + + await self._save() + + async def _load_config_from_file(self): + if self._store is not None: + store_data = await self._store.async_load() + + if store_data is not None: + self._data = store_data.get(self._entry_id) + + @staticmethod + def _get_defaults() -> dict: + data = {CONF_TEMPERATURE_UNIT: {}} + + return data + + async def _save(self): + if self._store is None: + return + + should_save = False + store_data = await self._store.async_load() + + if store_data is None: + store_data = {} + + entry_data = store_data.get(self._entry_id, {}) + + _LOGGER.debug( + f"Storing config data: {json.dumps(self._data)}, " + f"Exiting: {json.dumps(entry_data)}" + ) + + for key in self._data: + stored_value = entry_data.get(key) + + if key in [CONF_PASSWORD, CONF_USERNAME]: + entry_data.pop(key) + + if stored_value is not None: + should_save = True + + else: + current_value = self._data.get(key) + + if stored_value != current_value: + should_save = True + + entry_data[key] = self._data[key] + + if DEFAULT_ENTRY_ID in store_data: + store_data.pop(DEFAULT_ENTRY_ID) + should_save = True + + if should_save: + store_data[self._entry_id] = entry_data + + await self._store.async_save(store_data) + + def _load_entity_descriptions(self, product_id: str): + entities = copy(DEFAULT_ENTITY_DESCRIPTIONS) + + self._update_platforms(entities) + + self._entity_descriptions[product_id] = entities + + def _update_platforms(self, entity_descriptions): + for entity_description in entity_descriptions: + if ( + entity_description.platform not in self.platforms + and entity_description.platform is not None + ): + self.platforms.append(entity_description.platform) diff --git a/custom_components/hpprinter/managers/ha_coordinator.py b/custom_components/hpprinter/managers/ha_coordinator.py new file mode 100644 index 0000000..4d33017 --- /dev/null +++ b/custom_components/hpprinter/managers/ha_coordinator.py @@ -0,0 +1,311 @@ +from copy import copy +import logging + +from homeassistant.const import Platform +from homeassistant.core import Event +from homeassistant.helpers.entity import DeviceInfo +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed + +from ..common.consts import DOMAIN, UPDATE_API_INTERVAL +from ..common.entity_descriptions import ( + CARTRIDGE_ENTITY_DESCRIPTIONS, + DEFAULT_ENTITY_DESCRIPTIONS, + IntegrationEntityDescription, +) +from .ha_config_manager import HAConfigManager +from .rest_api import RestAPIv2 + +_LOGGER = logging.getLogger(__name__) + + +class HACoordinator(DataUpdateCoordinator): + """My custom coordinator.""" + + def __init__( + self, + hass, + config_manager: HAConfigManager, + ): + """Initialize my coordinator.""" + super().__init__( + hass, + _LOGGER, + name=config_manager.entry_title, + update_interval=UPDATE_API_INTERVAL, + update_method=self._async_update_data, + ) + + self._api = RestAPIv2(hass, config_manager) + self._config_manager = config_manager + self._main_device: DeviceInfo | None = None + self._devices: dict[str, DeviceInfo] = {} + self._device_type_mapping: dict[str, str] | None = None + + @property + def config_manager(self): + return self._config_manager + + async def on_home_assistant_start(self, _event_data: Event): + await self.initialize() + + async def initialize(self): + _LOGGER.debug("Initializing coordinator") + + entry = self.config_manager.entry + platforms = self.config_manager.platforms + await self.hass.config_entries.async_forward_entry_setups(entry, platforms) + + _LOGGER.info(f"Start loading {DOMAIN} integration, Entry ID: {entry.entry_id}") + + await self._api.initialize() + + await self.async_config_entry_first_refresh() + + def set_devices(self): + self._device_type_mapping = { + "Main": "main", + "Adapters": "adapter", + "Cartridges": "cartridge", + } + + self.create_main_device() + + sub_units = [ + entity_description.device_type + for entity_description in DEFAULT_ENTITY_DESCRIPTIONS + if entity_description.device_type != "Main" + ] + + sub_units = list(dict.fromkeys(sub_units)) + + for sub_unit in sub_units: + self.create_sub_unit_device(sub_unit) + + self._device_type_mapping[sub_unit] = sub_unit + + cartridge_details = self._api.data.get("Cartridges") + for cartridge_data in cartridge_details: + self.create_cartridge_device(cartridge_data) + + adapter_details = self._api.data.get("Adapters") + for adapter_data in adapter_details: + self.create_adapter_device(adapter_data) + + def create_main_device(self): + entry_id = self.config_entry.entry_id + entry_title = self.config_entry.entry_id + + product_details = self._api.data.get("Product") + + model = product_details.get("Make And Model") + serial_number = product_details.get("Serial Number") + manufacturer = product_details.get("Manufacturer Name") + + device_unique_id = f"{entry_id}.main" + + device_identifier = (DOMAIN, device_unique_id) + + device_info = DeviceInfo( + identifiers={device_identifier}, + name=entry_title, + model=model, + serial_number=serial_number, + manufacturer=manufacturer, + ) + + self._main_device = device_info + self._devices[device_unique_id] = device_info + + def create_sub_unit_device(self, sub_unit: str): + entry_id = self.config_entry.entry_id + entry_title = self.config_entry.entry_id + + serial_number = self._main_device.get("serial_number") + model = self._main_device.get("model") + manufacturer = self._main_device.get("manufacturer") + + device_unique_id = f"{entry_id}.{sub_unit.lower()}" + main_device_unique_id = f"{entry_id}.main" + + sub_unit_device_name = f"{entry_title} {sub_unit}" + + device_identifier = (DOMAIN, device_unique_id) + + device_info = DeviceInfo( + identifiers={device_identifier}, + name=sub_unit_device_name, + model=model, + serial_number=serial_number, + manufacturer=manufacturer, + via_device=(DOMAIN, main_device_unique_id), + ) + + self._devices[device_unique_id] = device_info + + def create_cartridge_device(self, cartridge_data: dict): + entry_id = self.config_entry.entry_id + entry_title = self.config_entry.entry_id + + printer_device_unique_id = f"{entry_id}.printer" + + device_name_parts = [entry_title] + cartridge_type: str = cartridge_data.get("Consumable Type Enum") + cartridge_color = cartridge_data.get("Marker Color") + manufacturer = cartridge_data.get("Brand") + serial_number = cartridge_data.get("Serial Number") + label_code = cartridge_data.get("Label Code") + model = cartridge_data.get("Consumable Selectibility Number") + + if cartridge_type == "printhead": + device_name_parts.append(cartridge_type.capitalize()) + model = cartridge_type + + else: + device_name_parts.append(cartridge_color) + device_name_parts.append(cartridge_type.capitalize()) + + device_unique_id = f"{entry_id}.cartridge.{label_code}" + + cartridge_device_name = " ".join(device_name_parts) + + device_identifier = (DOMAIN, device_unique_id) + + device_info = DeviceInfo( + identifiers={device_identifier}, + name=cartridge_device_name, + model=model, + serial_number=serial_number, + manufacturer=manufacturer, + via_device=(DOMAIN, printer_device_unique_id), + ) + + self._devices[device_unique_id] = device_info + + def create_adapter_device(self, adapter_data: dict): + entry_id = self.config_entry.entry_id + entry_title = self.config_entry.entry_id + + serial_number = self._main_device.get("serial_number") + manufacturer = self._main_device.get("manufacturer") + + adapter_name = adapter_data.get("Name").upper() + model = ( + adapter_data.get("DeviceConnectivityPortType") + .replace("Embedded", "") + .upper() + ) + + device_unique_id = f"{entry_id}.adapter.{adapter_name.lower()}" + main_device_unique_id = f"{entry_id}.main" + + adapter_device_name = f"{entry_title} Adapter {adapter_name}" + + device_identifier = (DOMAIN, device_unique_id) + + device_info = DeviceInfo( + identifiers={device_identifier}, + name=adapter_device_name, + model=model, + serial_number=serial_number, + manufacturer=manufacturer, + via_device=(DOMAIN, main_device_unique_id), + ) + + self._devices[device_unique_id] = device_info + + def get_device(self, device_type: str, item_id: str | None) -> DeviceInfo: + device_id = self._device_type_mapping.get(device_type) + + if item_id is not None: + device_id = f"{device_id}.{item_id}" + + device_unique_key = f"{self.config_entry.entry_id}.{device_id}" + + device_info = self._devices.get(device_unique_key) + + return device_info + + def get_device_data( + self, + section: str, + key: str | None = None, + array_key: str | None = None, + item_id: str | None = None, + ): + data = self._api.data.get(section, {}) + + if array_key: + items = [item for item in data if item.get(array_key) == item_id] + + data = None if len(items) == 0 else items[0] + + if key and data is not None: + return data.get(key) + + return data + + async def get_debug_data(self) -> dict: + await self._api.update_full() + + data = {"raw": self._api.raw_data, "processed": self._api.data} + + return data + + async def _async_update_data(self): + try: + await self._api.update() + + return self._api.data + + except Exception as err: + raise UpdateFailed(f"Error communicating with API: {err}") + + def get_entity_descriptions( + self, platform: Platform + ) -> list[IntegrationEntityDescription]: + entity_descriptions = [ + copy(entity_description) + for entity_description in DEFAULT_ENTITY_DESCRIPTIONS + if entity_description.platform == platform + ] + + cartridges = self.get_device_data("Cartridges") + + for cartridge in cartridges: + cartridge_entity_descriptions = self._get_cartridge_entity_descriptions( + platform, cartridge + ) + + entity_descriptions.extend(cartridge_entity_descriptions) + + return entity_descriptions + + def _get_cartridge_entity_descriptions( + self, platform: Platform, cartridge: dict + ) -> list[IntegrationEntityDescription]: + entity_descriptions = [ + self._get_cartridge_entity_description(entity_description, cartridge) + for entity_description in CARTRIDGE_ENTITY_DESCRIPTIONS + if entity_description.platform == platform + ] + + result = [ + entity_description + for entity_description in entity_descriptions + if entity_description is not None + ] + + return result + + @staticmethod + def _get_cartridge_entity_description( + entity_description: IntegrationEntityDescription, cartridge: dict + ) -> IntegrationEntityDescription | None: + is_valid = entity_description.filter(cartridge) + + if not is_valid: + return None + + result = copy(entity_description) + + return result diff --git a/custom_components/hpprinter/managers/home_assistant.py b/custom_components/hpprinter/managers/home_assistant.py deleted file mode 100644 index a690265..0000000 --- a/custom_components/hpprinter/managers/home_assistant.py +++ /dev/null @@ -1,220 +0,0 @@ -""" -Support for HP Printer. -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/hpprinter/ -""" -from datetime import datetime, timedelta -import logging -import sys -from typing import Optional - -from homeassistant.config_entries import ConfigEntry -from homeassistant.core import HomeAssistant -from homeassistant.helpers.dispatcher import async_dispatcher_send -from homeassistant.helpers.entity_registry import EntityRegistry, async_get -from homeassistant.helpers.event import async_track_time_interval - -from ..helpers.const import * -from ..managers.HPDeviceData import HPDeviceData -from ..managers.configuration_manager import ConfigManager -from ..managers.device_manager import DeviceManager -from ..managers.entity_manager import EntityManager -from ..models.config_data import ConfigData - -_LOGGER = logging.getLogger(__name__) - - -class HPPrinterHomeAssistant: - def __init__(self, hass: HomeAssistant): - self._hass = hass - - self._remove_async_track_time = None - - self._is_initialized = False - self._is_updating = False - - self._entity_registry = None - - self._api = None - self._entity_manager = None - self._device_manager = None - self._data_manager = None - - self._config_manager = ConfigManager() - - def update_entities(now): - self._hass.async_create_task(self.async_update(now)) - - self._update_entities = update_entities - - @property - def data(self): - return self._data_manager.device_data - - @property - def data_manager(self) -> HPDeviceData: - return self._data_manager - - @property - def entity_manager(self) -> EntityManager: - return self._entity_manager - - @property - def device_manager(self) -> DeviceManager: - return self._device_manager - - @property - def entity_registry(self) -> EntityRegistry: - return self._entity_registry - - @property - def config_data(self) -> Optional[ConfigData]: - if self._config_manager is not None: - return self._config_manager.data - - return None - - async def async_init(self, entry: ConfigEntry): - try: - self._config_manager.update(entry) - - self._data_manager = HPDeviceData(self._hass, self._config_manager) - self._entity_manager = EntityManager(self._hass, self) - self._device_manager = DeviceManager(self._hass, self) - - self._hass.loop.create_task(self._async_init()) - except Exception as ex: - exc_type, exc_obj, tb = sys.exc_info() - line_number = tb.tb_lineno - - _LOGGER.error(f"Failed to async_init, error: {ex}, line: {line_number}") - - async def _async_init(self): - await self._data_manager.initialize() - - self._entity_registry = async_get(self._hass) - - load = self._hass.config_entries.async_forward_entry_setup - - for domain in SIGNALS: - await load(self._config_manager.config_entry, domain) - - self._is_initialized = True - - await self.async_update_entry() - - async def async_update_entry(self, entry: ConfigEntry = None): - _LOGGER.debug("Updating config entry") - - is_update = entry is not None - - if is_update: - _LOGGER.info(f"Handling ConfigEntry change: {entry.as_dict()}") - - previous_interval = self.config_data.update_interval - - self._config_manager.update(entry) - - current_interval = self.config_data.update_interval - - is_interval_changed = previous_interval != current_interval - - if is_interval_changed and self._remove_async_track_time is not None: - msg = f"ConfigEntry interval changed from {previous_interval} to {current_interval}" - _LOGGER.info(msg) - - self._remove_async_track_time() - self._remove_async_track_time = None - else: - entry = self._config_manager.config_entry - - _LOGGER.info(f"Handling ConfigEntry initialization: {entry.as_dict()}") - - current_interval = self.config_data.update_interval - - if self._remove_async_track_time is None: - interval = timedelta(seconds=current_interval) - - self._remove_async_track_time = async_track_time_interval( - self._hass, self._update_entities, interval - ) - - await self.async_update(datetime.now()) - - async def async_remove(self): - config_entry = self._config_manager.config_entry - _LOGGER.info(f"Removing current integration - {config_entry.title}") - - if self._remove_async_track_time is not None: - self._remove_async_track_time() - self._remove_async_track_time = None - - unload = self._hass.config_entries.async_forward_entry_unload - - for domain in SIGNALS: - await unload(config_entry, domain) - - await self._device_manager.async_remove() - - _LOGGER.info(f"Current integration ({config_entry.title}) removed") - - async def async_update(self, event_time): - if not self._is_initialized: - _LOGGER.info(f"NOT INITIALIZED - Failed updating @{event_time}") - return - - try: - if self._is_updating: - _LOGGER.debug(f"Skip updating @{event_time}") - return - - _LOGGER.debug(f"Updating @{event_time}") - - self._is_updating = True - - await self.data_manager.update() - - self.device_manager.update() - self.entity_manager.update() - - await self.dispatch_all() - except Exception as ex: - exc_type, exc_obj, tb = sys.exc_info() - line_number = tb.tb_lineno - - _LOGGER.error(f"Failed to async_update, Error: {ex}, Line: {line_number}") - - self._is_updating = False - - async def delete_entity(self, domain, name): - try: - entity = self.entity_manager.get_entity(domain, name) - device_name = entity.device_name - unique_id = entity.unique_id - - self.entity_manager.delete_entity(domain, name) - - device_in_use = self.entity_manager.is_device_name_in_use(device_name) - - entity_id = self.entity_registry.async_get_entity_id( - domain, DOMAIN, unique_id - ) - self.entity_registry.async_remove(entity_id) - - if not device_in_use: - await self.device_manager.delete_device(device_name) - except Exception as ex: - exc_type, exc_obj, tb = sys.exc_info() - line_number = tb.tb_lineno - - _LOGGER.error(f"Failed to delete_entity, Error: {ex}, Line: {line_number}") - - async def dispatch_all(self): - if not self._is_initialized: - _LOGGER.info("NOT INITIALIZED - Failed discovering components") - return - - for domain in SIGNALS: - signal = SIGNALS.get(domain) - - async_dispatcher_send(self._hass, signal) diff --git a/custom_components/hpprinter/managers/rest_api.py b/custom_components/hpprinter/managers/rest_api.py new file mode 100644 index 0000000..326817b --- /dev/null +++ b/custom_components/hpprinter/managers/rest_api.py @@ -0,0 +1,354 @@ +import json +import logging +import os +from pathlib import Path +import sys + +from aiohttp import ClientResponseError, ClientSession, ClientTimeout, TCPConnector +from defusedxml import ElementTree +import xmltodict + +from homeassistant.helpers.aiohttp_client import ( + ENABLE_CLEANUP_CLOSED, + MAXIMUM_CONNECTIONS, + MAXIMUM_CONNECTIONS_PER_HOST, +) +from homeassistant.helpers.dispatcher import async_dispatcher_send +from homeassistant.util import ssl +from homeassistant.util.ssl import SSLCipherList + +from ..common.consts import IGNORED_KEYS, SIGNAL_HA_DEVICE_NEW +from ..models.config_data import ConfigData +from ..models.exceptions import IntegrationAPIError +from .ha_config_manager import HAConfigManager + +_LOGGER = logging.getLogger(__name__) + + +class RestAPIv2: + def __init__(self, hass, config_manager: HAConfigManager): + self._loop = hass.loop + self._config_manager = config_manager + self._hass = hass + + self._session: ClientSession | None = None + + self._data: dict = {} + self._raw_data: dict = {} + + self._resources: dict | None = None + self._data_points: dict | None = None + + self._endpoints: list[str] | None = None + self._all_endpoints: list[str] | None = None + + self._exclude_uri_list: list[str] | None = None + self._exclude_type_list: list[str] | None = None + + self._is_connected: bool = False + + @property + def data(self) -> dict | None: + return self._data + + @property + def raw_data(self) -> dict | None: + return self._raw_data + + @property + def config_data(self) -> ConfigData | None: + if self._config_manager is not None: + return self._config_manager.config_data + + return None + + @property + def _base_url(self): + config_data = self.config_data + + url = f"{config_data.protocol}://{config_data.hostname}:{config_data.port}" + + return url + + async def initialize(self): + try: + if not self.config_data.hostname: + _LOGGER.error("Failed to get hostname") + return + + if self._session is None: + self._session = ClientSession( + loop=self._loop, connector=self._get_ssl_connector() + ) + + self._load_exclude_endpoints_configuration() + self._load_data_points_configuration() + + await self._load_metadata() + + except Exception as ex: + exc_type, exc_obj, tb = sys.exc_info() + line_number = tb.tb_lineno + + _LOGGER.warning( + f"Failed to initialize session, Error: {ex}, Line: {line_number}" + ) + + def _load_data_points_configuration(self): + self._endpoints = [] + + self._data_points = self._get_data_from_file("data_points") + + endpoint_objects = self._data_points.get("objects") + + for endpoint in endpoint_objects: + endpoint_uri = endpoint.get("endpoint") + + if ( + endpoint_uri not in self._endpoints + and endpoint_uri not in self._exclude_uri_list + ): + self._endpoints.append(endpoint_uri) + + def _load_exclude_endpoints_configuration(self): + endpoints = self._get_data_from_file("endpoint_validations") + + self._exclude_uri_list = endpoints.get("exclude_uri") + self._exclude_type_list = endpoints.get("exclude_type") + + @staticmethod + def _get_data_from_file(file_name: str) -> dict: + config_file = f"{file_name}.json" + current_path = Path(__file__) + parent_directory = current_path.parents[1] + file_path = os.path.join(parent_directory, "data", config_file) + + with open(file_path) as f: + data = json.load(f) + + return data + + def _get_ssl_connector(self): + ssl_context = ssl.create_no_verify_ssl_context(SSLCipherList.INTERMEDIATE) + + connector = TCPConnector( + loop=self._loop, + enable_cleanup_closed=ENABLE_CLEANUP_CLOSED, + ssl=ssl_context, + limit=MAXIMUM_CONNECTIONS, + limit_per_host=MAXIMUM_CONNECTIONS_PER_HOST, + ) + + return connector + + async def _load_metadata(self): + self._all_endpoints = [] + + endpoints = await self._get_request("/Prefetch?type=dtree") + + if not endpoints: + raise IntegrationAPIError(self._base_url) + + for endpoint in endpoints: + is_valid = self._is_valid_endpoint(endpoint) + + if is_valid: + endpoint_uri = endpoint.get("uri") + + self._all_endpoints.append(endpoint_uri) + + self._is_connected = True + + async_dispatcher_send( + self._hass, + SIGNAL_HA_DEVICE_NEW, + self._config_manager.entry_id, + ) + + def _is_valid_endpoint(self, endpoint: dict): + endpoint_type = endpoint.get("type") + uri = endpoint.get("uri") + methods = endpoint.get("methods", ["get"]) + + is_invalid_type = endpoint_type in self._exclude_type_list + invalid_endpoint_uri = uri in self._exclude_uri_list + invalid_uri_resource = uri.endswith("Cap.xml") + invalid_uri_parameter = "{" in uri or "}" in uri + invalid_methods = "get" not in methods + + invalid_data = [ + is_invalid_type, + invalid_uri_resource, + invalid_uri_parameter, + invalid_methods, + invalid_endpoint_uri, + ] + + is_valid = True not in invalid_data + + return is_valid + + async def update(self): + await self._update_data(self._endpoints) + + async def update_full(self): + await self._update_data(self._all_endpoints) + + async def _update_data(self, endpoints): + if not self._is_connected: + return + + for endpoint in endpoints: + resource_data = await self._get_request(endpoint) + + self._raw_data[endpoint] = resource_data + + endpoint_objects = self._data_points.get("objects") + merge_items = self._data_points.get("merge") + + for data_point in endpoint_objects: + data_point_name = data_point.get("name") + data_point_endpoint = data_point.get("endpoint") + data_point_path = data_point.get("path") + data_point_sub_path = data_point.get("subPath") + + if data_point_endpoint is not None: + data_item = self._raw_data.get(data_point_endpoint) + + item = self._get_data_item(data_item, data_point_path) + + if data_point_sub_path is not None: + if isinstance(item, list): + item = self._get_data_items(item, data_point_sub_path) + + else: + item = self._get_sub_items(item, data_point_sub_path) + + self._data[data_point_name] = item + + for merge_item in merge_items: + self._merge_data_items(merge_item) + + @staticmethod + def _get_data_item(data_item: dict, path: str): + path_parts = path.split(".") + value = data_item.copy() + + for path_part in path_parts: + value = value.get(path_part) + + if value is None: + break + + return value + + def _get_sub_items(self, data_item: dict, sub_path_mapping: dict) -> dict: + data = {} + + for sub_path_name in sub_path_mapping: + sub_path = sub_path_mapping.get(sub_path_name) + + data[sub_path_name] = self._get_data_item(data_item, sub_path) + + return data + + def _get_data_items( + self, data_items: list[dict], sub_path_mapping: dict + ) -> list[dict]: + result = [] + + for data_item in data_items: + data = self._get_sub_items(data_item, sub_path_mapping) + + result.append(data) + + return result + + def _merge_data_items(self, merge_item: dict): + key_to = merge_item.get("to") + key_from = merge_item.get("from") + key = merge_item.get("key") + + data_items = self._data.get(key_to) + additional_data = self._data.get(key_from).copy() + + del self._data[key_from] + + additional_data_mapped = { + additional_data_item[key]: additional_data_item + for additional_data_item in additional_data + } + + for data_item in data_items: + data_item_key = data_item.get(key) + additional_data_item = additional_data_mapped.get(data_item_key) + + if additional_data_item is not None: + data_item.update(additional_data_item) + + async def _get_request(self, endpoint: str) -> dict | None: + result: dict | None = None + try: + url = f"{self._base_url}{endpoint}" + + timeout = ClientTimeout(connect=3, sock_read=10) + + async with self._session.get(url, timeout=timeout) as response: + response.raise_for_status() + + if response.content_type == "application/javascript": + content = await response.text() + result = json.loads(content) + + else: + content = await response.text() + result = self._clean_data(content) + + if result is not None: + result_keys = list(result.keys()) + root_key = result_keys[0] + + for ignored_key in IGNORED_KEYS: + if ignored_key in result[root_key]: + del result[root_key][ignored_key] + + _LOGGER.debug(f"Request to {url}") + + except ClientResponseError as cre: + exc_type, exc_obj, tb = sys.exc_info() + line_number = tb.tb_lineno + _LOGGER.error( + f"Failed to get response from {endpoint}, Error: {cre}, Line: {line_number}" + ) + + except Exception as ex: + print(ex) + exc_type, exc_obj, tb = sys.exc_info() + line_number = tb.tb_lineno + _LOGGER.error(f"Failed to get {endpoint}, Error: {ex}, Line: {line_number}") + + return result + + def _clean_data(self, xml) -> dict: + xml_data = ElementTree.fromstring(xml) + + self._strip_namespace(xml_data) + + data = xmltodict.parse(ElementTree.tostring(xml_data)) + + return data + + def _strip_namespace(self, el): + if el.tag.startswith("{"): + el.tag = el.tag.split("}", 1)[1] + + keys = list(el.attrib.keys()) + + for k in keys: + if k.startswith("{"): + k2 = k.split("}", 1)[1] + el.attrib[k2] = el.attrib[k] + del el.attrib[k] + + for child in el: + self._strip_namespace(child) diff --git a/custom_components/hpprinter/managers/storage_manager.py b/custom_components/hpprinter/managers/storage_manager.py deleted file mode 100644 index 5773b73..0000000 --- a/custom_components/hpprinter/managers/storage_manager.py +++ /dev/null @@ -1,49 +0,0 @@ -"""Storage handlers.""" -import logging - -from homeassistant.helpers.json import JSONEncoder -from homeassistant.helpers.storage import Store -from homeassistant.util import slugify - -from ..helpers.const import * -from ..models.config_data import ConfigData -from .configuration_manager import ConfigManager - -STORAGE_VERSION = 1 - -_LOGGER = logging.getLogger(__name__) - - -class StorageManager: - def __init__(self, hass, config_manager: ConfigManager): - self._hass = hass - self._config_manager = config_manager - - @property - def config_data(self) -> ConfigData: - config_data = None - - if self._config_manager is not None: - config_data = self._config_manager.data - - return config_data - - @property - def file_name(self): - file_name = f".{DOMAIN}.{slugify(self.config_data.name)}" - - return file_name - - async def async_load_from_store(self): - """Load the retained data from store and return de-serialized data.""" - store = Store(self._hass, STORAGE_VERSION, self.file_name, encoder=JSONEncoder) - - data = await store.async_load() - - return data - - async def async_save_to_store(self, data): - """Generate dynamic data to store and save it to the filesystem.""" - store = Store(self._hass, STORAGE_VERSION, self.file_name, encoder=JSONEncoder) - - await store.async_save(data) diff --git a/custom_components/hpprinter/manifest.json b/custom_components/hpprinter/manifest.json index f2fb8c8..56f7ded 100644 --- a/custom_components/hpprinter/manifest.json +++ b/custom_components/hpprinter/manifest.json @@ -8,6 +8,6 @@ "documentation": "https://github.com/elad-bar/ha-hpprinter", "iot_class": "local_polling", "issue_tracker": "https://github.com/elad-bar/ha-hpprinter/issues", - "requirements": ["xmltodict==0.12.0"], - "version": "1.0.12" + "requirements": ["xmltodict~=0.13.0"], + "version": "2.0.0" } diff --git a/custom_components/hpprinter/models/base_entity.py b/custom_components/hpprinter/models/base_entity.py deleted file mode 100644 index 58bd169..0000000 --- a/custom_components/hpprinter/models/base_entity.py +++ /dev/null @@ -1,153 +0,0 @@ -import logging -import sys -from typing import Any, Callable, Optional - -from homeassistant.config_entries import ConfigEntry -from homeassistant.const import CONF_NAME -from homeassistant.core import HomeAssistant, callback -from homeassistant.helpers.dispatcher import async_dispatcher_connect -from homeassistant.helpers.entity import Entity - -from ..helpers import get_ha -from ..helpers.const import * -from .entity_data import EntityData - -_LOGGER = logging.getLogger(__name__) - - -async def async_setup_base_entry( - hass: HomeAssistant, - entry: ConfigEntry, - async_add_entities, - domain: str, - component: Callable[[HomeAssistant, Any, EntityData], Any], -): - """Set up HP Printer based off an entry.""" - _LOGGER.debug(f"Starting async_setup_entry {domain}") - - try: - entry_data = entry.data - name = entry_data.get(CONF_NAME) - - ha = get_ha(hass, name) - entity_manager = ha.entity_manager - entity_manager.set_domain_component(domain, async_add_entities, component) - except Exception as ex: - exc_type, exc_obj, tb = sys.exc_info() - line_number = tb.tb_lineno - - _LOGGER.error(f"Failed to load {domain}, error: {ex}, line: {line_number}") - - -class HPPrinterEntity(Entity): - """Representation a binary sensor that is updated by HP Printer.""" - - hass: HomeAssistant = None - integration_name: str = None - entity: EntityData = None - remove_dispatcher = None - current_domain: str = None - - ha = None - entity_manager = None - device_manager = None - - def initialize( - self, - hass: HomeAssistant, - integration_name: str, - entity: EntityData, - current_domain: str, - ): - self.hass = hass - self.integration_name = integration_name - self.entity = entity - self.remove_dispatcher = None - self.current_domain = current_domain - - self.ha = get_ha(self.hass, self.integration_name) - - if self.ha is None: - _LOGGER.error( - f"HPPrinterHomeAssistant was not found for {self.integration_name}" - ) - - else: - self.entity_manager = self.ha.entity_manager - self.device_manager = self.ha.device_manager - - @property - def unique_id(self) -> Optional[str]: - """Return the name of the node.""" - return self.entity.unique_id - - @property - def device_info(self): - return self.device_manager.get(self.entity.device_name) - - @property - def name(self): - """Return the name of the node.""" - return self.entity.name - - @property - def icon(self): - """Return the name of the node.""" - return self.entity.icon - - @property - def should_poll(self): - """Return the polling state.""" - return False - - @property - def extra_state_attributes(self): - """Return true if the binary sensor is on.""" - return self.entity.attributes - - async def async_added_to_hass(self): - """Register callbacks.""" - async_dispatcher_connect( - self.hass, SIGNALS[self.current_domain], self._schedule_immediate_update - ) - - await self.async_added_to_hass_local() - - async def async_will_remove_from_hass(self) -> None: - if self.remove_dispatcher is not None: - self.remove_dispatcher() - self.remove_dispatcher = None - - await self.async_will_remove_from_hass_local() - - @callback - def _schedule_immediate_update(self): - self.hass.async_create_task(self._async_schedule_immediate_update()) - - async def _async_schedule_immediate_update(self): - if self.entity_manager is None: - _LOGGER.debug( - f"Cannot update {self.current_domain} - Entity Manager is None | {self.name}" - ) - else: - if self.entity is not None: - previous_state = self.entity.state - - entity = self.entity_manager.get_entity(self.current_domain, self.name) - - if entity.disabled: - _LOGGER.debug(f"Skip updating {self.name}, Entity is disabled") - - else: - self.entity = entity - if self.entity is not None: - self._immediate_update(previous_state) - - async def async_added_to_hass_local(self): - pass - - async def async_will_remove_from_hass_local(self): - pass - - def _immediate_update(self, previous_state: int): - self.async_schedule_update_ha_state(True) diff --git a/custom_components/hpprinter/models/config_data.py b/custom_components/hpprinter/models/config_data.py index 97dac6a..0396938 100644 --- a/custom_components/hpprinter/models/config_data.py +++ b/custom_components/hpprinter/models/config_data.py @@ -1,47 +1,65 @@ -from typing import Any +import voluptuous as vol +from voluptuous import Schema -from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PORT, CONF_SSL +from homeassistant.const import CONF_HOST, CONF_PORT, CONF_SSL -from ..helpers.const import * +from ..common.consts import PROTOCOLS class ConfigData: - name: str - host: str - ssl: bool - port: int - should_store: bool - update_interval: int - log_level: str - file_reader: Any + _host: str | None + _ssl: bool | None + _port: int | None def __init__(self): - self.name = "" - self.host = "" - self.ssl = False - self.port = 80 - self.should_store = False - self.update_interval = 60 - self.log_level = LOG_LEVEL_DEFAULT - self.file_reader = None + self._host = "" + self._ssl = False + self._port = 80 + + @property + def hostname(self) -> str: + return self._host + + @property + def port(self) -> int: + return self._port + + @property + def is_ssl(self) -> bool: + return self._ssl @property def protocol(self): - protocol = PROTOCOLS[self.ssl] + protocol = PROTOCOLS[self._ssl] return protocol + def update(self, data: dict): + self._ssl = str(data.get(CONF_SSL, False)).lower() == str(True).lower() + self._host = data.get(CONF_HOST) + self._port = data.get(CONF_PORT) + + def to_dict(self): + obj = {CONF_HOST: self._host, CONF_PORT: self._port, CONF_SSL: self._ssl} + + return obj + def __repr__(self): - obj = { - CONF_NAME: self.name, - CONF_HOST: self.host, - CONF_SSL: self.ssl, - CONF_PORT: self.port, - CONF_STORE_DATA: self.should_store, - CONF_UPDATE_INTERVAL: self.update_interval, - CONF_LOG_LEVEL: self.log_level, + to_string = f"{self.to_dict()}" + + return to_string + + @staticmethod + def default_schema(user_input: dict | None) -> Schema: + if user_input is None: + user_input = {} + + new_user_input = { + vol.Required(CONF_HOST, default=user_input.get(CONF_HOST)): str, + vol.Required(CONF_PORT, default=user_input.get(CONF_PORT, 80)): int, + vol.Optional(CONF_SSL, default=user_input.get(CONF_SSL, False)): bool, } - to_string = f"{obj}" + schema = vol.Schema(new_user_input) - return to_string + return schema diff --git a/custom_components/hpprinter/models/entity_data.py b/custom_components/hpprinter/models/entity_data.py deleted file mode 100644 index c505fb4..0000000 --- a/custom_components/hpprinter/models/entity_data.py +++ /dev/null @@ -1,52 +0,0 @@ -from typing import Optional - -from homeassistant.components.binary_sensor import BinarySensorDeviceClass -from homeassistant.components.sensor import SensorDeviceClass, SensorStateClass - -from ..helpers.const import * - - -class EntityData: - unique_id: str - name: str - state: int - attributes: dict - icon: str - device_name: str - status: str - disabled: bool - binary_sensor_device_class: Optional[BinarySensorDeviceClass] - sensor_device_class: Optional[SensorDeviceClass] - sensor_state_class: Optional[SensorStateClass] - - def __init__(self): - self.unique_id = "" - self.name = "" - self.state = 0 - self.attributes = {} - self.icon = "" - self.device_name = "" - self.status = ENTITY_STATUS_CREATED - self.disabled = False - self.binary_sensor_device_class = None - self.sensor_device_class = None - self.sensor_state_class = None - - def __repr__(self): - obj = { - ENTITY_NAME: self.name, - ENTITY_STATE: self.state, - ENTITY_ATTRIBUTES: self.attributes, - ENTITY_ICON: self.icon, - ENTITY_DEVICE_NAME: self.device_name, - ENTITY_STATUS: self.status, - ENTITY_UNIQUE_ID: self.unique_id, - ENTITY_DISABLED: self.disabled, - ENTITY_BINARY_SENSOR_DEVICE_CLASS: self.binary_sensor_device_class, - ENTITY_SENSOR_DEVICE_CLASS: self.sensor_device_class, - ENTITY_SENSOR_STATE_CLASS: self.sensor_state_class, - } - - to_string = f"{obj}" - - return to_string diff --git a/custom_components/hpprinter/models/exceptions.py b/custom_components/hpprinter/models/exceptions.py new file mode 100644 index 0000000..bc37611 --- /dev/null +++ b/custom_components/hpprinter/models/exceptions.py @@ -0,0 +1,10 @@ +from homeassistant.exceptions import HomeAssistantError + + +class IntegrationAPIError(HomeAssistantError): + def __init__(self, url): + self._url = url + self._message = f"Failed to connect to URL: {url}" + + def __str__(self): + return self._message diff --git a/custom_components/hpprinter/sensor.py b/custom_components/hpprinter/sensor.py index 5c5abbc..3830af0 100644 --- a/custom_components/hpprinter/sensor.py +++ b/custom_components/hpprinter/sensor.py @@ -1,73 +1,53 @@ -""" -Support for HP Printer binary sensors. -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/binary_sensor.hp_printer/ -""" -from __future__ import annotations - import logging -from homeassistant.components.sensor import ( - SensorDeviceClass, - SensorEntity, - SensorStateClass, -) +from homeassistant.components.sensor import SensorEntity +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import Platform from homeassistant.core import HomeAssistant -from .helpers.const import * -from .models.base_entity import HPPrinterEntity, async_setup_base_entry -from .models.entity_data import EntityData +from .common.base_entity import BaseEntity, async_setup_base_entry +from .common.entity_descriptions import IntegrationSensorEntityDescription +from .managers.ha_coordinator import HACoordinator _LOGGER = logging.getLogger(__name__) -CURRENT_DOMAIN = DOMAIN_SENSOR - - -def get_device_tracker(hass: HomeAssistant, integration_name: str, entity: EntityData): - sensor = HPPrinterSensor() - sensor.initialize(hass, integration_name, entity, CURRENT_DOMAIN) - - return sensor - -async def async_setup_entry(hass: HomeAssistant, entry, async_add_entities): - """Set up HP Printer based off an entry.""" +async def async_setup_entry( + hass: HomeAssistant, entry: ConfigEntry, async_add_entities +): await async_setup_base_entry( - hass, entry, async_add_entities, CURRENT_DOMAIN, get_device_tracker + hass, + entry, + Platform.SENSOR, + AquaTempSensorEntity, + async_add_entities, ) -async def async_unload_entry(_hass, config_entry): - _LOGGER.info(f"async_unload_entry {CURRENT_DOMAIN}: {config_entry}") - - return True - - -class HPPrinterSensor(SensorEntity, HPPrinterEntity): - """Representation a binary sensor that is updated by HP Printer.""" +class AquaTempSensorEntity(BaseEntity, SensorEntity): + """Representation of a sensor.""" - @property - def native_value(self): - """Return the state of the sensor.""" - return self.entity.state + def __init__( + self, + entity_description: IntegrationSensorEntityDescription, + coordinator: HACoordinator, + device_code: str, + ): + super().__init__(entity_description, coordinator, device_code) - @property - def device_class(self) -> SensorDeviceClass | str | None: - """Return the class of this sensor.""" - return self.entity.sensor_device_class + self._attr_device_class = entity_description.device_class + self._attr_native_unit_of_measurement = ( + entity_description.native_unit_of_measurement + ) - @property - def state_class(self) -> SensorStateClass | str | None: - """Return the class of this sensor.""" - return self.entity.sensor_state_class + def _handle_coordinator_update(self) -> None: + """Fetch new state parameters for the sensor.""" + device_data = self.get_data() + state = device_data.get(self._data_key) - async def async_added_to_hass_local(self): - _LOGGER.info(f"Added new {self.name}") + if self.native_unit_of_measurement in ["pages", "%", "refill"]: + state = float(state) - def _immediate_update(self, previous_state: bool): - if previous_state != self.entity.state: - _LOGGER.debug( - f"{self.name} updated from {previous_state} to {self.entity.state}" - ) + self._attr_native_value = state - super()._immediate_update(previous_state) + self.async_write_ha_state() diff --git a/custom_components/hpprinter/strings.json b/custom_components/hpprinter/strings.json index 9e1beba..11a14d4 100644 --- a/custom_components/hpprinter/strings.json +++ b/custom_components/hpprinter/strings.json @@ -5,7 +5,8 @@ "title": "Set up HP Printer", "data": { "host": "Host", - "name": "Name" + "port": "Port", + "ssl": "Is SSL" } } }, @@ -24,15 +25,14 @@ "description": "Define additional settings for HP Printer integration", "data": { "host": "Host", - "name": "Name", - "update_interval": "Update interval (Seconds)", - "log_level": "Log level", - "store_data": "Should store responses?" + "port": "Port", + "ssl": "Is SSL", + "update_interval": "Update interval (Seconds)" } } }, "error": { - "error_400": "Invalid server details, please try manually access to `http://{IP}//DevMgmt/ProductStatusDyn.xml` (Replace placeholder with IP or Hostname), in case it's accessible, please report the issue with logs", + "error_400": "Invalid server details, please try manually access to `http{ssl?s}://{host}:{port}/Prefetch?type=dtree`, in case it's accessible, please report the issue with logs", "error_404": "Unsupported API", "already_configured": "HP Printer integration ({name}) already configured" } diff --git a/requirements.txt b/requirements.txt index e8472a8..a2c1f3b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,9 @@ pre-commit -homeassistant +homeassistant~=2024.1.0b0 -aiohttp -xmltodict -voluptuous +voluptuous~=0.13.1 + +aiohttp~=3.9.1 +xmltodict~=0.13.0 + +defusedxml diff --git a/tests/api_test.py b/tests/api_test.py new file mode 100644 index 0000000..dc0b9dc --- /dev/null +++ b/tests/api_test.py @@ -0,0 +1,66 @@ +import asyncio +import json +import logging +import os +import sys + +from custom_components.hpprinter import HAConfigManager +from custom_components.hpprinter.common.consts import DATA_KEYS +from custom_components.hpprinter.managers.rest_api import RestAPIv2 +from homeassistant.core import HomeAssistant + +DEBUG = str(os.environ.get("DEBUG", False)).lower() == str(True).lower() + +log_level = logging.DEBUG if DEBUG else logging.INFO + +root = logging.getLogger() +root.setLevel(log_level) + +stream_handler = logging.StreamHandler(sys.stdout) +stream_handler.setLevel(log_level) +formatter = logging.Formatter("%(asctime)s %(levelname)s %(name)s %(message)s") +stream_handler.setFormatter(formatter) +root.addHandler(stream_handler) + +_LOGGER = logging.getLogger(__name__) + + +class APITest: + def __init__(self): + self._api: RestAPIv2 | None = None + self._config_manager: HAConfigManager | None = None + + self._config_data = { + key: os.environ.get(key) + for key in DATA_KEYS + } + + async def initialize(self): + hass = HomeAssistant(".") + + self._config_manager = HAConfigManager(None, None) + await self._config_manager.initialize(self._config_data) + + self._api = RestAPIv2(hass, self._config_manager) + await self._api.initialize() + + await self._api.update() + + print(json.dumps(self._api.raw_data, indent=4)) + print(json.dumps(self._api.data, indent=4)) + + +if __name__ == "__main__": + loop = asyncio.new_event_loop() + + instance = APITest() + + try: + loop.create_task(instance.initialize()) + loop.run_forever() + + except KeyboardInterrupt: + _LOGGER.info("Aborted") + + except Exception as rex: + _LOGGER.error(f"Error: {rex}") From f42b88fb7e5958cfab1fd7ea9f2ce2c7b28954b5 Mon Sep 17 00:00:00 2001 From: Elad Bar Date: Sat, 17 Feb 2024 18:13:57 +0200 Subject: [PATCH 02/25] functionality fixed --- CHANGELOG.md | 11 + __main__.py | 82 ----- custom_components/hpprinter/__init__.py | 6 +- custom_components/hpprinter/binary_sensor.py | 15 +- .../hpprinter/common/base_entity.py | 102 ++++-- custom_components/hpprinter/common/consts.py | 6 +- .../hpprinter/common/entity_descriptions.py | 251 +------------ .../hpprinter/common/parameter_type.py | 6 + .../hpprinter/data/data_points.json | 140 ------- custom_components/hpprinter/diagnostics.py | 7 +- .../hpprinter/managers/flow_manager.py | 7 +- .../hpprinter/managers/ha_config_manager.py | 208 +++++++++-- .../hpprinter/managers/ha_coordinator.py | 284 +++++++------- .../hpprinter/managers/rest_api.py | 292 +++++++-------- custom_components/hpprinter/manifest.json | 2 +- .../hpprinter/models/config_data.py | 19 +- .../hpprinter/models/exceptions.py | 9 + .../hpprinter/parameters/data_points.json | 346 ++++++++++++++++++ .../endpoint_validations.json | 0 custom_components/hpprinter/sensor.py | 16 +- custom_components/hpprinter/strings.json | 11 +- requirements.txt | 1 + setup.cfg | 2 - tests/api_test.py | 13 +- 24 files changed, 946 insertions(+), 890 deletions(-) delete mode 100644 __main__.py create mode 100644 custom_components/hpprinter/common/parameter_type.py delete mode 100644 custom_components/hpprinter/data/data_points.json create mode 100644 custom_components/hpprinter/parameters/data_points.json rename custom_components/hpprinter/{data => parameters}/endpoint_validations.json (100%) diff --git a/CHANGELOG.md b/CHANGELOG.md index a0f32af..6f83410 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog +## v2.0.0 + +- Refactor to full HP Printer EWS support + +TODO: + +- Entity translations +- Icons +- Configuration test +- Functionality test + ## v1.0.12 - Fix missing references [Issue #103](https://github.com/elad-bar/ha-hpprinter/issues/103) diff --git a/__main__.py b/__main__.py deleted file mode 100644 index b1281e9..0000000 --- a/__main__.py +++ /dev/null @@ -1,82 +0,0 @@ -from custom_components.hpprinter.managers.HPDeviceData import * -from homeassistant.config_entries import ConfigEntry -from homeassistant.const import CONF_HOST, CONF_NAME -from homeassistant.core import HomeAssistant - -logging.basicConfig(level=logging.DEBUG, filename="myapp.log", filemode="w") - -_LOGGER = logging.getLogger(__name__) - - -class Test: - def __init__(self): - _LOGGER.info("Started") - - self._data = None - self._config_manager = ConfigManager() - - data = {CONF_HOST: "", CONF_NAME: DEFAULT_NAME} - - config_entry: ConfigEntry = ConfigEntry( - version=0, - minor_version=0, - domain=DOMAIN, - title=DEFAULT_NAME, - data=data, - source="", - connection_class="", - system_options={}, - ) - print("1.1") - self._config_manager.update(config_entry) - - print("1.2") - - self._config_manager.data.file_reader = self.file_data_provider - - print("1.3") - - hass = HomeAssistant() - - print("1.4") - - self._device_data = HPDeviceData(hass, self._config_manager) - - async def async_parse(self): - print("2.1") - - await self._device_data.update() - - print("2.2") - - json_data = json.dumps(self._device_data.device_data) - _LOGGER.debug(json_data) - - print("2.3") - - await self.terminate() - - async def terminate(self): - print("4.1") - - await self._device_data.terminate() - - print("4.2") - - @staticmethod - def file_data_provider(data_type): - print(f"3.{data_type}") - - with open(f"samples/ink/{data_type}.json") as json_file: - data = json.load(json_file) - - return data - - -if __name__ == "__main__": - # execute only if run as the entry point into the program - - t = Test() - hass = HomeAssistant() - - hass.loop.run_until_complete(t.async_parse()) diff --git a/custom_components/hpprinter/__init__.py b/custom_components/hpprinter/__init__.py index 64533ea..e8f66e4 100644 --- a/custom_components/hpprinter/__init__.py +++ b/custom_components/hpprinter/__init__.py @@ -2,7 +2,7 @@ import sys from homeassistant.config_entries import ConfigEntry -from homeassistant.const import EVENT_HOMEASSISTANT_START +from homeassistant.const import EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP from homeassistant.core import HomeAssistant from .common.consts import DEFAULT_NAME, DOMAIN @@ -41,6 +41,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: EVENT_HOMEASSISTANT_START, coordinator.on_home_assistant_start ) + hass.bus.async_listen_once( + EVENT_HOMEASSISTANT_STOP, coordinator.on_home_assistant_stop + ) + _LOGGER.info("Finished loading integration") initialized = is_initialized diff --git a/custom_components/hpprinter/binary_sensor.py b/custom_components/hpprinter/binary_sensor.py index 3104d75..0627bc0 100644 --- a/custom_components/hpprinter/binary_sensor.py +++ b/custom_components/hpprinter/binary_sensor.py @@ -31,28 +31,19 @@ def __init__( self, entity_description: IntegrationBinarySensorEntityDescription, coordinator: HACoordinator, + device_key: str, ): - super().__init__(entity_description, coordinator) + super().__init__(entity_description, coordinator, device_key) self._attr_device_class = entity_description.device_class - self._entity_attributes = entity_description.attributes self._entity_on_value = entity_description.on_value def _handle_coordinator_update(self) -> None: """Fetch new state parameters for the sensor.""" - device_data = self.get_data() - state = device_data.get(self._data_key) + state = self.get_value() is_on = str(state).lower() == str(self._entity_on_value).lower() - attributes = {} - if self._entity_attributes is not None: - for attribute_key in self._entity_attributes: - value = device_data.get(attribute_key) - - attributes[attribute_key] = value - self._attr_is_on = is_on - self._attr_extra_state_attributes = attributes self.async_write_ha_state() diff --git a/custom_components/hpprinter/common/base_entity.py b/custom_components/hpprinter/common/base_entity.py index 0f52365..8c18fdb 100644 --- a/custom_components/hpprinter/common/base_entity.py +++ b/custom_components/hpprinter/common/base_entity.py @@ -9,7 +9,7 @@ from homeassistant.util import slugify from ..managers.ha_coordinator import HACoordinator -from .consts import ADD_COMPONENT_SIGNALS, DOMAIN +from .consts import DOMAIN, SIGNAL_HA_DEVICE_CREATED from .entity_descriptions import IntegrationEntityDescription _LOGGER = logging.getLogger(__name__) @@ -23,66 +23,100 @@ async def async_setup_base_entry( async_add_entities, ): @callback - def _async_handle_device(entry_id: str): + def _async_handle_device( + entry_id: str, device_key: str, device_data: dict, device_config: dict + ): if entry.entry_id != entry_id: return - try: - coordinator: HACoordinator = hass.data[DOMAIN][entry.entry_id] + coordinator: HACoordinator = hass.data[DOMAIN][entry.entry_id] - entity_descriptions = coordinator.get_entity_descriptions(platform) + _async_handle_device_created( + coordinator, + platform, + entity_type, + async_add_entities, + device_key, + device_data, + device_config, + ) - entities = [ - entity_type(entity_description, coordinator) - for entity_description in entity_descriptions - if entity_description.platform == platform - ] + entry.async_on_unload( + async_dispatcher_connect(hass, SIGNAL_HA_DEVICE_CREATED, _async_handle_device) + ) - _LOGGER.debug(f"Setting up {platform} entities: {entities}") - async_add_entities(entities, True) +def _async_handle_device_created( + coordinator: HACoordinator, + platform: Platform, + entity_type: type, + async_add_entities, + device_key: str, + device_data: dict, + device_config: dict, +): + entities = [] + + device_type = device_config.get("device_type") + + entity_descriptions: list[ + IntegrationEntityDescription + ] = coordinator.config_manager.get_entity_descriptions( + platform, device_type, device_data + ) + + for entity_description in entity_descriptions: + try: + entity = entity_type(entity_description, coordinator, device_key) + + entities.append(entity) except Exception as ex: exc_type, exc_obj, tb = sys.exc_info() line_number = tb.tb_lineno _LOGGER.error( - f"Failed to initialize {platform}, Error: {ex}, Line: {line_number}" + f"Failed to initialize {platform}.{entity_description.key}, " + f"Device Type: {device_type}, " + f"Error: {ex}, Line: {line_number}" ) - for add_component_signal in ADD_COMPONENT_SIGNALS: - entry.async_on_unload( - async_dispatcher_connect(hass, add_component_signal, _async_handle_device) - ) + _LOGGER.debug(f"Setting up {platform} entities: {entities}") + + if entities: + async_add_entities(entities, True) class BaseEntity(CoordinatorEntity): - _device_code: str - _entity_description: IntegrationEntityDescription _translations: dict def __init__( self, entity_description: IntegrationEntityDescription, coordinator: HACoordinator, - item_id: str | None, + device_key: str, ): super().__init__(coordinator) self.entity_description = entity_description - self._item_id = item_id - self._data_key = self._entity_description.data_point_key + self._device_key = device_key + self._device_type = entity_description.device_type - device_info = coordinator.get_device( - entity_description.device_type, self._item_id - ) + device_info = coordinator.get_device(device_key) + + _LOGGER.info(device_info) entity_name = coordinator.config_manager.get_entity_name( entity_description, device_info ) - unique_id_parts = [DOMAIN, entity_description.platform, entity_description.key] + unique_id_parts = [ + DOMAIN, + device_key, + entity_description.platform, + entity_description.key, + ] unique_id = slugify("_".join(unique_id_parts)) @@ -90,22 +124,18 @@ def __init__( self._attr_name = entity_name self._attr_unique_id = unique_id - self._data = {} - @property def local_coordinator(self) -> HACoordinator: return self.coordinator - @property - def data(self) -> dict | None: - return self._data - def get_data(self) -> dict: - section = self._entity_description.data_point_section - array_key = self._entity_description.data_point_item_key + data = self.local_coordinator.get_device_data(self._device_key) + + return data - data = self.local_coordinator.get_device_data( - section, array_key=array_key, item_id=self._item_id + def get_value(self) -> str: + data = self.local_coordinator.get_device_value( + self._device_key, self.entity_description.key ) return data diff --git a/custom_components/hpprinter/common/consts.py b/custom_components/hpprinter/common/consts.py index 78c4e6e..6417239 100644 --- a/custom_components/hpprinter/common/consts.py +++ b/custom_components/hpprinter/common/consts.py @@ -27,15 +27,17 @@ IGNORED_KEYS = ["@schemaLocation", "Version"] -SIGNAL_HA_DEVICE_NEW = f"signal_{DOMAIN}_device_new" +SIGNAL_HA_DEVICE_CREATED = f"signal_{DOMAIN}_device_created" +SIGNAL_HA_DEVICE_DISCOVERED = f"signal_{DOMAIN}_device_discovered" CONFIGURATION_FILE = f"{DOMAIN}.config.json" LEGACY_KEY_FILE = f"{DOMAIN}.key" UPDATE_API_INTERVAL = timedelta(minutes=5) -ADD_COMPONENT_SIGNALS = [SIGNAL_HA_DEVICE_NEW] DEFAULT_ENTRY_ID = "config" CONF_UPDATE_INTERVAL = "update_interval" CONF_TITLE = "title" +DEFAULT_PORT = 80 + DATA_KEYS = [CONF_HOST, CONF_PORT, CONF_SSL] diff --git a/custom_components/hpprinter/common/entity_descriptions.py b/custom_components/hpprinter/common/entity_descriptions.py index c1fa90c..10aaf84 100644 --- a/custom_components/hpprinter/common/entity_descriptions.py +++ b/custom_components/hpprinter/common/entity_descriptions.py @@ -1,8 +1,6 @@ from dataclasses import dataclass -from typing import Callable from homeassistant.components.binary_sensor import BinarySensorEntityDescription -from homeassistant.components.number import NumberEntityDescription from homeassistant.components.sensor import SensorEntityDescription from homeassistant.const import Platform from homeassistant.helpers.entity import EntityDescription @@ -12,10 +10,7 @@ class IntegrationEntityDescription(EntityDescription): platform: Platform | None = None device_type: str | None = None - data_point_section: str | None = None - data_point_key: str | None = None - data_point_item_key: str | None = None - filter: Callable[[dict | None], bool] | None = lambda d: True + exclude: dict | None = None @dataclass(frozen=True, kw_only=True) @@ -24,7 +19,6 @@ class IntegrationBinarySensorEntityDescription( ): platform: Platform | None = Platform.BINARY_SENSOR on_value: str | bool | None = None - attributes: list[str] | None = None @dataclass(frozen=True, kw_only=True) @@ -32,246 +26,3 @@ class IntegrationSensorEntityDescription( SensorEntityDescription, IntegrationEntityDescription ): platform: Platform | None = Platform.SENSOR - - -@dataclass(frozen=True, kw_only=True) -class IntegrationNumberEntityDescription( - NumberEntityDescription, IntegrationEntityDescription -): - platform: Platform | None = Platform.NUMBER - - -DEFAULT_ENTITY_DESCRIPTIONS: list[IntegrationEntityDescription] = [ - IntegrationSensorEntityDescription( - key="printer_total_pages_printed", - device_type="Printer", - data_point_section="Printer", - data_point_key="Total pages printed", - native_unit_of_measurement="pages", - ), - IntegrationSensorEntityDescription( - key="printer_total_black_and_white_pages_printed", - device_type="Printer", - data_point_section="Printer", - data_point_key="Total black-and-white pages printed", - native_unit_of_measurement="pages", - ), - IntegrationSensorEntityDescription( - key="printer_total_color_pages_printed", - device_type="Printer", - data_point_section="Printer", - data_point_key="Total color pages printed", - native_unit_of_measurement="pages", - ), - IntegrationSensorEntityDescription( - key="printer_total_single_sided_pages_printed", - device_type="Printer", - data_point_section="Printer", - data_point_key="Total single-sided pages printed", - native_unit_of_measurement="pages", - ), - IntegrationSensorEntityDescription( - key="printer_total_double_sided_pages_printed", - device_type="Printer", - data_point_section="Printer", - data_point_key="Total double-sided pages printed", - native_unit_of_measurement="pages", - ), - IntegrationSensorEntityDescription( - key="printer_total_jams", - device_type="Printer", - data_point_section="Printer", - data_point_key="Total jams", - native_unit_of_measurement="pages", - ), - IntegrationSensorEntityDescription( - key="printer_miss_picks", - device_type="Printer", - data_point_section="Printer", - data_point_key="Total miss picks", - native_unit_of_measurement="pages", - ), - IntegrationSensorEntityDescription( - key="scanner_total_scanned_pages", - device_type="Scanner", - data_point_section="Scanner", - data_point_key="Total scanned pages", - native_unit_of_measurement="pages", - ), - IntegrationSensorEntityDescription( - key="scanner_total_scanned_pages_from_adf", - device_type="Scanner", - data_point_section="Scanner", - data_point_key="Total scanned pages from ADF", - native_unit_of_measurement="pages", - ), - IntegrationSensorEntityDescription( - key="scanner_total_pages_from_scanner_glass", - device_type="Scanner", - data_point_section="Scanner", - data_point_key="Total pages from scanner glass", - native_unit_of_measurement="pages", - ), - IntegrationSensorEntityDescription( - key="scanner_total_double_sided_pages_scanned", - device_type="Scanner", - data_point_section="Scanner", - data_point_key="Total double-sided pages scanned", - native_unit_of_measurement="pages", - ), - IntegrationSensorEntityDescription( - key="scanner_total_jams", - device_type="Scanner", - data_point_section="Scanner", - data_point_key="Total jams", - native_unit_of_measurement="pages", - ), - IntegrationSensorEntityDescription( - key="scanner_miss_picks", - device_type="Scanner", - data_point_section="Scanner", - data_point_key="Total miss picks", - native_unit_of_measurement="pages", - ), - IntegrationSensorEntityDescription( - key="copy_total_copies", - device_type="Copy", - data_point_section="Copy", - data_point_key="Total copies", - native_unit_of_measurement="pages", - ), - IntegrationSensorEntityDescription( - key="copy_total_copies_from_adf", - device_type="Copy", - data_point_section="Copy", - data_point_key="Total copies from ADF", - native_unit_of_measurement="pages", - ), - IntegrationSensorEntityDescription( - key="copy_total_pages_from_scanner_glass", - device_type="Copy", - data_point_section="Copy", - data_point_key="Total pages from scanner glass", - native_unit_of_measurement="pages", - ), - IntegrationSensorEntityDescription( - key="copy_total_black_and_white_copies", - device_type="Copy", - data_point_section="Copy", - data_point_key="Total black-and-white copies", - native_unit_of_measurement="pages", - ), - IntegrationSensorEntityDescription( - key="copy_total_color_copies", - device_type="Copy", - data_point_section="Copy", - data_point_key="Total color copies", - native_unit_of_measurement="pages", - ), - IntegrationSensorEntityDescription( - key="fax_total_faxed", - device_type="Fax", - data_point_section="Fax", - data_point_key="Total faxed", - native_unit_of_measurement="pages", - ), - IntegrationBinarySensorEntityDescription( - key="eprint_registration_state", - device_type="Main", - data_point_section="ePrint", - data_point_key="Registration State", - on_value="registered", - ), - IntegrationBinarySensorEntityDescription( - key="eprint_status", - device_type="Main", - data_point_section="ePrint", - data_point_key="Status", - on_value="enabled", - ), -] - -CARTRIDGE_ENTITY_DESCRIPTIONS: list[IntegrationEntityDescription] = [ - IntegrationSensorEntityDescription( - key="cartridges_consumable_type_enum", - device_type="Cartridge", - data_point_section="Cartridges", - data_point_item_key="Label Code", - data_point_key="Consumable Type Enum", - ), - IntegrationSensorEntityDescription( - key="cartridges_installation_date", - device_type="Cartridge", - data_point_section="Cartridges", - data_point_item_key="Label Code", - data_point_key="Installation Date", - ), - IntegrationSensorEntityDescription( - key="cartridges_warranty_expiration_date", - device_type="Cartridge", - data_point_section="Cartridges", - data_point_item_key="Label Code", - data_point_key="Warranty Expiration Date", - filter=lambda d: d.get("Consumable Type Enum") != "printhead", - ), - IntegrationSensorEntityDescription( - key="cartridges_consumable_percentage_level_remaining", - device_type="Cartridge", - data_point_section="Cartridges", - data_point_item_key="Label Code", - data_point_key="Consumable Percentage Level Remaining", - native_unit_of_measurement="%", - filter=lambda d: d.get("Consumable Type Enum") != "printhead", - ), - IntegrationSensorEntityDescription( - key="cartridges_estimated_pages_remaining", - device_type="Cartridge", - data_point_section="Cartridges", - data_point_item_key="Label Code", - data_point_key="Estimated Pages Remaining", - native_unit_of_measurement="pages", - filter=lambda d: d.get("Consumable Type Enum") != "printhead", - ), - IntegrationSensorEntityDescription( - key="cartridges_counterfeit_refilled_count", - device_type="Cartridge", - data_point_section="Cartridges", - data_point_item_key="Label Code", - data_point_key="Counterfeit Refilled Count", - native_unit_of_measurement="refill", - ), - IntegrationSensorEntityDescription( - key="cartridges_genuine_refilled_count", - device_type="Cartridge", - data_point_section="Cartridges", - data_point_item_key="Label Code", - data_point_key="Genuine Refilled Count", - native_unit_of_measurement="refill", - ), - IntegrationBinarySensorEntityDescription( - key="cartridges_consumable_state", - device_type="Cartridge", - data_point_section="Cartridges", - data_point_item_key="Label Code", - data_point_key="ConsumableState", - on_value="ok", - ), -] - -ADAPTER_ENTITY_DESCRIPTIONS: list[IntegrationEntityDescription] = [ - IntegrationSensorEntityDescription( - key="adapters_device_connectivity_port_type", - device_type="Main", - data_point_section="Adapters", - data_point_item_key="Name", - data_point_key="DeviceConnectivityPortType", - ), - IntegrationBinarySensorEntityDescription( - key="adapters_is_connected", - device_type="Main", - data_point_section="Adapters", - data_point_item_key="Name", - data_point_key="IsConnected", - on_value="true", - ), -] diff --git a/custom_components/hpprinter/common/parameter_type.py b/custom_components/hpprinter/common/parameter_type.py new file mode 100644 index 0000000..7e28ede --- /dev/null +++ b/custom_components/hpprinter/common/parameter_type.py @@ -0,0 +1,6 @@ +from enum import StrEnum + + +class ParameterType(StrEnum): + DATA_POINTS = "data_points" + ENDPOINT_VALIDATIONS = "endpoint_validations" diff --git a/custom_components/hpprinter/data/data_points.json b/custom_components/hpprinter/data/data_points.json deleted file mode 100644 index c4e8d90..0000000 --- a/custom_components/hpprinter/data/data_points.json +++ /dev/null @@ -1,140 +0,0 @@ -{ - "objects": [ - { - "name": "Adapters", - "endpoint": "/IoMgmt/Adapters", - "path": "Adapters.Adapter", - "subPath": { - "Name": "HardwareConfig.Name", - "DeviceConnectivityPortType": "HardwareConfig.DeviceConnectivityPortType", - "IsConnected": "HardwareConfig.IsConnected" - } - }, - { - "name": "ePrint", - "endpoint": "/ePrint/ePrintConfigDyn.xml", - "path": "ePrintConfigDyn", - "subPath": { - "Printer ID": "PrinterID", - "Registration State": "RegistrationState", - "Status": "CloudServicesSwitch.Status" - } - }, - { - "name": "Wifi Direct Current Configuration", - "endpoint": "/DevMgmt/NetAppsSecureDyn.xml", - "path": "NetAppsSecureDyn.WirelessDirectConfig", - "subPath": { - "SSID Prefix": "SSIDPrefix", - "Connection Method": "ConnectionMethod" - } - }, - { - "name": "Product", - "endpoint": "/DevMgmt/ProductConfigDyn.xml", - "path": "ProductConfigDyn.ProductInformation", - "subPath": { - "Make And Model": "MakeAndModel", - "Make And Model Family": "MakeAndModelFamily", - "SKU Identifier": "SKUIdentifier", - "Serial Number": "SerialNumber", - "Product Number": "ProductNumber", - "Manufacturer Name": "Manufacturer.Name", - "Manufactured At": "Manufacturer.Date" - } - }, - { - "name": "Cartridges", - "endpoint": "/DevMgmt/ConsumableConfigDyn.xml", - "path": "ConsumableConfigDyn.ConsumableInfo", - "subPath": { - "Label Code": "ConsumableLabelCode", - "Consumable State": "ConsumableLifeState.ConsumableState", - "Brand": "ConsumableLifeState.Brand", - "Consumable Station": "ConsumableStation", - "Consumable Type Enum": "ConsumableTypeEnum", - "Installation Date": "Installation.Date", - "Max Capacity": "Capacity.MaxCapacity", - "Consumable Percentage Level Remaining": "ConsumablePercentageLevelRemaining", - "Consumable Selectibility Number": "ConsumableSelectibilityNumber", - "Manufacturer Name": "Manufacturer.Name", - "Manufactured At": "Manufacturer.Date", - "Serial Number": "SerialNumber", - "Product Number": "ProductNumber", - "Warranty Expiration Date": "Warranty.ExpirationDate", - "Consumable Unique ID": "ConsumableUniqueID" - } - }, - { - "name": "Cartridges Usage", - "endpoint": "/DevMgmt/ProductUsageDyn.xml", - "path": "ProductUsageDyn.ConsumableSubunit.Consumable", - "subPath": { - "Consumable Station": "ConsumableStation", - "Marker Color": "MarkerColor", - "Consumable Type Enum": "ConsumableTypeEnum", - "Estimated Pages Remaining": "EstimatedPagesRemaining", - "ConsumableState": "ConsumableState", - "Consumable Raw Percentage Level Remaining": "ConsumableRawPercentageLevelRemaining", - "Serial Number": "SupplySerialNumber.#text", - "Counterfeit Refilled Count": "RefilledCount.CounterfeitRefilledCount.#text", - "Genuine Refilled Count": "RefilledCount.GenuineRefilledCount" - } - }, - { - "name": "Printer", - "endpoint": "/DevMgmt/ProductUsageDyn.xml", - "path": "ProductUsageDyn.PrinterSubunit", - "subPath": { - "Total pages printed": "TotalImpressions.#text", - "Total black-and-white pages printed": "MonochromeImpressions", - "Total color pages printed": "ColorImpressions", - "Total single-sided pages printed": "SimplexSheets", - "Total double-sided pages printed": "DuplexSheets.#text", - "Total jams": "JamEvents.#text", - "Total miss picks": "MispickEvents" - } - }, - { - "name": "Scanner", - "endpoint": "/DevMgmt/ProductUsageDyn.xml", - "path": "ProductUsageDyn.ScannerEngineSubunit", - "subPath": { - "Total scanned pages": "ScanImages.#text", - "Total scanned pages from ADF": "AdfImages.#text", - "Total double-sided pages scanned": "DuplexSheets.#text", - "Total pages from scanner glass": "FlatbedImages", - "Total jams": "JamEvents", - "Total miss picks": "MispickEvents" - } - }, - { - "name": "Copy", - "endpoint": "/DevMgmt/ProductUsageDyn.xml", - "path": "ProductUsageDyn.CopyApplicationSubunit", - "subPath": { - "Total copies": "TotalImpressions.#text", - "Total copies from ADF": "AdfImages", - "Total double-sided pages scanned": "DuplexSheets.#text", - "Total pages from scanner glass": "FlatbedImages", - "Total black-and-white copies": "MonochromeImpressions", - "Total color copies": "ColorImpressions" - } - }, - { - "name": "Fax", - "endpoint": "/DevMgmt/ProductUsageDyn.xml", - "path": "ProductUsageDyn.FaxApplicationSubunit", - "subPath": { - "Total faxes": "TotalImpressions.#text" - } - } - ], - "merge": [ - { - "from": "Cartridges Usage", - "to": "Cartridges", - "key": "Consumable Station" - } - ] -} diff --git a/custom_components/hpprinter/diagnostics.py b/custom_components/hpprinter/diagnostics.py index fc0b9a1..dc9e0e2 100644 --- a/custom_components/hpprinter/diagnostics.py +++ b/custom_components/hpprinter/diagnostics.py @@ -11,7 +11,6 @@ from .common.consts import DOMAIN from .managers.ha_coordinator import HACoordinator -from .managers.rest_api import RestAPIv2 _LOGGER = logging.getLogger(__name__) @@ -42,10 +41,6 @@ async def _async_get_diagnostics( coordinator: HACoordinator = hass.data[DOMAIN][entry.entry_id] - rest_api = RestAPIv2(hass, coordinator.config_manager) - - await rest_api.initialize() - data = { "disabled_by": entry.disabled_by, "disabled_polling": entry.pref_disable_polling, @@ -72,7 +67,7 @@ def _async_device_as_dict( "name_by_user": ha_device.name_by_user, "disabled": ha_device.disabled, "disabled_by": ha_device.disabled_by, - "data": additional_data, + "parameters": additional_data, "entities": [], } diff --git a/custom_components/hpprinter/managers/flow_manager.py b/custom_components/hpprinter/managers/flow_manager.py index 4a2291c..eabcae4 100644 --- a/custom_components/hpprinter/managers/flow_manager.py +++ b/custom_components/hpprinter/managers/flow_manager.py @@ -11,7 +11,7 @@ from ..common.consts import DATA_KEYS, DEFAULT_NAME from ..models.config_data import ConfigData -from ..models.exceptions import IntegrationAPIError +from ..models.exceptions import IntegrationAPIError, IntegrationParameterError from .ha_config_manager import HAConfigManager from .rest_api import RestAPIv2 @@ -72,6 +72,11 @@ async def async_step(self, user_input: dict | None = None): return self._flow_handler.async_create_entry(title=title, data=data) + except IntegrationParameterError as ipex: + form_errors = {"base": "error_400"} + + _LOGGER.warning(f"Failed to setup integration, Error: {ipex}") + except IntegrationAPIError as iapiex: form_errors = {"base": "error_404"} diff --git a/custom_components/hpprinter/managers/ha_config_manager.py b/custom_components/hpprinter/managers/ha_config_manager.py index fc3f6e5..1fb6c78 100644 --- a/custom_components/hpprinter/managers/ha_config_manager.py +++ b/custom_components/hpprinter/managers/ha_config_manager.py @@ -1,9 +1,10 @@ -from copy import copy import json import logging +import os +from pathlib import Path from homeassistant.config_entries import STORAGE_VERSION, ConfigEntry -from homeassistant.const import CONF_PASSWORD, CONF_TEMPERATURE_UNIT, CONF_USERNAME +from homeassistant.const import Platform from homeassistant.core import HomeAssistant from homeassistant.helpers import translation from homeassistant.helpers.entity import DeviceInfo @@ -18,16 +19,17 @@ DOMAIN, ) from ..common.entity_descriptions import ( - DEFAULT_ENTITY_DESCRIPTIONS, + IntegrationBinarySensorEntityDescription, IntegrationEntityDescription, + IntegrationSensorEntityDescription, ) +from ..common.parameter_type import ParameterType from ..models.config_data import ConfigData _LOGGER = logging.getLogger(__name__) class HAConfigManager: - _api_config: dict | None _translations: dict | None _entry: ConfigEntry | None _entry_id: str @@ -41,19 +43,15 @@ def __init__(self, hass: HomeAssistant | None, entry: ConfigEntry | None): self._data = None self.platforms = [] - self._entity_descriptions = {} - self._protocol_codes = {} - self._protocol_codes_configuration = {} + self._entity_descriptions: list[IntegrationEntityDescription] | None = None - self._hvac_modes = {} - self._hvac_modes_reverse = {} + self._translations = None - self._fan_modes = {} - self._fan_modes_reverse = {} + self._endpoints: list[str] | None = None - self._devices = {} - self._api_config = None - self._translations = None + self._data_points: dict | None = None + self._exclude_uri_list: list[str] | None = None + self._exclude_type_list: list[str] | None = None self._entry = entry self._entry_id = DEFAULT_ENTRY_ID if entry is None else entry.entry_id @@ -105,11 +103,28 @@ def update_interval(self) -> int: return interval + @property + def endpoints(self) -> list[str] | None: + endpoints = self._endpoints + + return endpoints + + @property + def data_points(self) -> dict | None: + data_points = self._data_points + + return data_points + async def initialize(self, entry_config: dict): await self._load() self._config_data.update(entry_config) + self._load_exclude_endpoints_configuration() + self._load_data_points_configuration() + + self._load_entity_descriptions() + if self._hass: self._translations = await translation.async_get_translations( self._hass, self._hass.config.language, "entity", {DOMAIN} @@ -158,7 +173,7 @@ def get_entity_name( ) entity_name = ( - device_name + f"{device_name} {entity_description.name}" if translated_name is None or translated_name == "" else f"{device_name} {translated_name}" ) @@ -207,7 +222,7 @@ async def _load_config_from_file(self): @staticmethod def _get_defaults() -> dict: - data = {CONF_TEMPERATURE_UNIT: {}} + data = {CONF_UPDATE_INTERVAL: 5} return data @@ -231,19 +246,12 @@ async def _save(self): for key in self._data: stored_value = entry_data.get(key) - if key in [CONF_PASSWORD, CONF_USERNAME]: - entry_data.pop(key) + current_value = self._data.get(key) - if stored_value is not None: - should_save = True + if stored_value != current_value: + should_save = True - else: - current_value = self._data.get(key) - - if stored_value != current_value: - should_save = True - - entry_data[key] = self._data[key] + entry_data[key] = self._data[key] if DEFAULT_ENTRY_ID in store_data: store_data.pop(DEFAULT_ENTRY_ID) @@ -254,17 +262,151 @@ async def _save(self): await self._store.async_save(store_data) - def _load_entity_descriptions(self, product_id: str): - entities = copy(DEFAULT_ENTITY_DESCRIPTIONS) + def _load_entity_descriptions(self): + self._entity_descriptions = [] + + for data_point in self._data_points: + device_type = data_point.get("device_type") + properties = data_point.get("properties") + + for property_key in properties: + property_data = properties[property_key] + + if "platform" in property_data: + property_platform = property_data.get("platform") + exclude = property_data.get("exclude") + device_class = property_data.get("device_class") + + if property_platform == str(Platform.BINARY_SENSOR): + on_value = property_data.get("on_value") + + entity_description = IntegrationBinarySensorEntityDescription( + key=property_key, + name=property_key, + device_type=device_type, + exclude=exclude, + on_value=on_value, + device_class=device_class, + ) + + self._entity_descriptions.append(entity_description) + + elif property_platform == str(Platform.SENSOR): + unit_of_measurement = property_data.get("unit_of_measurement") - self._update_platforms(entities) + entity_description = IntegrationSensorEntityDescription( + key=property_key, + name=property_key, + device_type=device_type, + exclude=exclude, + native_unit_of_measurement=unit_of_measurement, + device_class=device_class, + ) - self._entity_descriptions[product_id] = entities + self._entity_descriptions.append(entity_description) - def _update_platforms(self, entity_descriptions): - for entity_description in entity_descriptions: + self._update_platforms() + + def _update_platforms(self): + for entity_description in self._entity_descriptions: if ( entity_description.platform not in self.platforms and entity_description.platform is not None ): self.platforms.append(entity_description.platform) + + def get_entity_descriptions( + self, platform: Platform, device_type: str, device_data: dict + ) -> list[IntegrationEntityDescription]: + entity_descriptions = [ + entity_description + for entity_description in self._entity_descriptions + if self._is_valid_entity( + entity_description, device_data, device_type, platform + ) + ] + + return entity_descriptions + + @staticmethod + def _is_valid_entity( + entity_description: IntegrationEntityDescription, + data: dict, + device_type: str, + platform: Platform, + ) -> bool: + key = entity_description.key + exclude = entity_description.exclude + + is_valid = ( + entity_description.platform == platform + and entity_description.device_type == device_type + and key in data + ) + + if is_valid and exclude: + for exclude_key in exclude: + exclude_value = exclude[exclude_key] + + if data.get(exclude_key) == exclude_value: + is_valid = False + break + + return is_valid + + def _load_data_points_configuration(self): + self._endpoints = [] + + self._data_points = self._get_parameters(ParameterType.DATA_POINTS) + + endpoint_objects = self._data_points + + for endpoint in endpoint_objects: + endpoint_uri = endpoint.get("endpoint") + + if ( + endpoint_uri not in self._endpoints + and endpoint_uri not in self._exclude_uri_list + ): + self._endpoints.append(endpoint_uri) + + def _load_exclude_endpoints_configuration(self): + endpoints = self._get_parameters(ParameterType.ENDPOINT_VALIDATIONS) + + self._exclude_uri_list = endpoints.get("exclude_uri") + self._exclude_type_list = endpoints.get("exclude_type") + + @staticmethod + def _get_parameters(parameter_type: ParameterType) -> dict: + config_file = f"{parameter_type}.json" + current_path = Path(__file__) + parent_directory = current_path.parents[1] + file_path = os.path.join(parent_directory, "parameters", config_file) + + with open(file_path) as f: + data = json.load(f) + + return data + + def is_valid_endpoint(self, endpoint: dict): + endpoint_type = endpoint.get("type") + uri = endpoint.get("uri") + methods = endpoint.get("methods", ["get"]) + + is_invalid_type = endpoint_type in self._exclude_type_list + invalid_endpoint_uri = uri in self._exclude_uri_list + invalid_uri_resource = uri.endswith("Cap.xml") + invalid_uri_parameter = "{" in uri or "}" in uri + invalid_methods = "get" not in methods + + invalid_data = [ + is_invalid_type, + invalid_uri_resource, + invalid_uri_parameter, + invalid_methods, + invalid_endpoint_uri, + ] + + is_valid = True not in invalid_data + + return is_valid diff --git a/custom_components/hpprinter/managers/ha_coordinator.py b/custom_components/hpprinter/managers/ha_coordinator.py index 4d33017..f35feb8 100644 --- a/custom_components/hpprinter/managers/ha_coordinator.py +++ b/custom_components/hpprinter/managers/ha_coordinator.py @@ -1,16 +1,19 @@ -from copy import copy import logging -from homeassistant.const import Platform from homeassistant.core import Event +from homeassistant.helpers.dispatcher import ( + async_dispatcher_connect, + async_dispatcher_send, +) from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed +from homeassistant.util import slugify -from ..common.consts import DOMAIN, UPDATE_API_INTERVAL -from ..common.entity_descriptions import ( - CARTRIDGE_ENTITY_DESCRIPTIONS, - DEFAULT_ENTITY_DESCRIPTIONS, - IntegrationEntityDescription, +from ..common.consts import ( + DOMAIN, + SIGNAL_HA_DEVICE_CREATED, + SIGNAL_HA_DEVICE_DISCOVERED, + UPDATE_API_INTERVAL, ) from .ha_config_manager import HAConfigManager from .rest_api import RestAPIv2 @@ -37,17 +40,45 @@ def __init__( self._api = RestAPIv2(hass, config_manager) self._config_manager = config_manager - self._main_device: DeviceInfo | None = None self._devices: dict[str, DeviceInfo] = {} - self._device_type_mapping: dict[str, str] | None = None + + self._main_device_data: dict | None = None + self._main_device_id: str | None = None + + self._device_handlers = { + "Main": self.create_main_device, + "Consumable": self.create_consumable_device, + "Adapter": self.create_adapter_device, + } + + self.config_entry.async_on_unload( + async_dispatcher_connect( + hass, SIGNAL_HA_DEVICE_DISCOVERED, self._on_device_discovered + ) + ) @property - def config_manager(self): + def api(self) -> RestAPIv2: + return self._api + + @property + def config_manager(self) -> HAConfigManager: return self._config_manager + @property + def entry_id(self) -> str: + return self._config_manager.entry_id + + @property + def entry_title(self) -> str: + return self._config_manager.entry_title + async def on_home_assistant_start(self, _event_data: Event): await self.initialize() + async def on_home_assistant_stop(self, _event_data: Event): + await self._api.terminate() + async def initialize(self): _LOGGER.debug("Initializing coordinator") @@ -61,73 +92,40 @@ async def initialize(self): await self.async_config_entry_first_refresh() - def set_devices(self): - self._device_type_mapping = { - "Main": "main", - "Adapters": "adapter", - "Cartridges": "cartridge", - } - - self.create_main_device() - - sub_units = [ - entity_description.device_type - for entity_description in DEFAULT_ENTITY_DESCRIPTIONS - if entity_description.device_type != "Main" - ] - - sub_units = list(dict.fromkeys(sub_units)) - - for sub_unit in sub_units: - self.create_sub_unit_device(sub_unit) - - self._device_type_mapping[sub_unit] = sub_unit - - cartridge_details = self._api.data.get("Cartridges") - for cartridge_data in cartridge_details: - self.create_cartridge_device(cartridge_data) - - adapter_details = self._api.data.get("Adapters") - for adapter_data in adapter_details: - self.create_adapter_device(adapter_data) - - def create_main_device(self): - entry_id = self.config_entry.entry_id - entry_title = self.config_entry.entry_id - - product_details = self._api.data.get("Product") - - model = product_details.get("Make And Model") - serial_number = product_details.get("Serial Number") - manufacturer = product_details.get("Manufacturer Name") + def create_main_device( + self, device_key: str, device_data: dict, _device_config: dict + ): + self._main_device_data = device_data + self._main_device_id = device_key - device_unique_id = f"{entry_id}.main" + model = device_data.get("make_and_model") + serial_number = device_data.get("serial_number") + manufacturer = device_data.get("manufacturer_name") - device_identifier = (DOMAIN, device_unique_id) + device_identifier = (DOMAIN, self._main_device_id) device_info = DeviceInfo( identifiers={device_identifier}, - name=entry_title, + name=self.entry_title, model=model, serial_number=serial_number, manufacturer=manufacturer, ) - self._main_device = device_info - self._devices[device_unique_id] = device_info + self._devices[device_key] = device_info - def create_sub_unit_device(self, sub_unit: str): - entry_id = self.config_entry.entry_id - entry_title = self.config_entry.entry_id + def create_sub_unit_device( + self, device_key: str, _device_data: dict, device_config: dict + ): + model = self._main_device_data.get("make_and_model") + serial_number = self._main_device_data.get("serial_number") + manufacturer = self._main_device_data.get("manufacturer_name") - serial_number = self._main_device.get("serial_number") - model = self._main_device.get("model") - manufacturer = self._main_device.get("manufacturer") + device_type = device_config.get("device_type") - device_unique_id = f"{entry_id}.{sub_unit.lower()}" - main_device_unique_id = f"{entry_id}.main" + device_unique_id = slugify(f"{self.entry_id}.{device_key}") - sub_unit_device_name = f"{entry_title} {sub_unit}" + sub_unit_device_name = f"{self.entry_title} {device_type}" device_identifier = (DOMAIN, device_unique_id) @@ -137,34 +135,33 @@ def create_sub_unit_device(self, sub_unit: str): model=model, serial_number=serial_number, manufacturer=manufacturer, - via_device=(DOMAIN, main_device_unique_id), + via_device=(DOMAIN, self._main_device_id), ) - self._devices[device_unique_id] = device_info + self._devices[device_key] = device_info - def create_cartridge_device(self, cartridge_data: dict): - entry_id = self.config_entry.entry_id - entry_title = self.config_entry.entry_id + def create_consumable_device( + self, device_key: str, device_data: dict, _device_config: dict + ): + printer_device_unique_id = slugify(f"{self.entry_id}.printer") - printer_device_unique_id = f"{entry_id}.printer" + device_name_parts = [self.entry_title] + cartridge_type: str = device_data.get("consumable_type_enum") + cartridge_color = device_data.get("marker_color") + manufacturer = device_data.get("consumable_life_state_brand") + serial_number = device_data.get("serial_number") - device_name_parts = [entry_title] - cartridge_type: str = cartridge_data.get("Consumable Type Enum") - cartridge_color = cartridge_data.get("Marker Color") - manufacturer = cartridge_data.get("Brand") - serial_number = cartridge_data.get("Serial Number") - label_code = cartridge_data.get("Label Code") - model = cartridge_data.get("Consumable Selectibility Number") + model = device_data.get("consumable_selectibility_number") if cartridge_type == "printhead": device_name_parts.append(cartridge_type.capitalize()) - model = cartridge_type + model = cartridge_type.capitalize() else: device_name_parts.append(cartridge_color) device_name_parts.append(cartridge_type.capitalize()) - device_unique_id = f"{entry_id}.cartridge.{label_code}" + device_unique_id = slugify(f"{self.entry_id}.{device_key}") cartridge_device_name = " ".join(device_name_parts) @@ -179,26 +176,26 @@ def create_cartridge_device(self, cartridge_data: dict): via_device=(DOMAIN, printer_device_unique_id), ) - self._devices[device_unique_id] = device_info + self._devices[device_key] = device_info - def create_adapter_device(self, adapter_data: dict): - entry_id = self.config_entry.entry_id - entry_title = self.config_entry.entry_id - - serial_number = self._main_device.get("serial_number") - manufacturer = self._main_device.get("manufacturer") + def create_adapter_device( + self, device_key: str, device_data: dict, device_config: dict + ): + serial_number = self._main_device_data.get("serial_number") + manufacturer = self._main_device_data.get("manufacturer_name") - adapter_name = adapter_data.get("Name").upper() + adapter_name = device_data.get("hardware_config_name").upper() model = ( - adapter_data.get("DeviceConnectivityPortType") + device_data.get("hardware_config_device_connectivity_port_type") .replace("Embedded", "") .upper() ) - device_unique_id = f"{entry_id}.adapter.{adapter_name.lower()}" - main_device_unique_id = f"{entry_id}.main" + device_type = device_config.get("device_type") - adapter_device_name = f"{entry_title} Adapter {adapter_name}" + device_unique_id = slugify(f"{self.entry_id}.{device_key}") + + adapter_device_name = f"{self.entry_title} {device_type} {adapter_name}" device_identifier = (DOMAIN, device_unique_id) @@ -208,36 +205,23 @@ def create_adapter_device(self, adapter_data: dict): model=model, serial_number=serial_number, manufacturer=manufacturer, - via_device=(DOMAIN, main_device_unique_id), + via_device=(DOMAIN, self._main_device_id), ) - self._devices[device_unique_id] = device_info - - def get_device(self, device_type: str, item_id: str | None) -> DeviceInfo: - device_id = self._device_type_mapping.get(device_type) - - if item_id is not None: - device_id = f"{device_id}.{item_id}" + self._devices[device_key] = device_info - device_unique_key = f"{self.config_entry.entry_id}.{device_id}" + def get_device(self, device_key: str) -> DeviceInfo | None: + result = self._devices.get(device_key) - device_info = self._devices.get(device_unique_key) - - return device_info + return result - def get_device_data( - self, - section: str, - key: str | None = None, - array_key: str | None = None, - item_id: str | None = None, - ): - data = self._api.data.get(section, {}) + def get_device_data(self, device_key: str): + data = self._api.data.get(device_key, {}) - if array_key: - items = [item for item in data if item.get(array_key) == item_id] + return data - data = None if len(items) == 0 else items[0] + def get_device_value(self, device_key: str, key: str | None): + data = self.get_device_data(device_key) if key and data is not None: return data.get(key) @@ -247,7 +231,11 @@ def get_device_data( async def get_debug_data(self) -> dict: await self._api.update_full() - data = {"raw": self._api.raw_data, "processed": self._api.data} + data = { + "rawData": self._api.raw_data, + "devicesData": self._api.data, + "devicesConfig": self._api.data_config, + } return data @@ -260,52 +248,34 @@ async def _async_update_data(self): except Exception as err: raise UpdateFailed(f"Error communicating with API: {err}") - def get_entity_descriptions( - self, platform: Platform - ) -> list[IntegrationEntityDescription]: - entity_descriptions = [ - copy(entity_description) - for entity_description in DEFAULT_ENTITY_DESCRIPTIONS - if entity_description.platform == platform - ] - - cartridges = self.get_device_data("Cartridges") - - for cartridge in cartridges: - cartridge_entity_descriptions = self._get_cartridge_entity_descriptions( - platform, cartridge - ) - - entity_descriptions.extend(cartridge_entity_descriptions) - - return entity_descriptions - - def _get_cartridge_entity_descriptions( - self, platform: Platform, cartridge: dict - ) -> list[IntegrationEntityDescription]: - entity_descriptions = [ - self._get_cartridge_entity_description(entity_description, cartridge) - for entity_description in CARTRIDGE_ENTITY_DESCRIPTIONS - if entity_description.platform == platform - ] + async def _on_device_discovered( + self, entry_id: str, device_key: str, device_data: dict, device_config: dict + ): + if entry_id != self.config_entry.entry_id: + return - result = [ - entity_description - for entity_description in entity_descriptions - if entity_description is not None + handlers = [ + device_prefix + for device_prefix in self._device_handlers + if device_key.startswith(device_prefix) ] - return result - - @staticmethod - def _get_cartridge_entity_description( - entity_description: IntegrationEntityDescription, cartridge: dict - ) -> IntegrationEntityDescription | None: - is_valid = entity_description.filter(cartridge) + if handlers: + handler_key = handlers[0] + handler = self._device_handlers[handler_key] - if not is_valid: - return None + handler(device_key, device_data, device_config) - result = copy(entity_description) + else: + self.create_sub_unit_device(device_key, device_data, device_config) + + async_dispatcher_send( + self.hass, + SIGNAL_HA_DEVICE_CREATED, + self.entry_id, + device_key, + device_data, + device_config, + ) - return result + self.hass.create_task(self.async_request_refresh()) diff --git a/custom_components/hpprinter/managers/rest_api.py b/custom_components/hpprinter/managers/rest_api.py index 326817b..6e014de 100644 --- a/custom_components/hpprinter/managers/rest_api.py +++ b/custom_components/hpprinter/managers/rest_api.py @@ -1,13 +1,13 @@ import json import logging -import os -from pathlib import Path import sys from aiohttp import ClientResponseError, ClientSession, ClientTimeout, TCPConnector from defusedxml import ElementTree +from flatten_json import flatten import xmltodict +from homeassistant.const import CONF_HOST from homeassistant.helpers.aiohttp_client import ( ENABLE_CLEANUP_CLOSED, MAXIMUM_CONNECTIONS, @@ -17,9 +17,9 @@ from homeassistant.util import ssl from homeassistant.util.ssl import SSLCipherList -from ..common.consts import IGNORED_KEYS, SIGNAL_HA_DEVICE_NEW +from ..common.consts import IGNORED_KEYS, SIGNAL_HA_DEVICE_DISCOVERED from ..models.config_data import ConfigData -from ..models.exceptions import IntegrationAPIError +from ..models.exceptions import IntegrationAPIError, IntegrationParameterError from .ha_config_manager import HAConfigManager _LOGGER = logging.getLogger(__name__) @@ -34,23 +34,22 @@ def __init__(self, hass, config_manager: HAConfigManager): self._session: ClientSession | None = None self._data: dict = {} - self._raw_data: dict = {} - - self._resources: dict | None = None - self._data_points: dict | None = None + self._data_config: dict = {} - self._endpoints: list[str] | None = None - self._all_endpoints: list[str] | None = None - - self._exclude_uri_list: list[str] | None = None - self._exclude_type_list: list[str] | None = None + self._raw_data: dict = {} self._is_connected: bool = False + self._device_dispatched: list[str] = [] + @property def data(self) -> dict | None: return self._data + @property + def data_config(self) -> dict | None: + return self._data_config + @property def raw_data(self) -> dict | None: return self._raw_data @@ -62,31 +61,32 @@ def config_data(self) -> ConfigData | None: return None - @property - def _base_url(self): - config_data = self.config_data + async def terminate(self): + _LOGGER.info("Terminating session to HP Printer EWS") + + self._is_connected = False - url = f"{config_data.protocol}://{config_data.hostname}:{config_data.port}" + if self._session is not None: + await self._session.close() - return url + self._session = None - async def initialize(self): + async def initialize(self, throw_exception: bool = False): try: if not self.config_data.hostname: - _LOGGER.error("Failed to get hostname") - return + raise IntegrationParameterError(CONF_HOST) if self._session is None: self._session = ClientSession( loop=self._loop, connector=self._get_ssl_connector() ) - self._load_exclude_endpoints_configuration() - self._load_data_points_configuration() - await self._load_metadata() except Exception as ex: + if throw_exception: + raise ex + exc_type, exc_obj, tb = sys.exc_info() line_number = tb.tb_lineno @@ -94,40 +94,6 @@ async def initialize(self): f"Failed to initialize session, Error: {ex}, Line: {line_number}" ) - def _load_data_points_configuration(self): - self._endpoints = [] - - self._data_points = self._get_data_from_file("data_points") - - endpoint_objects = self._data_points.get("objects") - - for endpoint in endpoint_objects: - endpoint_uri = endpoint.get("endpoint") - - if ( - endpoint_uri not in self._endpoints - and endpoint_uri not in self._exclude_uri_list - ): - self._endpoints.append(endpoint_uri) - - def _load_exclude_endpoints_configuration(self): - endpoints = self._get_data_from_file("endpoint_validations") - - self._exclude_uri_list = endpoints.get("exclude_uri") - self._exclude_type_list = endpoints.get("exclude_type") - - @staticmethod - def _get_data_from_file(file_name: str) -> dict: - config_file = f"{file_name}.json" - current_path = Path(__file__) - parent_directory = current_path.parents[1] - file_path = os.path.join(parent_directory, "data", config_file) - - with open(file_path) as f: - data = json.load(f) - - return data - def _get_ssl_connector(self): ssl_context = ssl.create_no_verify_ssl_context(SSLCipherList.INTERMEDIATE) @@ -147,10 +113,10 @@ async def _load_metadata(self): endpoints = await self._get_request("/Prefetch?type=dtree") if not endpoints: - raise IntegrationAPIError(self._base_url) + raise IntegrationAPIError(self.config_data.url) for endpoint in endpoints: - is_valid = self._is_valid_endpoint(endpoint) + is_valid = self._config_manager.is_valid_endpoint(endpoint) if is_valid: endpoint_uri = endpoint.get("uri") @@ -159,37 +125,8 @@ async def _load_metadata(self): self._is_connected = True - async_dispatcher_send( - self._hass, - SIGNAL_HA_DEVICE_NEW, - self._config_manager.entry_id, - ) - - def _is_valid_endpoint(self, endpoint: dict): - endpoint_type = endpoint.get("type") - uri = endpoint.get("uri") - methods = endpoint.get("methods", ["get"]) - - is_invalid_type = endpoint_type in self._exclude_type_list - invalid_endpoint_uri = uri in self._exclude_uri_list - invalid_uri_resource = uri.endswith("Cap.xml") - invalid_uri_parameter = "{" in uri or "}" in uri - invalid_methods = "get" not in methods - - invalid_data = [ - is_invalid_type, - invalid_uri_resource, - invalid_uri_parameter, - invalid_methods, - invalid_endpoint_uri, - ] - - is_valid = True not in invalid_data - - return is_valid - async def update(self): - await self._update_data(self._endpoints) + await self._update_data(self._config_manager.endpoints) async def update_full(self): await self._update_data(self._all_endpoints) @@ -203,93 +140,141 @@ async def _update_data(self, endpoints): self._raw_data[endpoint] = resource_data - endpoint_objects = self._data_points.get("objects") - merge_items = self._data_points.get("merge") + devices = self._get_devices_data() - for data_point in endpoint_objects: - data_point_name = data_point.get("name") - data_point_endpoint = data_point.get("endpoint") - data_point_path = data_point.get("path") - data_point_sub_path = data_point.get("subPath") + self._extract_data(devices) - if data_point_endpoint is not None: - data_item = self._raw_data.get(data_point_endpoint) + def _extract_data(self, devices: list[dict]): + device_data = {} + device_config = {} - item = self._get_data_item(data_item, data_point_path) + for item in devices: + item_config = item.get("config") + item_data = item.get("data") - if data_point_sub_path is not None: - if isinstance(item, list): - item = self._get_data_items(item, data_point_sub_path) + device_type = item_config.get("device_type") + identifier = item_config.get("identifier") + properties = item_config.get("properties") - else: - item = self._get_sub_items(item, data_point_sub_path) + device_key = device_type + + if identifier is not None: + device_id = item_data.get(identifier) + device_key = f"{device_type}.{device_id}" + + data = device_data[device_key] if device_key in device_data else {} + data.update(item_data) + + if device_key in device_config: + config = device_config[device_key] + config["properties"].update(properties) + + else: + device_config[device_key] = { + "device_type": device_type, + "properties": properties, + } + + device_data[device_key] = data + + self._data_config = device_config + self._data = device_data + + for device_key in self._data: + self.device_data_changed(device_key) + + @staticmethod + def _get_device_from_list( + data: list[dict], identifier_key: str, device_id + ) -> dict | None: + data_items = [ + data_item + for data_item in data + if data_item.get(identifier_key) == device_id + ] + + if data_items: + return data_items[0] - self._data[data_point_name] = item + else: + return None - for merge_item in merge_items: - self._merge_data_items(merge_item) + def _get_device_config(self, device_type: str) -> dict | None: + data_configs = [ + data_point + for data_point in self._config_manager.data_points + if device_type == data_point.get("name") + ] + + if data_configs: + return data_configs[0] + + else: + return None @staticmethod - def _get_data_item(data_item: dict, path: str): + def _get_data_section(data: dict, path: str) -> dict: path_parts = path.split(".") - value = data_item.copy() + result = data for path_part in path_parts: - value = value.get(path_part) + result = result.get(path_part) - if value is None: - break + return result - return value + def _get_devices_data(self): + devices = [] - def _get_sub_items(self, data_item: dict, sub_path_mapping: dict) -> dict: - data = {} + for data_point in self._config_manager.data_points: + endpoint = data_point.get("endpoint") + path = data_point.get("path") + properties = data_point.get("properties") - for sub_path_name in sub_path_mapping: - sub_path = sub_path_mapping.get(sub_path_name) + if endpoint is not None: + data_item = self._raw_data.get(endpoint) - data[sub_path_name] = self._get_data_item(data_item, sub_path) + data = self._get_data_section(data_item, path) - return data + if properties is not None: + if isinstance(data, list): + devices.extend( + [ + self._get_device_data(data_item, properties, data_point) + for data_item in data + ] + ) - def _get_data_items( - self, data_items: list[dict], sub_path_mapping: dict - ) -> list[dict]: - result = [] + else: + device = self._get_device_data(data, properties, data_point) - for data_item in data_items: - data = self._get_sub_items(data_item, sub_path_mapping) + devices.append(device) - result.append(data) + return devices - return result + @staticmethod + def _get_device_data( + data_item: dict, properties: dict, device_config: dict + ) -> dict: + device_data = {} - def _merge_data_items(self, merge_item: dict): - key_to = merge_item.get("to") - key_from = merge_item.get("from") - key = merge_item.get("key") + data_item_flat = flatten(data_item, ".") - data_items = self._data.get(key_to) - additional_data = self._data.get(key_from).copy() + for property_key in properties: + property_details = properties.get(property_key) + property_path = property_details.get("path") - del self._data[key_from] + value = data_item_flat.get(property_path) - additional_data_mapped = { - additional_data_item[key]: additional_data_item - for additional_data_item in additional_data - } + device_data[property_key] = value - for data_item in data_items: - data_item_key = data_item.get(key) - additional_data_item = additional_data_mapped.get(data_item_key) + data = {"config": device_config, "data": device_data} - if additional_data_item is not None: - data_item.update(additional_data_item) + return data async def _get_request(self, endpoint: str) -> dict | None: result: dict | None = None try: - url = f"{self._base_url}{endpoint}" + url = f"{self.config_data.url}{endpoint}" timeout = ClientTimeout(connect=3, sock_read=10) @@ -322,7 +307,6 @@ async def _get_request(self, endpoint: str) -> dict | None: ) except Exception as ex: - print(ex) exc_type, exc_obj, tb = sys.exc_info() line_number = tb.tb_lineno _LOGGER.error(f"Failed to get {endpoint}, Error: {ex}, Line: {line_number}") @@ -352,3 +336,19 @@ def _strip_namespace(self, el): for child in el: self._strip_namespace(child) + + def device_data_changed(self, device_key: str): + device_data = self._data.get(device_key) + device_config = self._data_config.get(device_key) + + if device_key not in self._device_dispatched: + self._device_dispatched.append(device_key) + + async_dispatcher_send( + self._hass, + SIGNAL_HA_DEVICE_DISCOVERED, + self._config_manager.entry_id, + device_key, + device_data, + device_config, + ) diff --git a/custom_components/hpprinter/manifest.json b/custom_components/hpprinter/manifest.json index 56f7ded..34ca757 100644 --- a/custom_components/hpprinter/manifest.json +++ b/custom_components/hpprinter/manifest.json @@ -8,6 +8,6 @@ "documentation": "https://github.com/elad-bar/ha-hpprinter", "iot_class": "local_polling", "issue_tracker": "https://github.com/elad-bar/ha-hpprinter/issues", - "requirements": ["xmltodict~=0.13.0"], + "requirements": ["xmltodict~=0.13.0", "flatten_json"], "version": "2.0.0" } diff --git a/custom_components/hpprinter/models/config_data.py b/custom_components/hpprinter/models/config_data.py index 0396938..8e5d1de 100644 --- a/custom_components/hpprinter/models/config_data.py +++ b/custom_components/hpprinter/models/config_data.py @@ -3,7 +3,7 @@ from homeassistant.const import CONF_HOST, CONF_PORT, CONF_SSL -from ..common.consts import PROTOCOLS +from ..common.consts import DEFAULT_PORT, PROTOCOLS class ConfigData: @@ -14,7 +14,7 @@ class ConfigData: def __init__(self): self._host = "" self._ssl = False - self._port = 80 + self._port = DEFAULT_PORT @property def hostname(self) -> str: @@ -34,10 +34,19 @@ def protocol(self): return protocol + @property + def url(self): + url = f"{self.protocol}://{self.hostname}:{self.port}" + + return url + def update(self, data: dict): self._ssl = str(data.get(CONF_SSL, False)).lower() == str(True).lower() self._host = data.get(CONF_HOST) - self._port = data.get(CONF_PORT) + self._port = data.get(CONF_PORT, DEFAULT_PORT) + + if self._port is None: + self._port = DEFAULT_PORT def to_dict(self): obj = {CONF_HOST: self._host, CONF_PORT: self._port, CONF_SSL: self._ssl} @@ -56,7 +65,9 @@ def default_schema(user_input: dict | None) -> Schema: new_user_input = { vol.Required(CONF_HOST, default=user_input.get(CONF_HOST)): str, - vol.Required(CONF_PORT, default=user_input.get(CONF_PORT, 80)): int, + vol.Required( + CONF_PORT, default=user_input.get(CONF_PORT, DEFAULT_PORT) + ): int, vol.Optional(CONF_SSL, default=user_input.get(CONF_SSL, False)): bool, } diff --git a/custom_components/hpprinter/models/exceptions.py b/custom_components/hpprinter/models/exceptions.py index bc37611..e255c6b 100644 --- a/custom_components/hpprinter/models/exceptions.py +++ b/custom_components/hpprinter/models/exceptions.py @@ -1,6 +1,15 @@ from homeassistant.exceptions import HomeAssistantError +class IntegrationParameterError(HomeAssistantError): + def __init__(self, parameter): + self._parameter = parameter + self._message = f"Invalid parameter value provided, Parameter: {parameter}" + + def __str__(self): + return self._message + + class IntegrationAPIError(HomeAssistantError): def __init__(self, url): self._url = url diff --git a/custom_components/hpprinter/parameters/data_points.json b/custom_components/hpprinter/parameters/data_points.json new file mode 100644 index 0000000..4571717 --- /dev/null +++ b/custom_components/hpprinter/parameters/data_points.json @@ -0,0 +1,346 @@ +[ + { + "name": "Main", + "endpoint": "/DevMgmt/ProductConfigDyn.xml", + "path": "ProductConfigDyn.ProductInformation", + "device_type": "Main", + "properties": { + "make_and_model": { + "path": "MakeAndModel" + }, + "make_and_model_family": { + "path": "MakeAndModelFamily" + }, + "sku_identifier": { + "path": "SKUIdentifier" + }, + "serial_number": { + "path": "SerialNumber" + }, + "product_number": { + "path": "ProductNumber" + }, + "manufacturer_name": { + "path": "Manufacturer.Name" + }, + "manufactured_at": { + "path": "Manufacturer.Date" + } + } + }, + { + "name": "Consumable", + "endpoint": "/DevMgmt/ConsumableConfigDyn.xml", + "path": "ConsumableConfigDyn.ConsumableInfo", + "device_type": "Consumable", + "identifier": "consumable_station", + "properties": { + "consumable_label_code": { + "path": "ConsumableLabelCode" + }, + "consumable_life_state_consumable_state": { + "path": "ConsumableLifeState.ConsumableState", + "platform": "binary_sensor", + "on_value": "ok" + }, + "consumable_life_state_brand": { + "path": "ConsumableLifeState.Brand" + }, + "consumable_station": { + "path": "ConsumableStation", + "platform": "sensor" + }, + "consumable_type_enum": { + "path": "ConsumableTypeEnum", + "platform": "sensor" + }, + "installation_date": { + "path": "Installation.Date", + "platform": "sensor" + }, + "capacity_max_capacity": { + "path": "Capacity.MaxCapacity" + }, + "consumable_percentage_level_remaining": { + "path": "ConsumablePercentageLevelRemaining", + "platform": "sensor", + "unit_of_measurement": "%", + "exclude": { + "consumable_type_enum": "printhead" + } + }, + "consumable_selectibility_number": { + "path": "ConsumableSelectibilityNumber" + }, + "manufacturer_name": { + "path": "Manufacturer.Name" + }, + "manufactured_at": { + "path": "Manufacturer.Date" + }, + "serial_number": { + "path": "SerialNumber" + }, + "product_number": { + "path": "ProductNumber" + }, + "warranty_expiration_date": { + "path": "Warranty.ExpirationDate", + "platform": "sensor", + "exclude": { + "consumable_type_enum": "printhead" + } + }, + "consumable_unique_id": { + "path": "ConsumableUniqueID" + } + } + }, + { + "name": "Consumable Usage", + "endpoint": "/DevMgmt/ProductUsageDyn.xml", + "path": "ProductUsageDyn.ConsumableSubunit.Consumable", + "device_type": "Consumable", + "identifier": "consumable_station", + "properties": { + "consumable_station": { + "path": "ConsumableStation" + }, + "marker_color": { + "path": "MarkerColor" + }, + "consumable_type_enum": { + "path": "ConsumableTypeEnum", + "platform": "sensor" + }, + "estimated_pages_remaining": { + "path": "EstimatedPagesRemaining", + "platform": "sensor", + "unit_of_measurement": "pages", + "exclude": { + "consumable_type_enum": "printhead" + } + }, + "consumable_state": { + "path": "ConsumableState" + }, + "consumable_raw_percentage_level_remaining": { + "path": "ConsumableRawPercentageLevelRemaining" + }, + "supply_serial_number": { + "path": "SupplySerialNumber.#text" + }, + "refilled_count_counterfeit_refilled_count": { + "path": "RefilledCount.CounterfeitRefilledCount.#text", + "platform": "sensor", + "unit_of_measurement": "refill" + }, + "refilled_count_genuine_refilled_count": { + "path": "RefilledCount.GenuineRefilledCount", + "platform": "sensor", + "unit_of_measurement": "refill" + } + } + }, + { + "name": "Printer", + "endpoint": "/DevMgmt/ProductUsageDyn.xml", + "path": "ProductUsageDyn.PrinterSubunit", + "device_type": "Printer", + "properties": { + "total_impressions": { + "path": "TotalImpressions.#text", + "description": "Total pages printed", + "platform": "sensor", + "unit_of_measurement": "pages" + }, + "monochrome_impressions": { + "path": "MonochromeImpressions", + "description": "Total black-and-white pages printed", + "platform": "sensor", + "unit_of_measurement": "pages" + }, + "color_impressions": { + "path": "ColorImpressions", + "description": "Total color pages printed", + "platform": "sensor", + "unit_of_measurement": "pages" + }, + "simplex_sheets": { + "path": "SimplexSheets", + "description": "Total single-sided pages printed", + "platform": "sensor", + "unit_of_measurement": "pages" + }, + "duplex_sheets": { + "path": "DuplexSheets.#text", + "description": "Total double-sided pages printed", + "platform": "sensor", + "unit_of_measurement": "pages" + }, + "jam_events": { + "path": "JamEvents.#text", + "description": "Total jams", + "platform": "sensor", + "unit_of_measurement": "pages" + }, + "mispick_events": { + "path": "MispickEvents", + "description": "Total miss picks", + "platform": "sensor", + "unit_of_measurement": "pages" + } + } + }, + { + "name": "Scanner", + "endpoint": "/DevMgmt/ProductUsageDyn.xml", + "path": "ProductUsageDyn.ScannerEngineSubunit", + "device_type": "Scanner", + "properties": { + "scan_images": { + "path": "ScanImages.#text", + "description": "Total scanned pages", + "platform": "sensor", + "unit_of_measurement": "pages" + }, + "adf_images": { + "path": "AdfImages.#text", + "description": "Total scanned pages from ADF", + "platform": "sensor", + "unit_of_measurement": "pages" + }, + "duplex_sheets": { + "path": "DuplexSheets.#text", + "description": "Total double-sided pages scanned", + "platform": "sensor", + "unit_of_measurement": "pages" + }, + "flatbed_images": { + "path": "FlatbedImages", + "description": "Total pages from scanner glass", + "platform": "sensor", + "unit_of_measurement": "pages" + }, + "jam_events": { + "path": "JamEvents", + "description": "Total jams", + "platform": "sensor", + "unit_of_measurement": "pages" + }, + "mispick_events": { + "path": "MispickEvents", + "description": "Total miss picks", + "platform": "sensor", + "unit_of_measurement": "pages" + } + } + }, + { + "name": "Copy", + "endpoint": "/DevMgmt/ProductUsageDyn.xml", + "path": "ProductUsageDyn.CopyApplicationSubunit", + "device_type": "Copy", + "properties": { + "total_impressions": { + "path": "TotalImpressions.#text", + "description": "Total copies", + "platform": "sensor", + "unit_of_measurement": "pages" + }, + "adf_images": { + "path": "AdfImages", + "description": "Total copies from ADF", + "platform": "sensor", + "unit_of_measurement": "pages" + }, + "flatbed_images": { + "path": "FlatbedImages", + "description": "Total pages from scanner glass", + "platform": "sensor", + "unit_of_measurement": "pages" + }, + "monochrome_impressions": { + "path": "MonochromeImpressions", + "description": "Total black-and-white copies", + "platform": "sensor", + "unit_of_measurement": "pages" + }, + "color_impressions": { + "path": "ColorImpressions", + "description": "Total color copies", + "platform": "sensor", + "unit_of_measurement": "pages" + } + } + }, + { + "name": "Fax", + "endpoint": "/DevMgmt/ProductUsageDyn.xml", + "path": "ProductUsageDyn.FaxApplicationSubunit", + "device_type": "Fax", + "properties": { + "total_impressions": { + "path": "TotalImpressions.#text", + "description": "Total faxes", + "platform": "sensor", + "unit_of_measurement": "pages" + } + } + }, + { + "name": "Adapter", + "endpoint": "/IoMgmt/Adapters", + "path": "Adapters.Adapter", + "device_type": "Adapter", + "identifier": "hardware_config_name", + "properties": { + "hardware_config_name": { + "path": "HardwareConfig.Name" + }, + "hardware_config_device_connectivity_port_type": { + "path": "HardwareConfig.DeviceConnectivityPortType", + "platform": "sensor" + }, + "hardware_config_is_connected": { + "path": "HardwareConfig.IsConnected", + "platform": "binary_sensor", + "on_value": "true" + } + } + }, + { + "name": "ePrint", + "endpoint": "/ePrint/ePrintConfigDyn.xml", + "path": "ePrintConfigDyn", + "device_type": "Main", + "properties": { + "printer_id": { + "path": "PrinterID" + }, + "registration_state": { + "path": "RegistrationState", + "platform": "sensor" + }, + "cloud_services_switch_status": { + "path": "CloudServicesSwitch.Status", + "platform": "binary_sensor", + "on_value": "enabled" + } + } + }, + { + "name": "Wifi", + "endpoint": "/DevMgmt/NetAppsSecureDyn.xml", + "path": "NetAppsSecureDyn.WirelessDirectConfig", + "device_type": "Main", + "properties": { + "ssid_prefix": { + "path": "SSIDPrefix" + }, + "connection_method": { + "path": "ConnectionMethod" + } + } + } +] diff --git a/custom_components/hpprinter/data/endpoint_validations.json b/custom_components/hpprinter/parameters/endpoint_validations.json similarity index 100% rename from custom_components/hpprinter/data/endpoint_validations.json rename to custom_components/hpprinter/parameters/endpoint_validations.json diff --git a/custom_components/hpprinter/sensor.py b/custom_components/hpprinter/sensor.py index 3830af0..897fc41 100644 --- a/custom_components/hpprinter/sensor.py +++ b/custom_components/hpprinter/sensor.py @@ -19,21 +19,21 @@ async def async_setup_entry( hass, entry, Platform.SENSOR, - AquaTempSensorEntity, + HASensorEntity, async_add_entities, ) -class AquaTempSensorEntity(BaseEntity, SensorEntity): +class HASensorEntity(BaseEntity, SensorEntity): """Representation of a sensor.""" def __init__( self, entity_description: IntegrationSensorEntityDescription, coordinator: HACoordinator, - device_code: str, + device_key: str, ): - super().__init__(entity_description, coordinator, device_code) + super().__init__(entity_description, coordinator, device_key) self._attr_device_class = entity_description.device_class self._attr_native_unit_of_measurement = ( @@ -42,12 +42,14 @@ def __init__( def _handle_coordinator_update(self) -> None: """Fetch new state parameters for the sensor.""" - device_data = self.get_data() - state = device_data.get(self._data_key) + state = self.get_value() - if self.native_unit_of_measurement in ["pages", "%", "refill"]: + if self.native_unit_of_measurement in ["%"]: state = float(state) + elif self.native_unit_of_measurement in ["pages", "refill"]: + state = int(state) + self._attr_native_value = state self.async_write_ha_state() diff --git a/custom_components/hpprinter/strings.json b/custom_components/hpprinter/strings.json index 11a14d4..ae2b439 100644 --- a/custom_components/hpprinter/strings.json +++ b/custom_components/hpprinter/strings.json @@ -2,7 +2,7 @@ "config": { "step": { "user": { - "title": "Set up HP Printer", + "title": "Set up HP Printer Embedded Web Server (EWS)", "data": { "host": "Host", "port": "Port", @@ -14,8 +14,8 @@ "already_configured": "HP Printer integration ({name}) already configured" }, "error": { - "error_400": "Invalid server details, please try manually access to `http://{IP}//DevMgmt/ProductStatusDyn.xml` (Replace placeholder with IP or Hostname), in case it's accessible, please report the issue with logs", - "error_404": "Unsupported API" + "error_400": "Invalid parameters provided", + "error_404": "HP Printer Embedded Web Server (EWS) not was not found, please check logs for more details" } }, "options": { @@ -32,9 +32,8 @@ } }, "error": { - "error_400": "Invalid server details, please try manually access to `http{ssl?s}://{host}:{port}/Prefetch?type=dtree`, in case it's accessible, please report the issue with logs", - "error_404": "Unsupported API", - "already_configured": "HP Printer integration ({name}) already configured" + "error_400": "Invalid parameters provided", + "error_404": "HP Printer Embedded Web Server (EWS) not was not found, please check logs for more details" } } } diff --git a/requirements.txt b/requirements.txt index a2c1f3b..8095ac8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,3 +7,4 @@ aiohttp~=3.9.1 xmltodict~=0.13.0 defusedxml +flatten_json diff --git a/setup.cfg b/setup.cfg index 1620535..9d64dac 100644 --- a/setup.cfg +++ b/setup.cfg @@ -29,8 +29,6 @@ force_grid_wrap=0 use_parentheses=True line_length=88 indent = " " -# by default isort don't check module indexes -not_skip = __init__.py # will group `import x` and `from x import` of the same module. force_sort_within_sections = true sections = FUTURE,STDLIB,INBETWEENS,THIRDPARTY,FIRSTPARTY,LOCALFOLDER diff --git a/tests/api_test.py b/tests/api_test.py index dc0b9dc..77d0aaa 100644 --- a/tests/api_test.py +++ b/tests/api_test.py @@ -1,5 +1,4 @@ import asyncio -import json import logging import os import sys @@ -42,12 +41,15 @@ async def initialize(self): await self._config_manager.initialize(self._config_data) self._api = RestAPIv2(hass, self._config_manager) - await self._api.initialize() + await self._api.initialize(True) await self._api.update() - print(json.dumps(self._api.raw_data, indent=4)) - print(json.dumps(self._api.data, indent=4)) + # print(json.dumps(self._api.raw_data, indent=4)) + # print(json.dumps(self._api.data, indent=4)) + + async def terminate(self): + await self._api.terminate() if __name__ == "__main__": @@ -64,3 +66,6 @@ async def initialize(self): except Exception as rex: _LOGGER.error(f"Error: {rex}") + + finally: + loop.run_until_complete(instance.terminate()) From 9ea7013f43ff57bcb9f43766916c903a27ddbcf7 Mon Sep 17 00:00:00 2001 From: Elad Bar Date: Sun, 18 Feb 2024 08:15:30 +0200 Subject: [PATCH 03/25] more details to entities --- CHANGELOG.md | 3 - custom_components/hpprinter/binary_sensor.py | 1 + .../hpprinter/common/base_entity.py | 1 + .../hpprinter/managers/ha_config_manager.py | 8 +- .../hpprinter/managers/rest_api.py | 22 +++- .../hpprinter/parameters/data_points.json | 104 ++++++++++++------ custom_components/hpprinter/sensor.py | 6 +- tests/api_test.py | 5 +- 8 files changed, 108 insertions(+), 42 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6f83410..8041c47 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,9 +7,6 @@ TODO: - Entity translations -- Icons -- Configuration test -- Functionality test ## v1.0.12 diff --git a/custom_components/hpprinter/binary_sensor.py b/custom_components/hpprinter/binary_sensor.py index 0627bc0..5201c1f 100644 --- a/custom_components/hpprinter/binary_sensor.py +++ b/custom_components/hpprinter/binary_sensor.py @@ -45,5 +45,6 @@ def _handle_coordinator_update(self) -> None: is_on = str(state).lower() == str(self._entity_on_value).lower() self._attr_is_on = is_on + self._attr_extra_state_attributes = {"state": state} self.async_write_ha_state() diff --git a/custom_components/hpprinter/common/base_entity.py b/custom_components/hpprinter/common/base_entity.py index 8c18fdb..4f1eabd 100644 --- a/custom_components/hpprinter/common/base_entity.py +++ b/custom_components/hpprinter/common/base_entity.py @@ -123,6 +123,7 @@ def __init__( self._attr_device_info = device_info self._attr_name = entity_name self._attr_unique_id = unique_id + self._attr_icon = entity_description.icon @property def local_coordinator(self) -> HACoordinator: diff --git a/custom_components/hpprinter/managers/ha_config_manager.py b/custom_components/hpprinter/managers/ha_config_manager.py index 1fb6c78..753c978 100644 --- a/custom_components/hpprinter/managers/ha_config_manager.py +++ b/custom_components/hpprinter/managers/ha_config_manager.py @@ -274,19 +274,22 @@ def _load_entity_descriptions(self): if "platform" in property_data: property_platform = property_data.get("platform") + description = property_data.get("description") exclude = property_data.get("exclude") device_class = property_data.get("device_class") + icon = property_data.get("icon") if property_platform == str(Platform.BINARY_SENSOR): on_value = property_data.get("on_value") entity_description = IntegrationBinarySensorEntityDescription( key=property_key, - name=property_key, + name=description, device_type=device_type, exclude=exclude, on_value=on_value, device_class=device_class, + icon=icon, ) self._entity_descriptions.append(entity_description) @@ -296,11 +299,12 @@ def _load_entity_descriptions(self): entity_description = IntegrationSensorEntityDescription( key=property_key, - name=property_key, + name=description, device_type=device_type, exclude=exclude, native_unit_of_measurement=unit_of_measurement, device_class=device_class, + icon=icon, ) self._entity_descriptions.append(entity_description) diff --git a/custom_components/hpprinter/managers/rest_api.py b/custom_components/hpprinter/managers/rest_api.py index 6e014de..f86b477 100644 --- a/custom_components/hpprinter/managers/rest_api.py +++ b/custom_components/hpprinter/managers/rest_api.py @@ -14,7 +14,7 @@ MAXIMUM_CONNECTIONS_PER_HOST, ) from homeassistant.helpers.dispatcher import async_dispatcher_send -from homeassistant.util import ssl +from homeassistant.util import slugify, ssl from homeassistant.util.ssl import SSLCipherList from ..common.consts import IGNORED_KEYS, SIGNAL_HA_DEVICE_DISCOVERED @@ -155,12 +155,30 @@ def _extract_data(self, devices: list[dict]): device_type = item_config.get("device_type") identifier = item_config.get("identifier") properties = item_config.get("properties") + flat = item_config.get("flat", False) device_key = device_type if identifier is not None: device_id = item_data.get(identifier) - device_key = f"{device_type}.{device_id}" + if flat: + new_items_data = { + slugify(f"{device_id}_{key}"): item_data[key] + for key in item_data + if key != identifier + } + + new_properties = { + slugify(f"{device_id}_{key}"): properties[key] + for key in properties + if key != identifier + } + + item_data = new_items_data + properties = new_properties + + else: + device_key = f"{device_type}.{device_id}" data = device_data[device_key] if device_key in device_data else {} data.update(item_data) diff --git a/custom_components/hpprinter/parameters/data_points.json b/custom_components/hpprinter/parameters/data_points.json index 4571717..0332723 100644 --- a/custom_components/hpprinter/parameters/data_points.json +++ b/custom_components/hpprinter/parameters/data_points.json @@ -40,29 +40,37 @@ }, "consumable_life_state_consumable_state": { "path": "ConsumableLifeState.ConsumableState", + "description": "Status", "platform": "binary_sensor", - "on_value": "ok" + "on_value": "newGenuineHP", + "device_class": "plug" }, "consumable_life_state_brand": { "path": "ConsumableLifeState.Brand" }, "consumable_station": { "path": "ConsumableStation", + "description": "Station", "platform": "sensor" }, "consumable_type_enum": { "path": "ConsumableTypeEnum", - "platform": "sensor" + "description": "Type", + "platform": "sensor", + "device_class": "enum" }, "installation_date": { "path": "Installation.Date", - "platform": "sensor" + "description": "Installation Date", + "platform": "sensor", + "device_class": "date" }, "capacity_max_capacity": { "path": "Capacity.MaxCapacity" }, "consumable_percentage_level_remaining": { "path": "ConsumablePercentageLevelRemaining", + "description": "Level", "platform": "sensor", "unit_of_measurement": "%", "exclude": { @@ -86,7 +94,9 @@ }, "warranty_expiration_date": { "path": "Warranty.ExpirationDate", + "description": "Expiration Date", "platform": "sensor", + "device_class": "date", "exclude": { "consumable_type_enum": "printhead" } @@ -109,12 +119,9 @@ "marker_color": { "path": "MarkerColor" }, - "consumable_type_enum": { - "path": "ConsumableTypeEnum", - "platform": "sensor" - }, "estimated_pages_remaining": { "path": "EstimatedPagesRemaining", + "description": "Remaining", "platform": "sensor", "unit_of_measurement": "pages", "exclude": { @@ -132,13 +139,17 @@ }, "refilled_count_counterfeit_refilled_count": { "path": "RefilledCount.CounterfeitRefilledCount.#text", + "description": "Counterfeit Refilled", "platform": "sensor", - "unit_of_measurement": "refill" + "unit_of_measurement": "refills", + "icon": "mdi:format-color-fill" }, "refilled_count_genuine_refilled_count": { "path": "RefilledCount.GenuineRefilledCount", + "description": "Genuine Refilled", "platform": "sensor", - "unit_of_measurement": "refill" + "unit_of_measurement": "refills", + "icon": "mdi:format-color-fill" } } }, @@ -152,43 +163,50 @@ "path": "TotalImpressions.#text", "description": "Total pages printed", "platform": "sensor", - "unit_of_measurement": "pages" + "unit_of_measurement": "pages", + "icon": "mdi:file-document-check" }, "monochrome_impressions": { "path": "MonochromeImpressions", "description": "Total black-and-white pages printed", "platform": "sensor", - "unit_of_measurement": "pages" + "unit_of_measurement": "pages", + "icon": "mdi:file-document-check" }, "color_impressions": { "path": "ColorImpressions", "description": "Total color pages printed", "platform": "sensor", - "unit_of_measurement": "pages" + "unit_of_measurement": "pages", + "icon": "mdi:file-document-check" }, "simplex_sheets": { "path": "SimplexSheets", "description": "Total single-sided pages printed", "platform": "sensor", - "unit_of_measurement": "pages" + "unit_of_measurement": "pages", + "icon": "mdi:file-document-check" }, "duplex_sheets": { "path": "DuplexSheets.#text", "description": "Total double-sided pages printed", "platform": "sensor", - "unit_of_measurement": "pages" + "unit_of_measurement": "pages", + "icon": "mdi:file-document-multiple" }, "jam_events": { "path": "JamEvents.#text", "description": "Total jams", "platform": "sensor", - "unit_of_measurement": "pages" + "unit_of_measurement": "pages", + "icon": "mdi:file-document-remove" }, "mispick_events": { "path": "MispickEvents", "description": "Total miss picks", "platform": "sensor", - "unit_of_measurement": "pages" + "unit_of_measurement": "pages", + "icon": "mdi:file-document-minus" } } }, @@ -202,37 +220,43 @@ "path": "ScanImages.#text", "description": "Total scanned pages", "platform": "sensor", - "unit_of_measurement": "pages" + "unit_of_measurement": "pages", + "icon": "mdi:credit-card-scan" }, "adf_images": { "path": "AdfImages.#text", "description": "Total scanned pages from ADF", "platform": "sensor", - "unit_of_measurement": "pages" + "unit_of_measurement": "pages", + "icon": "mdi:credit-card-scan" }, "duplex_sheets": { "path": "DuplexSheets.#text", "description": "Total double-sided pages scanned", "platform": "sensor", - "unit_of_measurement": "pages" + "unit_of_measurement": "pages", + "icon": "mdi:credit-card-scan" }, "flatbed_images": { "path": "FlatbedImages", "description": "Total pages from scanner glass", "platform": "sensor", - "unit_of_measurement": "pages" + "unit_of_measurement": "pages", + "icon": "mdi:credit-card-scan" }, "jam_events": { "path": "JamEvents", "description": "Total jams", "platform": "sensor", - "unit_of_measurement": "pages" + "unit_of_measurement": "pages", + "icon": "mdi:credit-card-scan" }, "mispick_events": { "path": "MispickEvents", "description": "Total miss picks", "platform": "sensor", - "unit_of_measurement": "pages" + "unit_of_measurement": "pages", + "icon": "mdi:credit-card-scan" } } }, @@ -246,31 +270,36 @@ "path": "TotalImpressions.#text", "description": "Total copies", "platform": "sensor", - "unit_of_measurement": "pages" + "unit_of_measurement": "pages", + "icon": "mdi:content-copy" }, "adf_images": { "path": "AdfImages", "description": "Total copies from ADF", "platform": "sensor", - "unit_of_measurement": "pages" + "unit_of_measurement": "pages", + "icon": "mdi:content-copy" }, "flatbed_images": { "path": "FlatbedImages", "description": "Total pages from scanner glass", "platform": "sensor", - "unit_of_measurement": "pages" + "unit_of_measurement": "pages", + "icon": "mdi:content-copy" }, "monochrome_impressions": { "path": "MonochromeImpressions", "description": "Total black-and-white copies", "platform": "sensor", - "unit_of_measurement": "pages" + "unit_of_measurement": "pages", + "icon": "mdi:content-copy" }, "color_impressions": { "path": "ColorImpressions", "description": "Total color copies", "platform": "sensor", - "unit_of_measurement": "pages" + "unit_of_measurement": "pages", + "icon": "mdi:content-copy" } } }, @@ -284,7 +313,8 @@ "path": "TotalImpressions.#text", "description": "Total faxes", "platform": "sensor", - "unit_of_measurement": "pages" + "unit_of_measurement": "pages", + "icon": "mdi:email-fast" } } }, @@ -292,20 +322,24 @@ "name": "Adapter", "endpoint": "/IoMgmt/Adapters", "path": "Adapters.Adapter", - "device_type": "Adapter", + "device_type": "Main", "identifier": "hardware_config_name", + "flat": true, "properties": { "hardware_config_name": { "path": "HardwareConfig.Name" }, "hardware_config_device_connectivity_port_type": { "path": "HardwareConfig.DeviceConnectivityPortType", + "description": "Port Type", "platform": "sensor" }, "hardware_config_is_connected": { "path": "HardwareConfig.IsConnected", + "description": "Connected", "platform": "binary_sensor", - "on_value": "true" + "on_value": "true", + "device_class": "connectivity" } } }, @@ -320,12 +354,18 @@ }, "registration_state": { "path": "RegistrationState", - "platform": "sensor" + "description": "ePrint Registered", + "platform": "binary_sensor", + "on_value": "registered", + "device_class": "plug", + "icon": "mdi:cloud-print" }, "cloud_services_switch_status": { "path": "CloudServicesSwitch.Status", + "description": "ePrint Status", "platform": "binary_sensor", - "on_value": "enabled" + "on_value": "enabled", + "device_class": "connectivity" } } }, diff --git a/custom_components/hpprinter/sensor.py b/custom_components/hpprinter/sensor.py index 897fc41..1fb877d 100644 --- a/custom_components/hpprinter/sensor.py +++ b/custom_components/hpprinter/sensor.py @@ -1,6 +1,7 @@ +from datetime import datetime import logging -from homeassistant.components.sensor import SensorEntity +from homeassistant.components.sensor import SensorDeviceClass, SensorEntity from homeassistant.config_entries import ConfigEntry from homeassistant.const import Platform from homeassistant.core import HomeAssistant @@ -50,6 +51,9 @@ def _handle_coordinator_update(self) -> None: elif self.native_unit_of_measurement in ["pages", "refill"]: state = int(state) + if self.device_class == SensorDeviceClass.DATE: + state = datetime.fromisoformat(state) + self._attr_native_value = state self.async_write_ha_state() diff --git a/tests/api_test.py b/tests/api_test.py index 77d0aaa..2798cfd 100644 --- a/tests/api_test.py +++ b/tests/api_test.py @@ -1,4 +1,5 @@ import asyncio +import json import logging import os import sys @@ -45,8 +46,8 @@ async def initialize(self): await self._api.update() - # print(json.dumps(self._api.raw_data, indent=4)) - # print(json.dumps(self._api.data, indent=4)) + print(json.dumps(self._api.data_config, indent=4)) + print(json.dumps(self._api.data, indent=4)) async def terminate(self): await self._api.terminate() From aa9fbc5ab37b74164e52c65a934f32bcdc2a7ff0 Mon Sep 17 00:00:00 2001 From: Elad Bar Date: Sun, 18 Feb 2024 16:04:36 +0200 Subject: [PATCH 04/25] translations fix, DE translated, documentation --- CHANGELOG.md | 4 - README.md | 196 +++++++++++------- .../hpprinter/common/base_entity.py | 10 +- .../hpprinter/managers/ha_config_manager.py | 5 + custom_components/hpprinter/manifest.json | 2 +- custom_components/hpprinter/strings.json | 116 ++++++++++- .../hpprinter/translations/de.json | 132 +++++++++++- .../hpprinter/translations/dk.json | 158 ++++++++++++-- .../hpprinter/translations/en.json | 154 ++++++++++++-- .../hpprinter/translations/es.json | 154 ++++++++++++-- .../hpprinter/translations/fr.json | 134 +++++++++++- .../hpprinter/translations/nb.json | 154 ++++++++++++-- .../hpprinter/translations/nl.json | 132 +++++++++++- .../hpprinter/translations/pl.json | 154 ++++++++++++-- .../hpprinter/translations/pt-BR.json | 154 ++++++++++++-- info.md | 196 +++++++++++------- requirements.txt | 7 + tests/generate_translations.py | 192 +++++++++++++++++ 18 files changed, 1753 insertions(+), 301 deletions(-) create mode 100644 tests/generate_translations.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 8041c47..af7458d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,10 +4,6 @@ - Refactor to full HP Printer EWS support -TODO: - -- Entity translations - ## v1.0.12 - Fix missing references [Issue #103](https://github.com/elad-bar/ha-hpprinter/issues/103) diff --git a/README.md b/README.md index e572680..d0750a5 100644 --- a/README.md +++ b/README.md @@ -6,104 +6,148 @@ Configuration support multiple HP Printer devices through Configuration -> Integ [Changelog](https://github.com/elad-bar/ha-hpprinter/blob/master/CHANGELOG.md) -### How to set it up: +## How to -Look for "HP Printers Integration" and install +### Requirements -#### Requirements +- HP Printer with EWS (Embedded Web Server) support -- HP Printer supporting XML API - to check printer's compatibility to the component try to get to the printer's XML API (replace placeholder with real IP / Hostname): - `http://{IP}//DevMgmt/ProductStatusDyn.xml` +### Installations via HACS [![hacs_badge](https://img.shields.io/badge/HACS-Custom-41BDF5.svg)](https://github.com/hacs/integration) -#### Basic configuration +- In HACS, look for "Aqua Temp" and install and restart +- If integration was not found, please add custom repository `elad-bar/hpprinter` as integration +- In Settings --> Devices & Services - (Lower Right) "Add Integration" -- Configuration should be done via Configuration -> Integrations. -- In case you are already using that integration with YAML Configuration - please remove it -- Integration supports **multiple** devices -- In the setup form, the following details are mandatory: - - Name - Unique - - Host (or IP) -- Upon submitting the form of creating an integration, a request to the printer will take place and will cause failure in case: - - Unsupported API - - Invalid server details - when cannot reach host +### Setup -#### Settings for Monitoring interfaces, devices, tracked devices and update interval +To add integration use Configuration -> Integrations -> Add `HP Printer` +Integration supports **multiple** accounts and devices -_Configuration -> Integrations -> {Integration} -> Options_
+| Fields name | Type | Required | Default | Description | +| ----------- | ------- | -------- | ------- | -------------------------------------------- | +| Host | Textbox | + | - | Defines hostname or IP of the HP Printer EWS | +| Port | Textbox | + | 80 | Defines port of the HP Printer EWS | +| Is SSL | Boolean | + | False | Defines which protocol to use HTTP/S | -``` -Name - Unique -Host (or IP) -Update Interval: Textbox, number of seconds to update entities, default=60 -Log level: Drop-down list, change component's log level (more details below), default=Default -Should store responses?: Check-box, saves XML and JSON files for debugging purpose, default=False -``` +It is also possible to change configuration after setting up using integration configuration. -###### Log Level's drop-down +#### Validation errors -New feature to set the log level for the component without need to set log_level in `customization:` and restart or call manually `logger.set_level` and loose it after restart. +| Errors | +| ------------------------------------------------------ | +| Invalid parameters provided | +| HP Printer Embedded Web Server (EWS) not was not found | -Upon startup or integration's option update, based on the value chosen, the component will make a service call to `logger.set_level` for that component with the desired value, +## Devices -In case `Default` option is chosen, flow will skip calling the service, after changing from any other option to `Default`, it will not take place automatically, only after restart +### Main device -###### Store responses +Device that holds entities related to the integration and relations to other sub devices as described below. -Stores the XML and JSON of each request and final JSON to files, Path in CONFIG_PATH/\*, -Files that will be generated (Prefix to the file is name of the integration): +#### Entities -- ProductUsageDyn.XML - Raw XML from HP Printer of Usage Details -- ProductUsageDyn.json - JSON based on the Raw XML of Usage Details after transformed by the component -- ConsumableConfigDyn.XML - Raw XML from HP Printer of consumable details -- ConsumableConfigDyn.json - JSON based on the Raw XML of consumable details after transformed by the component -- ProductConfigDyn.XML - Raw XML from HP Printer of Config Details -- ProductConfigDyn.json - JSON based on the Raw XML of Config Details after transformed by the component -- Final.json - JSON based on the 2 JSONs above, merged into simpler data structure for the HA to create sensor based on +_Binary Sensor_ -## Components: +- ePrint Registered +- ePrint Status -#### Device status - Binary Sensor +### Printer -``` -State: connected? -``` +Device holds entities of sensors related to number of pages printed and relation to sub devices of consumables -#### Printer details - Sensor +_Sensor_ -``` -State: # of pages printed -Attributes: - Color - # of printed documents using color cartridges - Monochrome - # of printed documents using black cartridges - Jams - # of print jobs jammed - Cancelled - # of print jobs that were cancelled -``` +- Total pages printed +- Total black-and-white pages printed +- Total color pages printed +- Total single-sided pages printed +- Total double-sided pages printed +- Total jams +- Total miss picks -#### Scanner details - Sensor (For AIO only) +### Scanner -``` -State: # of pages scanned -Attributes: - ADF - # of scanned documents from the ADF - Duplex - # of scanned documents from the ADF using duplex mode - Flatbed - # of scanned documents from the flatbed - Jams - # of scanned jammed - Mispick - # of scanned documents failed to take the document from the feeder -``` +Device holds entities of sensors related to number of pages scanned -#### Cartridges details - Sensor (Per cartridge) +_Sensor_ +- Total scanned pages +- Total scanned pages from ADF +- Total double-sided pages scanned +- Total pages from scanner glass +- Total jams +- Total miss picks + +### Copy + +Device holds entities of sensors related to number of pages copied + +_Sensor_ + +- Total copies +- Total copies from ADF +- Total pages from scanner glass +- Total black-and-white copies +- Total color copies + +### Fax + +Device holds entities of sensors related to number of pages faxed + +_Sensor_ + +- Total faxed + +### Scanner + +Device holds entities related to consumable (Ink, Tuner, Printhead) of a printer device + +_Binary Sensor_ + +- Status + +_Sensor_ + +- Station +- Type +- Installation Date +- Level (will not be available for Printhead) +- Expiration Date (will not be available for Printhead) +- Remaining (will not be available for Printhead) +- Counterfeit Refilled +- Genuine Refilled + +## Troubleshooting + +Before opening an issue, please provide logs and diagnostic file data related to the issue. + +### Logs + +For debug log level, please add the following to your config.yaml + +```yaml +logger: + default: warning + logs: + custom_components.hpprinter: debug ``` -State: Remaining level % -Attributes: - Color - Type - Ink / Toner / Print head - Station - Position of the cartridge - Product Number - Serial Number - Manufactured By - Manufactured At - Warranty Expiration Date - Installed At -``` + +Or use the HA capability in device page: + +1. Settings +2. Devices & Services +3. HP Printer +4. 3 dots menu +5. Enable debug logging + +When done and would like to extract the log, repeat steps, in step #5 - Disable debug logging + +### Diagnostic details + +Please attach also diagnostic details of the integration, available in: + +1. Settings +2. Devices & Services +3. HP Printer +4. 3 dots menu +5. Download diagnostics diff --git a/custom_components/hpprinter/common/base_entity.py b/custom_components/hpprinter/common/base_entity.py index 4f1eabd..e9408e2 100644 --- a/custom_components/hpprinter/common/base_entity.py +++ b/custom_components/hpprinter/common/base_entity.py @@ -81,7 +81,13 @@ def _async_handle_device_created( f"Error: {ex}, Line: {line_number}" ) - _LOGGER.debug(f"Setting up {platform} entities: {entities}") + entity_keys = [entity.unique_id for entity in entities] + + entity_keys_str = ", ".join(entity_keys) + + _LOGGER.debug( + f"Setting up {platform} {len(entities)} entities, Keys: {entity_keys_str}" + ) if entities: async_add_entities(entities, True) @@ -105,8 +111,6 @@ def __init__( device_info = coordinator.get_device(device_key) - _LOGGER.info(device_info) - entity_name = coordinator.config_manager.get_entity_name( entity_description, device_info ) diff --git a/custom_components/hpprinter/managers/ha_config_manager.py b/custom_components/hpprinter/managers/ha_config_manager.py index 753c978..d714ff1 100644 --- a/custom_components/hpprinter/managers/ha_config_manager.py +++ b/custom_components/hpprinter/managers/ha_config_manager.py @@ -3,6 +3,8 @@ import os from pathlib import Path +from slugify import slugify + from homeassistant.config_entries import STORAGE_VERSION, ConfigEntry from homeassistant.const import Platform from homeassistant.core import HomeAssistant @@ -278,6 +280,7 @@ def _load_entity_descriptions(self): exclude = property_data.get("exclude") device_class = property_data.get("device_class") icon = property_data.get("icon") + translation_key = slugify(f"{device_type}_{property_key}") if property_platform == str(Platform.BINARY_SENSOR): on_value = property_data.get("on_value") @@ -290,6 +293,7 @@ def _load_entity_descriptions(self): on_value=on_value, device_class=device_class, icon=icon, + translation_key=translation_key, ) self._entity_descriptions.append(entity_description) @@ -305,6 +309,7 @@ def _load_entity_descriptions(self): native_unit_of_measurement=unit_of_measurement, device_class=device_class, icon=icon, + translation_key=translation_key, ) self._entity_descriptions.append(entity_description) diff --git a/custom_components/hpprinter/manifest.json b/custom_components/hpprinter/manifest.json index 34ca757..b633132 100644 --- a/custom_components/hpprinter/manifest.json +++ b/custom_components/hpprinter/manifest.json @@ -8,6 +8,6 @@ "documentation": "https://github.com/elad-bar/ha-hpprinter", "iot_class": "local_polling", "issue_tracker": "https://github.com/elad-bar/ha-hpprinter/issues", - "requirements": ["xmltodict~=0.13.0", "flatten_json"], + "requirements": ["xmltodict~=0.13.0", "flatten_json", "defusedxml"], "version": "2.0.0" } diff --git a/custom_components/hpprinter/strings.json b/custom_components/hpprinter/strings.json index ae2b439..b6a57fd 100644 --- a/custom_components/hpprinter/strings.json +++ b/custom_components/hpprinter/strings.json @@ -4,15 +4,13 @@ "user": { "title": "Set up HP Printer Embedded Web Server (EWS)", "data": { - "host": "Host", - "port": "Port", + "host": "Hostname or IP", + "port": "Port number", "ssl": "Is SSL" } } }, - "abort": { - "already_configured": "HP Printer integration ({name}) already configured" - }, + "abort": {}, "error": { "error_400": "Invalid parameters provided", "error_404": "HP Printer Embedded Web Server (EWS) not was not found, please check logs for more details" @@ -20,12 +18,12 @@ }, "options": { "step": { - "hp_printer_additional_settings": { + "init": { "title": "Options for HP Printer.", "description": "Define additional settings for HP Printer integration", "data": { - "host": "Host", - "port": "Port", + "host": "Hostname or IP", + "port": "Port number", "ssl": "Is SSL", "update_interval": "Update interval (Seconds)" } @@ -35,5 +33,107 @@ "error_400": "Invalid parameters provided", "error_404": "HP Printer Embedded Web Server (EWS) not was not found, please check logs for more details" } + }, + "entity": { + "binary_sensor": { + "consumable_consumable_life_state_consumable_state": { + "name": "Status" + }, + "main_hardware_config_is_connected": { + "name": "Connected" + }, + "main_registration_state": { + "name": "ePrint Registered" + }, + "main_cloud_services_switch_status": { + "name": "ePrint Status" + } + }, + "sensor": { + "consumable_consumable_station": { + "name": "Station" + }, + "consumable_consumable_type_enum": { + "name": "Type" + }, + "consumable_installation_date": { + "name": "Installation Date" + }, + "consumable_consumable_percentage_level_remaining": { + "name": "Level" + }, + "consumable_warranty_expiration_date": { + "name": "Expiration Date" + }, + "consumable_estimated_pages_remaining": { + "name": "Remaining" + }, + "consumable_refilled_count_counterfeit_refilled_count": { + "name": "Counterfeit Refilled" + }, + "consumable_refilled_count_genuine_refilled_count": { + "name": "Genuine Refilled" + }, + "printer_total_impressions": { + "name": "Total pages printed" + }, + "printer_monochrome_impressions": { + "name": "Total black-and-white pages printed" + }, + "printer_color_impressions": { + "name": "Total color pages printed" + }, + "printer_simplex_sheets": { + "name": "Total single-sided pages printed" + }, + "printer_duplex_sheets": { + "name": "Total double-sided pages printed" + }, + "printer_jam_events": { + "name": "Total jams" + }, + "printer_mispick_events": { + "name": "Total miss picks" + }, + "scanner_scan_images": { + "name": "Total scanned pages" + }, + "scanner_adf_images": { + "name": "Total scanned pages from ADF" + }, + "scanner_duplex_sheets": { + "name": "Total double-sided pages scanned" + }, + "scanner_flatbed_images": { + "name": "Total pages from scanner glass" + }, + "scanner_jam_events": { + "name": "Total jams" + }, + "scanner_mispick_events": { + "name": "Total miss picks" + }, + "copy_total_impressions": { + "name": "Total copies" + }, + "copy_adf_images": { + "name": "Total copies from ADF" + }, + "copy_flatbed_images": { + "name": "Total pages from scanner glass" + }, + "copy_monochrome_impressions": { + "name": "Total black-and-white copies" + }, + "copy_color_impressions": { + "name": "Total color copies" + }, + "fax_total_impressions": { + "name": "Total faxes" + }, + "main_hardware_config_device_connectivity_port_type": { + "name": "Port Type" + } + } } } diff --git a/custom_components/hpprinter/translations/de.json b/custom_components/hpprinter/translations/de.json index 452a231..57d6488 100644 --- a/custom_components/hpprinter/translations/de.json +++ b/custom_components/hpprinter/translations/de.json @@ -1,12 +1,138 @@ { "config": { + "error": { + "error_400": "Ung\u00fcltige Parameter bereitgestellt", + "error_404": "Der eingebettete Webserver (EWS) HP -Drucker wurde nicht gefunden. \u00dcberpr\u00fcfen Sie die Protokolle f\u00fcr weitere Details, um weitere Informationen zu erhalten." + }, "step": { "user": { - "title": "HP Drucker einrichten ", "data": { "host": "Host", - "name": "Name" - } + "name": "Name", + "port": "Port-Nummer", + "ssl": "Ist SSL" + }, + "title": "HP Drucker einrichten " + } + } + }, + "entity": { + "binary_sensor": { + "consumable_consumable_life_state_consumable_state": { + "name": "Status" + }, + "main_cloud_services_switch_status": { + "name": "ePrint Status" + }, + "main_hardware_config_is_connected": { + "name": "In Verbindung gebracht" + }, + "main_registration_state": { + "name": "ePrint Eingetragen" + } + }, + "sensor": { + "consumable_consumable_percentage_level_remaining": { + "name": "Ebene" + }, + "consumable_consumable_station": { + "name": "Bahnhof" + }, + "consumable_consumable_type_enum": { + "name": "Typ" + }, + "consumable_estimated_pages_remaining": { + "name": "\u00dcbrig" + }, + "consumable_installation_date": { + "name": "Installationsdatum" + }, + "consumable_refilled_count_counterfeit_refilled_count": { + "name": "Gef\u00e4lscht nachgedacht" + }, + "consumable_refilled_count_genuine_refilled_count": { + "name": "Echt nachgedacht" + }, + "consumable_warranty_expiration_date": { + "name": "Verfallsdatum" + }, + "copy_adf_images": { + "name": "Gesamtkopien von ADF" + }, + "copy_color_impressions": { + "name": "Gesamtfarbkopien" + }, + "copy_flatbed_images": { + "name": "Gesamtseiten aus Scannerglas" + }, + "copy_monochrome_impressions": { + "name": "Gesamtschwarz-Wei\u00df-Kopien" + }, + "copy_total_impressions": { + "name": "Gesamtkopien" + }, + "fax_total_impressions": { + "name": "Gesamtfaxen" + }, + "main_hardware_config_device_connectivity_port_type": { + "name": "Port -Typ" + }, + "printer_color_impressions": { + "name": "Gesamtfarbseiten gedruckt" + }, + "printer_duplex_sheets": { + "name": "Gesamt doppelseitige Seiten gedruckt" + }, + "printer_jam_events": { + "name": "Gesamtmarmelade" + }, + "printer_mispick_events": { + "name": "Total Miss Picks" + }, + "printer_monochrome_impressions": { + "name": "Gesamtschwarz-Wei\u00df-Seiten gedruckt" + }, + "printer_simplex_sheets": { + "name": "Gesamt einseitige Seiten gedruckt" + }, + "printer_total_impressions": { + "name": "Gesamtseiten gedruckt" + }, + "scanner_adf_images": { + "name": "Gesamts gescannte Seiten von ADF" + }, + "scanner_duplex_sheets": { + "name": "Gesamt doppelseitige Seiten gescannt" + }, + "scanner_flatbed_images": { + "name": "Gesamtseiten aus Scannerglas" + }, + "scanner_jam_events": { + "name": "Gesamtmarmelade" + }, + "scanner_mispick_events": { + "name": "Total Miss Picks" + }, + "scanner_scan_images": { + "name": "Gesamts gescannte Seiten" + } + } + }, + "options": { + "error": { + "error_400": "Ung\u00fcltige Parameter bereitgestellt", + "error_404": "Der eingebettete Webserver (EWS) HP -Drucker wurde nicht gefunden. \u00dcberpr\u00fcfen Sie die Protokolle f\u00fcr weitere Details, um weitere Informationen zu erhalten." + }, + "step": { + "init": { + "data": { + "host": "Hostname oder IP", + "port": "Port-Nummer", + "ssl": "Ist SSL", + "update_interval": "Aktualisierungsintervall (Sekunden)" + }, + "description": "Definieren Sie zus\u00e4tzliche Einstellungen f\u00fcr die HP -Druckerintegration", + "title": "Optionen f\u00fcr HP -Drucker." } } } diff --git a/custom_components/hpprinter/translations/dk.json b/custom_components/hpprinter/translations/dk.json index 09cb76b..07b4e89 100644 --- a/custom_components/hpprinter/translations/dk.json +++ b/custom_components/hpprinter/translations/dk.json @@ -1,40 +1,154 @@ { "config": { + "abort": { + "already_configured": "HP Printer integration ({name}) er allerede konfigureret" + }, + "error": { + "error_400": "Ugyldige serveroplysninger, pr\u00c3\u00b8v venligst manuelt at f\u00c3\u00a5 adgang til `http://{IP}//DevMgmt/ProductStatusDyn.xml` (Erstat pladsholder med IP eller v\u00c3\u00a6rtsnavn ), hvis det er tilg\u00c3\u00a6ngeligt, bedes du rapportere problemet med logfiler", + "error_404": "Ikke underst\u00c3\u00b8ttet API" + }, "step": { "user": { - "title": "Konfigurer HP Printer ", "data": { - "host": "Vært", - "name": "Navn" - } + "host": "V\u00c3\u00a6rt", + "name": "Navn", + "port": "Portnummer", + "ssl": "Er SSL" + }, + "title": "Konfigurer HP Printer " + } + } + }, + "entity": { + "binary_sensor": { + "consumable_consumable_life_state_consumable_state": { + "name": "Status" + }, + "main_cloud_services_switch_status": { + "name": "ePrint Status" + }, + "main_hardware_config_is_connected": { + "name": "Tilsluttet" + }, + "main_registration_state": { + "name": "ePrint Registreret" } }, - "abort": { - "already_configured": "HP Printer integration ({name}) er allerede konfigureret" - }, - "error": { - "error_400": "Ugyldige serveroplysninger, prøv venligst manuelt at få adgang til `http://{IP}//DevMgmt/ProductStatusDyn.xml` (Erstat pladsholder med IP eller værtsnavn ), hvis det er tilgængeligt, bedes du rapportere problemet med logfiler", - "error_404": "Ikke understøttet API" + "sensor": { + "consumable_consumable_percentage_level_remaining": { + "name": "Niveau" + }, + "consumable_consumable_station": { + "name": "Station" + }, + "consumable_consumable_type_enum": { + "name": "Type" + }, + "consumable_estimated_pages_remaining": { + "name": "Resterende" + }, + "consumable_installation_date": { + "name": "Installationsdato" + }, + "consumable_refilled_count_counterfeit_refilled_count": { + "name": "Forfalsket genopfyldt" + }, + "consumable_refilled_count_genuine_refilled_count": { + "name": "\u00c6gte genopfyldt" + }, + "consumable_warranty_expiration_date": { + "name": "Udl\u00f8bsdato" + }, + "copy_adf_images": { + "name": "Samlede kopier fra ADF" + }, + "copy_color_impressions": { + "name": "Samlede farvekopier" + }, + "copy_flatbed_images": { + "name": "Samlede sider fra scannerglas" + }, + "copy_monochrome_impressions": { + "name": "Samlede sort-hvide kopier" + }, + "copy_total_impressions": { + "name": "Samlede kopier" + }, + "fax_total_impressions": { + "name": "Samlede faxer" + }, + "main_hardware_config_device_connectivity_port_type": { + "name": "Porttype" + }, + "printer_color_impressions": { + "name": "Samlede farvesider trykt" + }, + "printer_duplex_sheets": { + "name": "Samlede dobbeltsidede sider trykt" + }, + "printer_jam_events": { + "name": "Samlede syltet\u00f8j" + }, + "printer_mispick_events": { + "name": "Total Miss Picks" + }, + "printer_monochrome_impressions": { + "name": "Samlede sorte-hvide sider trykt" + }, + "printer_simplex_sheets": { + "name": "Samlede enkeltsidede sider trykt" + }, + "printer_total_impressions": { + "name": "Samlede sider udskrivning" + }, + "scanner_adf_images": { + "name": "Samlede scannede sider fra ADF" + }, + "scanner_duplex_sheets": { + "name": "Samlede dobbeltsidede sider scannet" + }, + "scanner_flatbed_images": { + "name": "Samlede sider fra scannerglas" + }, + "scanner_jam_events": { + "name": "Samlede syltet\u00f8j" + }, + "scanner_mispick_events": { + "name": "Total Miss Picks" + }, + "scanner_scan_images": { + "name": "Samlede scannede sider" + } } }, "options": { + "error": { + "already_configured": "HP Printer integration ({name}) er allerede konfigureret", + "error_400": "Ugyldige serveroplysninger, pr\u00c3\u00b8v venligst manuelt at f\u00c3\u00a5 adgang til `http://{IP}//DevMgmt/ProductStatusDyn.xml` (Erstat pladsholder med IP eller v\u00c3\u00a6rtsnavn ), hvis det er tilg\u00c3\u00a6ngeligt, bedes du rapportere problemet med logfiler", + "error_404": "Ikke underst\u00c3\u00b8ttet API" + }, "step": { "hp_printer_additional_settings": { - "title": "Indstillinger for HP Printer.", - "description": "Definer yderligere indstillinger for HP Printer integration ", "data": { - "host": "Vært", - "name": "Navn", - "update_interval": "Opdateringsinterval (sekunder)", + "host": "V\u00c3\u00a6rt", "log_level": "Log level", - "store_data": "Skal svar gemmes?" - } + "name": "Navn", + "store_data": "Skal svar gemmes?", + "update_interval": "Opdateringsinterval (sekunder)" + }, + "description": "Definer yderligere indstillinger for HP Printer integration ", + "title": "Indstillinger for HP Printer." + }, + "init": { + "data": { + "host": "V\u00e6rtsnavn eller IP", + "port": "Portnummer", + "ssl": "Er SSL", + "update_interval": "Opdateringsinterval (sekunder)" + }, + "description": "Definer yderligere indstillinger til HP -printerintegration", + "title": "Valgmuligheder til HP -printer." } - }, - "error": { - "error_400": "Ugyldige serveroplysninger, prøv venligst manuelt at få adgang til `http://{IP}//DevMgmt/ProductStatusDyn.xml` (Erstat pladsholder med IP eller værtsnavn ), hvis det er tilgængeligt, bedes du rapportere problemet med logfiler", - "error_404": "Ikke understøttet API", - "already_configured": "HP Printer integration ({name}) er allerede konfigureret" } } } diff --git a/custom_components/hpprinter/translations/en.json b/custom_components/hpprinter/translations/en.json index 9e1beba..270db46 100644 --- a/custom_components/hpprinter/translations/en.json +++ b/custom_components/hpprinter/translations/en.json @@ -1,40 +1,154 @@ { "config": { - "step": { - "user": { - "title": "Set up HP Printer", - "data": { - "host": "Host", - "name": "Name" - } - } - }, "abort": { "already_configured": "HP Printer integration ({name}) already configured" }, "error": { "error_400": "Invalid server details, please try manually access to `http://{IP}//DevMgmt/ProductStatusDyn.xml` (Replace placeholder with IP or Hostname), in case it's accessible, please report the issue with logs", "error_404": "Unsupported API" + }, + "step": { + "user": { + "data": { + "host": "Host", + "name": "Name", + "port": "Port number", + "ssl": "Is SSL" + }, + "title": "Set up HP Printer" + } + } + }, + "entity": { + "binary_sensor": { + "consumable_consumable_life_state_consumable_state": { + "name": "Status" + }, + "main_cloud_services_switch_status": { + "name": "ePrint Status" + }, + "main_hardware_config_is_connected": { + "name": "Connected" + }, + "main_registration_state": { + "name": "ePrint Registered" + } + }, + "sensor": { + "consumable_consumable_percentage_level_remaining": { + "name": "Level" + }, + "consumable_consumable_station": { + "name": "Station" + }, + "consumable_consumable_type_enum": { + "name": "Type" + }, + "consumable_estimated_pages_remaining": { + "name": "Remaining" + }, + "consumable_installation_date": { + "name": "Installation Date" + }, + "consumable_refilled_count_counterfeit_refilled_count": { + "name": "Counterfeit Refilled" + }, + "consumable_refilled_count_genuine_refilled_count": { + "name": "Genuine Refilled" + }, + "consumable_warranty_expiration_date": { + "name": "Expiration Date" + }, + "copy_adf_images": { + "name": "Total copies from ADF" + }, + "copy_color_impressions": { + "name": "Total color copies" + }, + "copy_flatbed_images": { + "name": "Total pages from scanner glass" + }, + "copy_monochrome_impressions": { + "name": "Total black-and-white copies" + }, + "copy_total_impressions": { + "name": "Total copies" + }, + "fax_total_impressions": { + "name": "Total faxes" + }, + "main_hardware_config_device_connectivity_port_type": { + "name": "Port Type" + }, + "printer_color_impressions": { + "name": "Total color pages printed" + }, + "printer_duplex_sheets": { + "name": "Total double-sided pages printed" + }, + "printer_jam_events": { + "name": "Total jams" + }, + "printer_mispick_events": { + "name": "Total miss picks" + }, + "printer_monochrome_impressions": { + "name": "Total black-and-white pages printed" + }, + "printer_simplex_sheets": { + "name": "Total single-sided pages printed" + }, + "printer_total_impressions": { + "name": "Total pages printed" + }, + "scanner_adf_images": { + "name": "Total scanned pages from ADF" + }, + "scanner_duplex_sheets": { + "name": "Total double-sided pages scanned" + }, + "scanner_flatbed_images": { + "name": "Total pages from scanner glass" + }, + "scanner_jam_events": { + "name": "Total jams" + }, + "scanner_mispick_events": { + "name": "Total miss picks" + }, + "scanner_scan_images": { + "name": "Total scanned pages" + } } }, "options": { + "error": { + "already_configured": "HP Printer integration ({name}) already configured", + "error_400": "Invalid server details, please try manually access to `http://{IP}//DevMgmt/ProductStatusDyn.xml` (Replace placeholder with IP or Hostname), in case it's accessible, please report the issue with logs", + "error_404": "Unsupported API" + }, "step": { "hp_printer_additional_settings": { - "title": "Options for HP Printer.", - "description": "Define additional settings for HP Printer integration", "data": { "host": "Host", - "name": "Name", - "update_interval": "Update interval (Seconds)", "log_level": "Log level", - "store_data": "Should store responses?" - } + "name": "Name", + "store_data": "Should store responses?", + "update_interval": "Update interval (Seconds)" + }, + "description": "Define additional settings for HP Printer integration", + "title": "Options for HP Printer." + }, + "init": { + "data": { + "host": "Hostname or IP", + "port": "Port number", + "ssl": "Is SSL", + "update_interval": "Update interval (Seconds)" + }, + "description": "Define additional settings for HP Printer integration", + "title": "Options for HP Printer." } - }, - "error": { - "error_400": "Invalid server details, please try manually access to `http://{IP}//DevMgmt/ProductStatusDyn.xml` (Replace placeholder with IP or Hostname), in case it's accessible, please report the issue with logs", - "error_404": "Unsupported API", - "already_configured": "HP Printer integration ({name}) already configured" } } } diff --git a/custom_components/hpprinter/translations/es.json b/custom_components/hpprinter/translations/es.json index d1ba5a0..30316f7 100644 --- a/custom_components/hpprinter/translations/es.json +++ b/custom_components/hpprinter/translations/es.json @@ -1,40 +1,154 @@ { "config": { + "abort": { + "already_configured": "La integraci\u00c3\u00b3n de la impresora HP ({name}) ya est\u00c3\u00a1 configurada" + }, + "error": { + "error_400": "Los detalles del servidor no son v\u00c3\u00a1lidos", + "error_404": "API no compatible" + }, "step": { "user": { - "title": "Configurar impresora HP", "data": { "host": "Host", - "name": "Nombre" - } + "name": "Nombre", + "port": "N\u00famero de puerto", + "ssl": "Es ssl" + }, + "title": "Configurar impresora HP" + } + } + }, + "entity": { + "binary_sensor": { + "consumable_consumable_life_state_consumable_state": { + "name": "Estado" + }, + "main_cloud_services_switch_status": { + "name": "ePrint Estado" + }, + "main_hardware_config_is_connected": { + "name": "Conectada Conectado" + }, + "main_registration_state": { + "name": "ePrint Registrado" } }, - "abort": { - "already_configured": "La integración de la impresora HP ({name}) ya está configurada" - }, - "error": { - "error_400": "Los detalles del servidor no son válidos", - "error_404": "API no compatible" + "sensor": { + "consumable_consumable_percentage_level_remaining": { + "name": "Nivel" + }, + "consumable_consumable_station": { + "name": "Estaci\u00f3n" + }, + "consumable_consumable_type_enum": { + "name": "Tipo" + }, + "consumable_estimated_pages_remaining": { + "name": "Restante" + }, + "consumable_installation_date": { + "name": "Fecha de instalaci\u00f3n" + }, + "consumable_refilled_count_counterfeit_refilled_count": { + "name": "Falsificado" + }, + "consumable_refilled_count_genuine_refilled_count": { + "name": "Regilado genuino" + }, + "consumable_warranty_expiration_date": { + "name": "Fecha de caducidad" + }, + "copy_adf_images": { + "name": "Copias totales de ADF" + }, + "copy_color_impressions": { + "name": "Copias de color total" + }, + "copy_flatbed_images": { + "name": "P\u00e1ginas totales del vidrio del esc\u00e1ner" + }, + "copy_monochrome_impressions": { + "name": "Copias totales en blanco y negro" + }, + "copy_total_impressions": { + "name": "Copias totales" + }, + "fax_total_impressions": { + "name": "Faxes totales" + }, + "main_hardware_config_device_connectivity_port_type": { + "name": "Tipo de puerto" + }, + "printer_color_impressions": { + "name": "P\u00e1ginas de color totales impresas" + }, + "printer_duplex_sheets": { + "name": "P\u00e1ginas totales de doble cara impresas" + }, + "printer_jam_events": { + "name": "Mermeladas totales" + }, + "printer_mispick_events": { + "name": "Total de las selecciones de la se\u00f1orita" + }, + "printer_monochrome_impressions": { + "name": "P\u00e1ginas totales en blanco y negro impresas" + }, + "printer_simplex_sheets": { + "name": "P\u00e1ginas totales de un solo lado impreso" + }, + "printer_total_impressions": { + "name": "P\u00e1ginas totales impresas" + }, + "scanner_adf_images": { + "name": "P\u00e1ginas escanadas totales de ADF" + }, + "scanner_duplex_sheets": { + "name": "P\u00e1ginas totales de doble cara escaneadas" + }, + "scanner_flatbed_images": { + "name": "P\u00e1ginas totales del vidrio del esc\u00e1ner" + }, + "scanner_jam_events": { + "name": "Mermeladas totales" + }, + "scanner_mispick_events": { + "name": "Total de las selecciones de la se\u00f1orita" + }, + "scanner_scan_images": { + "name": "P\u00e1ginas escaneadas totales" + } } }, "options": { + "error": { + "already_configured": "La integraci\u00c3\u00b3n de la impresora HP ({name}) ya est\u00c3\u00a1 configurada", + "error_400": "Los detalles del servidor no son v\u00c3\u00a1lidos", + "error_404": "API no compatible" + }, "step": { "hp_printer_additional_settings": { - "title": "Opciones para la impresora HP.", - "description": "Definir configuraciones adicionales para la integración de la impresora HP", "data": { "host": "Host", - "name": "Nombre", - "update_interval": "Intervalo de actualización (segundos)", "log_level": "Nivel del registro", - "store_data": "Debería almacenar respuestas?" - } + "name": "Nombre", + "store_data": "Deber\u00c3\u00ada almacenar respuestas?", + "update_interval": "Intervalo de actualizaci\u00c3\u00b3n (segundos)" + }, + "description": "Definir configuraciones adicionales para la integraci\u00c3\u00b3n de la impresora HP", + "title": "Opciones para la impresora HP." + }, + "init": { + "data": { + "host": "Nombre de host o IP", + "port": "N\u00famero de puerto", + "ssl": "Es ssl", + "update_interval": "Intervalo de actualizaci\u00f3n (segundos)" + }, + "description": "Definir configuraciones adicionales para la integraci\u00f3n de la impresora HP", + "title": "Opciones para la impresora HP." } - }, - "error": { - "error_400": "Los detalles del servidor no son válidos", - "error_404": "API no compatible", - "already_configured": "La integración de la impresora HP ({name}) ya está configurada" } } } diff --git a/custom_components/hpprinter/translations/fr.json b/custom_components/hpprinter/translations/fr.json index 231cfa9..07704ab 100644 --- a/custom_components/hpprinter/translations/fr.json +++ b/custom_components/hpprinter/translations/fr.json @@ -1,12 +1,138 @@ { "config": { + "error": { + "error_400": "Param\u00e8tres non valides fournis", + "error_404": "HP Imprimante Embedded Web Server (EWS) n'a pas \u00e9t\u00e9 trouv\u00e9e, veuillez consulter les journaux pour plus de d\u00e9tails" + }, "step": { "user": { - "title": "Configurer l'imprimante HP", "data": { - "host": "Hôte", - "name": "Nom" - } + "host": "H\u00c3\u00b4te", + "name": "Nom", + "port": "Num\u00e9ro de port", + "ssl": "Est ssl" + }, + "title": "Configurer l'imprimante HP" + } + } + }, + "entity": { + "binary_sensor": { + "consumable_consumable_life_state_consumable_state": { + "name": "Statut" + }, + "main_cloud_services_switch_status": { + "name": "ePrint Statut" + }, + "main_hardware_config_is_connected": { + "name": "Connect\u00e9" + }, + "main_registration_state": { + "name": "ePrint enregistr\u00e9" + } + }, + "sensor": { + "consumable_consumable_percentage_level_remaining": { + "name": "Niveau" + }, + "consumable_consumable_station": { + "name": "Gare" + }, + "consumable_consumable_type_enum": { + "name": "Taper" + }, + "consumable_estimated_pages_remaining": { + "name": "Restante Restant" + }, + "consumable_installation_date": { + "name": "Date d'installation" + }, + "consumable_refilled_count_counterfeit_refilled_count": { + "name": "Contrefait rempli" + }, + "consumable_refilled_count_genuine_refilled_count": { + "name": "Authentique rempli" + }, + "consumable_warranty_expiration_date": { + "name": "Date d'expiration" + }, + "copy_adf_images": { + "name": "Total des copies de l'ADF" + }, + "copy_color_impressions": { + "name": "Copies de couleur totale" + }, + "copy_flatbed_images": { + "name": "Pages totales du verre du scanner" + }, + "copy_monochrome_impressions": { + "name": "Copies totales en noir et blanc" + }, + "copy_total_impressions": { + "name": "Copies totales" + }, + "fax_total_impressions": { + "name": "T\u00e9l\u00e9copies totales" + }, + "main_hardware_config_device_connectivity_port_type": { + "name": "Type de port" + }, + "printer_color_impressions": { + "name": "Pages de couleurs totales imprim\u00e9es" + }, + "printer_duplex_sheets": { + "name": "Pages totales double face imprim\u00e9es" + }, + "printer_jam_events": { + "name": "Jams totaux" + }, + "printer_mispick_events": { + "name": "Picks Miss Total" + }, + "printer_monochrome_impressions": { + "name": "Pages totales en noir et blanc imprim\u00e9es" + }, + "printer_simplex_sheets": { + "name": "Pages totales \u00e0 un seul c\u00f4t\u00e9 imprim\u00e9" + }, + "printer_total_impressions": { + "name": "Pages totales imprim\u00e9es" + }, + "scanner_adf_images": { + "name": "Pages scann\u00e9es totales de l'ADF" + }, + "scanner_duplex_sheets": { + "name": "Pages totales double face num\u00e9ris\u00e9es" + }, + "scanner_flatbed_images": { + "name": "Pages totales du verre du scanner" + }, + "scanner_jam_events": { + "name": "Jams totaux" + }, + "scanner_mispick_events": { + "name": "Picks Miss Total" + }, + "scanner_scan_images": { + "name": "Pages scann\u00e9es totales" + } + } + }, + "options": { + "error": { + "error_400": "Param\u00e8tres non valides fournis", + "error_404": "HP Imprimante Embedded Web Server (EWS) n'a pas \u00e9t\u00e9 trouv\u00e9e, veuillez consulter les journaux pour plus de d\u00e9tails" + }, + "step": { + "init": { + "data": { + "host": "Nom d'h\u00f4te ou IP", + "port": "Num\u00e9ro de port", + "ssl": "Est ssl", + "update_interval": "Mettre \u00e0 jour l'intervalle (secondes)" + }, + "description": "D\u00e9finir des param\u00e8tres suppl\u00e9mentaires pour l'int\u00e9gration de l'imprimante HP", + "title": "Options pour l'imprimante HP." } } } diff --git a/custom_components/hpprinter/translations/nb.json b/custom_components/hpprinter/translations/nb.json index ed17b4e..112c9bf 100644 --- a/custom_components/hpprinter/translations/nb.json +++ b/custom_components/hpprinter/translations/nb.json @@ -1,40 +1,154 @@ { "config": { + "abort": { + "already_configured": "HP-skriverintegrasjon ({name}) er allerede konfigurert" + }, + "error": { + "error_400": "Ugyldige serveropplysninger, pr\u00c3\u00b8v manuelt \u00c3\u00a5 f\u00c3\u00a5 tilgang til `http://{IP}//DevMgmt/ProductStatusDyn.xml` (Erstatt plassholder med IP eller vertsnavn), hvis det er tilgjengelig, vennligst rapporter problemet med logger", + "error_404": "API st\u00c3\u00b8ttes ikke" + }, "step": { "user": { - "title": "Sett opp HP-skriver", "data": { "host": "Vert", - "name": "Navn" - } + "name": "Navn", + "port": "Portnummer", + "ssl": "Er SSL" + }, + "title": "Sett opp HP-skriver" + } + } + }, + "entity": { + "binary_sensor": { + "consumable_consumable_life_state_consumable_state": { + "name": "Status" + }, + "main_cloud_services_switch_status": { + "name": "ePrint Status" + }, + "main_hardware_config_is_connected": { + "name": "Tilkoblet" + }, + "main_registration_state": { + "name": "ePrint Registrert" } }, - "abort": { - "already_configured": "HP-skriverintegrasjon ({name}) er allerede konfigurert" - }, - "error": { - "error_400": "Ugyldige serveropplysninger, prøv manuelt å få tilgang til `http://{IP}//DevMgmt/ProductStatusDyn.xml` (Erstatt plassholder med IP eller vertsnavn), hvis det er tilgjengelig, vennligst rapporter problemet med logger", - "error_404": "API støttes ikke" + "sensor": { + "consumable_consumable_percentage_level_remaining": { + "name": "Niv\u00e5" + }, + "consumable_consumable_station": { + "name": "Stasjon" + }, + "consumable_consumable_type_enum": { + "name": "Type" + }, + "consumable_estimated_pages_remaining": { + "name": "Gjenst\u00e5ende" + }, + "consumable_installation_date": { + "name": "Installasjonsdato" + }, + "consumable_refilled_count_counterfeit_refilled_count": { + "name": "Forfalsket p\u00e5fyllt" + }, + "consumable_refilled_count_genuine_refilled_count": { + "name": "Ekte p\u00e5fyllt" + }, + "consumable_warranty_expiration_date": { + "name": "Utl\u00f8psdato" + }, + "copy_adf_images": { + "name": "Totalt eksemplarer fra ADF" + }, + "copy_color_impressions": { + "name": "Total fargekopier" + }, + "copy_flatbed_images": { + "name": "Totalt sider fra skannerglass" + }, + "copy_monochrome_impressions": { + "name": "Totalt svart-hvitt eksemplarer" + }, + "copy_total_impressions": { + "name": "Totalt eksemplarer" + }, + "fax_total_impressions": { + "name": "Totalt fakser" + }, + "main_hardware_config_device_connectivity_port_type": { + "name": "Porttype" + }, + "printer_color_impressions": { + "name": "Total fargesider trykt" + }, + "printer_duplex_sheets": { + "name": "Totalt tosidige sider trykt" + }, + "printer_jam_events": { + "name": "Total syltet\u00f8y" + }, + "printer_mispick_events": { + "name": "Totalt glipp av valg" + }, + "printer_monochrome_impressions": { + "name": "Total svart-hvitt sider trykt" + }, + "printer_simplex_sheets": { + "name": "Totalt enkeltsidede sider trykt" + }, + "printer_total_impressions": { + "name": "Totalt sider trykt" + }, + "scanner_adf_images": { + "name": "Total skannede sider fra ADF" + }, + "scanner_duplex_sheets": { + "name": "Totalt tosidige sider skannet" + }, + "scanner_flatbed_images": { + "name": "Totalt sider fra skannerglass" + }, + "scanner_jam_events": { + "name": "Total syltet\u00f8y" + }, + "scanner_mispick_events": { + "name": "Totalt glipp av valg" + }, + "scanner_scan_images": { + "name": "Totalt skannede sider" + } } }, "options": { + "error": { + "already_configured": "HP-skriverintegrasjon ({name}) er allerede konfigurert", + "error_400": "Ugyldige serveropplysninger. Pr\u00c3\u00b8v manuelt \u00c3\u00a5 f\u00c3\u00a5 tilgang ti `http://{IP}//DevMgmt/ProductStatusDyn.xml` (Erstatt plassholder med IP eller vertsnavn), hvis det er tilgjengelig, vennligst rapporter problemet med logger", + "error_404": "API st\u00c3\u00b8ttes ikke" + }, "step": { "hp_printer_additional_settings": { - "title": "Alternativer for HP-skriver.", - "description": "Definer tilleggsinnstillinger for HP-skriverintegrasjon", "data": { "host": "Vert", + "log_level": "Loggniv\u00c3\u00a5", "name": "Navn", - "update_interval": "Oppdateringsintervall (sekunder)", - "log_level": "Loggnivå", - "store_data": "Bør lagre svar?" - } + "store_data": "B\u00c3\u00b8r lagre svar?", + "update_interval": "Oppdateringsintervall (sekunder)" + }, + "description": "Definer tilleggsinnstillinger for HP-skriverintegrasjon", + "title": "Alternativer for HP-skriver." + }, + "init": { + "data": { + "host": "Vertsnavn eller ip", + "port": "Portnummer", + "ssl": "Er SSL", + "update_interval": "Oppdateringsintervall (sekunder)" + }, + "description": "Definer flere innstillinger for HP -skriverintegrasjon", + "title": "Alternativer for HP -skriver." } - }, - "error": { - "error_400": "Ugyldige serveropplysninger. Prøv manuelt å få tilgang ti `http://{IP}//DevMgmt/ProductStatusDyn.xml` (Erstatt plassholder med IP eller vertsnavn), hvis det er tilgjengelig, vennligst rapporter problemet med logger", - "error_404": "API støttes ikke", - "already_configured": "HP-skriverintegrasjon ({name}) er allerede konfigurert" } } } diff --git a/custom_components/hpprinter/translations/nl.json b/custom_components/hpprinter/translations/nl.json index b3d0ffb..4e42637 100644 --- a/custom_components/hpprinter/translations/nl.json +++ b/custom_components/hpprinter/translations/nl.json @@ -1,16 +1,140 @@ { "config": { + "error": { + "cannot_reach_printer": "HP Printer is onbereikbaar vanwege een van de volgende redenen: de hostnaam is verkeerd, de printer is niet verbonden of de printer ondersteund de API in deze integratie niet", + "error_400": "Ongeldige parameters verstrekt", + "error_404": "HP Printer Embedded Web Server (EWS) is niet niet gevonden, controleer logboeken voor meer informatie" + }, "step": { "user": { - "title": "HP Printer instellen", "data": { "host": "Host", - "name": "Naam" - } + "name": "Naam", + "port": "Poortnummer", + "ssl": "Is SSL" + }, + "title": "HP Printer instellen" + } + } + }, + "entity": { + "binary_sensor": { + "consumable_consumable_life_state_consumable_state": { + "name": "Toestand" + }, + "main_cloud_services_switch_status": { + "name": "ePrint Toestand" + }, + "main_hardware_config_is_connected": { + "name": "Verbonden" + }, + "main_registration_state": { + "name": "ePrint Geregistreerd" } }, + "sensor": { + "consumable_consumable_percentage_level_remaining": { + "name": "Niveau" + }, + "consumable_consumable_station": { + "name": "Station" + }, + "consumable_consumable_type_enum": { + "name": "Type" + }, + "consumable_estimated_pages_remaining": { + "name": "Overig" + }, + "consumable_installation_date": { + "name": "Installatie datum" + }, + "consumable_refilled_count_counterfeit_refilled_count": { + "name": "Vervalste bijgevuld" + }, + "consumable_refilled_count_genuine_refilled_count": { + "name": "Echt bijgevuld" + }, + "consumable_warranty_expiration_date": { + "name": "uiterste houdbaarheidsdatum" + }, + "copy_adf_images": { + "name": "Totale exemplaren van ADF" + }, + "copy_color_impressions": { + "name": "Totale kleurkopie\u00ebn" + }, + "copy_flatbed_images": { + "name": "Totale pagina's van scannerglas" + }, + "copy_monochrome_impressions": { + "name": "Totaal zwart-wit kopie\u00ebn" + }, + "copy_total_impressions": { + "name": "Totale exemplaren" + }, + "fax_total_impressions": { + "name": "Totaal faxen" + }, + "main_hardware_config_device_connectivity_port_type": { + "name": "Poorttype" + }, + "printer_color_impressions": { + "name": "Totale kleurpagina's afgedrukt" + }, + "printer_duplex_sheets": { + "name": "Totaal dubbelzijdige pagina's afgedrukt" + }, + "printer_jam_events": { + "name": "Totale jam" + }, + "printer_mispick_events": { + "name": "Totale Miss Picks" + }, + "printer_monochrome_impressions": { + "name": "Totaal zwart-wit pagina's afgedrukt" + }, + "printer_simplex_sheets": { + "name": "Totaal afgedrukte pagina's met \u00e9\u00e9n zijdeling" + }, + "printer_total_impressions": { + "name": "Totale pagina's afgedrukt" + }, + "scanner_adf_images": { + "name": "Totaal gescande pagina's van ADF" + }, + "scanner_duplex_sheets": { + "name": "Totale dubbelzijdige pagina's gescand" + }, + "scanner_flatbed_images": { + "name": "Totale pagina's van scannerglas" + }, + "scanner_jam_events": { + "name": "Totale jam" + }, + "scanner_mispick_events": { + "name": "Totale Miss Picks" + }, + "scanner_scan_images": { + "name": "Totaal gescande pagina's" + } + } + }, + "options": { "error": { - "cannot_reach_printer": "HP Printer is onbereikbaar vanwege een van de volgende redenen: de hostnaam is verkeerd, de printer is niet verbonden of de printer ondersteund de API in deze integratie niet" + "error_400": "Ongeldige parameters verstrekt", + "error_404": "HP Printer Embedded Web Server (EWS) is niet niet gevonden, controleer logboeken voor meer informatie" + }, + "step": { + "init": { + "data": { + "host": "Hostnaam of ip", + "port": "Poortnummer", + "ssl": "Is SSL", + "update_interval": "Update interval (seconden)" + }, + "description": "Definieer extra instellingen voor HP -printerintegratie", + "title": "Opties voor HP -printer." + } } } } diff --git a/custom_components/hpprinter/translations/pl.json b/custom_components/hpprinter/translations/pl.json index 4434588..799c2eb 100644 --- a/custom_components/hpprinter/translations/pl.json +++ b/custom_components/hpprinter/translations/pl.json @@ -1,40 +1,154 @@ { "config": { + "abort": { + "already_configured": "Integracja drukarki HP ({name}) jest ju\u00c5\u00bc skonfigurowana" + }, + "error": { + "error_400": "Nieprawid\u00c5\u201aowe dane serwera, spr\u00c3\u00b3buj r\u00c4\u2122cznie uzyska\u00c4\u2021 otworzy\u00c4\u2021 `http://{IP}//DevMgmt/ProductStatusDyn.xml` (Zamie\u00c5\u201e tekst zast\u00c4\u2122pczy na IP lub nazw\u00c4\u2122 hosta), je\u00c5\u203ali to mo\u00c5\u00bcliwe - zg\u00c5\u201ao\u00c5\u203a problem wraz z logami.", + "error_404": "Niewspierane API" + }, "step": { "user": { - "title": "Skonfiguruj drukarkę HP", "data": { "host": "Host lub IP", - "name": "Nazwa" - } + "name": "Nazwa", + "port": "Numer portu", + "ssl": "Jest SSL" + }, + "title": "Skonfiguruj drukark\u00c4\u2122 HP" + } + } + }, + "entity": { + "binary_sensor": { + "consumable_consumable_life_state_consumable_state": { + "name": "Status" + }, + "main_cloud_services_switch_status": { + "name": "ePrint Status" + }, + "main_hardware_config_is_connected": { + "name": "Po\u0142\u0105czony" + }, + "main_registration_state": { + "name": "ePrint Zarejestrowany" } }, - "abort": { - "already_configured": "Integracja drukarki HP ({name}) jest już skonfigurowana" - }, - "error": { - "error_400": "Nieprawidłowe dane serwera, spróbuj ręcznie uzyskać otworzyć `http://{IP}//DevMgmt/ProductStatusDyn.xml` (Zamień tekst zastępczy na IP lub nazwę hosta), jeśli to możliwe - zgłoś problem wraz z logami.", - "error_404": "Niewspierane API" + "sensor": { + "consumable_consumable_percentage_level_remaining": { + "name": "Poziom" + }, + "consumable_consumable_station": { + "name": "Stacja" + }, + "consumable_consumable_type_enum": { + "name": "Typ" + }, + "consumable_estimated_pages_remaining": { + "name": "Pozosta\u0142y" + }, + "consumable_installation_date": { + "name": "Data instalacji" + }, + "consumable_refilled_count_counterfeit_refilled_count": { + "name": "FRAKTHICE FOLOKLED" + }, + "consumable_refilled_count_genuine_refilled_count": { + "name": "Oryginalny nape\u0142niony" + }, + "consumable_warranty_expiration_date": { + "name": "Termin wa\u017cno\u015bci" + }, + "copy_adf_images": { + "name": "Ca\u0142kowite kopie z ADF" + }, + "copy_color_impressions": { + "name": "Ca\u0142kowite kopie kolor\u00f3w" + }, + "copy_flatbed_images": { + "name": "Ca\u0142kowite strony ze szk\u0142a skanera" + }, + "copy_monochrome_impressions": { + "name": "Ca\u0142kowite czarno-bia\u0142e kopie" + }, + "copy_total_impressions": { + "name": "Ca\u0142kowite kopie" + }, + "fax_total_impressions": { + "name": "Ca\u0142kowite faksy" + }, + "main_hardware_config_device_connectivity_port_type": { + "name": "Typ portu" + }, + "printer_color_impressions": { + "name": "Wydrukowano ca\u0142kowit\u0105 strony kolor\u00f3w" + }, + "printer_duplex_sheets": { + "name": "Wydrukowano ca\u0142kowit\u0105 dwustronne strony" + }, + "printer_jam_events": { + "name": "Ca\u0142kowite zaci\u0119cia" + }, + "printer_mispick_events": { + "name": "Total Miss Pick" + }, + "printer_monochrome_impressions": { + "name": "Wydrukowane ca\u0142kowit\u0105 czarno-bia\u0142e strony" + }, + "printer_simplex_sheets": { + "name": "Wydrukowano ca\u0142kowit\u0105 jednostronne strony" + }, + "printer_total_impressions": { + "name": "Wydrukowano ca\u0142kowit\u0105 strony" + }, + "scanner_adf_images": { + "name": "Ca\u0142kowite zeskanowane strony z ADF" + }, + "scanner_duplex_sheets": { + "name": "Ca\u0142kowita dwustronna strony zeskanowane" + }, + "scanner_flatbed_images": { + "name": "Ca\u0142kowite strony ze szk\u0142a skanera" + }, + "scanner_jam_events": { + "name": "Ca\u0142kowite zaci\u0119cia" + }, + "scanner_mispick_events": { + "name": "Total Miss Pick" + }, + "scanner_scan_images": { + "name": "Ca\u0142kowite zeskanowane strony" + } } }, "options": { + "error": { + "already_configured": "Integracja drukarki HP ({name}) jest ju\u00c5\u00bc skonfigurowana", + "error_400": "Nieprawid\u00c5\u201aowe dane serwera, spr\u00c3\u00b3buj r\u00c4\u2122cznie uzyska\u00c4\u2021 otworzy\u00c4\u2021 `http://{IP}//DevMgmt/ProductStatusDyn.xml` (Zamie\u00c5\u201e tekst zast\u00c4\u2122pczy na IP lub nazw\u00c4\u2122 hosta), je\u00c5\u203ali to mo\u00c5\u00bcliwe - zg\u00c5\u201ao\u00c5\u203a problem wraz z logami.", + "error_404": "Niewspierane API" + }, "step": { "hp_printer_additional_settings": { - "title": "Opcje dla drukarki HP.", - "description": "Zdefiniuj dodatkowe ustawienia dla integracji drukarki HP", "data": { "host": "Host lub IP", - "name": "Nazwa", - "update_interval": "Interwał aktualizacji (sekundy)", "log_level": "Poziom logowania", - "store_data": "Czy należy przechowywać odpowiedzi?" - } + "name": "Nazwa", + "store_data": "Czy nale\u00c5\u00bcy przechowywa\u00c4\u2021 odpowiedzi?", + "update_interval": "Interwa\u00c5\u201a aktualizacji (sekundy)" + }, + "description": "Zdefiniuj dodatkowe ustawienia dla integracji drukarki HP", + "title": "Opcje dla drukarki HP." + }, + "init": { + "data": { + "host": "Nazwa hosta lub IP", + "port": "Numer portu", + "ssl": "Jest SSL", + "update_interval": "Interwa\u0142 aktualizacji (sekundy)" + }, + "description": "Zdefiniuj dodatkowe ustawienia integracji drukarki HP", + "title": "Opcje drukarki HP." } - }, - "error": { - "error_400": "Nieprawidłowe dane serwera, spróbuj ręcznie uzyskać otworzyć `http://{IP}//DevMgmt/ProductStatusDyn.xml` (Zamień tekst zastępczy na IP lub nazwę hosta), jeśli to możliwe - zgłoś problem wraz z logami.", - "error_404": "Niewspierane API", - "already_configured": "Integracja drukarki HP ({name}) jest już skonfigurowana" } } } diff --git a/custom_components/hpprinter/translations/pt-BR.json b/custom_components/hpprinter/translations/pt-BR.json index 035499e..f10b54c 100644 --- a/custom_components/hpprinter/translations/pt-BR.json +++ b/custom_components/hpprinter/translations/pt-BR.json @@ -1,40 +1,154 @@ { "config": { + "abort": { + "already_configured": "Integra\u00c3\u00a7\u00c3\u00a3o HP Printer ({name}) j\u00c3\u00a1 configurada" + }, + "error": { + "error_400": "Detalhes do servidor inv\u00c3\u00a1lidos, tente acessar manualmente `http://{IP}//DevMgmt/ProductStatusDyn.xml` (Substituir o marcador de posi\u00c3\u00a7\u00c3\u00a3o por IP ou Hostname), caso esteja acess\u00c3\u00advel, informe o problema com os logs", + "error_404": "API Sem suporte" + }, "step": { "user": { - "title": "Configurar HP Printer", "data": { "host": "Host", - "name": "Nome" - } + "name": "Nome", + "port": "N\u00famero da porta", + "ssl": "\u00c9 ssl" + }, + "title": "Configurar HP Printer" + } + } + }, + "entity": { + "binary_sensor": { + "consumable_consumable_life_state_consumable_state": { + "name": "Status" + }, + "main_cloud_services_switch_status": { + "name": "ePrint Status" + }, + "main_hardware_config_is_connected": { + "name": "Conectada Conectado" + }, + "main_registration_state": { + "name": "ePrint Registrado" } }, - "abort": { - "already_configured": "Integração HP Printer ({name}) já configurada" - }, - "error": { - "error_400": "Detalhes do servidor inválidos, tente acessar manualmente `http://{IP}//DevMgmt/ProductStatusDyn.xml` (Substituir o marcador de posição por IP ou Hostname), caso esteja acessível, informe o problema com os logs", - "error_404": "API Sem suporte" + "sensor": { + "consumable_consumable_percentage_level_remaining": { + "name": "N\u00edvel" + }, + "consumable_consumable_station": { + "name": "Esta\u00e7\u00e3o" + }, + "consumable_consumable_type_enum": { + "name": "Tipo" + }, + "consumable_estimated_pages_remaining": { + "name": "Restante" + }, + "consumable_installation_date": { + "name": "Data de instala\u00e7\u00e3o" + }, + "consumable_refilled_count_counterfeit_refilled_count": { + "name": "Falsificado reabastecido" + }, + "consumable_refilled_count_genuine_refilled_count": { + "name": "Reabastecido genu\u00edno" + }, + "consumable_warranty_expiration_date": { + "name": "Data de validade" + }, + "copy_adf_images": { + "name": "Total de c\u00f3pias do ADF" + }, + "copy_color_impressions": { + "name": "C\u00f3pias de cores totais" + }, + "copy_flatbed_images": { + "name": "P\u00e1ginas totais de vidro do scanner" + }, + "copy_monochrome_impressions": { + "name": "C\u00f3pias totais em preto e branco" + }, + "copy_total_impressions": { + "name": "C\u00f3pias totais" + }, + "fax_total_impressions": { + "name": "Faxes totais" + }, + "main_hardware_config_device_connectivity_port_type": { + "name": "Tipo de porta" + }, + "printer_color_impressions": { + "name": "P\u00e1ginas coloridas totais impressas" + }, + "printer_duplex_sheets": { + "name": "P\u00e1ginas totais de dupla face impressas" + }, + "printer_jam_events": { + "name": "Total Jams" + }, + "printer_mispick_events": { + "name": "Total Miss Picks" + }, + "printer_monochrome_impressions": { + "name": "P\u00e1ginas totais em preto e branco impressas" + }, + "printer_simplex_sheets": { + "name": "P\u00e1ginas totais de um lado impresso" + }, + "printer_total_impressions": { + "name": "P\u00e1ginas totais impressas" + }, + "scanner_adf_images": { + "name": "P\u00e1ginas digitalizadas totais do ADF" + }, + "scanner_duplex_sheets": { + "name": "P\u00e1ginas totais totais digitalizadas" + }, + "scanner_flatbed_images": { + "name": "P\u00e1ginas totais de vidro do scanner" + }, + "scanner_jam_events": { + "name": "Total Jams" + }, + "scanner_mispick_events": { + "name": "Total Miss Picks" + }, + "scanner_scan_images": { + "name": "P\u00e1ginas digitalizadas totais" + } } }, "options": { + "error": { + "already_configured": "Integra\u00c3\u00a7\u00c3\u00a3o HP Printer ({name}) j\u00c3\u00a1 configurada", + "error_400": "Detalhes do servidor inv\u00c3\u00a1lidos, tente acessar manualmente `http://{IP}//DevMgmt/ProductStatusDyn.xml` (Substituir o marcador de posi\u00c3\u00a7\u00c3\u00a3o por IP ou Hostname), caso esteja acess\u00c3\u00advel, informe o problema com os logs", + "error_404": "API sem suporte" + }, "step": { "hp_printer_additional_settings": { - "title": "Opções para HP Printer.", - "description": "Defina configurações adicionais para a integração HP Printer", "data": { "host": "Host", + "log_level": "N\u00c3\u00advel de registro", "name": "Nome", - "update_interval": "Intervalo de atualização (segundos)", - "log_level": "Nível de registro", - "store_data": "Deve armazenar respostas?" - } + "store_data": "Deve armazenar respostas?", + "update_interval": "Intervalo de atualiza\u00c3\u00a7\u00c3\u00a3o (segundos)" + }, + "description": "Defina configura\u00c3\u00a7\u00c3\u00b5es adicionais para a integra\u00c3\u00a7\u00c3\u00a3o HP Printer", + "title": "Op\u00c3\u00a7\u00c3\u00b5es para HP Printer." + }, + "init": { + "data": { + "host": "Nome do host ou IP", + "port": "N\u00famero da porta", + "ssl": "\u00c9 ssl", + "update_interval": "Intervalo de atualiza\u00e7\u00e3o (segundos)" + }, + "description": "Defina configura\u00e7\u00f5es adicionais para a integra\u00e7\u00e3o da impressora HP", + "title": "Op\u00e7\u00f5es para a impressora HP." } - }, - "error": { - "error_400": "Detalhes do servidor inválidos, tente acessar manualmente `http://{IP}//DevMgmt/ProductStatusDyn.xml` (Substituir o marcador de posição por IP ou Hostname), caso esteja acessível, informe o problema com os logs", - "error_404": "API sem suporte", - "already_configured": "Integração HP Printer ({name}) já configurada" } } } diff --git a/info.md b/info.md index e572680..d0750a5 100644 --- a/info.md +++ b/info.md @@ -6,104 +6,148 @@ Configuration support multiple HP Printer devices through Configuration -> Integ [Changelog](https://github.com/elad-bar/ha-hpprinter/blob/master/CHANGELOG.md) -### How to set it up: +## How to -Look for "HP Printers Integration" and install +### Requirements -#### Requirements +- HP Printer with EWS (Embedded Web Server) support -- HP Printer supporting XML API - to check printer's compatibility to the component try to get to the printer's XML API (replace placeholder with real IP / Hostname): - `http://{IP}//DevMgmt/ProductStatusDyn.xml` +### Installations via HACS [![hacs_badge](https://img.shields.io/badge/HACS-Custom-41BDF5.svg)](https://github.com/hacs/integration) -#### Basic configuration +- In HACS, look for "Aqua Temp" and install and restart +- If integration was not found, please add custom repository `elad-bar/hpprinter` as integration +- In Settings --> Devices & Services - (Lower Right) "Add Integration" -- Configuration should be done via Configuration -> Integrations. -- In case you are already using that integration with YAML Configuration - please remove it -- Integration supports **multiple** devices -- In the setup form, the following details are mandatory: - - Name - Unique - - Host (or IP) -- Upon submitting the form of creating an integration, a request to the printer will take place and will cause failure in case: - - Unsupported API - - Invalid server details - when cannot reach host +### Setup -#### Settings for Monitoring interfaces, devices, tracked devices and update interval +To add integration use Configuration -> Integrations -> Add `HP Printer` +Integration supports **multiple** accounts and devices -_Configuration -> Integrations -> {Integration} -> Options_
+| Fields name | Type | Required | Default | Description | +| ----------- | ------- | -------- | ------- | -------------------------------------------- | +| Host | Textbox | + | - | Defines hostname or IP of the HP Printer EWS | +| Port | Textbox | + | 80 | Defines port of the HP Printer EWS | +| Is SSL | Boolean | + | False | Defines which protocol to use HTTP/S | -``` -Name - Unique -Host (or IP) -Update Interval: Textbox, number of seconds to update entities, default=60 -Log level: Drop-down list, change component's log level (more details below), default=Default -Should store responses?: Check-box, saves XML and JSON files for debugging purpose, default=False -``` +It is also possible to change configuration after setting up using integration configuration. -###### Log Level's drop-down +#### Validation errors -New feature to set the log level for the component without need to set log_level in `customization:` and restart or call manually `logger.set_level` and loose it after restart. +| Errors | +| ------------------------------------------------------ | +| Invalid parameters provided | +| HP Printer Embedded Web Server (EWS) not was not found | -Upon startup or integration's option update, based on the value chosen, the component will make a service call to `logger.set_level` for that component with the desired value, +## Devices -In case `Default` option is chosen, flow will skip calling the service, after changing from any other option to `Default`, it will not take place automatically, only after restart +### Main device -###### Store responses +Device that holds entities related to the integration and relations to other sub devices as described below. -Stores the XML and JSON of each request and final JSON to files, Path in CONFIG_PATH/\*, -Files that will be generated (Prefix to the file is name of the integration): +#### Entities -- ProductUsageDyn.XML - Raw XML from HP Printer of Usage Details -- ProductUsageDyn.json - JSON based on the Raw XML of Usage Details after transformed by the component -- ConsumableConfigDyn.XML - Raw XML from HP Printer of consumable details -- ConsumableConfigDyn.json - JSON based on the Raw XML of consumable details after transformed by the component -- ProductConfigDyn.XML - Raw XML from HP Printer of Config Details -- ProductConfigDyn.json - JSON based on the Raw XML of Config Details after transformed by the component -- Final.json - JSON based on the 2 JSONs above, merged into simpler data structure for the HA to create sensor based on +_Binary Sensor_ -## Components: +- ePrint Registered +- ePrint Status -#### Device status - Binary Sensor +### Printer -``` -State: connected? -``` +Device holds entities of sensors related to number of pages printed and relation to sub devices of consumables -#### Printer details - Sensor +_Sensor_ -``` -State: # of pages printed -Attributes: - Color - # of printed documents using color cartridges - Monochrome - # of printed documents using black cartridges - Jams - # of print jobs jammed - Cancelled - # of print jobs that were cancelled -``` +- Total pages printed +- Total black-and-white pages printed +- Total color pages printed +- Total single-sided pages printed +- Total double-sided pages printed +- Total jams +- Total miss picks -#### Scanner details - Sensor (For AIO only) +### Scanner -``` -State: # of pages scanned -Attributes: - ADF - # of scanned documents from the ADF - Duplex - # of scanned documents from the ADF using duplex mode - Flatbed - # of scanned documents from the flatbed - Jams - # of scanned jammed - Mispick - # of scanned documents failed to take the document from the feeder -``` +Device holds entities of sensors related to number of pages scanned -#### Cartridges details - Sensor (Per cartridge) +_Sensor_ +- Total scanned pages +- Total scanned pages from ADF +- Total double-sided pages scanned +- Total pages from scanner glass +- Total jams +- Total miss picks + +### Copy + +Device holds entities of sensors related to number of pages copied + +_Sensor_ + +- Total copies +- Total copies from ADF +- Total pages from scanner glass +- Total black-and-white copies +- Total color copies + +### Fax + +Device holds entities of sensors related to number of pages faxed + +_Sensor_ + +- Total faxed + +### Scanner + +Device holds entities related to consumable (Ink, Tuner, Printhead) of a printer device + +_Binary Sensor_ + +- Status + +_Sensor_ + +- Station +- Type +- Installation Date +- Level (will not be available for Printhead) +- Expiration Date (will not be available for Printhead) +- Remaining (will not be available for Printhead) +- Counterfeit Refilled +- Genuine Refilled + +## Troubleshooting + +Before opening an issue, please provide logs and diagnostic file data related to the issue. + +### Logs + +For debug log level, please add the following to your config.yaml + +```yaml +logger: + default: warning + logs: + custom_components.hpprinter: debug ``` -State: Remaining level % -Attributes: - Color - Type - Ink / Toner / Print head - Station - Position of the cartridge - Product Number - Serial Number - Manufactured By - Manufactured At - Warranty Expiration Date - Installed At -``` + +Or use the HA capability in device page: + +1. Settings +2. Devices & Services +3. HP Printer +4. 3 dots menu +5. Enable debug logging + +When done and would like to extract the log, repeat steps, in step #5 - Disable debug logging + +### Diagnostic details + +Please attach also diagnostic details of the integration, available in: + +1. Settings +2. Devices & Services +3. HP Printer +4. 3 dots menu +5. Download diagnostics diff --git a/requirements.txt b/requirements.txt index 8095ac8..58e9772 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,3 +8,10 @@ xmltodict~=0.13.0 defusedxml flatten_json + +requests~=2.27 +python-lokalise-api~=1.6 +python-dotenv~=0.20 +googletrans==4.0.0rc1 +translators~= 5.4 +deep-translator~=1.9 diff --git a/tests/generate_translations.py b/tests/generate_translations.py new file mode 100644 index 0000000..8d7427d --- /dev/null +++ b/tests/generate_translations.py @@ -0,0 +1,192 @@ +import asyncio +import json +import logging +import os +from pathlib import Path +import sys + +from flatten_json import flatten, unflatten +import translators as ts + +DEBUG = str(os.environ.get("DEBUG", False)).lower() == str(True).lower() + +log_level = logging.DEBUG if DEBUG else logging.INFO + +root = logging.getLogger() +root.setLevel(log_level) + +logging.getLogger("urllib3").setLevel(logging.WARNING) + +stream_handler = logging.StreamHandler(sys.stdout) +stream_handler.setLevel(log_level) +formatter = logging.Formatter("%(asctime)s %(levelname)s %(name)s %(message)s") +stream_handler.setFormatter(formatter) +root.addHandler(stream_handler) + +_LOGGER = logging.getLogger(__name__) + +SOURCE_LANGUAGE = "en" +TRANSLATION_PROVIDER = "google" +E_PRINT_TERM = "ePrint" +E_PRINT_PLACEHOLDER = "***" +FLAT_SEPARATOR = "." + + +class TranslationGenerator: + def __init__(self): + self._config = self._get_parameters() + self._source_translations = self._get_source_translations() + + self._destinations = { + "en": "en", + "de": "de", + "dk": "da", + "es": "es", + "fr": "fr", + "nb": "no", + "nl": "nl", + "pl": "pl", + "pt-BR": "pt" + } + + async def initialize(self): + values = flatten(self._source_translations, FLAT_SEPARATOR) + value_keys = list(values.keys()) + last_key = value_keys[len(value_keys) - 1] + + _LOGGER.info( + f"Process will translate {len(values)} sentences " + f"to {len(list(self._destinations.keys()))} languages" + ) + + for lang in self._destinations: + original_values = values.copy() + translated_data = self._get_translations(lang) + translated_values = flatten(translated_data, FLAT_SEPARATOR) + + provider_lang = self._destinations[lang] + lang_cache = {} + + lang_title = provider_lang.upper() + + for key in original_values: + english_value = original_values[key] + + if not isinstance(english_value, str): + continue + + if key in translated_values: + translated_value = translated_values[key] + + _LOGGER.debug( + f"Skip translation to '{lang_title}', " + f"translation of '{english_value}' already exists - '{translated_value}'" + ) + + continue + + if english_value in lang_cache: + translated_value = lang_cache[english_value] + + _LOGGER.debug( + f"Skip translation to '{lang_title}', " + f"translation of '{english_value}' available in cache - {translated_value}" + ) + + elif lang == SOURCE_LANGUAGE: + translated_value = english_value + + _LOGGER.debug( + f"Skip translation to '{lang_title}', " + f"source and destination languages are the same - {translated_value}" + ) + + else: + has_e_print = E_PRINT_TERM in english_value + original_english_value = english_value + + if has_e_print: + english_value = english_value.replace(E_PRINT_TERM, E_PRINT_PLACEHOLDER) + + sleep_seconds = 10 if last_key == key else 0 + + translated_value = ts.translate_text( + english_value, + translator=TRANSLATION_PROVIDER, + to_language=provider_lang, + sleep_seconds=sleep_seconds + ) + + if has_e_print: + translated_value = translated_value.replace(E_PRINT_PLACEHOLDER, E_PRINT_TERM) + + lang_cache[english_value] = translated_value + + _LOGGER.debug(f"Translating '{original_english_value}' to {lang_title}: {translated_value}") + + translated_values[key] = translated_value + + translated_data = unflatten(translated_values, FLAT_SEPARATOR) + + self._save_translations(lang, translated_data) + + @staticmethod + def _get_parameters() -> dict: + config_file = "data_points.json" + current_path = Path(__file__) + parent_directory = current_path.parents[1] + file_path = os.path.join(parent_directory, "custom_components", "hpprinter", "parameters", config_file) + + with open(file_path) as f: + data = json.load(f) + + return data + + @staticmethod + def _get_source_translations() -> dict: + current_path = Path(__file__) + parent_directory = current_path.parents[1] + file_path = os.path.join(parent_directory, "custom_components", "hpprinter", "strings.json") + + with open(file_path) as f: + data = json.load(f) + + return data + + @staticmethod + def _get_translations(lang: str): + current_path = Path(__file__) + parent_directory = current_path.parents[1] + file_path = os.path.join(parent_directory, "custom_components", "hpprinter", "translations", f"{lang}.json") + + with open(file_path) as file: + data = json.load(file) + + return data + + @staticmethod + def _save_translations(lang: str, data: dict): + current_path = Path(__file__) + parent_directory = current_path.parents[1] + file_path = os.path.join(parent_directory, "custom_components", "hpprinter", "translations", f"{lang}.json") + + with open(file_path, "w+") as file: + file.write(json.dumps(data, indent=4)) + + _LOGGER.info(f"Translation for {lang.upper()} stored") + + +if __name__ == "__main__": + loop = asyncio.new_event_loop() + + instance = TranslationGenerator() + + try: + loop.create_task(instance.initialize()) + loop.run_forever() + + except KeyboardInterrupt: + _LOGGER.info("Aborted") + + except Exception as rex: + _LOGGER.error(f"Error: {rex}") From 3d1ba25f132494d0f28bf0963bc897e1de9adf71 Mon Sep 17 00:00:00 2001 From: Elad Bar Date: Sun, 18 Feb 2024 16:25:52 +0200 Subject: [PATCH 05/25] more documentation --- README.md | 46 +++++++++++++++++++++++ info.md | 46 +++++++++++++++++++++++ requirements.txt | 4 +- {tests => utils}/api_test.py | 0 {tests => utils}/generate_translations.py | 24 ++++++------ 5 files changed, 108 insertions(+), 12 deletions(-) rename {tests => utils}/api_test.py (100%) rename {tests => utils}/generate_translations.py (95%) diff --git a/README.md b/README.md index d0750a5..ca308f7 100644 --- a/README.md +++ b/README.md @@ -151,3 +151,49 @@ Please attach also diagnostic details of the integration, available in: 3. HP Printer 4. 3 dots menu 5. Download diagnostics + +Diagnostic file contains 3 section related to data extracted from the device: + +- data.debug.rawData - Raw data extracted from all endpoints of the device, from that source you can extract ideas for additional entities to suggest +- data.debug.devicesConfig - Configuration of mapping to convert data from HP Printer EWS to HA devices and entities, that will be the section that new entities will be added +- data.debug.devicesData - Data extracted for HA entities, just relevant data points, according to mapped objects available in section `data.debug.devicesConfig` + +## Translations + +Integration translated from English to: + +- German +- Danish +- Spanish +- French +- Dutch +- Norwegian +- Polish +- Portuguese + +Translation is being auto-generated from Google Translate using `utils/generate_translations.py` script, + +```json +{ + "en": "en", + "de": "de", + "dk": "da", + "es": "es", + "fr": "fr", + "nb": "no", + "nl": "nl", + "pl": "pl", + "pt-BR": "pt" +} +``` + +If you would like to add new translation language, please add to the `DESTINATION_LANGUAGES` constant the relevant language, +format is: + +```json +{ + "HA language": "Google Translate language" +} +``` + +Script is translating only, new missing values, it will not override translated values. diff --git a/info.md b/info.md index d0750a5..ca308f7 100644 --- a/info.md +++ b/info.md @@ -151,3 +151,49 @@ Please attach also diagnostic details of the integration, available in: 3. HP Printer 4. 3 dots menu 5. Download diagnostics + +Diagnostic file contains 3 section related to data extracted from the device: + +- data.debug.rawData - Raw data extracted from all endpoints of the device, from that source you can extract ideas for additional entities to suggest +- data.debug.devicesConfig - Configuration of mapping to convert data from HP Printer EWS to HA devices and entities, that will be the section that new entities will be added +- data.debug.devicesData - Data extracted for HA entities, just relevant data points, according to mapped objects available in section `data.debug.devicesConfig` + +## Translations + +Integration translated from English to: + +- German +- Danish +- Spanish +- French +- Dutch +- Norwegian +- Polish +- Portuguese + +Translation is being auto-generated from Google Translate using `utils/generate_translations.py` script, + +```json +{ + "en": "en", + "de": "de", + "dk": "da", + "es": "es", + "fr": "fr", + "nb": "no", + "nl": "nl", + "pl": "pl", + "pt-BR": "pt" +} +``` + +If you would like to add new translation language, please add to the `DESTINATION_LANGUAGES` constant the relevant language, +format is: + +```json +{ + "HA language": "Google Translate language" +} +``` + +Script is translating only, new missing values, it will not override translated values. diff --git a/requirements.txt b/requirements.txt index 58e9772..4c1b808 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,7 +6,7 @@ voluptuous~=0.13.1 aiohttp~=3.9.1 xmltodict~=0.13.0 -defusedxml +defusedxml~=0.7.1 flatten_json requests~=2.27 @@ -15,3 +15,5 @@ python-dotenv~=0.20 googletrans==4.0.0rc1 translators~= 5.4 deep-translator~=1.9 + +python-slugify~=4.0.1 diff --git a/tests/api_test.py b/utils/api_test.py similarity index 100% rename from tests/api_test.py rename to utils/api_test.py diff --git a/tests/generate_translations.py b/utils/generate_translations.py similarity index 95% rename from tests/generate_translations.py rename to utils/generate_translations.py index 8d7427d..ce51789 100644 --- a/tests/generate_translations.py +++ b/utils/generate_translations.py @@ -26,6 +26,18 @@ _LOGGER = logging.getLogger(__name__) SOURCE_LANGUAGE = "en" +DESTINATION_LANGUAGES = { + "en": "en", + "de": "de", + "dk": "da", + "es": "es", + "fr": "fr", + "nb": "no", + "nl": "nl", + "pl": "pl", + "pt-BR": "pt" +} + TRANSLATION_PROVIDER = "google" E_PRINT_TERM = "ePrint" E_PRINT_PLACEHOLDER = "***" @@ -37,17 +49,7 @@ def __init__(self): self._config = self._get_parameters() self._source_translations = self._get_source_translations() - self._destinations = { - "en": "en", - "de": "de", - "dk": "da", - "es": "es", - "fr": "fr", - "nb": "no", - "nl": "nl", - "pl": "pl", - "pt-BR": "pt" - } + self._destinations = DESTINATION_LANGUAGES async def initialize(self): values = flatten(self._source_translations, FLAT_SEPARATOR) From 010430424a41910841f827e6ed8c31c72d8aae34 Mon Sep 17 00:00:00 2001 From: Elad Bar Date: Sun, 18 Feb 2024 19:52:12 +0200 Subject: [PATCH 06/25] more translations --- custom_components/hpprinter/binary_sensor.py | 4 +- .../hpprinter/common/entity_descriptions.py | 2 +- .../hpprinter/managers/ha_config_manager.py | 46 ++++++++++------- .../hpprinter/managers/ha_coordinator.py | 4 +- .../hpprinter/parameters/data_points.json | 51 +++++-------------- custom_components/hpprinter/strings.json | 6 +++ .../hpprinter/translations/de.json | 6 +++ .../hpprinter/translations/dk.json | 6 +++ .../hpprinter/translations/en.json | 6 +++ .../hpprinter/translations/es.json | 6 +++ .../hpprinter/translations/fr.json | 6 +++ .../hpprinter/translations/nb.json | 6 +++ .../hpprinter/translations/nl.json | 6 +++ .../hpprinter/translations/pl.json | 6 +++ .../hpprinter/translations/pt-BR.json | 6 +++ 15 files changed, 106 insertions(+), 61 deletions(-) diff --git a/custom_components/hpprinter/binary_sensor.py b/custom_components/hpprinter/binary_sensor.py index 5201c1f..0063086 100644 --- a/custom_components/hpprinter/binary_sensor.py +++ b/custom_components/hpprinter/binary_sensor.py @@ -36,13 +36,13 @@ def __init__( super().__init__(entity_description, coordinator, device_key) self._attr_device_class = entity_description.device_class - self._entity_on_value = entity_description.on_value + self._entity_on_values = entity_description.on_values def _handle_coordinator_update(self) -> None: """Fetch new state parameters for the sensor.""" state = self.get_value() - is_on = str(state).lower() == str(self._entity_on_value).lower() + is_on = str(state).lower() in self._entity_on_values self._attr_is_on = is_on self._attr_extra_state_attributes = {"state": state} diff --git a/custom_components/hpprinter/common/entity_descriptions.py b/custom_components/hpprinter/common/entity_descriptions.py index 10aaf84..01b9fdb 100644 --- a/custom_components/hpprinter/common/entity_descriptions.py +++ b/custom_components/hpprinter/common/entity_descriptions.py @@ -18,7 +18,7 @@ class IntegrationBinarySensorEntityDescription( BinarySensorEntityDescription, IntegrationEntityDescription ): platform: Platform | None = Platform.BINARY_SENSOR - on_value: str | bool | None = None + on_values: list[str] | None = None @dataclass(frozen=True, kw_only=True) diff --git a/custom_components/hpprinter/managers/ha_config_manager.py b/custom_components/hpprinter/managers/ha_config_manager.py index d714ff1..79a5610 100644 --- a/custom_components/hpprinter/managers/ha_config_manager.py +++ b/custom_components/hpprinter/managers/ha_config_manager.py @@ -101,7 +101,7 @@ def config_data(self) -> ConfigData: @property def update_interval(self) -> int: - interval = self._data.get(CONF_UPDATE_INTERVAL, 60) + interval = self._data.get(CONF_UPDATE_INTERVAL, 5) return interval @@ -158,32 +158,40 @@ async def remove(self, entry_id: str): def get_entity_name( self, entity_description: IntegrationEntityDescription, device_info: DeviceInfo ) -> str: - entity_key = entity_description.key + translation_key = entity_description.translation_key platform = entity_description.platform device_name = device_info.get("name") - translation_key = f"component.{DOMAIN}.entity.{platform}.{entity_key}.name" + translation_key = f"component.{DOMAIN}.entity.{platform}.{translation_key}.name" translated_name = self._translations.get( translation_key, entity_description.name ) - _LOGGER.debug( - f"Translations requested, Key: {translation_key}, " - f"Entity: {entity_description.name}, Value: {translated_name}" - ) + if translated_name is None or translated_name == "": + entity_name = f"{device_name} {translated_name}" - entity_name = ( - f"{device_name} {entity_description.name}" - if translated_name is None or translated_name == "" - else f"{device_name} {translated_name}" - ) + _LOGGER.debug( + f"Translations requested, " + f"Key: {translation_key}, " + f"Entity: {entity_description.name}, " + f"Value: {translated_name}" + ) + + else: + entity_name = f"{device_name} {entity_description.name}" + + _LOGGER.warning( + f"Translations not found, " + f"Key: {translation_key}, " + f"Entity: {entity_description.name}" + ) return entity_name async def set_update_interval(self, value: int): - _LOGGER.debug(f"Set update interval in seconds to to {value}") + _LOGGER.debug(f"Set update interval in minutes to to {value}") self._data[CONF_UPDATE_INTERVAL] = value @@ -276,21 +284,23 @@ def _load_entity_descriptions(self): if "platform" in property_data: property_platform = property_data.get("platform") - description = property_data.get("description") exclude = property_data.get("exclude") device_class = property_data.get("device_class") icon = property_data.get("icon") translation_key = slugify(f"{device_type}_{property_key}") if property_platform == str(Platform.BINARY_SENSOR): - on_value = property_data.get("on_value") + on_values = [ + value.lower() + for value in property_data.get("on_values", []) + ] entity_description = IntegrationBinarySensorEntityDescription( key=property_key, - name=description, + name=property_key, device_type=device_type, exclude=exclude, - on_value=on_value, + on_values=on_values, device_class=device_class, icon=icon, translation_key=translation_key, @@ -303,7 +313,7 @@ def _load_entity_descriptions(self): entity_description = IntegrationSensorEntityDescription( key=property_key, - name=description, + name=property_key, device_type=device_type, exclude=exclude, native_unit_of_measurement=unit_of_measurement, diff --git a/custom_components/hpprinter/managers/ha_coordinator.py b/custom_components/hpprinter/managers/ha_coordinator.py index f35feb8..e9c5686 100644 --- a/custom_components/hpprinter/managers/ha_coordinator.py +++ b/custom_components/hpprinter/managers/ha_coordinator.py @@ -1,3 +1,4 @@ +from datetime import timedelta import logging from homeassistant.core import Event @@ -13,7 +14,6 @@ DOMAIN, SIGNAL_HA_DEVICE_CREATED, SIGNAL_HA_DEVICE_DISCOVERED, - UPDATE_API_INTERVAL, ) from .ha_config_manager import HAConfigManager from .rest_api import RestAPIv2 @@ -34,7 +34,7 @@ def __init__( hass, _LOGGER, name=config_manager.entry_title, - update_interval=UPDATE_API_INTERVAL, + update_interval=timedelta(minutes=config_manager.update_interval), update_method=self._async_update_data, ) diff --git a/custom_components/hpprinter/parameters/data_points.json b/custom_components/hpprinter/parameters/data_points.json index 0332723..0e8670e 100644 --- a/custom_components/hpprinter/parameters/data_points.json +++ b/custom_components/hpprinter/parameters/data_points.json @@ -24,7 +24,9 @@ "path": "Manufacturer.Name" }, "manufactured_at": { - "path": "Manufacturer.Date" + "path": "Manufacturer.Date", + "platform": "sensor", + "device_class": "date" } } }, @@ -40,9 +42,8 @@ }, "consumable_life_state_consumable_state": { "path": "ConsumableLifeState.ConsumableState", - "description": "Status", "platform": "binary_sensor", - "on_value": "newGenuineHP", + "on_values": ["ok", "newGenuineHP"], "device_class": "plug" }, "consumable_life_state_brand": { @@ -50,18 +51,15 @@ }, "consumable_station": { "path": "ConsumableStation", - "description": "Station", "platform": "sensor" }, "consumable_type_enum": { "path": "ConsumableTypeEnum", - "description": "Type", "platform": "sensor", "device_class": "enum" }, "installation_date": { "path": "Installation.Date", - "description": "Installation Date", "platform": "sensor", "device_class": "date" }, @@ -70,7 +68,6 @@ }, "consumable_percentage_level_remaining": { "path": "ConsumablePercentageLevelRemaining", - "description": "Level", "platform": "sensor", "unit_of_measurement": "%", "exclude": { @@ -84,7 +81,12 @@ "path": "Manufacturer.Name" }, "manufactured_at": { - "path": "Manufacturer.Date" + "path": "Manufacturer.Date", + "platform": "sensor", + "device_class": "date", + "exclude": { + "consumable_type_enum": "printhead" + } }, "serial_number": { "path": "SerialNumber" @@ -94,7 +96,6 @@ }, "warranty_expiration_date": { "path": "Warranty.ExpirationDate", - "description": "Expiration Date", "platform": "sensor", "device_class": "date", "exclude": { @@ -121,7 +122,6 @@ }, "estimated_pages_remaining": { "path": "EstimatedPagesRemaining", - "description": "Remaining", "platform": "sensor", "unit_of_measurement": "pages", "exclude": { @@ -139,14 +139,12 @@ }, "refilled_count_counterfeit_refilled_count": { "path": "RefilledCount.CounterfeitRefilledCount.#text", - "description": "Counterfeit Refilled", "platform": "sensor", "unit_of_measurement": "refills", "icon": "mdi:format-color-fill" }, "refilled_count_genuine_refilled_count": { "path": "RefilledCount.GenuineRefilledCount", - "description": "Genuine Refilled", "platform": "sensor", "unit_of_measurement": "refills", "icon": "mdi:format-color-fill" @@ -161,49 +159,42 @@ "properties": { "total_impressions": { "path": "TotalImpressions.#text", - "description": "Total pages printed", "platform": "sensor", "unit_of_measurement": "pages", "icon": "mdi:file-document-check" }, "monochrome_impressions": { "path": "MonochromeImpressions", - "description": "Total black-and-white pages printed", "platform": "sensor", "unit_of_measurement": "pages", "icon": "mdi:file-document-check" }, "color_impressions": { "path": "ColorImpressions", - "description": "Total color pages printed", "platform": "sensor", "unit_of_measurement": "pages", "icon": "mdi:file-document-check" }, "simplex_sheets": { "path": "SimplexSheets", - "description": "Total single-sided pages printed", "platform": "sensor", "unit_of_measurement": "pages", "icon": "mdi:file-document-check" }, "duplex_sheets": { "path": "DuplexSheets.#text", - "description": "Total double-sided pages printed", "platform": "sensor", "unit_of_measurement": "pages", "icon": "mdi:file-document-multiple" }, "jam_events": { "path": "JamEvents.#text", - "description": "Total jams", "platform": "sensor", "unit_of_measurement": "pages", "icon": "mdi:file-document-remove" }, "mispick_events": { "path": "MispickEvents", - "description": "Total miss picks", "platform": "sensor", "unit_of_measurement": "pages", "icon": "mdi:file-document-minus" @@ -218,42 +209,36 @@ "properties": { "scan_images": { "path": "ScanImages.#text", - "description": "Total scanned pages", "platform": "sensor", "unit_of_measurement": "pages", "icon": "mdi:credit-card-scan" }, "adf_images": { "path": "AdfImages.#text", - "description": "Total scanned pages from ADF", "platform": "sensor", "unit_of_measurement": "pages", "icon": "mdi:credit-card-scan" }, "duplex_sheets": { "path": "DuplexSheets.#text", - "description": "Total double-sided pages scanned", "platform": "sensor", "unit_of_measurement": "pages", "icon": "mdi:credit-card-scan" }, "flatbed_images": { "path": "FlatbedImages", - "description": "Total pages from scanner glass", "platform": "sensor", "unit_of_measurement": "pages", "icon": "mdi:credit-card-scan" }, "jam_events": { "path": "JamEvents", - "description": "Total jams", "platform": "sensor", "unit_of_measurement": "pages", "icon": "mdi:credit-card-scan" }, "mispick_events": { "path": "MispickEvents", - "description": "Total miss picks", "platform": "sensor", "unit_of_measurement": "pages", "icon": "mdi:credit-card-scan" @@ -268,35 +253,30 @@ "properties": { "total_impressions": { "path": "TotalImpressions.#text", - "description": "Total copies", "platform": "sensor", "unit_of_measurement": "pages", "icon": "mdi:content-copy" }, "adf_images": { "path": "AdfImages", - "description": "Total copies from ADF", "platform": "sensor", "unit_of_measurement": "pages", "icon": "mdi:content-copy" }, "flatbed_images": { "path": "FlatbedImages", - "description": "Total pages from scanner glass", "platform": "sensor", "unit_of_measurement": "pages", "icon": "mdi:content-copy" }, "monochrome_impressions": { "path": "MonochromeImpressions", - "description": "Total black-and-white copies", "platform": "sensor", "unit_of_measurement": "pages", "icon": "mdi:content-copy" }, "color_impressions": { "path": "ColorImpressions", - "description": "Total color copies", "platform": "sensor", "unit_of_measurement": "pages", "icon": "mdi:content-copy" @@ -311,7 +291,6 @@ "properties": { "total_impressions": { "path": "TotalImpressions.#text", - "description": "Total faxes", "platform": "sensor", "unit_of_measurement": "pages", "icon": "mdi:email-fast" @@ -331,14 +310,12 @@ }, "hardware_config_device_connectivity_port_type": { "path": "HardwareConfig.DeviceConnectivityPortType", - "description": "Port Type", "platform": "sensor" }, "hardware_config_is_connected": { "path": "HardwareConfig.IsConnected", - "description": "Connected", "platform": "binary_sensor", - "on_value": "true", + "on_values": ["true"], "device_class": "connectivity" } } @@ -354,17 +331,15 @@ }, "registration_state": { "path": "RegistrationState", - "description": "ePrint Registered", "platform": "binary_sensor", - "on_value": "registered", + "on_values": ["registered"], "device_class": "plug", "icon": "mdi:cloud-print" }, "cloud_services_switch_status": { "path": "CloudServicesSwitch.Status", - "description": "ePrint Status", "platform": "binary_sensor", - "on_value": "enabled", + "on_values": ["enabled"], "device_class": "connectivity" } } diff --git a/custom_components/hpprinter/strings.json b/custom_components/hpprinter/strings.json index b6a57fd..93ef8f0 100644 --- a/custom_components/hpprinter/strings.json +++ b/custom_components/hpprinter/strings.json @@ -50,6 +50,12 @@ } }, "sensor": { + "main_manufactured_at": { + "name": "Manufactured Date" + }, + "consumable_manufactured_at": { + "name": "Manufactured Date" + }, "consumable_consumable_station": { "name": "Station" }, diff --git a/custom_components/hpprinter/translations/de.json b/custom_components/hpprinter/translations/de.json index 57d6488..c2d629a 100644 --- a/custom_components/hpprinter/translations/de.json +++ b/custom_components/hpprinter/translations/de.json @@ -47,6 +47,9 @@ "consumable_installation_date": { "name": "Installationsdatum" }, + "consumable_manufactured_at": { + "name": "Herstellungsdatum" + }, "consumable_refilled_count_counterfeit_refilled_count": { "name": "Gef\u00e4lscht nachgedacht" }, @@ -77,6 +80,9 @@ "main_hardware_config_device_connectivity_port_type": { "name": "Port -Typ" }, + "main_manufactured_at": { + "name": "Herstellungsdatum" + }, "printer_color_impressions": { "name": "Gesamtfarbseiten gedruckt" }, diff --git a/custom_components/hpprinter/translations/dk.json b/custom_components/hpprinter/translations/dk.json index 07b4e89..bf015ba 100644 --- a/custom_components/hpprinter/translations/dk.json +++ b/custom_components/hpprinter/translations/dk.json @@ -50,6 +50,9 @@ "consumable_installation_date": { "name": "Installationsdato" }, + "consumable_manufactured_at": { + "name": "Fremstillet dato" + }, "consumable_refilled_count_counterfeit_refilled_count": { "name": "Forfalsket genopfyldt" }, @@ -80,6 +83,9 @@ "main_hardware_config_device_connectivity_port_type": { "name": "Porttype" }, + "main_manufactured_at": { + "name": "Fremstillet dato" + }, "printer_color_impressions": { "name": "Samlede farvesider trykt" }, diff --git a/custom_components/hpprinter/translations/en.json b/custom_components/hpprinter/translations/en.json index 270db46..9e29f27 100644 --- a/custom_components/hpprinter/translations/en.json +++ b/custom_components/hpprinter/translations/en.json @@ -50,6 +50,9 @@ "consumable_installation_date": { "name": "Installation Date" }, + "consumable_manufactured_at": { + "name": "Manufactured Date" + }, "consumable_refilled_count_counterfeit_refilled_count": { "name": "Counterfeit Refilled" }, @@ -80,6 +83,9 @@ "main_hardware_config_device_connectivity_port_type": { "name": "Port Type" }, + "main_manufactured_at": { + "name": "Manufactured Date" + }, "printer_color_impressions": { "name": "Total color pages printed" }, diff --git a/custom_components/hpprinter/translations/es.json b/custom_components/hpprinter/translations/es.json index 30316f7..d8a7bd7 100644 --- a/custom_components/hpprinter/translations/es.json +++ b/custom_components/hpprinter/translations/es.json @@ -50,6 +50,9 @@ "consumable_installation_date": { "name": "Fecha de instalaci\u00f3n" }, + "consumable_manufactured_at": { + "name": "Fecha de manufacturacion" + }, "consumable_refilled_count_counterfeit_refilled_count": { "name": "Falsificado" }, @@ -80,6 +83,9 @@ "main_hardware_config_device_connectivity_port_type": { "name": "Tipo de puerto" }, + "main_manufactured_at": { + "name": "Fecha de manufacturacion" + }, "printer_color_impressions": { "name": "P\u00e1ginas de color totales impresas" }, diff --git a/custom_components/hpprinter/translations/fr.json b/custom_components/hpprinter/translations/fr.json index 07704ab..93a3472 100644 --- a/custom_components/hpprinter/translations/fr.json +++ b/custom_components/hpprinter/translations/fr.json @@ -47,6 +47,9 @@ "consumable_installation_date": { "name": "Date d'installation" }, + "consumable_manufactured_at": { + "name": "Date de fabrication" + }, "consumable_refilled_count_counterfeit_refilled_count": { "name": "Contrefait rempli" }, @@ -77,6 +80,9 @@ "main_hardware_config_device_connectivity_port_type": { "name": "Type de port" }, + "main_manufactured_at": { + "name": "Date de fabrication" + }, "printer_color_impressions": { "name": "Pages de couleurs totales imprim\u00e9es" }, diff --git a/custom_components/hpprinter/translations/nb.json b/custom_components/hpprinter/translations/nb.json index 112c9bf..cd751d8 100644 --- a/custom_components/hpprinter/translations/nb.json +++ b/custom_components/hpprinter/translations/nb.json @@ -50,6 +50,9 @@ "consumable_installation_date": { "name": "Installasjonsdato" }, + "consumable_manufactured_at": { + "name": "Produsert dato" + }, "consumable_refilled_count_counterfeit_refilled_count": { "name": "Forfalsket p\u00e5fyllt" }, @@ -80,6 +83,9 @@ "main_hardware_config_device_connectivity_port_type": { "name": "Porttype" }, + "main_manufactured_at": { + "name": "Produsert dato" + }, "printer_color_impressions": { "name": "Total fargesider trykt" }, diff --git a/custom_components/hpprinter/translations/nl.json b/custom_components/hpprinter/translations/nl.json index 4e42637..707006b 100644 --- a/custom_components/hpprinter/translations/nl.json +++ b/custom_components/hpprinter/translations/nl.json @@ -48,6 +48,9 @@ "consumable_installation_date": { "name": "Installatie datum" }, + "consumable_manufactured_at": { + "name": "Gefabriceerde datum" + }, "consumable_refilled_count_counterfeit_refilled_count": { "name": "Vervalste bijgevuld" }, @@ -78,6 +81,9 @@ "main_hardware_config_device_connectivity_port_type": { "name": "Poorttype" }, + "main_manufactured_at": { + "name": "Gefabriceerde datum" + }, "printer_color_impressions": { "name": "Totale kleurpagina's afgedrukt" }, diff --git a/custom_components/hpprinter/translations/pl.json b/custom_components/hpprinter/translations/pl.json index 799c2eb..13ae3f8 100644 --- a/custom_components/hpprinter/translations/pl.json +++ b/custom_components/hpprinter/translations/pl.json @@ -50,6 +50,9 @@ "consumable_installation_date": { "name": "Data instalacji" }, + "consumable_manufactured_at": { + "name": "Data produkcji" + }, "consumable_refilled_count_counterfeit_refilled_count": { "name": "FRAKTHICE FOLOKLED" }, @@ -80,6 +83,9 @@ "main_hardware_config_device_connectivity_port_type": { "name": "Typ portu" }, + "main_manufactured_at": { + "name": "Data produkcji" + }, "printer_color_impressions": { "name": "Wydrukowano ca\u0142kowit\u0105 strony kolor\u00f3w" }, diff --git a/custom_components/hpprinter/translations/pt-BR.json b/custom_components/hpprinter/translations/pt-BR.json index f10b54c..d76a1a6 100644 --- a/custom_components/hpprinter/translations/pt-BR.json +++ b/custom_components/hpprinter/translations/pt-BR.json @@ -50,6 +50,9 @@ "consumable_installation_date": { "name": "Data de instala\u00e7\u00e3o" }, + "consumable_manufactured_at": { + "name": "Data de fabrica\u00e7\u00e3o" + }, "consumable_refilled_count_counterfeit_refilled_count": { "name": "Falsificado reabastecido" }, @@ -80,6 +83,9 @@ "main_hardware_config_device_connectivity_port_type": { "name": "Tipo de porta" }, + "main_manufactured_at": { + "name": "Data de fabrica\u00e7\u00e3o" + }, "printer_color_impressions": { "name": "P\u00e1ginas coloridas totais impressas" }, From a74d9216d6c65c9575ce8f0e60658af0e6f19422 Mon Sep 17 00:00:00 2001 From: Elad Bar Date: Mon, 19 Feb 2024 07:28:47 +0200 Subject: [PATCH 07/25] add translations --- README.md | 2 +- custom_components/hpprinter/binary_sensor.py | 4 ++-- custom_components/hpprinter/common/consts.py | 5 +++++ custom_components/hpprinter/managers/ha_config_manager.py | 6 ++++-- custom_components/hpprinter/managers/ha_coordinator.py | 3 +-- custom_components/hpprinter/parameters/data_points.json | 3 ++- custom_components/hpprinter/sensor.py | 7 ++++--- custom_components/hpprinter/strings.json | 8 +++++++- custom_components/hpprinter/translations/de.json | 8 +++++++- custom_components/hpprinter/translations/dk.json | 8 +++++++- custom_components/hpprinter/translations/en.json | 8 +++++++- custom_components/hpprinter/translations/es.json | 8 +++++++- custom_components/hpprinter/translations/fr.json | 8 +++++++- custom_components/hpprinter/translations/nb.json | 8 +++++++- custom_components/hpprinter/translations/nl.json | 8 +++++++- custom_components/hpprinter/translations/pl.json | 8 +++++++- custom_components/hpprinter/translations/pt-BR.json | 8 +++++++- info.md | 2 +- 18 files changed, 90 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index ca308f7..837840a 100644 --- a/README.md +++ b/README.md @@ -100,7 +100,7 @@ _Sensor_ ### Scanner -Device holds entities related to consumable (Ink, Tuner, Printhead) of a printer device +Device holds entities related to consumable (Ink, Toner, Printhead) of a printer device _Binary Sensor_ diff --git a/custom_components/hpprinter/binary_sensor.py b/custom_components/hpprinter/binary_sensor.py index 0063086..11c696e 100644 --- a/custom_components/hpprinter/binary_sensor.py +++ b/custom_components/hpprinter/binary_sensor.py @@ -2,7 +2,7 @@ from homeassistant.components.binary_sensor import BinarySensorEntity from homeassistant.config_entries import ConfigEntry -from homeassistant.const import Platform +from homeassistant.const import ATTR_STATE, Platform from homeassistant.core import HomeAssistant from .common.base_entity import BaseEntity, async_setup_base_entry @@ -45,6 +45,6 @@ def _handle_coordinator_update(self) -> None: is_on = str(state).lower() in self._entity_on_values self._attr_is_on = is_on - self._attr_extra_state_attributes = {"state": state} + self._attr_extra_state_attributes = {ATTR_STATE: state} self.async_write_ha_state() diff --git a/custom_components/hpprinter/common/consts.py b/custom_components/hpprinter/common/consts.py index 6417239..d9c31d8 100644 --- a/custom_components/hpprinter/common/consts.py +++ b/custom_components/hpprinter/common/consts.py @@ -41,3 +41,8 @@ DEFAULT_PORT = 80 DATA_KEYS = [CONF_HOST, CONF_PORT, CONF_SSL] + +UNIT_OF_MEASUREMENT_PAGES = "pages" +UNIT_OF_MEASUREMENT_REFILLS = "refills" + +NUMERIC_UNITS_OF_MEASUREMENT = [UNIT_OF_MEASUREMENT_PAGES, UNIT_OF_MEASUREMENT_REFILLS] diff --git a/custom_components/hpprinter/managers/ha_config_manager.py b/custom_components/hpprinter/managers/ha_config_manager.py index 79a5610..70aa8a8 100644 --- a/custom_components/hpprinter/managers/ha_config_manager.py +++ b/custom_components/hpprinter/managers/ha_config_manager.py @@ -1,3 +1,4 @@ +from datetime import timedelta import json import logging import os @@ -100,10 +101,11 @@ def config_data(self) -> ConfigData: return config_data @property - def update_interval(self) -> int: + def update_interval(self) -> timedelta: interval = self._data.get(CONF_UPDATE_INTERVAL, 5) + result = timedelta(minutes=interval) - return interval + return result @property def endpoints(self) -> list[str] | None: diff --git a/custom_components/hpprinter/managers/ha_coordinator.py b/custom_components/hpprinter/managers/ha_coordinator.py index e9c5686..0fedc6d 100644 --- a/custom_components/hpprinter/managers/ha_coordinator.py +++ b/custom_components/hpprinter/managers/ha_coordinator.py @@ -1,4 +1,3 @@ -from datetime import timedelta import logging from homeassistant.core import Event @@ -34,7 +33,7 @@ def __init__( hass, _LOGGER, name=config_manager.entry_title, - update_interval=timedelta(minutes=config_manager.update_interval), + update_interval=config_manager.update_interval, update_method=self._async_update_data, ) diff --git a/custom_components/hpprinter/parameters/data_points.json b/custom_components/hpprinter/parameters/data_points.json index 0e8670e..ad65df7 100644 --- a/custom_components/hpprinter/parameters/data_points.json +++ b/custom_components/hpprinter/parameters/data_points.json @@ -56,7 +56,8 @@ "consumable_type_enum": { "path": "ConsumableTypeEnum", "platform": "sensor", - "device_class": "enum" + "device_class": "enum", + "options": ["ink", "inkCartridge", "printhead", "toner"] }, "installation_date": { "path": "Installation.Date", diff --git a/custom_components/hpprinter/sensor.py b/custom_components/hpprinter/sensor.py index 1fb877d..8e6d4a1 100644 --- a/custom_components/hpprinter/sensor.py +++ b/custom_components/hpprinter/sensor.py @@ -3,10 +3,11 @@ from homeassistant.components.sensor import SensorDeviceClass, SensorEntity from homeassistant.config_entries import ConfigEntry -from homeassistant.const import Platform +from homeassistant.const import PERCENTAGE, Platform from homeassistant.core import HomeAssistant from .common.base_entity import BaseEntity, async_setup_base_entry +from .common.consts import NUMERIC_UNITS_OF_MEASUREMENT from .common.entity_descriptions import IntegrationSensorEntityDescription from .managers.ha_coordinator import HACoordinator @@ -45,10 +46,10 @@ def _handle_coordinator_update(self) -> None: """Fetch new state parameters for the sensor.""" state = self.get_value() - if self.native_unit_of_measurement in ["%"]: + if self.native_unit_of_measurement in [PERCENTAGE]: state = float(state) - elif self.native_unit_of_measurement in ["pages", "refill"]: + elif self.native_unit_of_measurement in NUMERIC_UNITS_OF_MEASUREMENT: state = int(state) if self.device_class == SensorDeviceClass.DATE: diff --git a/custom_components/hpprinter/strings.json b/custom_components/hpprinter/strings.json index 93ef8f0..8a99e13 100644 --- a/custom_components/hpprinter/strings.json +++ b/custom_components/hpprinter/strings.json @@ -60,7 +60,13 @@ "name": "Station" }, "consumable_consumable_type_enum": { - "name": "Type" + "name": "Type", + "state": { + "ink": "Ink", + "inkCartridge": "Ink", + "toner": "Toner", + "printhead": "Printhead" + } }, "consumable_installation_date": { "name": "Installation Date" diff --git a/custom_components/hpprinter/translations/de.json b/custom_components/hpprinter/translations/de.json index c2d629a..f32d401 100644 --- a/custom_components/hpprinter/translations/de.json +++ b/custom_components/hpprinter/translations/de.json @@ -39,7 +39,13 @@ "name": "Bahnhof" }, "consumable_consumable_type_enum": { - "name": "Typ" + "name": "Typ", + "state": { + "ink": "Tinte", + "inkCartridge": "Tinte", + "printhead": "Druckkopf", + "toner": "Toner" + } }, "consumable_estimated_pages_remaining": { "name": "\u00dcbrig" diff --git a/custom_components/hpprinter/translations/dk.json b/custom_components/hpprinter/translations/dk.json index bf015ba..0f0b8a7 100644 --- a/custom_components/hpprinter/translations/dk.json +++ b/custom_components/hpprinter/translations/dk.json @@ -42,7 +42,13 @@ "name": "Station" }, "consumable_consumable_type_enum": { - "name": "Type" + "name": "Type", + "state": { + "ink": "Bl\u00e6k", + "inkCartridge": "Bl\u00e6k", + "printhead": "Printhead", + "toner": "Toner" + } }, "consumable_estimated_pages_remaining": { "name": "Resterende" diff --git a/custom_components/hpprinter/translations/en.json b/custom_components/hpprinter/translations/en.json index 9e29f27..f190b26 100644 --- a/custom_components/hpprinter/translations/en.json +++ b/custom_components/hpprinter/translations/en.json @@ -42,7 +42,13 @@ "name": "Station" }, "consumable_consumable_type_enum": { - "name": "Type" + "name": "Type", + "state": { + "ink": "Ink", + "inkCartridge": "Ink", + "printhead": "Printhead", + "toner": "Toner" + } }, "consumable_estimated_pages_remaining": { "name": "Remaining" diff --git a/custom_components/hpprinter/translations/es.json b/custom_components/hpprinter/translations/es.json index d8a7bd7..9292ce8 100644 --- a/custom_components/hpprinter/translations/es.json +++ b/custom_components/hpprinter/translations/es.json @@ -42,7 +42,13 @@ "name": "Estaci\u00f3n" }, "consumable_consumable_type_enum": { - "name": "Tipo" + "name": "Tipo", + "state": { + "ink": "Tinta", + "inkCartridge": "Tinta", + "printhead": "Cabezal", + "toner": "Virador" + } }, "consumable_estimated_pages_remaining": { "name": "Restante" diff --git a/custom_components/hpprinter/translations/fr.json b/custom_components/hpprinter/translations/fr.json index 93a3472..a048f1d 100644 --- a/custom_components/hpprinter/translations/fr.json +++ b/custom_components/hpprinter/translations/fr.json @@ -39,7 +39,13 @@ "name": "Gare" }, "consumable_consumable_type_enum": { - "name": "Taper" + "name": "Taper", + "state": { + "ink": "Encre", + "inkCartridge": "Encre", + "printhead": "T\u00eate d'impression", + "toner": "Toner" + } }, "consumable_estimated_pages_remaining": { "name": "Restante Restant" diff --git a/custom_components/hpprinter/translations/nb.json b/custom_components/hpprinter/translations/nb.json index cd751d8..cc2564a 100644 --- a/custom_components/hpprinter/translations/nb.json +++ b/custom_components/hpprinter/translations/nb.json @@ -42,7 +42,13 @@ "name": "Stasjon" }, "consumable_consumable_type_enum": { - "name": "Type" + "name": "Type", + "state": { + "ink": "Blekk", + "inkCartridge": "Blekk", + "printhead": "Skrivehode", + "toner": "Toner" + } }, "consumable_estimated_pages_remaining": { "name": "Gjenst\u00e5ende" diff --git a/custom_components/hpprinter/translations/nl.json b/custom_components/hpprinter/translations/nl.json index 707006b..106472b 100644 --- a/custom_components/hpprinter/translations/nl.json +++ b/custom_components/hpprinter/translations/nl.json @@ -40,7 +40,13 @@ "name": "Station" }, "consumable_consumable_type_enum": { - "name": "Type" + "name": "Type", + "state": { + "ink": "Inkt", + "inkCartridge": "Inkt", + "printhead": "Printhead", + "toner": "Toner" + } }, "consumable_estimated_pages_remaining": { "name": "Overig" diff --git a/custom_components/hpprinter/translations/pl.json b/custom_components/hpprinter/translations/pl.json index 13ae3f8..5820dec 100644 --- a/custom_components/hpprinter/translations/pl.json +++ b/custom_components/hpprinter/translations/pl.json @@ -42,7 +42,13 @@ "name": "Stacja" }, "consumable_consumable_type_enum": { - "name": "Typ" + "name": "Typ", + "state": { + "ink": "Atrament", + "inkCartridge": "Atrament", + "printhead": "Printhead", + "toner": "Toner" + } }, "consumable_estimated_pages_remaining": { "name": "Pozosta\u0142y" diff --git a/custom_components/hpprinter/translations/pt-BR.json b/custom_components/hpprinter/translations/pt-BR.json index d76a1a6..037fabc 100644 --- a/custom_components/hpprinter/translations/pt-BR.json +++ b/custom_components/hpprinter/translations/pt-BR.json @@ -42,7 +42,13 @@ "name": "Esta\u00e7\u00e3o" }, "consumable_consumable_type_enum": { - "name": "Tipo" + "name": "Tipo", + "state": { + "ink": "Tinta", + "inkCartridge": "Tinta", + "printhead": "Cabe\u00e7ote de impress\u00e3o", + "toner": "Toner" + } }, "consumable_estimated_pages_remaining": { "name": "Restante" diff --git a/info.md b/info.md index ca308f7..837840a 100644 --- a/info.md +++ b/info.md @@ -100,7 +100,7 @@ _Sensor_ ### Scanner -Device holds entities related to consumable (Ink, Tuner, Printhead) of a printer device +Device holds entities related to consumable (Ink, Toner, Printhead) of a printer device _Binary Sensor_ From e4cfeeddf9eea4d9db62353a18f479a49aebe3bc Mon Sep 17 00:00:00 2001 From: Elad Bar Date: Mon, 19 Feb 2024 07:35:20 +0200 Subject: [PATCH 08/25] add sensor (enum) options support --- custom_components/hpprinter/managers/ha_config_manager.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/custom_components/hpprinter/managers/ha_config_manager.py b/custom_components/hpprinter/managers/ha_config_manager.py index 70aa8a8..17a0c86 100644 --- a/custom_components/hpprinter/managers/ha_config_manager.py +++ b/custom_components/hpprinter/managers/ha_config_manager.py @@ -312,6 +312,7 @@ def _load_entity_descriptions(self): elif property_platform == str(Platform.SENSOR): unit_of_measurement = property_data.get("unit_of_measurement") + options = property_data.get("options") entity_description = IntegrationSensorEntityDescription( key=property_key, @@ -322,6 +323,7 @@ def _load_entity_descriptions(self): device_class=device_class, icon=icon, translation_key=translation_key, + options=options, ) self._entity_descriptions.append(entity_description) From 9fddb4c48fcf9fb0512800c9c9020f26039acc1c Mon Sep 17 00:00:00 2001 From: Elad Bar Date: Mon, 19 Feb 2024 07:58:29 +0200 Subject: [PATCH 09/25] protect data extraction process, in case data is not available for specific device --- .../hpprinter/managers/rest_api.py | 30 +++++++++++-------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/custom_components/hpprinter/managers/rest_api.py b/custom_components/hpprinter/managers/rest_api.py index f86b477..708da1f 100644 --- a/custom_components/hpprinter/managers/rest_api.py +++ b/custom_components/hpprinter/managers/rest_api.py @@ -181,19 +181,21 @@ def _extract_data(self, devices: list[dict]): device_key = f"{device_type}.{device_id}" data = device_data[device_key] if device_key in device_data else {} + has_data = len(list(item_data.keys())) > 0 data.update(item_data) - if device_key in device_config: - config = device_config[device_key] - config["properties"].update(properties) + if has_data: + device_data[device_key] = data - else: - device_config[device_key] = { - "device_type": device_type, - "properties": properties, - } + if device_key in device_config: + config = device_config[device_key] + config["properties"].update(properties) - device_data[device_key] = data + else: + device_config[device_key] = { + "device_type": device_type, + "properties": properties, + } self._data_config = device_config self._data = device_data @@ -235,8 +237,9 @@ def _get_data_section(data: dict, path: str) -> dict: path_parts = path.split(".") result = data - for path_part in path_parts: - result = result.get(path_part) + if result is not None: + for path_part in path_parts: + result = result.get(path_part) return result @@ -275,7 +278,7 @@ def _get_device_data( ) -> dict: device_data = {} - data_item_flat = flatten(data_item, ".") + data_item_flat = {} if data_item is None else flatten(data_item, ".") for property_key in properties: property_details = properties.get(property_key) @@ -283,7 +286,8 @@ def _get_device_data( value = data_item_flat.get(property_path) - device_data[property_key] = value + if value is not None: + device_data[property_key] = value data = {"config": device_config, "data": device_data} From 0080a5caa88680dba2ffa2ed93457a2f2178376d Mon Sep 17 00:00:00 2001 From: Elad Bar Date: Mon, 19 Feb 2024 07:58:51 +0200 Subject: [PATCH 10/25] fix typo --- README.md | 13 +++++++++---- .../hpprinter/parameters/data_points.json | 4 ++-- custom_components/hpprinter/strings.json | 8 ++++---- custom_components/hpprinter/translations/de.json | 4 ++-- custom_components/hpprinter/translations/dk.json | 8 ++++---- custom_components/hpprinter/translations/en.json | 8 ++++---- custom_components/hpprinter/translations/es.json | 8 ++++---- custom_components/hpprinter/translations/fr.json | 4 ++-- custom_components/hpprinter/translations/nb.json | 8 ++++---- custom_components/hpprinter/translations/nl.json | 8 ++++---- custom_components/hpprinter/translations/pl.json | 4 ++-- custom_components/hpprinter/translations/pt-BR.json | 4 ++-- info.md | 13 +++++++++---- 13 files changed, 52 insertions(+), 42 deletions(-) diff --git a/README.md b/README.md index 837840a..f55b73f 100644 --- a/README.md +++ b/README.md @@ -40,17 +40,21 @@ It is also possible to change configuration after setting up using integration c ## Devices +Will extract data of the relevant devices, devices that are not available will be ignored. + ### Main device Device that holds entities related to the integration and relations to other sub devices as described below. -#### Entities - _Binary Sensor_ - ePrint Registered - ePrint Status +_Sensor_ + +- Manufacture Date + ### Printer Device holds entities of sensors related to number of pages printed and relation to sub devices of consumables @@ -98,9 +102,9 @@ _Sensor_ - Total faxed -### Scanner +### Consumable -Device holds entities related to consumable (Ink, Toner, Printhead) of a printer device +Devices (device per consumable) holds entities related to consumable (Ink, Toner, Printhead) of a printer device _Binary Sensor_ @@ -116,6 +120,7 @@ _Sensor_ - Remaining (will not be available for Printhead) - Counterfeit Refilled - Genuine Refilled +- Manufacture Date ## Troubleshooting diff --git a/custom_components/hpprinter/parameters/data_points.json b/custom_components/hpprinter/parameters/data_points.json index ad65df7..2500664 100644 --- a/custom_components/hpprinter/parameters/data_points.json +++ b/custom_components/hpprinter/parameters/data_points.json @@ -23,7 +23,7 @@ "manufacturer_name": { "path": "Manufacturer.Name" }, - "manufactured_at": { + "manufacture_at": { "path": "Manufacturer.Date", "platform": "sensor", "device_class": "date" @@ -81,7 +81,7 @@ "manufacturer_name": { "path": "Manufacturer.Name" }, - "manufactured_at": { + "manufacture_at": { "path": "Manufacturer.Date", "platform": "sensor", "device_class": "date", diff --git a/custom_components/hpprinter/strings.json b/custom_components/hpprinter/strings.json index 8a99e13..4d07728 100644 --- a/custom_components/hpprinter/strings.json +++ b/custom_components/hpprinter/strings.json @@ -50,11 +50,11 @@ } }, "sensor": { - "main_manufactured_at": { - "name": "Manufactured Date" + "main_manufacture_at": { + "name": "Manufacture Date" }, - "consumable_manufactured_at": { - "name": "Manufactured Date" + "consumable_manufacture_at": { + "name": "Manufacture Date" }, "consumable_consumable_station": { "name": "Station" diff --git a/custom_components/hpprinter/translations/de.json b/custom_components/hpprinter/translations/de.json index f32d401..c760096 100644 --- a/custom_components/hpprinter/translations/de.json +++ b/custom_components/hpprinter/translations/de.json @@ -53,7 +53,7 @@ "consumable_installation_date": { "name": "Installationsdatum" }, - "consumable_manufactured_at": { + "consumable_manufacture_at": { "name": "Herstellungsdatum" }, "consumable_refilled_count_counterfeit_refilled_count": { @@ -86,7 +86,7 @@ "main_hardware_config_device_connectivity_port_type": { "name": "Port -Typ" }, - "main_manufactured_at": { + "main_manufacture_at": { "name": "Herstellungsdatum" }, "printer_color_impressions": { diff --git a/custom_components/hpprinter/translations/dk.json b/custom_components/hpprinter/translations/dk.json index 0f0b8a7..a363f1a 100644 --- a/custom_components/hpprinter/translations/dk.json +++ b/custom_components/hpprinter/translations/dk.json @@ -56,8 +56,8 @@ "consumable_installation_date": { "name": "Installationsdato" }, - "consumable_manufactured_at": { - "name": "Fremstillet dato" + "consumable_manufacture_at": { + "name": "Fremstillingsdato" }, "consumable_refilled_count_counterfeit_refilled_count": { "name": "Forfalsket genopfyldt" @@ -89,8 +89,8 @@ "main_hardware_config_device_connectivity_port_type": { "name": "Porttype" }, - "main_manufactured_at": { - "name": "Fremstillet dato" + "main_manufacture_at": { + "name": "Fremstillingsdato" }, "printer_color_impressions": { "name": "Samlede farvesider trykt" diff --git a/custom_components/hpprinter/translations/en.json b/custom_components/hpprinter/translations/en.json index f190b26..8c464ed 100644 --- a/custom_components/hpprinter/translations/en.json +++ b/custom_components/hpprinter/translations/en.json @@ -56,8 +56,8 @@ "consumable_installation_date": { "name": "Installation Date" }, - "consumable_manufactured_at": { - "name": "Manufactured Date" + "consumable_manufacture_at": { + "name": "Manufacture Date" }, "consumable_refilled_count_counterfeit_refilled_count": { "name": "Counterfeit Refilled" @@ -89,8 +89,8 @@ "main_hardware_config_device_connectivity_port_type": { "name": "Port Type" }, - "main_manufactured_at": { - "name": "Manufactured Date" + "main_manufacture_at": { + "name": "Manufacture Date" }, "printer_color_impressions": { "name": "Total color pages printed" diff --git a/custom_components/hpprinter/translations/es.json b/custom_components/hpprinter/translations/es.json index 9292ce8..0145254 100644 --- a/custom_components/hpprinter/translations/es.json +++ b/custom_components/hpprinter/translations/es.json @@ -56,8 +56,8 @@ "consumable_installation_date": { "name": "Fecha de instalaci\u00f3n" }, - "consumable_manufactured_at": { - "name": "Fecha de manufacturacion" + "consumable_manufacture_at": { + "name": "Fecha de fabricacion" }, "consumable_refilled_count_counterfeit_refilled_count": { "name": "Falsificado" @@ -89,8 +89,8 @@ "main_hardware_config_device_connectivity_port_type": { "name": "Tipo de puerto" }, - "main_manufactured_at": { - "name": "Fecha de manufacturacion" + "main_manufacture_at": { + "name": "Fecha de fabricacion" }, "printer_color_impressions": { "name": "P\u00e1ginas de color totales impresas" diff --git a/custom_components/hpprinter/translations/fr.json b/custom_components/hpprinter/translations/fr.json index a048f1d..a5133c1 100644 --- a/custom_components/hpprinter/translations/fr.json +++ b/custom_components/hpprinter/translations/fr.json @@ -53,7 +53,7 @@ "consumable_installation_date": { "name": "Date d'installation" }, - "consumable_manufactured_at": { + "consumable_manufacture_at": { "name": "Date de fabrication" }, "consumable_refilled_count_counterfeit_refilled_count": { @@ -86,7 +86,7 @@ "main_hardware_config_device_connectivity_port_type": { "name": "Type de port" }, - "main_manufactured_at": { + "main_manufacture_at": { "name": "Date de fabrication" }, "printer_color_impressions": { diff --git a/custom_components/hpprinter/translations/nb.json b/custom_components/hpprinter/translations/nb.json index cc2564a..933fed5 100644 --- a/custom_components/hpprinter/translations/nb.json +++ b/custom_components/hpprinter/translations/nb.json @@ -56,8 +56,8 @@ "consumable_installation_date": { "name": "Installasjonsdato" }, - "consumable_manufactured_at": { - "name": "Produsert dato" + "consumable_manufacture_at": { + "name": "Produksjonsdato" }, "consumable_refilled_count_counterfeit_refilled_count": { "name": "Forfalsket p\u00e5fyllt" @@ -89,8 +89,8 @@ "main_hardware_config_device_connectivity_port_type": { "name": "Porttype" }, - "main_manufactured_at": { - "name": "Produsert dato" + "main_manufacture_at": { + "name": "Produksjonsdato" }, "printer_color_impressions": { "name": "Total fargesider trykt" diff --git a/custom_components/hpprinter/translations/nl.json b/custom_components/hpprinter/translations/nl.json index 106472b..e2046e8 100644 --- a/custom_components/hpprinter/translations/nl.json +++ b/custom_components/hpprinter/translations/nl.json @@ -54,8 +54,8 @@ "consumable_installation_date": { "name": "Installatie datum" }, - "consumable_manufactured_at": { - "name": "Gefabriceerde datum" + "consumable_manufacture_at": { + "name": "Productiedatum" }, "consumable_refilled_count_counterfeit_refilled_count": { "name": "Vervalste bijgevuld" @@ -87,8 +87,8 @@ "main_hardware_config_device_connectivity_port_type": { "name": "Poorttype" }, - "main_manufactured_at": { - "name": "Gefabriceerde datum" + "main_manufacture_at": { + "name": "Productiedatum" }, "printer_color_impressions": { "name": "Totale kleurpagina's afgedrukt" diff --git a/custom_components/hpprinter/translations/pl.json b/custom_components/hpprinter/translations/pl.json index 5820dec..6b32e9e 100644 --- a/custom_components/hpprinter/translations/pl.json +++ b/custom_components/hpprinter/translations/pl.json @@ -56,7 +56,7 @@ "consumable_installation_date": { "name": "Data instalacji" }, - "consumable_manufactured_at": { + "consumable_manufacture_at": { "name": "Data produkcji" }, "consumable_refilled_count_counterfeit_refilled_count": { @@ -89,7 +89,7 @@ "main_hardware_config_device_connectivity_port_type": { "name": "Typ portu" }, - "main_manufactured_at": { + "main_manufacture_at": { "name": "Data produkcji" }, "printer_color_impressions": { diff --git a/custom_components/hpprinter/translations/pt-BR.json b/custom_components/hpprinter/translations/pt-BR.json index 037fabc..f6b300c 100644 --- a/custom_components/hpprinter/translations/pt-BR.json +++ b/custom_components/hpprinter/translations/pt-BR.json @@ -56,7 +56,7 @@ "consumable_installation_date": { "name": "Data de instala\u00e7\u00e3o" }, - "consumable_manufactured_at": { + "consumable_manufacture_at": { "name": "Data de fabrica\u00e7\u00e3o" }, "consumable_refilled_count_counterfeit_refilled_count": { @@ -89,7 +89,7 @@ "main_hardware_config_device_connectivity_port_type": { "name": "Tipo de porta" }, - "main_manufactured_at": { + "main_manufacture_at": { "name": "Data de fabrica\u00e7\u00e3o" }, "printer_color_impressions": { diff --git a/info.md b/info.md index 837840a..f55b73f 100644 --- a/info.md +++ b/info.md @@ -40,17 +40,21 @@ It is also possible to change configuration after setting up using integration c ## Devices +Will extract data of the relevant devices, devices that are not available will be ignored. + ### Main device Device that holds entities related to the integration and relations to other sub devices as described below. -#### Entities - _Binary Sensor_ - ePrint Registered - ePrint Status +_Sensor_ + +- Manufacture Date + ### Printer Device holds entities of sensors related to number of pages printed and relation to sub devices of consumables @@ -98,9 +102,9 @@ _Sensor_ - Total faxed -### Scanner +### Consumable -Device holds entities related to consumable (Ink, Toner, Printhead) of a printer device +Devices (device per consumable) holds entities related to consumable (Ink, Toner, Printhead) of a printer device _Binary Sensor_ @@ -116,6 +120,7 @@ _Sensor_ - Remaining (will not be available for Printhead) - Counterfeit Refilled - Genuine Refilled +- Manufacture Date ## Troubleshooting From b64986fab32e4aa2ea80f46bb3d0b111afef7cc1 Mon Sep 17 00:00:00 2001 From: Elad Bar Date: Fri, 22 Mar 2024 16:49:42 +0200 Subject: [PATCH 11/25] docs --- CHANGELOG.md | 2 +- README.md | 2 +- custom_components/hpprinter/manifest.json | 2 +- info.md | 2 +- requirements.txt | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index af7458d..c1c4664 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## v2.0.0 +## v2.0.00b1 - Refactor to full HP Printer EWS support diff --git a/README.md b/README.md index f55b73f..ad2e188 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ Configuration support multiple HP Printer devices through Configuration -> Integ ### Installations via HACS [![hacs_badge](https://img.shields.io/badge/HACS-Custom-41BDF5.svg)](https://github.com/hacs/integration) -- In HACS, look for "Aqua Temp" and install and restart +- In HACS, look for "HP Printer" and install and restart - If integration was not found, please add custom repository `elad-bar/hpprinter` as integration - In Settings --> Devices & Services - (Lower Right) "Add Integration" diff --git a/custom_components/hpprinter/manifest.json b/custom_components/hpprinter/manifest.json index b633132..630c78b 100644 --- a/custom_components/hpprinter/manifest.json +++ b/custom_components/hpprinter/manifest.json @@ -9,5 +9,5 @@ "iot_class": "local_polling", "issue_tracker": "https://github.com/elad-bar/ha-hpprinter/issues", "requirements": ["xmltodict~=0.13.0", "flatten_json", "defusedxml"], - "version": "2.0.0" + "version": "2.0.0b1" } diff --git a/info.md b/info.md index f55b73f..ad2e188 100644 --- a/info.md +++ b/info.md @@ -14,7 +14,7 @@ Configuration support multiple HP Printer devices through Configuration -> Integ ### Installations via HACS [![hacs_badge](https://img.shields.io/badge/HACS-Custom-41BDF5.svg)](https://github.com/hacs/integration) -- In HACS, look for "Aqua Temp" and install and restart +- In HACS, look for "HP Printer" and install and restart - If integration was not found, please add custom repository `elad-bar/hpprinter` as integration - In Settings --> Devices & Services - (Lower Right) "Add Integration" diff --git a/requirements.txt b/requirements.txt index 4c1b808..8af47d0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ pre-commit -homeassistant~=2024.1.0b0 +homeassistant~=2024.3.0b0 voluptuous~=0.13.1 From f10d2e520ccca868e7776fdd7e36479f46d17aa8 Mon Sep 17 00:00:00 2001 From: Elad Bar Date: Fri, 22 Mar 2024 16:53:14 +0200 Subject: [PATCH 12/25] fix type in change log --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c1c4664..38dfa6c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## v2.0.00b1 +## 2.0.0b1 - Refactor to full HP Printer EWS support From ab81ec987d316574536ead27fad706215819eb37 Mon Sep 17 00:00:00 2001 From: Elad Bar Date: Fri, 22 Mar 2024 17:03:41 +0200 Subject: [PATCH 13/25] Fix wrong library usage for slugify, causing wrong translation key to get picked up --- CHANGELOG.md | 4 ++++ custom_components/hpprinter/managers/ha_config_manager.py | 3 +-- custom_components/hpprinter/manifest.json | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 38dfa6c..3b74812 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 2.0.0b2 + +- Fix wrong library usage for slugify, causing wrong translation key to get picked up + ## 2.0.0b1 - Refactor to full HP Printer EWS support diff --git a/custom_components/hpprinter/managers/ha_config_manager.py b/custom_components/hpprinter/managers/ha_config_manager.py index 17a0c86..66f138b 100644 --- a/custom_components/hpprinter/managers/ha_config_manager.py +++ b/custom_components/hpprinter/managers/ha_config_manager.py @@ -4,8 +4,6 @@ import os from pathlib import Path -from slugify import slugify - from homeassistant.config_entries import STORAGE_VERSION, ConfigEntry from homeassistant.const import Platform from homeassistant.core import HomeAssistant @@ -13,6 +11,7 @@ from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.json import JSONEncoder from homeassistant.helpers.storage import Store +from homeassistant.util import slugify from ..common.consts import ( CONF_UPDATE_INTERVAL, diff --git a/custom_components/hpprinter/manifest.json b/custom_components/hpprinter/manifest.json index 630c78b..469fc63 100644 --- a/custom_components/hpprinter/manifest.json +++ b/custom_components/hpprinter/manifest.json @@ -9,5 +9,5 @@ "iot_class": "local_polling", "issue_tracker": "https://github.com/elad-bar/ha-hpprinter/issues", "requirements": ["xmltodict~=0.13.0", "flatten_json", "defusedxml"], - "version": "2.0.0b1" + "version": "2.0.0b2" } From 35bb47f1aaf318aa03e64724a41e08629e9f7668 Mon Sep 17 00:00:00 2001 From: Elad Bar Date: Fri, 22 Mar 2024 17:39:44 +0200 Subject: [PATCH 14/25] fix entity translations --- CHANGELOG.md | 4 ++++ .../hpprinter/managers/ha_config_manager.py | 18 +++++++++--------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b74812..a143271 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 2.0.0b3 + +- Fix entity translations + ## 2.0.0b2 - Fix wrong library usage for slugify, causing wrong translation key to get picked up diff --git a/custom_components/hpprinter/managers/ha_config_manager.py b/custom_components/hpprinter/managers/ha_config_manager.py index 66f138b..cd43e78 100644 --- a/custom_components/hpprinter/managers/ha_config_manager.py +++ b/custom_components/hpprinter/managers/ha_config_manager.py @@ -171,22 +171,22 @@ def get_entity_name( ) if translated_name is None or translated_name == "": - entity_name = f"{device_name} {translated_name}" + entity_name = f"{device_name} {entity_description.name}" - _LOGGER.debug( - f"Translations requested, " + _LOGGER.warning( + f"Translations not found, " f"Key: {translation_key}, " - f"Entity: {entity_description.name}, " - f"Value: {translated_name}" + f"Entity: {entity_description.name}" ) else: - entity_name = f"{device_name} {entity_description.name}" + entity_name = f"{device_name} {translated_name}" - _LOGGER.warning( - f"Translations not found, " + _LOGGER.debug( + f"Translations requested, " f"Key: {translation_key}, " - f"Entity: {entity_description.name}" + f"Entity: {entity_description.name}, " + f"Value: {translated_name}" ) return entity_name From befd384272d29f7070ac58bf5ffd926c59d68a10 Mon Sep 17 00:00:00 2001 From: Elad Bar Date: Fri, 22 Mar 2024 18:17:35 +0200 Subject: [PATCH 15/25] Fix main device manufacture date --- CHANGELOG.md | 7 ++++--- custom_components/hpprinter/binary_sensor.py | 10 +++++++--- custom_components/hpprinter/manifest.json | 2 +- custom_components/hpprinter/sensor.py | 10 +++++++--- 4 files changed, 19 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a143271..384b4c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,14 +1,15 @@ # Changelog -## 2.0.0b3 +## v2.0.0b3 - Fix entity translations +- Fix main device manufacture date -## 2.0.0b2 +## v2.0.0b2 - Fix wrong library usage for slugify, causing wrong translation key to get picked up -## 2.0.0b1 +## v2.0.0b1 - Refactor to full HP Printer EWS support diff --git a/custom_components/hpprinter/binary_sensor.py b/custom_components/hpprinter/binary_sensor.py index 11c696e..40678cb 100644 --- a/custom_components/hpprinter/binary_sensor.py +++ b/custom_components/hpprinter/binary_sensor.py @@ -38,8 +38,9 @@ def __init__( self._attr_device_class = entity_description.device_class self._entity_on_values = entity_description.on_values - def _handle_coordinator_update(self) -> None: - """Fetch new state parameters for the sensor.""" + self._set_value() + + def _set_value(self): state = self.get_value() is_on = str(state).lower() in self._entity_on_values @@ -47,4 +48,7 @@ def _handle_coordinator_update(self) -> None: self._attr_is_on = is_on self._attr_extra_state_attributes = {ATTR_STATE: state} - self.async_write_ha_state() + def _handle_coordinator_update(self) -> None: + """Fetch new state parameters for the sensor.""" + self._set_value() + super()._handle_coordinator_update() diff --git a/custom_components/hpprinter/manifest.json b/custom_components/hpprinter/manifest.json index 469fc63..9074674 100644 --- a/custom_components/hpprinter/manifest.json +++ b/custom_components/hpprinter/manifest.json @@ -9,5 +9,5 @@ "iot_class": "local_polling", "issue_tracker": "https://github.com/elad-bar/ha-hpprinter/issues", "requirements": ["xmltodict~=0.13.0", "flatten_json", "defusedxml"], - "version": "2.0.0b2" + "version": "2.0.0b3" } diff --git a/custom_components/hpprinter/sensor.py b/custom_components/hpprinter/sensor.py index 8e6d4a1..3445830 100644 --- a/custom_components/hpprinter/sensor.py +++ b/custom_components/hpprinter/sensor.py @@ -42,8 +42,9 @@ def __init__( entity_description.native_unit_of_measurement ) - def _handle_coordinator_update(self) -> None: - """Fetch new state parameters for the sensor.""" + self._set_value() + + def _set_value(self): state = self.get_value() if self.native_unit_of_measurement in [PERCENTAGE]: @@ -57,4 +58,7 @@ def _handle_coordinator_update(self) -> None: self._attr_native_value = state - self.async_write_ha_state() + def _handle_coordinator_update(self) -> None: + """Fetch new state parameters for the sensor.""" + self._set_value() + super()._handle_coordinator_update() From bf5d475c518408d7a4ba559486f39f545e0c6326 Mon Sep 17 00:00:00 2001 From: Elad Bar Date: Fri, 12 Apr 2024 13:26:01 +0300 Subject: [PATCH 16/25] fix translations --- custom_components/hpprinter/managers/ha_config_manager.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/custom_components/hpprinter/managers/ha_config_manager.py b/custom_components/hpprinter/managers/ha_config_manager.py index cd43e78..6b1fd96 100644 --- a/custom_components/hpprinter/managers/ha_config_manager.py +++ b/custom_components/hpprinter/managers/ha_config_manager.py @@ -163,8 +163,9 @@ def get_entity_name( platform = entity_description.platform device_name = device_info.get("name") - - translation_key = f"component.{DOMAIN}.entity.{platform}.{translation_key}.name" + translation_key = slugify( + f"component.{DOMAIN}.entity.{platform}.{device_name}_{translation_key}.name" + ) translated_name = self._translations.get( translation_key, entity_description.name From ddfe54c877ba0d0f5013e04440b85c89eb077255 Mon Sep 17 00:00:00 2001 From: Elad Bar Date: Fri, 12 Apr 2024 14:02:00 +0300 Subject: [PATCH 17/25] support no prefetch mode --- CHANGELOG.md | 4 ++ .../hpprinter/managers/rest_api.py | 68 +++++++++++++------ custom_components/hpprinter/manifest.json | 2 +- 3 files changed, 53 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 384b4c2..66b3df4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## v2.0.0b4 + +- Support no prefetch mode + ## v2.0.0b3 - Fix entity translations diff --git a/custom_components/hpprinter/managers/rest_api.py b/custom_components/hpprinter/managers/rest_api.py index 708da1f..c6779d9 100644 --- a/custom_components/hpprinter/managers/rest_api.py +++ b/custom_components/hpprinter/managers/rest_api.py @@ -41,6 +41,8 @@ def __init__(self, hass, config_manager: HAConfigManager): self._is_connected: bool = False self._device_dispatched: list[str] = [] + self._all_endpoints: list[str] = [] + self._support_prefetch: bool = False @property def data(self) -> dict | None: @@ -110,20 +112,40 @@ def _get_ssl_connector(self): async def _load_metadata(self): self._all_endpoints = [] - endpoints = await self._get_request("/Prefetch?type=dtree") + endpoints = await self._get_request("/Prefetch?type=dtree", True) - if not endpoints: - raise IntegrationAPIError(self.config_data.url) + self._support_prefetch = endpoints is not None + is_connected = self._support_prefetch - for endpoint in endpoints: - is_valid = self._config_manager.is_valid_endpoint(endpoint) + if self._support_prefetch: + for endpoint in endpoints: + is_valid = self._config_manager.is_valid_endpoint(endpoint) + + if is_valid: + endpoint_uri = endpoint.get("uri") + + self._all_endpoints.append(endpoint_uri) + + else: + self._all_endpoints = self._config_manager.endpoints.copy() + + await self._update_data(self._config_manager.endpoints, False) - if is_valid: - endpoint_uri = endpoint.get("uri") + endpoints_found = len(self._raw_data.keys()) + is_connected = endpoints_found > 0 + available_endpoints = len(self._all_endpoints) + + if is_connected: + _LOGGER.info( + "No support for prefetch endpoint, " + f"{endpoints_found}/{available_endpoints} Endpoints found" + ) + else: + endpoint_urls = ", ".join(self._all_endpoints) - self._all_endpoints.append(endpoint_uri) + raise IntegrationAPIError(endpoint_urls) - self._is_connected = True + self._is_connected = is_connected async def update(self): await self._update_data(self._config_manager.endpoints) @@ -131,8 +153,8 @@ async def update(self): async def update_full(self): await self._update_data(self._all_endpoints) - async def _update_data(self, endpoints): - if not self._is_connected: + async def _update_data(self, endpoints: list[str], connectivity_check: bool = True): + if not self._is_connected and connectivity_check: return for endpoint in endpoints: @@ -293,7 +315,9 @@ def _get_device_data( return data - async def _get_request(self, endpoint: str) -> dict | None: + async def _get_request( + self, endpoint: str, ignore_error: bool = False + ) -> dict | None: result: dict | None = None try: url = f"{self.config_data.url}{endpoint}" @@ -322,16 +346,20 @@ async def _get_request(self, endpoint: str) -> dict | None: _LOGGER.debug(f"Request to {url}") except ClientResponseError as cre: - exc_type, exc_obj, tb = sys.exc_info() - line_number = tb.tb_lineno - _LOGGER.error( - f"Failed to get response from {endpoint}, Error: {cre}, Line: {line_number}" - ) + if not ignore_error: + exc_type, exc_obj, tb = sys.exc_info() + line_number = tb.tb_lineno + _LOGGER.error( + f"Failed to get response from {endpoint}, Error: {cre}, Line: {line_number}" + ) except Exception as ex: - exc_type, exc_obj, tb = sys.exc_info() - line_number = tb.tb_lineno - _LOGGER.error(f"Failed to get {endpoint}, Error: {ex}, Line: {line_number}") + if not ignore_error: + exc_type, exc_obj, tb = sys.exc_info() + line_number = tb.tb_lineno + _LOGGER.error( + f"Failed to get {endpoint}, Error: {ex}, Line: {line_number}" + ) return result diff --git a/custom_components/hpprinter/manifest.json b/custom_components/hpprinter/manifest.json index 9074674..d9b139d 100644 --- a/custom_components/hpprinter/manifest.json +++ b/custom_components/hpprinter/manifest.json @@ -9,5 +9,5 @@ "iot_class": "local_polling", "issue_tracker": "https://github.com/elad-bar/ha-hpprinter/issues", "requirements": ["xmltodict~=0.13.0", "flatten_json", "defusedxml"], - "version": "2.0.0b3" + "version": "2.0.0b4" } From 590bae1e3f8221ba2af7ba62226a077c20df0587 Mon Sep 17 00:00:00 2001 From: Elad Bar Date: Fri, 12 Apr 2024 14:47:46 +0300 Subject: [PATCH 18/25] fix all translations --- CHANGELOG.md | 1 + .../hpprinter/managers/ha_config_manager.py | 7 +- custom_components/hpprinter/strings.json | 179 ++++++++---------- .../hpprinter/translations/de.json | 141 ++++++-------- .../hpprinter/translations/dk.json | 140 ++++++-------- .../hpprinter/translations/en.json | 118 +++++------- .../hpprinter/translations/es.json | 140 ++++++-------- .../hpprinter/translations/fr.json | 139 ++++++-------- .../hpprinter/translations/nb.json | 136 ++++++------- .../hpprinter/translations/nl.json | 142 ++++++-------- .../hpprinter/translations/pl.json | 142 ++++++-------- .../hpprinter/translations/pt-BR.json | 140 ++++++-------- utils/generate_translations.py | 7 +- 13 files changed, 596 insertions(+), 836 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 66b3df4..57bcbf7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## v2.0.0b4 - Support no prefetch mode +- Fix all translations ## v2.0.0b3 diff --git a/custom_components/hpprinter/managers/ha_config_manager.py b/custom_components/hpprinter/managers/ha_config_manager.py index 6b1fd96..de00d75 100644 --- a/custom_components/hpprinter/managers/ha_config_manager.py +++ b/custom_components/hpprinter/managers/ha_config_manager.py @@ -11,7 +11,6 @@ from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.json import JSONEncoder from homeassistant.helpers.storage import Store -from homeassistant.util import slugify from ..common.consts import ( CONF_UPDATE_INTERVAL, @@ -163,9 +162,7 @@ def get_entity_name( platform = entity_description.platform device_name = device_info.get("name") - translation_key = slugify( - f"component.{DOMAIN}.entity.{platform}.{device_name}_{translation_key}.name" - ) + translation_key = f"component.{DOMAIN}.entity.{platform}.{translation_key}.name" translated_name = self._translations.get( translation_key, entity_description.name @@ -289,7 +286,7 @@ def _load_entity_descriptions(self): exclude = property_data.get("exclude") device_class = property_data.get("device_class") icon = property_data.get("icon") - translation_key = slugify(f"{device_type}_{property_key}") + translation_key = property_key if property_platform == str(Platform.BINARY_SENSOR): on_values = [ diff --git a/custom_components/hpprinter/strings.json b/custom_components/hpprinter/strings.json index 4d07728..5be1608 100644 --- a/custom_components/hpprinter/strings.json +++ b/custom_components/hpprinter/strings.json @@ -1,150 +1,135 @@ { "config": { - "step": { - "user": { - "title": "Set up HP Printer Embedded Web Server (EWS)", - "data": { - "host": "Hostname or IP", - "port": "Port number", - "ssl": "Is SSL" - } - } + "abort": { + "already_configured": "HP Printer integration ({name}) already configured" }, - "abort": {}, "error": { - "error_400": "Invalid parameters provided", - "error_404": "HP Printer Embedded Web Server (EWS) not was not found, please check logs for more details" - } - }, - "options": { + "error_400": "Invalid server details, please try manually access to `http://{IP}/DevMgmt/ProductStatusDyn.xml` (Replace placeholder with IP or Hostname), in case it's accessible, please report the issue with logs", + "error_404": "Unsupported API" + }, "step": { - "init": { - "title": "Options for HP Printer.", - "description": "Define additional settings for HP Printer integration", + "user": { "data": { - "host": "Hostname or IP", + "host": "Host", + "name": "Name", "port": "Port number", - "ssl": "Is SSL", - "update_interval": "Update interval (Seconds)" - } + "ssl": "Is SSL" + }, + "title": "Set up HP Printer" } - }, - "error": { - "error_400": "Invalid parameters provided", - "error_404": "HP Printer Embedded Web Server (EWS) not was not found, please check logs for more details" } }, "entity": { "binary_sensor": { - "consumable_consumable_life_state_consumable_state": { + "consumable_life_state_consumable_state": { "name": "Status" }, - "main_hardware_config_is_connected": { + "cloud_services_switch_status": { + "name": "ePrint Status" + }, + "hardware_config_is_connected": { "name": "Connected" }, - "main_registration_state": { + "registration_state": { "name": "ePrint Registered" - }, - "main_cloud_services_switch_status": { - "name": "ePrint Status" } }, "sensor": { - "main_manufacture_at": { - "name": "Manufacture Date" - }, - "consumable_manufacture_at": { - "name": "Manufacture Date" + "consumable_percentage_level_remaining": { + "name": "Level" }, - "consumable_consumable_station": { + "consumable_station": { "name": "Station" }, - "consumable_consumable_type_enum": { + "consumable_type_enum": { "name": "Type", "state": { "ink": "Ink", "inkCartridge": "Ink", - "toner": "Toner", - "printhead": "Printhead" + "printhead": "Printhead", + "toner": "Toner" } }, - "consumable_installation_date": { - "name": "Installation Date" - }, - "consumable_consumable_percentage_level_remaining": { - "name": "Level" + "estimated_pages_remaining": { + "name": "Remaining" }, - "consumable_warranty_expiration_date": { - "name": "Expiration Date" + "installation_date": { + "name": "Installation Date" }, - "consumable_estimated_pages_remaining": { - "name": "Remaining" + "manufacture_at": { + "name": "Manufacture Date" }, - "consumable_refilled_count_counterfeit_refilled_count": { + "refilled_count_counterfeit_refilled_count": { "name": "Counterfeit Refilled" }, - "consumable_refilled_count_genuine_refilled_count": { + "refilled_count_genuine_refilled_count": { "name": "Genuine Refilled" }, - "printer_total_impressions": { - "name": "Total pages printed" - }, - "printer_monochrome_impressions": { - "name": "Total black-and-white pages printed" - }, - "printer_color_impressions": { - "name": "Total color pages printed" - }, - "printer_simplex_sheets": { - "name": "Total single-sided pages printed" + "warranty_expiration_date": { + "name": "Expiration Date" }, - "printer_duplex_sheets": { - "name": "Total double-sided pages printed" + "adf_images": { + "name": "Total pages from ADF" }, - "printer_jam_events": { - "name": "Total jams" + "color_impressions": { + "name": "Total color pages" }, - "printer_mispick_events": { - "name": "Total miss picks" + "flatbed_images": { + "name": "Total pages from scanner glass" }, - "scanner_scan_images": { - "name": "Total scanned pages" + "monochrome_impressions": { + "name": "Total black-and-white pages" }, - "scanner_adf_images": { - "name": "Total scanned pages from ADF" + "total_impressions": { + "name": "Total pages" }, - "scanner_duplex_sheets": { - "name": "Total double-sided pages scanned" + "hardware_config_device_connectivity_port_type": { + "name": "Port Type" }, - "scanner_flatbed_images": { - "name": "Total pages from scanner glass" + "duplex_sheets": { + "name": "Total double-sided pages" }, - "scanner_jam_events": { + "jam_events": { "name": "Total jams" }, - "scanner_mispick_events": { + "mispick_events": { "name": "Total miss picks" }, - "copy_total_impressions": { - "name": "Total copies" + "simplex_sheets": { + "name": "Total single-sided pages" }, - "copy_adf_images": { - "name": "Total copies from ADF" - }, - "copy_flatbed_images": { - "name": "Total pages from scanner glass" - }, - "copy_monochrome_impressions": { - "name": "Total black-and-white copies" - }, - "copy_color_impressions": { - "name": "Total color copies" - }, - "fax_total_impressions": { - "name": "Total faxes" + "scan_images": { + "name": "Total pages" + } + } + }, + "options": { + "error": { + "already_configured": "HP Printer integration ({name}) already configured", + "error_400": "Invalid server details, please try manually access to `http://{IP}/DevMgmt/ProductStatusDyn.xml` (Replace placeholder with IP or Hostname), in case it's accessible, please report the issue with logs", + "error_404": "Unsupported API" + }, + "step": { + "hp_printer_additional_settings": { + "data": { + "host": "Host", + "log_level": "Log level", + "name": "Name", + "store_data": "Should store responses?", + "update_interval": "Update interval (Seconds)" + }, + "description": "Define additional settings for HP Printer integration", + "title": "Options for HP Printer." }, - "main_hardware_config_device_connectivity_port_type": { - "name": "Port Type" + "init": { + "data": { + "host": "Hostname or IP", + "port": "Port number", + "ssl": "Is SSL", + "update_interval": "Update interval (Seconds)" + }, + "description": "Define additional settings for HP Printer integration", + "title": "Options for HP Printer." } } } diff --git a/custom_components/hpprinter/translations/de.json b/custom_components/hpprinter/translations/de.json index c760096..d84ecc5 100644 --- a/custom_components/hpprinter/translations/de.json +++ b/custom_components/hpprinter/translations/de.json @@ -1,44 +1,53 @@ { "config": { + "abort": { + "already_configured": "HP -Druckerintegration ({Name}) bereits konfiguriert" + }, "error": { - "error_400": "Ung\u00fcltige Parameter bereitgestellt", - "error_404": "Der eingebettete Webserver (EWS) HP -Drucker wurde nicht gefunden. \u00dcberpr\u00fcfen Sie die Protokolle f\u00fcr weitere Details, um weitere Informationen zu erhalten." + "error_400": "Ung\u00fcltige Serverdetails, bitte versuchen Sie manuell auf `http: // {ip} // devmgmt/productStatusdyn.xml` (Ersetzen Sie Platzhalter mit IP oder Hostname).", + "error_404": "Nicht unterst\u00fctzte API" }, "step": { "user": { "data": { - "host": "Host", + "host": "Gastgeber", "name": "Name", "port": "Port-Nummer", "ssl": "Ist SSL" }, - "title": "HP Drucker einrichten " + "title": "Richten Sie den HP -Drucker ein" } } }, "entity": { "binary_sensor": { - "consumable_consumable_life_state_consumable_state": { - "name": "Status" - }, - "main_cloud_services_switch_status": { + "cloud_services_switch_status": { "name": "ePrint Status" }, - "main_hardware_config_is_connected": { + "consumable_life_state_consumable_state": { + "name": "Status" + }, + "hardware_config_is_connected": { "name": "In Verbindung gebracht" }, - "main_registration_state": { + "registration_state": { "name": "ePrint Eingetragen" } }, "sensor": { - "consumable_consumable_percentage_level_remaining": { + "adf_images": { + "name": "Gesamtseiten von ADF" + }, + "color_impressions": { + "name": "Gesamtfarbseiten" + }, + "consumable_percentage_level_remaining": { "name": "Ebene" }, - "consumable_consumable_station": { + "consumable_station": { "name": "Bahnhof" }, - "consumable_consumable_type_enum": { + "consumable_type_enum": { "name": "Typ", "state": { "ink": "Tinte", @@ -47,95 +56,71 @@ "toner": "Toner" } }, - "consumable_estimated_pages_remaining": { - "name": "\u00dcbrig" - }, - "consumable_installation_date": { - "name": "Installationsdatum" - }, - "consumable_manufacture_at": { - "name": "Herstellungsdatum" - }, - "consumable_refilled_count_counterfeit_refilled_count": { - "name": "Gef\u00e4lscht nachgedacht" + "duplex_sheets": { + "name": "Gesamt zweiseitige Seiten" }, - "consumable_refilled_count_genuine_refilled_count": { - "name": "Echt nachgedacht" - }, - "consumable_warranty_expiration_date": { - "name": "Verfallsdatum" - }, - "copy_adf_images": { - "name": "Gesamtkopien von ADF" - }, - "copy_color_impressions": { - "name": "Gesamtfarbkopien" + "estimated_pages_remaining": { + "name": "\u00dcbrig" }, - "copy_flatbed_images": { + "flatbed_images": { "name": "Gesamtseiten aus Scannerglas" }, - "copy_monochrome_impressions": { - "name": "Gesamtschwarz-Wei\u00df-Kopien" - }, - "copy_total_impressions": { - "name": "Gesamtkopien" - }, - "fax_total_impressions": { - "name": "Gesamtfaxen" - }, - "main_hardware_config_device_connectivity_port_type": { + "hardware_config_device_connectivity_port_type": { "name": "Port -Typ" }, - "main_manufacture_at": { - "name": "Herstellungsdatum" - }, - "printer_color_impressions": { - "name": "Gesamtfarbseiten gedruckt" - }, - "printer_duplex_sheets": { - "name": "Gesamt doppelseitige Seiten gedruckt" + "installation_date": { + "name": "Installationsdatum" }, - "printer_jam_events": { + "jam_events": { "name": "Gesamtmarmelade" }, - "printer_mispick_events": { - "name": "Total Miss Picks" - }, - "printer_monochrome_impressions": { - "name": "Gesamtschwarz-Wei\u00df-Seiten gedruckt" + "manufacture_at": { + "name": "Herstellungsdatum" }, - "printer_simplex_sheets": { - "name": "Gesamt einseitige Seiten gedruckt" + "mispick_events": { + "name": "Total Miss Picks" }, - "printer_total_impressions": { - "name": "Gesamtseiten gedruckt" + "monochrome_impressions": { + "name": "Gesamtschwarz-Wei\u00df-Seiten" }, - "scanner_adf_images": { - "name": "Gesamts gescannte Seiten von ADF" + "refilled_count_counterfeit_refilled_count": { + "name": "Gef\u00e4lscht nachgedacht" }, - "scanner_duplex_sheets": { - "name": "Gesamt doppelseitige Seiten gescannt" + "refilled_count_genuine_refilled_count": { + "name": "Echt nachgedacht" }, - "scanner_flatbed_images": { - "name": "Gesamtseiten aus Scannerglas" + "scan_images": { + "name": "Alle Seiten" }, - "scanner_jam_events": { - "name": "Gesamtmarmelade" + "simplex_sheets": { + "name": "Gesamt einseitige Seiten" }, - "scanner_mispick_events": { - "name": "Total Miss Picks" + "total_impressions": { + "name": "Alle Seiten" }, - "scanner_scan_images": { - "name": "Gesamts gescannte Seiten" + "warranty_expiration_date": { + "name": "Verfallsdatum" } } }, "options": { "error": { - "error_400": "Ung\u00fcltige Parameter bereitgestellt", - "error_404": "Der eingebettete Webserver (EWS) HP -Drucker wurde nicht gefunden. \u00dcberpr\u00fcfen Sie die Protokolle f\u00fcr weitere Details, um weitere Informationen zu erhalten." + "already_configured": "HP -Druckerintegration ({Name}) bereits konfiguriert", + "error_400": "Ung\u00fcltige Serverdetails, bitte versuchen Sie manuell auf `http: // {ip} // devmgmt/productStatusdyn.xml` (Ersetzen Sie Platzhalter mit IP oder Hostname).", + "error_404": "Nicht unterst\u00fctzte API" }, "step": { + "hp_printer_additional_settings": { + "data": { + "host": "Gastgeber", + "log_level": "Protokollebene", + "name": "Name", + "store_data": "Sollte die Antworten speichern?", + "update_interval": "Aktualisierungsintervall (Sekunden)" + }, + "description": "Definieren Sie zus\u00e4tzliche Einstellungen f\u00fcr die HP -Druckerintegration", + "title": "Optionen f\u00fcr HP -Drucker." + }, "init": { "data": { "host": "Hostname oder IP", diff --git a/custom_components/hpprinter/translations/dk.json b/custom_components/hpprinter/translations/dk.json index a363f1a..b83c377 100644 --- a/custom_components/hpprinter/translations/dk.json +++ b/custom_components/hpprinter/translations/dk.json @@ -1,47 +1,53 @@ { "config": { "abort": { - "already_configured": "HP Printer integration ({name}) er allerede konfigureret" + "already_configured": "HP -printerintegration ({navn}) allerede konfigureret" }, "error": { - "error_400": "Ugyldige serveroplysninger, pr\u00c3\u00b8v venligst manuelt at f\u00c3\u00a5 adgang til `http://{IP}//DevMgmt/ProductStatusDyn.xml` (Erstat pladsholder med IP eller v\u00c3\u00a6rtsnavn ), hvis det er tilg\u00c3\u00a6ngeligt, bedes du rapportere problemet med logfiler", - "error_404": "Ikke underst\u00c3\u00b8ttet API" + "error_400": "Ugyldigt serveroplysninger, pr\u00f8v manuelt adgang til `http: // {ip} // devmgmt/produktstatusdyn.xml` (udskift pladsholder med IP eller v\u00e6rtsnavn), hvis det er tilg\u00e6ngeligt, skal du rapportere problemet med logfiler", + "error_404": "Ikke -underst\u00f8ttet API" }, "step": { "user": { "data": { - "host": "V\u00c3\u00a6rt", + "host": "V\u00e6rt", "name": "Navn", "port": "Portnummer", "ssl": "Er SSL" }, - "title": "Konfigurer HP Printer " + "title": "Opret HP -printer" } } }, "entity": { "binary_sensor": { - "consumable_consumable_life_state_consumable_state": { - "name": "Status" - }, - "main_cloud_services_switch_status": { + "cloud_services_switch_status": { "name": "ePrint Status" }, - "main_hardware_config_is_connected": { + "consumable_life_state_consumable_state": { + "name": "Status" + }, + "hardware_config_is_connected": { "name": "Tilsluttet" }, - "main_registration_state": { + "registration_state": { "name": "ePrint Registreret" } }, "sensor": { - "consumable_consumable_percentage_level_remaining": { + "adf_images": { + "name": "Samlede sider fra ADF" + }, + "color_impressions": { + "name": "Samlede farvesider" + }, + "consumable_percentage_level_remaining": { "name": "Niveau" }, - "consumable_consumable_station": { + "consumable_station": { "name": "Station" }, - "consumable_consumable_type_enum": { + "consumable_type_enum": { "name": "Type", "state": { "ink": "Bl\u00e6k", @@ -50,106 +56,70 @@ "toner": "Toner" } }, - "consumable_estimated_pages_remaining": { - "name": "Resterende" + "duplex_sheets": { + "name": "Samlede dobbeltsidede sider" }, - "consumable_installation_date": { - "name": "Installationsdato" - }, - "consumable_manufacture_at": { - "name": "Fremstillingsdato" - }, - "consumable_refilled_count_counterfeit_refilled_count": { - "name": "Forfalsket genopfyldt" - }, - "consumable_refilled_count_genuine_refilled_count": { - "name": "\u00c6gte genopfyldt" - }, - "consumable_warranty_expiration_date": { - "name": "Udl\u00f8bsdato" - }, - "copy_adf_images": { - "name": "Samlede kopier fra ADF" - }, - "copy_color_impressions": { - "name": "Samlede farvekopier" + "estimated_pages_remaining": { + "name": "Resterende" }, - "copy_flatbed_images": { + "flatbed_images": { "name": "Samlede sider fra scannerglas" }, - "copy_monochrome_impressions": { - "name": "Samlede sort-hvide kopier" - }, - "copy_total_impressions": { - "name": "Samlede kopier" - }, - "fax_total_impressions": { - "name": "Samlede faxer" - }, - "main_hardware_config_device_connectivity_port_type": { + "hardware_config_device_connectivity_port_type": { "name": "Porttype" }, - "main_manufacture_at": { - "name": "Fremstillingsdato" - }, - "printer_color_impressions": { - "name": "Samlede farvesider trykt" - }, - "printer_duplex_sheets": { - "name": "Samlede dobbeltsidede sider trykt" + "installation_date": { + "name": "Installationsdato" }, - "printer_jam_events": { + "jam_events": { "name": "Samlede syltet\u00f8j" }, - "printer_mispick_events": { - "name": "Total Miss Picks" - }, - "printer_monochrome_impressions": { - "name": "Samlede sorte-hvide sider trykt" + "manufacture_at": { + "name": "Fremstillingsdato" }, - "printer_simplex_sheets": { - "name": "Samlede enkeltsidede sider trykt" + "mispick_events": { + "name": "Total Miss Picks" }, - "printer_total_impressions": { - "name": "Samlede sider udskrivning" + "monochrome_impressions": { + "name": "Samlede sort-hvide sider" }, - "scanner_adf_images": { - "name": "Samlede scannede sider fra ADF" + "refilled_count_counterfeit_refilled_count": { + "name": "Forfalsket genopfyldt" }, - "scanner_duplex_sheets": { - "name": "Samlede dobbeltsidede sider scannet" + "refilled_count_genuine_refilled_count": { + "name": "\u00c6gte genopfyldt" }, - "scanner_flatbed_images": { - "name": "Samlede sider fra scannerglas" + "scan_images": { + "name": "Samlede sider" }, - "scanner_jam_events": { - "name": "Samlede syltet\u00f8j" + "simplex_sheets": { + "name": "Samlede enkeltsidede sider" }, - "scanner_mispick_events": { - "name": "Total Miss Picks" + "total_impressions": { + "name": "Samlede sider" }, - "scanner_scan_images": { - "name": "Samlede scannede sider" + "warranty_expiration_date": { + "name": "Udl\u00f8bsdato" } } }, "options": { "error": { - "already_configured": "HP Printer integration ({name}) er allerede konfigureret", - "error_400": "Ugyldige serveroplysninger, pr\u00c3\u00b8v venligst manuelt at f\u00c3\u00a5 adgang til `http://{IP}//DevMgmt/ProductStatusDyn.xml` (Erstat pladsholder med IP eller v\u00c3\u00a6rtsnavn ), hvis det er tilg\u00c3\u00a6ngeligt, bedes du rapportere problemet med logfiler", - "error_404": "Ikke underst\u00c3\u00b8ttet API" + "already_configured": "HP -printerintegration ({navn}) allerede konfigureret", + "error_400": "Ugyldigt serveroplysninger, pr\u00f8v manuelt adgang til `http: // {ip} // devmgmt/produktstatusdyn.xml` (udskift pladsholder med IP eller v\u00e6rtsnavn), hvis det er tilg\u00e6ngeligt, skal du rapportere problemet med logfiler", + "error_404": "Ikke -underst\u00f8ttet API" }, "step": { "hp_printer_additional_settings": { "data": { - "host": "V\u00c3\u00a6rt", - "log_level": "Log level", + "host": "V\u00e6rt", + "log_level": "Logniveau", "name": "Navn", - "store_data": "Skal svar gemmes?", + "store_data": "Skal gemme svar?", "update_interval": "Opdateringsinterval (sekunder)" }, - "description": "Definer yderligere indstillinger for HP Printer integration ", - "title": "Indstillinger for HP Printer." + "description": "Definer yderligere indstillinger til HP -printerintegration", + "title": "Valgmuligheder til HP -printer." }, "init": { "data": { diff --git a/custom_components/hpprinter/translations/en.json b/custom_components/hpprinter/translations/en.json index 8c464ed..6ba6737 100644 --- a/custom_components/hpprinter/translations/en.json +++ b/custom_components/hpprinter/translations/en.json @@ -4,7 +4,7 @@ "already_configured": "HP Printer integration ({name}) already configured" }, "error": { - "error_400": "Invalid server details, please try manually access to `http://{IP}//DevMgmt/ProductStatusDyn.xml` (Replace placeholder with IP or Hostname), in case it's accessible, please report the issue with logs", + "error_400": "Invalid server details, please try manually access to `http://{IP}/DevMgmt/ProductStatusDyn.xml` (Replace placeholder with IP or Hostname), in case it's accessible, please report the issue with logs", "error_404": "Unsupported API" }, "step": { @@ -21,27 +21,33 @@ }, "entity": { "binary_sensor": { - "consumable_consumable_life_state_consumable_state": { - "name": "Status" - }, - "main_cloud_services_switch_status": { + "cloud_services_switch_status": { "name": "ePrint Status" }, - "main_hardware_config_is_connected": { + "consumable_life_state_consumable_state": { + "name": "Status" + }, + "hardware_config_is_connected": { "name": "Connected" }, - "main_registration_state": { + "registration_state": { "name": "ePrint Registered" } }, "sensor": { - "consumable_consumable_percentage_level_remaining": { + "adf_images": { + "name": "Total pages from ADF" + }, + "color_impressions": { + "name": "Total color pages" + }, + "consumable_percentage_level_remaining": { "name": "Level" }, - "consumable_consumable_station": { + "consumable_station": { "name": "Station" }, - "consumable_consumable_type_enum": { + "consumable_type_enum": { "name": "Type", "state": { "ink": "Ink", @@ -50,93 +56,57 @@ "toner": "Toner" } }, - "consumable_estimated_pages_remaining": { - "name": "Remaining" - }, - "consumable_installation_date": { - "name": "Installation Date" - }, - "consumable_manufacture_at": { - "name": "Manufacture Date" - }, - "consumable_refilled_count_counterfeit_refilled_count": { - "name": "Counterfeit Refilled" - }, - "consumable_refilled_count_genuine_refilled_count": { - "name": "Genuine Refilled" - }, - "consumable_warranty_expiration_date": { - "name": "Expiration Date" - }, - "copy_adf_images": { - "name": "Total copies from ADF" + "duplex_sheets": { + "name": "Total double-sided pages" }, - "copy_color_impressions": { - "name": "Total color copies" + "estimated_pages_remaining": { + "name": "Remaining" }, - "copy_flatbed_images": { + "flatbed_images": { "name": "Total pages from scanner glass" }, - "copy_monochrome_impressions": { - "name": "Total black-and-white copies" - }, - "copy_total_impressions": { - "name": "Total copies" - }, - "fax_total_impressions": { - "name": "Total faxes" - }, - "main_hardware_config_device_connectivity_port_type": { + "hardware_config_device_connectivity_port_type": { "name": "Port Type" }, - "main_manufacture_at": { - "name": "Manufacture Date" - }, - "printer_color_impressions": { - "name": "Total color pages printed" - }, - "printer_duplex_sheets": { - "name": "Total double-sided pages printed" + "installation_date": { + "name": "Installation Date" }, - "printer_jam_events": { + "jam_events": { "name": "Total jams" }, - "printer_mispick_events": { - "name": "Total miss picks" - }, - "printer_monochrome_impressions": { - "name": "Total black-and-white pages printed" + "manufacture_at": { + "name": "Manufacture Date" }, - "printer_simplex_sheets": { - "name": "Total single-sided pages printed" + "mispick_events": { + "name": "Total miss picks" }, - "printer_total_impressions": { - "name": "Total pages printed" + "monochrome_impressions": { + "name": "Total black-and-white pages" }, - "scanner_adf_images": { - "name": "Total scanned pages from ADF" + "refilled_count_counterfeit_refilled_count": { + "name": "Counterfeit Refilled" }, - "scanner_duplex_sheets": { - "name": "Total double-sided pages scanned" + "refilled_count_genuine_refilled_count": { + "name": "Genuine Refilled" }, - "scanner_flatbed_images": { - "name": "Total pages from scanner glass" + "scan_images": { + "name": "Total pages" }, - "scanner_jam_events": { - "name": "Total jams" + "simplex_sheets": { + "name": "Total single-sided pages" }, - "scanner_mispick_events": { - "name": "Total miss picks" + "total_impressions": { + "name": "Total pages" }, - "scanner_scan_images": { - "name": "Total scanned pages" + "warranty_expiration_date": { + "name": "Expiration Date" } } }, "options": { "error": { "already_configured": "HP Printer integration ({name}) already configured", - "error_400": "Invalid server details, please try manually access to `http://{IP}//DevMgmt/ProductStatusDyn.xml` (Replace placeholder with IP or Hostname), in case it's accessible, please report the issue with logs", + "error_400": "Invalid server details, please try manually access to `http://{IP}/DevMgmt/ProductStatusDyn.xml` (Replace placeholder with IP or Hostname), in case it's accessible, please report the issue with logs", "error_404": "Unsupported API" }, "step": { diff --git a/custom_components/hpprinter/translations/es.json b/custom_components/hpprinter/translations/es.json index 0145254..a278203 100644 --- a/custom_components/hpprinter/translations/es.json +++ b/custom_components/hpprinter/translations/es.json @@ -1,47 +1,53 @@ { "config": { "abort": { - "already_configured": "La integraci\u00c3\u00b3n de la impresora HP ({name}) ya est\u00c3\u00a1 configurada" + "already_configured": "Integraci\u00f3n de la impresora HP ({nombre}) ya configurada" }, "error": { - "error_400": "Los detalles del servidor no son v\u00c3\u00a1lidos", - "error_404": "API no compatible" + "error_400": "Detalles del servidor no v\u00e1lidos, intente acceso manualmente a `http: // {ip} // devmgmt/productStatusDyn.xml` (reemplace el marcador de posici\u00f3n con IP o nombre de host), en caso de que est\u00e9 accesible, informe el problema con los registros", + "error_404": "API sin apoyo" }, "step": { "user": { "data": { - "host": "Host", + "host": "Anfitriona Anfitri\u00f3n", "name": "Nombre", "port": "N\u00famero de puerto", "ssl": "Es ssl" }, - "title": "Configurar impresora HP" + "title": "Configurar la impresora HP" } } }, "entity": { "binary_sensor": { - "consumable_consumable_life_state_consumable_state": { - "name": "Estado" - }, - "main_cloud_services_switch_status": { + "cloud_services_switch_status": { "name": "ePrint Estado" }, - "main_hardware_config_is_connected": { + "consumable_life_state_consumable_state": { + "name": "Estado" + }, + "hardware_config_is_connected": { "name": "Conectada Conectado" }, - "main_registration_state": { + "registration_state": { "name": "ePrint Registrado" } }, "sensor": { - "consumable_consumable_percentage_level_remaining": { + "adf_images": { + "name": "P\u00e1ginas totales de ADF" + }, + "color_impressions": { + "name": "P\u00e1ginas de colores totales" + }, + "consumable_percentage_level_remaining": { "name": "Nivel" }, - "consumable_consumable_station": { + "consumable_station": { "name": "Estaci\u00f3n" }, - "consumable_consumable_type_enum": { + "consumable_type_enum": { "name": "Tipo", "state": { "ink": "Tinta", @@ -50,105 +56,69 @@ "toner": "Virador" } }, - "consumable_estimated_pages_remaining": { - "name": "Restante" + "duplex_sheets": { + "name": "P\u00e1ginas totales de doble cara" }, - "consumable_installation_date": { - "name": "Fecha de instalaci\u00f3n" - }, - "consumable_manufacture_at": { - "name": "Fecha de fabricacion" - }, - "consumable_refilled_count_counterfeit_refilled_count": { - "name": "Falsificado" - }, - "consumable_refilled_count_genuine_refilled_count": { - "name": "Regilado genuino" - }, - "consumable_warranty_expiration_date": { - "name": "Fecha de caducidad" - }, - "copy_adf_images": { - "name": "Copias totales de ADF" - }, - "copy_color_impressions": { - "name": "Copias de color total" + "estimated_pages_remaining": { + "name": "Restante" }, - "copy_flatbed_images": { + "flatbed_images": { "name": "P\u00e1ginas totales del vidrio del esc\u00e1ner" }, - "copy_monochrome_impressions": { - "name": "Copias totales en blanco y negro" - }, - "copy_total_impressions": { - "name": "Copias totales" - }, - "fax_total_impressions": { - "name": "Faxes totales" - }, - "main_hardware_config_device_connectivity_port_type": { + "hardware_config_device_connectivity_port_type": { "name": "Tipo de puerto" }, - "main_manufacture_at": { - "name": "Fecha de fabricacion" - }, - "printer_color_impressions": { - "name": "P\u00e1ginas de color totales impresas" - }, - "printer_duplex_sheets": { - "name": "P\u00e1ginas totales de doble cara impresas" + "installation_date": { + "name": "Fecha de instalaci\u00f3n" }, - "printer_jam_events": { + "jam_events": { "name": "Mermeladas totales" }, - "printer_mispick_events": { - "name": "Total de las selecciones de la se\u00f1orita" - }, - "printer_monochrome_impressions": { - "name": "P\u00e1ginas totales en blanco y negro impresas" + "manufacture_at": { + "name": "Fecha de fabricacion" }, - "printer_simplex_sheets": { - "name": "P\u00e1ginas totales de un solo lado impreso" + "mispick_events": { + "name": "Total de las selecciones de la se\u00f1orita" }, - "printer_total_impressions": { - "name": "P\u00e1ginas totales impresas" + "monochrome_impressions": { + "name": "Total de p\u00e1ginas en blanco y negro" }, - "scanner_adf_images": { - "name": "P\u00e1ginas escanadas totales de ADF" + "refilled_count_counterfeit_refilled_count": { + "name": "Falsificado" }, - "scanner_duplex_sheets": { - "name": "P\u00e1ginas totales de doble cara escaneadas" + "refilled_count_genuine_refilled_count": { + "name": "Regilado genuino" }, - "scanner_flatbed_images": { - "name": "P\u00e1ginas totales del vidrio del esc\u00e1ner" + "scan_images": { + "name": "Paginas totales" }, - "scanner_jam_events": { - "name": "Mermeladas totales" + "simplex_sheets": { + "name": "P\u00e1ginas totales de un solo lado" }, - "scanner_mispick_events": { - "name": "Total de las selecciones de la se\u00f1orita" + "total_impressions": { + "name": "Paginas totales" }, - "scanner_scan_images": { - "name": "P\u00e1ginas escaneadas totales" + "warranty_expiration_date": { + "name": "Fecha de caducidad" } } }, "options": { "error": { - "already_configured": "La integraci\u00c3\u00b3n de la impresora HP ({name}) ya est\u00c3\u00a1 configurada", - "error_400": "Los detalles del servidor no son v\u00c3\u00a1lidos", - "error_404": "API no compatible" + "already_configured": "Integraci\u00f3n de la impresora HP ({nombre}) ya configurada", + "error_400": "Detalles del servidor no v\u00e1lidos, intente acceso manualmente a `http: // {ip} // devmgmt/productStatusDyn.xml` (reemplace el marcador de posici\u00f3n con IP o nombre de host), en caso de que est\u00e9 accesible, informe el problema con los registros", + "error_404": "API sin apoyo" }, "step": { "hp_printer_additional_settings": { "data": { - "host": "Host", - "log_level": "Nivel del registro", + "host": "Anfitriona Anfitri\u00f3n", + "log_level": "Nivel de registro", "name": "Nombre", - "store_data": "Deber\u00c3\u00ada almacenar respuestas?", - "update_interval": "Intervalo de actualizaci\u00c3\u00b3n (segundos)" + "store_data": "\u00bfDeber\u00edan las respuestas de la tienda?", + "update_interval": "Intervalo de actualizaci\u00f3n (segundos)" }, - "description": "Definir configuraciones adicionales para la integraci\u00c3\u00b3n de la impresora HP", + "description": "Definir configuraciones adicionales para la integraci\u00f3n de la impresora HP", "title": "Opciones para la impresora HP." }, "init": { diff --git a/custom_components/hpprinter/translations/fr.json b/custom_components/hpprinter/translations/fr.json index a5133c1..52ee58d 100644 --- a/custom_components/hpprinter/translations/fr.json +++ b/custom_components/hpprinter/translations/fr.json @@ -1,13 +1,16 @@ { "config": { + "abort": { + "already_configured": "HP Imprimante Integration ({name}) d\u00e9j\u00e0 configur\u00e9" + }, "error": { - "error_400": "Param\u00e8tres non valides fournis", - "error_404": "HP Imprimante Embedded Web Server (EWS) n'a pas \u00e9t\u00e9 trouv\u00e9e, veuillez consulter les journaux pour plus de d\u00e9tails" + "error_400": "D\u00e9tails du serveur non valides, veuillez essayer un acc\u00e8s manuellement \u00e0 `http: // {ip} // devmgmt / productStatusDyn.xml` (Remplacez l'espace r\u00e9serv\u00e9 par IP ou nom d'h\u00f4te), au cas o\u00f9 il est accessible, veuillez signaler le probl\u00e8me avec les journaux", + "error_404": "API non pris en charge" }, "step": { "user": { "data": { - "host": "H\u00c3\u00b4te", + "host": "H\u00f4tesse H\u00f4te", "name": "Nom", "port": "Num\u00e9ro de port", "ssl": "Est ssl" @@ -18,27 +21,33 @@ }, "entity": { "binary_sensor": { - "consumable_consumable_life_state_consumable_state": { - "name": "Statut" - }, - "main_cloud_services_switch_status": { + "cloud_services_switch_status": { "name": "ePrint Statut" }, - "main_hardware_config_is_connected": { + "consumable_life_state_consumable_state": { + "name": "Statut" + }, + "hardware_config_is_connected": { "name": "Connect\u00e9" }, - "main_registration_state": { + "registration_state": { "name": "ePrint enregistr\u00e9" } }, "sensor": { - "consumable_consumable_percentage_level_remaining": { + "adf_images": { + "name": "Pages totales de l'ADF" + }, + "color_impressions": { + "name": "Pages de couleur totale" + }, + "consumable_percentage_level_remaining": { "name": "Niveau" }, - "consumable_consumable_station": { + "consumable_station": { "name": "Gare" }, - "consumable_consumable_type_enum": { + "consumable_type_enum": { "name": "Taper", "state": { "ink": "Encre", @@ -47,95 +56,71 @@ "toner": "Toner" } }, - "consumable_estimated_pages_remaining": { - "name": "Restante Restant" - }, - "consumable_installation_date": { - "name": "Date d'installation" - }, - "consumable_manufacture_at": { - "name": "Date de fabrication" - }, - "consumable_refilled_count_counterfeit_refilled_count": { - "name": "Contrefait rempli" + "duplex_sheets": { + "name": "Pages totales double face" }, - "consumable_refilled_count_genuine_refilled_count": { - "name": "Authentique rempli" - }, - "consumable_warranty_expiration_date": { - "name": "Date d'expiration" - }, - "copy_adf_images": { - "name": "Total des copies de l'ADF" - }, - "copy_color_impressions": { - "name": "Copies de couleur totale" + "estimated_pages_remaining": { + "name": "Restante Restant" }, - "copy_flatbed_images": { + "flatbed_images": { "name": "Pages totales du verre du scanner" }, - "copy_monochrome_impressions": { - "name": "Copies totales en noir et blanc" - }, - "copy_total_impressions": { - "name": "Copies totales" - }, - "fax_total_impressions": { - "name": "T\u00e9l\u00e9copies totales" - }, - "main_hardware_config_device_connectivity_port_type": { + "hardware_config_device_connectivity_port_type": { "name": "Type de port" }, - "main_manufacture_at": { - "name": "Date de fabrication" - }, - "printer_color_impressions": { - "name": "Pages de couleurs totales imprim\u00e9es" - }, - "printer_duplex_sheets": { - "name": "Pages totales double face imprim\u00e9es" + "installation_date": { + "name": "Date d'installation" }, - "printer_jam_events": { + "jam_events": { "name": "Jams totaux" }, - "printer_mispick_events": { - "name": "Picks Miss Total" - }, - "printer_monochrome_impressions": { - "name": "Pages totales en noir et blanc imprim\u00e9es" + "manufacture_at": { + "name": "Date de fabrication" }, - "printer_simplex_sheets": { - "name": "Pages totales \u00e0 un seul c\u00f4t\u00e9 imprim\u00e9" + "mispick_events": { + "name": "Picks Miss Total" }, - "printer_total_impressions": { - "name": "Pages totales imprim\u00e9es" + "monochrome_impressions": { + "name": "Pages totales en noir et blanc" }, - "scanner_adf_images": { - "name": "Pages scann\u00e9es totales de l'ADF" + "refilled_count_counterfeit_refilled_count": { + "name": "Contrefait rempli" }, - "scanner_duplex_sheets": { - "name": "Pages totales double face num\u00e9ris\u00e9es" + "refilled_count_genuine_refilled_count": { + "name": "Authentique rempli" }, - "scanner_flatbed_images": { - "name": "Pages totales du verre du scanner" + "scan_images": { + "name": "Pages totales" }, - "scanner_jam_events": { - "name": "Jams totaux" + "simplex_sheets": { + "name": "Pages totales \u00e0 un c\u00f4t\u00e9 unique" }, - "scanner_mispick_events": { - "name": "Picks Miss Total" + "total_impressions": { + "name": "Pages totales" }, - "scanner_scan_images": { - "name": "Pages scann\u00e9es totales" + "warranty_expiration_date": { + "name": "Date d'expiration" } } }, "options": { "error": { - "error_400": "Param\u00e8tres non valides fournis", - "error_404": "HP Imprimante Embedded Web Server (EWS) n'a pas \u00e9t\u00e9 trouv\u00e9e, veuillez consulter les journaux pour plus de d\u00e9tails" + "already_configured": "HP Imprimante Integration ({name}) d\u00e9j\u00e0 configur\u00e9", + "error_400": "D\u00e9tails du serveur non valides, veuillez essayer un acc\u00e8s manuellement \u00e0 `http: // {ip} // devmgmt / productStatusDyn.xml` (Remplacez l'espace r\u00e9serv\u00e9 par IP ou nom d'h\u00f4te), au cas o\u00f9 il est accessible, veuillez signaler le probl\u00e8me avec les journaux", + "error_404": "API non pris en charge" }, "step": { + "hp_printer_additional_settings": { + "data": { + "host": "H\u00f4tesse H\u00f4te", + "log_level": "Niveau de journal", + "name": "Nom", + "store_data": "Doit les r\u00e9ponses du magasin?", + "update_interval": "Mettre \u00e0 jour l'intervalle (secondes)" + }, + "description": "D\u00e9finir des param\u00e8tres suppl\u00e9mentaires pour l'int\u00e9gration de l'imprimante HP", + "title": "Options pour l'imprimante HP." + }, "init": { "data": { "host": "Nom d'h\u00f4te ou IP", diff --git a/custom_components/hpprinter/translations/nb.json b/custom_components/hpprinter/translations/nb.json index 933fed5..5fdb47f 100644 --- a/custom_components/hpprinter/translations/nb.json +++ b/custom_components/hpprinter/translations/nb.json @@ -1,11 +1,11 @@ { "config": { "abort": { - "already_configured": "HP-skriverintegrasjon ({name}) er allerede konfigurert" + "already_configured": "HP Printer Integration ({name}) allerede konfigurert" }, "error": { - "error_400": "Ugyldige serveropplysninger, pr\u00c3\u00b8v manuelt \u00c3\u00a5 f\u00c3\u00a5 tilgang til `http://{IP}//DevMgmt/ProductStatusDyn.xml` (Erstatt plassholder med IP eller vertsnavn), hvis det er tilgjengelig, vennligst rapporter problemet med logger", - "error_404": "API st\u00c3\u00b8ttes ikke" + "error_400": "Ugyldige serverdetaljer, pr\u00f8v manuelt tilgang til `http: // {ip} // devmgmt/produktstatusdyn.xml` (erstatt plassholder med IP eller vertsnavn), i tilfelle det er tilgjengelig, vennligst rapporter problemet med logger", + "error_404": "Ikke st\u00f8ttet API" }, "step": { "user": { @@ -15,33 +15,39 @@ "port": "Portnummer", "ssl": "Er SSL" }, - "title": "Sett opp HP-skriver" + "title": "Sett opp HP -skriver" } } }, "entity": { "binary_sensor": { - "consumable_consumable_life_state_consumable_state": { - "name": "Status" - }, - "main_cloud_services_switch_status": { + "cloud_services_switch_status": { "name": "ePrint Status" }, - "main_hardware_config_is_connected": { + "consumable_life_state_consumable_state": { + "name": "Status" + }, + "hardware_config_is_connected": { "name": "Tilkoblet" }, - "main_registration_state": { + "registration_state": { "name": "ePrint Registrert" } }, "sensor": { - "consumable_consumable_percentage_level_remaining": { + "adf_images": { + "name": "Totalt sider fra ADF" + }, + "color_impressions": { + "name": "Total fargesider" + }, + "consumable_percentage_level_remaining": { "name": "Niv\u00e5" }, - "consumable_consumable_station": { + "consumable_station": { "name": "Stasjon" }, - "consumable_consumable_type_enum": { + "consumable_type_enum": { "name": "Type", "state": { "ink": "Blekk", @@ -50,106 +56,70 @@ "toner": "Toner" } }, - "consumable_estimated_pages_remaining": { - "name": "Gjenst\u00e5ende" + "duplex_sheets": { + "name": "Totalt tosidige sider" }, - "consumable_installation_date": { - "name": "Installasjonsdato" - }, - "consumable_manufacture_at": { - "name": "Produksjonsdato" - }, - "consumable_refilled_count_counterfeit_refilled_count": { - "name": "Forfalsket p\u00e5fyllt" - }, - "consumable_refilled_count_genuine_refilled_count": { - "name": "Ekte p\u00e5fyllt" - }, - "consumable_warranty_expiration_date": { - "name": "Utl\u00f8psdato" - }, - "copy_adf_images": { - "name": "Totalt eksemplarer fra ADF" - }, - "copy_color_impressions": { - "name": "Total fargekopier" + "estimated_pages_remaining": { + "name": "Gjenst\u00e5ende" }, - "copy_flatbed_images": { + "flatbed_images": { "name": "Totalt sider fra skannerglass" }, - "copy_monochrome_impressions": { - "name": "Totalt svart-hvitt eksemplarer" - }, - "copy_total_impressions": { - "name": "Totalt eksemplarer" - }, - "fax_total_impressions": { - "name": "Totalt fakser" - }, - "main_hardware_config_device_connectivity_port_type": { + "hardware_config_device_connectivity_port_type": { "name": "Porttype" }, - "main_manufacture_at": { - "name": "Produksjonsdato" - }, - "printer_color_impressions": { - "name": "Total fargesider trykt" - }, - "printer_duplex_sheets": { - "name": "Totalt tosidige sider trykt" + "installation_date": { + "name": "Installasjonsdato" }, - "printer_jam_events": { + "jam_events": { "name": "Total syltet\u00f8y" }, - "printer_mispick_events": { - "name": "Totalt glipp av valg" - }, - "printer_monochrome_impressions": { - "name": "Total svart-hvitt sider trykt" + "manufacture_at": { + "name": "Produksjonsdato" }, - "printer_simplex_sheets": { - "name": "Totalt enkeltsidede sider trykt" + "mispick_events": { + "name": "Totalt glipp av valg" }, - "printer_total_impressions": { - "name": "Totalt sider trykt" + "monochrome_impressions": { + "name": "Totalt svart-hvitt sider" }, - "scanner_adf_images": { - "name": "Total skannede sider fra ADF" + "refilled_count_counterfeit_refilled_count": { + "name": "Forfalsket p\u00e5fyllt" }, - "scanner_duplex_sheets": { - "name": "Totalt tosidige sider skannet" + "refilled_count_genuine_refilled_count": { + "name": "Ekte p\u00e5fyllt" }, - "scanner_flatbed_images": { - "name": "Totalt sider fra skannerglass" + "scan_images": { + "name": "Totalt sider" }, - "scanner_jam_events": { - "name": "Total syltet\u00f8y" + "simplex_sheets": { + "name": "Totalt ensidige sider" }, - "scanner_mispick_events": { - "name": "Totalt glipp av valg" + "total_impressions": { + "name": "Totalt sider" }, - "scanner_scan_images": { - "name": "Totalt skannede sider" + "warranty_expiration_date": { + "name": "Utl\u00f8psdato" } } }, "options": { "error": { - "already_configured": "HP-skriverintegrasjon ({name}) er allerede konfigurert", - "error_400": "Ugyldige serveropplysninger. Pr\u00c3\u00b8v manuelt \u00c3\u00a5 f\u00c3\u00a5 tilgang ti `http://{IP}//DevMgmt/ProductStatusDyn.xml` (Erstatt plassholder med IP eller vertsnavn), hvis det er tilgjengelig, vennligst rapporter problemet med logger", - "error_404": "API st\u00c3\u00b8ttes ikke" + "already_configured": "HP Printer Integration ({name}) allerede konfigurert", + "error_400": "Ugyldige serverdetaljer, pr\u00f8v manuelt tilgang til `http: // {ip} // devmgmt/produktstatusdyn.xml` (erstatt plassholder med IP eller vertsnavn), i tilfelle det er tilgjengelig, vennligst rapporter problemet med logger", + "error_404": "Ikke st\u00f8ttet API" }, "step": { "hp_printer_additional_settings": { "data": { "host": "Vert", - "log_level": "Loggniv\u00c3\u00a5", + "log_level": "Loggniv\u00e5", "name": "Navn", - "store_data": "B\u00c3\u00b8r lagre svar?", + "store_data": "B\u00f8r lagre svar?", "update_interval": "Oppdateringsintervall (sekunder)" }, - "description": "Definer tilleggsinnstillinger for HP-skriverintegrasjon", - "title": "Alternativer for HP-skriver." + "description": "Definer flere innstillinger for HP -skriverintegrasjon", + "title": "Alternativer for HP -skriver." }, "init": { "data": { diff --git a/custom_components/hpprinter/translations/nl.json b/custom_components/hpprinter/translations/nl.json index e2046e8..8539ee4 100644 --- a/custom_components/hpprinter/translations/nl.json +++ b/custom_components/hpprinter/translations/nl.json @@ -1,45 +1,53 @@ { "config": { + "abort": { + "already_configured": "HP Printer Integration ({Name}) al geconfigureerd" + }, "error": { - "cannot_reach_printer": "HP Printer is onbereikbaar vanwege een van de volgende redenen: de hostnaam is verkeerd, de printer is niet verbonden of de printer ondersteund de API in deze integratie niet", - "error_400": "Ongeldige parameters verstrekt", - "error_404": "HP Printer Embedded Web Server (EWS) is niet niet gevonden, controleer logboeken voor meer informatie" + "error_400": "Ongeldige servergegevens, probeer handmatig toegang tot `http: // {ip} // devmgmt/ProductStatusyn.xml` (Vervang Placeholder door IP of hostnaam), Rapporteer het probleem met logs met logs met logs", + "error_404": "Niet -ondersteunde API" }, "step": { "user": { "data": { - "host": "Host", + "host": "Gastheer", "name": "Naam", "port": "Poortnummer", "ssl": "Is SSL" }, - "title": "HP Printer instellen" + "title": "HP -printer instellen" } } }, "entity": { "binary_sensor": { - "consumable_consumable_life_state_consumable_state": { - "name": "Toestand" - }, - "main_cloud_services_switch_status": { + "cloud_services_switch_status": { "name": "ePrint Toestand" }, - "main_hardware_config_is_connected": { + "consumable_life_state_consumable_state": { + "name": "Toestand" + }, + "hardware_config_is_connected": { "name": "Verbonden" }, - "main_registration_state": { + "registration_state": { "name": "ePrint Geregistreerd" } }, "sensor": { - "consumable_consumable_percentage_level_remaining": { + "adf_images": { + "name": "Totale pagina's van ADF" + }, + "color_impressions": { + "name": "Totale kleurpagina's" + }, + "consumable_percentage_level_remaining": { "name": "Niveau" }, - "consumable_consumable_station": { + "consumable_station": { "name": "Station" }, - "consumable_consumable_type_enum": { + "consumable_type_enum": { "name": "Type", "state": { "ink": "Inkt", @@ -48,95 +56,71 @@ "toner": "Toner" } }, - "consumable_estimated_pages_remaining": { - "name": "Overig" - }, - "consumable_installation_date": { - "name": "Installatie datum" - }, - "consumable_manufacture_at": { - "name": "Productiedatum" - }, - "consumable_refilled_count_counterfeit_refilled_count": { - "name": "Vervalste bijgevuld" + "duplex_sheets": { + "name": "Totale dubbelzijdige pagina's" }, - "consumable_refilled_count_genuine_refilled_count": { - "name": "Echt bijgevuld" - }, - "consumable_warranty_expiration_date": { - "name": "uiterste houdbaarheidsdatum" - }, - "copy_adf_images": { - "name": "Totale exemplaren van ADF" - }, - "copy_color_impressions": { - "name": "Totale kleurkopie\u00ebn" + "estimated_pages_remaining": { + "name": "Overig" }, - "copy_flatbed_images": { + "flatbed_images": { "name": "Totale pagina's van scannerglas" }, - "copy_monochrome_impressions": { - "name": "Totaal zwart-wit kopie\u00ebn" - }, - "copy_total_impressions": { - "name": "Totale exemplaren" - }, - "fax_total_impressions": { - "name": "Totaal faxen" - }, - "main_hardware_config_device_connectivity_port_type": { + "hardware_config_device_connectivity_port_type": { "name": "Poorttype" }, - "main_manufacture_at": { - "name": "Productiedatum" - }, - "printer_color_impressions": { - "name": "Totale kleurpagina's afgedrukt" - }, - "printer_duplex_sheets": { - "name": "Totaal dubbelzijdige pagina's afgedrukt" + "installation_date": { + "name": "Installatie datum" }, - "printer_jam_events": { + "jam_events": { "name": "Totale jam" }, - "printer_mispick_events": { - "name": "Totale Miss Picks" - }, - "printer_monochrome_impressions": { - "name": "Totaal zwart-wit pagina's afgedrukt" + "manufacture_at": { + "name": "Productiedatum" }, - "printer_simplex_sheets": { - "name": "Totaal afgedrukte pagina's met \u00e9\u00e9n zijdeling" + "mispick_events": { + "name": "Totale Miss Picks" }, - "printer_total_impressions": { - "name": "Totale pagina's afgedrukt" + "monochrome_impressions": { + "name": "Totaal zwart-witpagina's" }, - "scanner_adf_images": { - "name": "Totaal gescande pagina's van ADF" + "refilled_count_counterfeit_refilled_count": { + "name": "Vervalste bijgevuld" }, - "scanner_duplex_sheets": { - "name": "Totale dubbelzijdige pagina's gescand" + "refilled_count_genuine_refilled_count": { + "name": "Echt bijgevuld" }, - "scanner_flatbed_images": { - "name": "Totale pagina's van scannerglas" + "scan_images": { + "name": "Totale pagina's" }, - "scanner_jam_events": { - "name": "Totale jam" + "simplex_sheets": { + "name": "Totaal enkelzijdige pagina's" }, - "scanner_mispick_events": { - "name": "Totale Miss Picks" + "total_impressions": { + "name": "Totale pagina's" }, - "scanner_scan_images": { - "name": "Totaal gescande pagina's" + "warranty_expiration_date": { + "name": "uiterste houdbaarheidsdatum" } } }, "options": { "error": { - "error_400": "Ongeldige parameters verstrekt", - "error_404": "HP Printer Embedded Web Server (EWS) is niet niet gevonden, controleer logboeken voor meer informatie" + "already_configured": "HP Printer Integration ({Name}) al geconfigureerd", + "error_400": "Ongeldige servergegevens, probeer handmatig toegang tot `http: // {ip} // devmgmt/ProductStatusyn.xml` (Vervang Placeholder door IP of hostnaam), Rapporteer het probleem met logs met logs met logs", + "error_404": "Niet -ondersteunde API" }, "step": { + "hp_printer_additional_settings": { + "data": { + "host": "Gastheer", + "log_level": "Log niveau", + "name": "Naam", + "store_data": "Moeten de reacties opslaan?", + "update_interval": "Update interval (seconden)" + }, + "description": "Definieer extra instellingen voor HP -printerintegratie", + "title": "Opties voor HP -printer." + }, "init": { "data": { "host": "Hostnaam of ip", diff --git a/custom_components/hpprinter/translations/pl.json b/custom_components/hpprinter/translations/pl.json index 6b32e9e..be8d1ee 100644 --- a/custom_components/hpprinter/translations/pl.json +++ b/custom_components/hpprinter/translations/pl.json @@ -1,47 +1,53 @@ { "config": { "abort": { - "already_configured": "Integracja drukarki HP ({name}) jest ju\u00c5\u00bc skonfigurowana" + "already_configured": "Integracja drukarki HP ({name}) ju\u017c skonfigurowana" }, "error": { - "error_400": "Nieprawid\u00c5\u201aowe dane serwera, spr\u00c3\u00b3buj r\u00c4\u2122cznie uzyska\u00c4\u2021 otworzy\u00c4\u2021 `http://{IP}//DevMgmt/ProductStatusDyn.xml` (Zamie\u00c5\u201e tekst zast\u00c4\u2122pczy na IP lub nazw\u00c4\u2122 hosta), je\u00c5\u203ali to mo\u00c5\u00bcliwe - zg\u00c5\u201ao\u00c5\u203a problem wraz z logami.", - "error_404": "Niewspierane API" + "error_400": "Nieprawid\u0142owe szczeg\u00f3\u0142y serwera, spr\u00f3buj r\u0119cznie uzyska\u0107 dost\u0119p do `http: // {ip} // devmgmt/productStatusdyn.xml` (zamie\u0144 symbol zast\u0119pczy na IP lub nazwa hosta), je\u015bli jest dost\u0119pny, zg\u0142o\u015b problem z dziennikami", + "error_404": "Nieobs\u0142ugiwany API" }, "step": { "user": { "data": { - "host": "Host lub IP", + "host": "Gospodarz", "name": "Nazwa", "port": "Numer portu", "ssl": "Jest SSL" }, - "title": "Skonfiguruj drukark\u00c4\u2122 HP" + "title": "Skonfiguruj drukark\u0119 HP" } } }, "entity": { "binary_sensor": { - "consumable_consumable_life_state_consumable_state": { - "name": "Status" - }, - "main_cloud_services_switch_status": { + "cloud_services_switch_status": { "name": "ePrint Status" }, - "main_hardware_config_is_connected": { + "consumable_life_state_consumable_state": { + "name": "Status" + }, + "hardware_config_is_connected": { "name": "Po\u0142\u0105czony" }, - "main_registration_state": { + "registration_state": { "name": "ePrint Zarejestrowany" } }, "sensor": { - "consumable_consumable_percentage_level_remaining": { + "adf_images": { + "name": "Ca\u0142kowite strony z ADF" + }, + "color_impressions": { + "name": "Ca\u0142kowite strony kolorowe" + }, + "consumable_percentage_level_remaining": { "name": "Poziom" }, - "consumable_consumable_station": { + "consumable_station": { "name": "Stacja" }, - "consumable_consumable_type_enum": { + "consumable_type_enum": { "name": "Typ", "state": { "ink": "Atrament", @@ -50,106 +56,70 @@ "toner": "Toner" } }, - "consumable_estimated_pages_remaining": { - "name": "Pozosta\u0142y" - }, - "consumable_installation_date": { - "name": "Data instalacji" - }, - "consumable_manufacture_at": { - "name": "Data produkcji" - }, - "consumable_refilled_count_counterfeit_refilled_count": { - "name": "FRAKTHICE FOLOKLED" - }, - "consumable_refilled_count_genuine_refilled_count": { - "name": "Oryginalny nape\u0142niony" + "duplex_sheets": { + "name": "Ca\u0142kowita dwustronna strony" }, - "consumable_warranty_expiration_date": { - "name": "Termin wa\u017cno\u015bci" - }, - "copy_adf_images": { - "name": "Ca\u0142kowite kopie z ADF" - }, - "copy_color_impressions": { - "name": "Ca\u0142kowite kopie kolor\u00f3w" + "estimated_pages_remaining": { + "name": "Pozosta\u0142y" }, - "copy_flatbed_images": { + "flatbed_images": { "name": "Ca\u0142kowite strony ze szk\u0142a skanera" }, - "copy_monochrome_impressions": { - "name": "Ca\u0142kowite czarno-bia\u0142e kopie" - }, - "copy_total_impressions": { - "name": "Ca\u0142kowite kopie" - }, - "fax_total_impressions": { - "name": "Ca\u0142kowite faksy" - }, - "main_hardware_config_device_connectivity_port_type": { + "hardware_config_device_connectivity_port_type": { "name": "Typ portu" }, - "main_manufacture_at": { - "name": "Data produkcji" - }, - "printer_color_impressions": { - "name": "Wydrukowano ca\u0142kowit\u0105 strony kolor\u00f3w" - }, - "printer_duplex_sheets": { - "name": "Wydrukowano ca\u0142kowit\u0105 dwustronne strony" + "installation_date": { + "name": "Data instalacji" }, - "printer_jam_events": { + "jam_events": { "name": "Ca\u0142kowite zaci\u0119cia" }, - "printer_mispick_events": { - "name": "Total Miss Pick" - }, - "printer_monochrome_impressions": { - "name": "Wydrukowane ca\u0142kowit\u0105 czarno-bia\u0142e strony" + "manufacture_at": { + "name": "Data produkcji" }, - "printer_simplex_sheets": { - "name": "Wydrukowano ca\u0142kowit\u0105 jednostronne strony" + "mispick_events": { + "name": "Total Miss Pick" }, - "printer_total_impressions": { - "name": "Wydrukowano ca\u0142kowit\u0105 strony" + "monochrome_impressions": { + "name": "Ca\u0142kowite czarno-bia\u0142e strony" }, - "scanner_adf_images": { - "name": "Ca\u0142kowite zeskanowane strony z ADF" + "refilled_count_counterfeit_refilled_count": { + "name": "FRAKTHICE FOLOKLED" }, - "scanner_duplex_sheets": { - "name": "Ca\u0142kowita dwustronna strony zeskanowane" + "refilled_count_genuine_refilled_count": { + "name": "Oryginalny nape\u0142niony" }, - "scanner_flatbed_images": { - "name": "Ca\u0142kowite strony ze szk\u0142a skanera" + "scan_images": { + "name": "Wszystkie strony" }, - "scanner_jam_events": { - "name": "Ca\u0142kowite zaci\u0119cia" + "simplex_sheets": { + "name": "Ca\u0142kowita jednostronna strony" }, - "scanner_mispick_events": { - "name": "Total Miss Pick" + "total_impressions": { + "name": "Wszystkie strony" }, - "scanner_scan_images": { - "name": "Ca\u0142kowite zeskanowane strony" + "warranty_expiration_date": { + "name": "Termin wa\u017cno\u015bci" } } }, "options": { "error": { - "already_configured": "Integracja drukarki HP ({name}) jest ju\u00c5\u00bc skonfigurowana", - "error_400": "Nieprawid\u00c5\u201aowe dane serwera, spr\u00c3\u00b3buj r\u00c4\u2122cznie uzyska\u00c4\u2021 otworzy\u00c4\u2021 `http://{IP}//DevMgmt/ProductStatusDyn.xml` (Zamie\u00c5\u201e tekst zast\u00c4\u2122pczy na IP lub nazw\u00c4\u2122 hosta), je\u00c5\u203ali to mo\u00c5\u00bcliwe - zg\u00c5\u201ao\u00c5\u203a problem wraz z logami.", - "error_404": "Niewspierane API" + "already_configured": "Integracja drukarki HP ({name}) ju\u017c skonfigurowana", + "error_400": "Nieprawid\u0142owe szczeg\u00f3\u0142y serwera, spr\u00f3buj r\u0119cznie uzyska\u0107 dost\u0119p do `http: // {ip} // devmgmt/productStatusdyn.xml` (zamie\u0144 symbol zast\u0119pczy na IP lub nazwa hosta), je\u015bli jest dost\u0119pny, zg\u0142o\u015b problem z dziennikami", + "error_404": "Nieobs\u0142ugiwany API" }, "step": { "hp_printer_additional_settings": { "data": { - "host": "Host lub IP", - "log_level": "Poziom logowania", + "host": "Gospodarz", + "log_level": "Poziom dziennika", "name": "Nazwa", - "store_data": "Czy nale\u00c5\u00bcy przechowywa\u00c4\u2021 odpowiedzi?", - "update_interval": "Interwa\u00c5\u201a aktualizacji (sekundy)" + "store_data": "Powinien przechowywa\u0107 odpowiedzi?", + "update_interval": "Interwa\u0142 aktualizacji (sekundy)" }, - "description": "Zdefiniuj dodatkowe ustawienia dla integracji drukarki HP", - "title": "Opcje dla drukarki HP." + "description": "Zdefiniuj dodatkowe ustawienia integracji drukarki HP", + "title": "Opcje drukarki HP." }, "init": { "data": { diff --git a/custom_components/hpprinter/translations/pt-BR.json b/custom_components/hpprinter/translations/pt-BR.json index f6b300c..482dcd3 100644 --- a/custom_components/hpprinter/translations/pt-BR.json +++ b/custom_components/hpprinter/translations/pt-BR.json @@ -1,47 +1,53 @@ { "config": { "abort": { - "already_configured": "Integra\u00c3\u00a7\u00c3\u00a3o HP Printer ({name}) j\u00c3\u00a1 configurada" + "already_configured": "Integra\u00e7\u00e3o da impressora HP ({nome}) j\u00e1 configurada" }, "error": { - "error_400": "Detalhes do servidor inv\u00c3\u00a1lidos, tente acessar manualmente `http://{IP}//DevMgmt/ProductStatusDyn.xml` (Substituir o marcador de posi\u00c3\u00a7\u00c3\u00a3o por IP ou Hostname), caso esteja acess\u00c3\u00advel, informe o problema com os logs", - "error_404": "API Sem suporte" + "error_400": "Detalhes do servidor inv\u00e1lido, tente acessar manualmente para `http: // {ip} // devmgmt/productStatusdyn.xml` (substitua o espa\u00e7o reservado por IP ou nome de host), caso esteja acess\u00edvel, relate o problema com os logs", + "error_404": "API n\u00e3o suportada" }, "step": { "user": { "data": { - "host": "Host", + "host": "Hospedar", "name": "Nome", "port": "N\u00famero da porta", "ssl": "\u00c9 ssl" }, - "title": "Configurar HP Printer" + "title": "Configurar impressora HP" } } }, "entity": { "binary_sensor": { - "consumable_consumable_life_state_consumable_state": { - "name": "Status" - }, - "main_cloud_services_switch_status": { + "cloud_services_switch_status": { "name": "ePrint Status" }, - "main_hardware_config_is_connected": { + "consumable_life_state_consumable_state": { + "name": "Status" + }, + "hardware_config_is_connected": { "name": "Conectada Conectado" }, - "main_registration_state": { + "registration_state": { "name": "ePrint Registrado" } }, "sensor": { - "consumable_consumable_percentage_level_remaining": { + "adf_images": { + "name": "P\u00e1ginas totais do ADF" + }, + "color_impressions": { + "name": "P\u00e1ginas coloridas totais" + }, + "consumable_percentage_level_remaining": { "name": "N\u00edvel" }, - "consumable_consumable_station": { + "consumable_station": { "name": "Esta\u00e7\u00e3o" }, - "consumable_consumable_type_enum": { + "consumable_type_enum": { "name": "Tipo", "state": { "ink": "Tinta", @@ -50,106 +56,70 @@ "toner": "Toner" } }, - "consumable_estimated_pages_remaining": { - "name": "Restante" - }, - "consumable_installation_date": { - "name": "Data de instala\u00e7\u00e3o" - }, - "consumable_manufacture_at": { - "name": "Data de fabrica\u00e7\u00e3o" - }, - "consumable_refilled_count_counterfeit_refilled_count": { - "name": "Falsificado reabastecido" - }, - "consumable_refilled_count_genuine_refilled_count": { - "name": "Reabastecido genu\u00edno" + "duplex_sheets": { + "name": "P\u00e1ginas totais de dupla face" }, - "consumable_warranty_expiration_date": { - "name": "Data de validade" - }, - "copy_adf_images": { - "name": "Total de c\u00f3pias do ADF" - }, - "copy_color_impressions": { - "name": "C\u00f3pias de cores totais" + "estimated_pages_remaining": { + "name": "Restante" }, - "copy_flatbed_images": { + "flatbed_images": { "name": "P\u00e1ginas totais de vidro do scanner" }, - "copy_monochrome_impressions": { - "name": "C\u00f3pias totais em preto e branco" - }, - "copy_total_impressions": { - "name": "C\u00f3pias totais" - }, - "fax_total_impressions": { - "name": "Faxes totais" - }, - "main_hardware_config_device_connectivity_port_type": { + "hardware_config_device_connectivity_port_type": { "name": "Tipo de porta" }, - "main_manufacture_at": { - "name": "Data de fabrica\u00e7\u00e3o" - }, - "printer_color_impressions": { - "name": "P\u00e1ginas coloridas totais impressas" - }, - "printer_duplex_sheets": { - "name": "P\u00e1ginas totais de dupla face impressas" + "installation_date": { + "name": "Data de instala\u00e7\u00e3o" }, - "printer_jam_events": { + "jam_events": { "name": "Total Jams" }, - "printer_mispick_events": { - "name": "Total Miss Picks" - }, - "printer_monochrome_impressions": { - "name": "P\u00e1ginas totais em preto e branco impressas" + "manufacture_at": { + "name": "Data de fabrica\u00e7\u00e3o" }, - "printer_simplex_sheets": { - "name": "P\u00e1ginas totais de um lado impresso" + "mispick_events": { + "name": "Total Miss Picks" }, - "printer_total_impressions": { - "name": "P\u00e1ginas totais impressas" + "monochrome_impressions": { + "name": "P\u00e1ginas totais em preto e branco" }, - "scanner_adf_images": { - "name": "P\u00e1ginas digitalizadas totais do ADF" + "refilled_count_counterfeit_refilled_count": { + "name": "Falsificado reabastecido" }, - "scanner_duplex_sheets": { - "name": "P\u00e1ginas totais totais digitalizadas" + "refilled_count_genuine_refilled_count": { + "name": "Reabastecido genu\u00edno" }, - "scanner_flatbed_images": { - "name": "P\u00e1ginas totais de vidro do scanner" + "scan_images": { + "name": "P\u00e1ginas totais" }, - "scanner_jam_events": { - "name": "Total Jams" + "simplex_sheets": { + "name": "P\u00e1ginas totais de um lado" }, - "scanner_mispick_events": { - "name": "Total Miss Picks" + "total_impressions": { + "name": "P\u00e1ginas totais" }, - "scanner_scan_images": { - "name": "P\u00e1ginas digitalizadas totais" + "warranty_expiration_date": { + "name": "Data de validade" } } }, "options": { "error": { - "already_configured": "Integra\u00c3\u00a7\u00c3\u00a3o HP Printer ({name}) j\u00c3\u00a1 configurada", - "error_400": "Detalhes do servidor inv\u00c3\u00a1lidos, tente acessar manualmente `http://{IP}//DevMgmt/ProductStatusDyn.xml` (Substituir o marcador de posi\u00c3\u00a7\u00c3\u00a3o por IP ou Hostname), caso esteja acess\u00c3\u00advel, informe o problema com os logs", - "error_404": "API sem suporte" + "already_configured": "Integra\u00e7\u00e3o da impressora HP ({nome}) j\u00e1 configurada", + "error_400": "Detalhes do servidor inv\u00e1lido, tente acessar manualmente para `http: // {ip} // devmgmt/productStatusdyn.xml` (substitua o espa\u00e7o reservado por IP ou nome de host), caso esteja acess\u00edvel, relate o problema com os logs", + "error_404": "API n\u00e3o suportada" }, "step": { "hp_printer_additional_settings": { "data": { - "host": "Host", - "log_level": "N\u00c3\u00advel de registro", + "host": "Hospedar", + "log_level": "N\u00edvel de log", "name": "Nome", "store_data": "Deve armazenar respostas?", - "update_interval": "Intervalo de atualiza\u00c3\u00a7\u00c3\u00a3o (segundos)" + "update_interval": "Intervalo de atualiza\u00e7\u00e3o (segundos)" }, - "description": "Defina configura\u00c3\u00a7\u00c3\u00b5es adicionais para a integra\u00c3\u00a7\u00c3\u00a3o HP Printer", - "title": "Op\u00c3\u00a7\u00c3\u00b5es para HP Printer." + "description": "Defina configura\u00e7\u00f5es adicionais para a integra\u00e7\u00e3o da impressora HP", + "title": "Op\u00e7\u00f5es para a impressora HP." }, "init": { "data": { diff --git a/utils/generate_translations.py b/utils/generate_translations.py index ce51789..4585a70 100644 --- a/utils/generate_translations.py +++ b/utils/generate_translations.py @@ -161,8 +161,11 @@ def _get_translations(lang: str): parent_directory = current_path.parents[1] file_path = os.path.join(parent_directory, "custom_components", "hpprinter", "translations", f"{lang}.json") - with open(file_path) as file: - data = json.load(file) + if os.path.exists(file_path): + with open(file_path) as file: + data = json.load(file) + else: + data = {} return data From b1cc44028de46111c8615bd945e5cc11aef436f6 Mon Sep 17 00:00:00 2001 From: Elad Bar Date: Fri, 3 May 2024 19:09:21 +0300 Subject: [PATCH 19/25] change version to 2.0.0b5 --- CHANGELOG.md | 2 +- custom_components/hpprinter/manifest.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 57bcbf7..67f616b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## v2.0.0b4 +## 2.0.0b5 - Support no prefetch mode - Fix all translations diff --git a/custom_components/hpprinter/manifest.json b/custom_components/hpprinter/manifest.json index d9b139d..580c02f 100644 --- a/custom_components/hpprinter/manifest.json +++ b/custom_components/hpprinter/manifest.json @@ -9,5 +9,5 @@ "iot_class": "local_polling", "issue_tracker": "https://github.com/elad-bar/ha-hpprinter/issues", "requirements": ["xmltodict~=0.13.0", "flatten_json", "defusedxml"], - "version": "2.0.0b4" + "version": "2.0.0b5" } From 543c61fbcc741be97d8380168ad7068cca41c48d Mon Sep 17 00:00:00 2001 From: Elad Bar Date: Tue, 7 May 2024 20:14:19 +0300 Subject: [PATCH 20/25] When constructing device name, avoid null parts of it, Changed the logic of errors from not found endpoints --- CHANGELOG.md | 9 ++++++++ .../hpprinter/managers/ha_coordinator.py | 6 ++++++ .../hpprinter/managers/rest_api.py | 21 +++++++++++++------ custom_components/hpprinter/manifest.json | 2 +- 4 files changed, 31 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 67f616b..0dc9723 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Changelog +## 2.0.0b6 + +- When constructing device name, avoid null parts of it [#113](https://github.com/elad-bar/ha-hpprinter/issues/113) +- Changed the logic of errors from not found endpoints [#120](https://github.com/elad-bar/ha-hpprinter/issues/120) + - On initial load / setting up integration - one of the endpoints must return valid response, otherwise the integration will fail to load. + - After the integration loaded, it will update data periodically, + - If one of the endpoints will return 404 (not found) - the data related to it will get reset, DEBUG message will be logged (instead of ERROR) + - If printer goes offline, all data will be set as Unknown. + ## 2.0.0b5 - Support no prefetch mode diff --git a/custom_components/hpprinter/managers/ha_coordinator.py b/custom_components/hpprinter/managers/ha_coordinator.py index 0fedc6d..a5a98af 100644 --- a/custom_components/hpprinter/managers/ha_coordinator.py +++ b/custom_components/hpprinter/managers/ha_coordinator.py @@ -160,6 +160,12 @@ def create_consumable_device( device_name_parts.append(cartridge_color) device_name_parts.append(cartridge_type.capitalize()) + device_name_parts = [ + device_name_part + for device_name_part in device_name_parts + if device_name_part is not None + ] + device_unique_id = slugify(f"{self.entry_id}.{device_key}") cartridge_device_name = " ".join(device_name_parts) diff --git a/custom_components/hpprinter/managers/rest_api.py b/custom_components/hpprinter/managers/rest_api.py index c6779d9..976f442 100644 --- a/custom_components/hpprinter/managers/rest_api.py +++ b/custom_components/hpprinter/managers/rest_api.py @@ -346,12 +346,21 @@ async def _get_request( _LOGGER.debug(f"Request to {url}") except ClientResponseError as cre: - if not ignore_error: - exc_type, exc_obj, tb = sys.exc_info() - line_number = tb.tb_lineno - _LOGGER.error( - f"Failed to get response from {endpoint}, Error: {cre}, Line: {line_number}" - ) + if cre.status == 404: + if not ignore_error: + exc_type, exc_obj, tb = sys.exc_info() + line_number = tb.tb_lineno + _LOGGER.debug( + f"Failed to get response from {endpoint}, Error: {cre}, Line: {line_number}" + ) + + else: + if not ignore_error: + exc_type, exc_obj, tb = sys.exc_info() + line_number = tb.tb_lineno + _LOGGER.error( + f"Failed to get response from {endpoint}, Error: {cre}, Line: {line_number}" + ) except Exception as ex: if not ignore_error: diff --git a/custom_components/hpprinter/manifest.json b/custom_components/hpprinter/manifest.json index 580c02f..4d32d8b 100644 --- a/custom_components/hpprinter/manifest.json +++ b/custom_components/hpprinter/manifest.json @@ -9,5 +9,5 @@ "iot_class": "local_polling", "issue_tracker": "https://github.com/elad-bar/ha-hpprinter/issues", "requirements": ["xmltodict~=0.13.0", "flatten_json", "defusedxml"], - "version": "2.0.0b5" + "version": "2.0.0b6" } From c3d63b72fa5011870782e5d9d51c8a2a1a32baf3 Mon Sep 17 00:00:00 2001 From: Elad Bar Date: Tue, 7 May 2024 20:43:47 +0300 Subject: [PATCH 21/25] Fix logic of constructing device name if cartridge type is not available --- CHANGELOG.md | 5 + .../hpprinter/managers/ha_coordinator.py | 221 +++++++++++------- custom_components/hpprinter/manifest.json | 2 +- 3 files changed, 142 insertions(+), 86 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0dc9723..562291c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## 2.0.0b7 + +- Safe code blocks (try / catch / log) for generating entities +- Fix logic of constructing device name if cartridge type is not available + ## 2.0.0b6 - When constructing device name, avoid null parts of it [#113](https://github.com/elad-bar/ha-hpprinter/issues/113) diff --git a/custom_components/hpprinter/managers/ha_coordinator.py b/custom_components/hpprinter/managers/ha_coordinator.py index a5a98af..5bf00d3 100644 --- a/custom_components/hpprinter/managers/ha_coordinator.py +++ b/custom_components/hpprinter/managers/ha_coordinator.py @@ -1,4 +1,5 @@ import logging +import sys from homeassistant.core import Event from homeassistant.helpers.dispatcher import ( @@ -92,128 +93,178 @@ async def initialize(self): await self.async_config_entry_first_refresh() def create_main_device( - self, device_key: str, device_data: dict, _device_config: dict + self, device_key: str, device_data: dict, device_config: dict ): - self._main_device_data = device_data - self._main_device_id = device_key + try: + self._main_device_data = device_data + self._main_device_id = device_key - model = device_data.get("make_and_model") - serial_number = device_data.get("serial_number") - manufacturer = device_data.get("manufacturer_name") + model = device_data.get("make_and_model") + serial_number = device_data.get("serial_number") + manufacturer = device_data.get("manufacturer_name") - device_identifier = (DOMAIN, self._main_device_id) + device_identifier = (DOMAIN, self._main_device_id) - device_info = DeviceInfo( - identifiers={device_identifier}, - name=self.entry_title, - model=model, - serial_number=serial_number, - manufacturer=manufacturer, - ) + device_info = DeviceInfo( + identifiers={device_identifier}, + name=self.entry_title, + model=model, + serial_number=serial_number, + manufacturer=manufacturer, + ) - self._devices[device_key] = device_info + self._devices[device_key] = device_info + + except Exception as ex: + exc_type, exc_obj, tb = sys.exc_info() + line_number = tb.tb_lineno + _LOGGER.error( + f"Failed to create main device, " + f"Device Key: {device_key}, " + f"Data: {device_data}, " + f"Config: {device_config}, " + f"Error: {ex}, " + f"Line: {line_number}" + ) def create_sub_unit_device( - self, device_key: str, _device_data: dict, device_config: dict + self, device_key: str, device_data: dict, device_config: dict ): - model = self._main_device_data.get("make_and_model") - serial_number = self._main_device_data.get("serial_number") - manufacturer = self._main_device_data.get("manufacturer_name") + try: + model = self._main_device_data.get("make_and_model") + serial_number = self._main_device_data.get("serial_number") + manufacturer = self._main_device_data.get("manufacturer_name") - device_type = device_config.get("device_type") + device_type = device_config.get("device_type") - device_unique_id = slugify(f"{self.entry_id}.{device_key}") + device_unique_id = slugify(f"{self.entry_id}.{device_key}") - sub_unit_device_name = f"{self.entry_title} {device_type}" + sub_unit_device_name = f"{self.entry_title} {device_type}" - device_identifier = (DOMAIN, device_unique_id) + device_identifier = (DOMAIN, device_unique_id) - device_info = DeviceInfo( - identifiers={device_identifier}, - name=sub_unit_device_name, - model=model, - serial_number=serial_number, - manufacturer=manufacturer, - via_device=(DOMAIN, self._main_device_id), - ) + device_info = DeviceInfo( + identifiers={device_identifier}, + name=sub_unit_device_name, + model=model, + serial_number=serial_number, + manufacturer=manufacturer, + via_device=(DOMAIN, self._main_device_id), + ) - self._devices[device_key] = device_info + self._devices[device_key] = device_info + + except Exception as ex: + exc_type, exc_obj, tb = sys.exc_info() + line_number = tb.tb_lineno + _LOGGER.error( + f"Failed to sub unit device, " + f"Device Key: {device_key}, " + f"Data: {device_data}, " + f"Config: {device_config}, " + f"Error: {ex}, " + f"Line: {line_number}" + ) def create_consumable_device( - self, device_key: str, device_data: dict, _device_config: dict + self, device_key: str, device_data: dict, device_config: dict ): - printer_device_unique_id = slugify(f"{self.entry_id}.printer") + try: + printer_device_unique_id = slugify(f"{self.entry_id}.printer") - device_name_parts = [self.entry_title] - cartridge_type: str = device_data.get("consumable_type_enum") - cartridge_color = device_data.get("marker_color") - manufacturer = device_data.get("consumable_life_state_brand") - serial_number = device_data.get("serial_number") + device_name_parts = [self.entry_title] + cartridge_type: str = device_data.get("consumable_type_enum") + cartridge_color = device_data.get("marker_color") + manufacturer = device_data.get("consumable_life_state_brand") + serial_number = device_data.get("serial_number") - model = device_data.get("consumable_selectibility_number") + model = device_data.get("consumable_selectibility_number") - if cartridge_type == "printhead": - device_name_parts.append(cartridge_type.capitalize()) - model = cartridge_type.capitalize() + if cartridge_type == "printhead": + model = cartridge_type.capitalize() + else: + device_name_parts.append(cartridge_color) - else: - device_name_parts.append(cartridge_color) - device_name_parts.append(cartridge_type.capitalize()) + if cartridge_type is not None: + device_name_parts.append(cartridge_type.capitalize()) - device_name_parts = [ - device_name_part - for device_name_part in device_name_parts - if device_name_part is not None - ] + device_name_parts = [ + device_name_part + for device_name_part in device_name_parts + if device_name_part is not None + ] - device_unique_id = slugify(f"{self.entry_id}.{device_key}") + device_unique_id = slugify(f"{self.entry_id}.{device_key}") - cartridge_device_name = " ".join(device_name_parts) + cartridge_device_name = " ".join(device_name_parts) - device_identifier = (DOMAIN, device_unique_id) + device_identifier = (DOMAIN, device_unique_id) - device_info = DeviceInfo( - identifiers={device_identifier}, - name=cartridge_device_name, - model=model, - serial_number=serial_number, - manufacturer=manufacturer, - via_device=(DOMAIN, printer_device_unique_id), - ) + device_info = DeviceInfo( + identifiers={device_identifier}, + name=cartridge_device_name, + model=model, + serial_number=serial_number, + manufacturer=manufacturer, + via_device=(DOMAIN, printer_device_unique_id), + ) - self._devices[device_key] = device_info + self._devices[device_key] = device_info + + except Exception as ex: + exc_type, exc_obj, tb = sys.exc_info() + line_number = tb.tb_lineno + _LOGGER.error( + f"Failed to consumable device, " + f"Device Key: {device_key}, " + f"Data: {device_data}, " + f"Config: {device_config}, " + f"Error: {ex}, " + f"Line: {line_number}" + ) def create_adapter_device( self, device_key: str, device_data: dict, device_config: dict ): - serial_number = self._main_device_data.get("serial_number") - manufacturer = self._main_device_data.get("manufacturer_name") - - adapter_name = device_data.get("hardware_config_name").upper() - model = ( - device_data.get("hardware_config_device_connectivity_port_type") - .replace("Embedded", "") - .upper() - ) + try: + serial_number = self._main_device_data.get("serial_number") + manufacturer = self._main_device_data.get("manufacturer_name") - device_type = device_config.get("device_type") + adapter_name = device_data.get("hardware_config_name").upper() - device_unique_id = slugify(f"{self.entry_id}.{device_key}") + port_type_key = "hardware_config_device_connectivity_port_type" + model = device_data.get(port_type_key).replace("Embedded", "").upper() - adapter_device_name = f"{self.entry_title} {device_type} {adapter_name}" + device_type = device_config.get("device_type") - device_identifier = (DOMAIN, device_unique_id) + device_unique_id = slugify(f"{self.entry_id}.{device_key}") - device_info = DeviceInfo( - identifiers={device_identifier}, - name=adapter_device_name, - model=model, - serial_number=serial_number, - manufacturer=manufacturer, - via_device=(DOMAIN, self._main_device_id), - ) + adapter_device_name = f"{self.entry_title} {device_type} {adapter_name}" + + device_identifier = (DOMAIN, device_unique_id) - self._devices[device_key] = device_info + device_info = DeviceInfo( + identifiers={device_identifier}, + name=adapter_device_name, + model=model, + serial_number=serial_number, + manufacturer=manufacturer, + via_device=(DOMAIN, self._main_device_id), + ) + + self._devices[device_key] = device_info + + except Exception as ex: + exc_type, exc_obj, tb = sys.exc_info() + line_number = tb.tb_lineno + _LOGGER.error( + f"Failed to adapter device, " + f"Device Key: {device_key}, " + f"Data: {device_data}, " + f"Config: {device_config}, " + f"Error: {ex}, " + f"Line: {line_number}" + ) def get_device(self, device_key: str) -> DeviceInfo | None: result = self._devices.get(device_key) diff --git a/custom_components/hpprinter/manifest.json b/custom_components/hpprinter/manifest.json index 4d32d8b..8b71443 100644 --- a/custom_components/hpprinter/manifest.json +++ b/custom_components/hpprinter/manifest.json @@ -9,5 +9,5 @@ "iot_class": "local_polling", "issue_tracker": "https://github.com/elad-bar/ha-hpprinter/issues", "requirements": ["xmltodict~=0.13.0", "flatten_json", "defusedxml"], - "version": "2.0.0b6" + "version": "2.0.0b7" } From d3c1f7bd843a2beccce1dc4b3bba188bcee14aa6 Mon Sep 17 00:00:00 2001 From: Elad Bar Date: Fri, 17 May 2024 12:49:07 +0300 Subject: [PATCH 22/25] Fix async dispatcher send, Change all sensors with date device class to timestamp #127 --- CHANGELOG.md | 5 +++++ .../hpprinter/managers/rest_api.py | 4 ++-- custom_components/hpprinter/manifest.json | 2 +- .../hpprinter/parameters/data_points.json | 8 ++++---- custom_components/hpprinter/sensor.py | 18 ++++++++++++------ 5 files changed, 24 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 562291c..3ab7fa9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## 2.0.0b8 + +- Fix async dispatcher send +- Change all sensors with date device class to timestamp [#127](https://github.com/elad-bar/ha-hpprinter/issues/127) + ## 2.0.0b7 - Safe code blocks (try / catch / log) for generating entities diff --git a/custom_components/hpprinter/managers/rest_api.py b/custom_components/hpprinter/managers/rest_api.py index 976f442..eb63468 100644 --- a/custom_components/hpprinter/managers/rest_api.py +++ b/custom_components/hpprinter/managers/rest_api.py @@ -13,7 +13,7 @@ MAXIMUM_CONNECTIONS, MAXIMUM_CONNECTIONS_PER_HOST, ) -from homeassistant.helpers.dispatcher import async_dispatcher_send +from homeassistant.helpers.dispatcher import dispatcher_send from homeassistant.util import slugify, ssl from homeassistant.util.ssl import SSLCipherList @@ -403,7 +403,7 @@ def device_data_changed(self, device_key: str): if device_key not in self._device_dispatched: self._device_dispatched.append(device_key) - async_dispatcher_send( + dispatcher_send( self._hass, SIGNAL_HA_DEVICE_DISCOVERED, self._config_manager.entry_id, diff --git a/custom_components/hpprinter/manifest.json b/custom_components/hpprinter/manifest.json index 8b71443..ba1a6ab 100644 --- a/custom_components/hpprinter/manifest.json +++ b/custom_components/hpprinter/manifest.json @@ -9,5 +9,5 @@ "iot_class": "local_polling", "issue_tracker": "https://github.com/elad-bar/ha-hpprinter/issues", "requirements": ["xmltodict~=0.13.0", "flatten_json", "defusedxml"], - "version": "2.0.0b7" + "version": "2.0.0b8" } diff --git a/custom_components/hpprinter/parameters/data_points.json b/custom_components/hpprinter/parameters/data_points.json index 2500664..e87da34 100644 --- a/custom_components/hpprinter/parameters/data_points.json +++ b/custom_components/hpprinter/parameters/data_points.json @@ -26,7 +26,7 @@ "manufacture_at": { "path": "Manufacturer.Date", "platform": "sensor", - "device_class": "date" + "device_class": "timestamp" } } }, @@ -62,7 +62,7 @@ "installation_date": { "path": "Installation.Date", "platform": "sensor", - "device_class": "date" + "device_class": "timestamp" }, "capacity_max_capacity": { "path": "Capacity.MaxCapacity" @@ -84,7 +84,7 @@ "manufacture_at": { "path": "Manufacturer.Date", "platform": "sensor", - "device_class": "date", + "device_class": "timestamp", "exclude": { "consumable_type_enum": "printhead" } @@ -98,7 +98,7 @@ "warranty_expiration_date": { "path": "Warranty.ExpirationDate", "platform": "sensor", - "device_class": "date", + "device_class": "timestamp", "exclude": { "consumable_type_enum": "printhead" } diff --git a/custom_components/hpprinter/sensor.py b/custom_components/hpprinter/sensor.py index 3445830..6be494d 100644 --- a/custom_components/hpprinter/sensor.py +++ b/custom_components/hpprinter/sensor.py @@ -47,14 +47,20 @@ def __init__( def _set_value(self): state = self.get_value() - if self.native_unit_of_measurement in [PERCENTAGE]: - state = float(state) + if state is not None: + if self.native_unit_of_measurement in [PERCENTAGE]: + state = float(state) - elif self.native_unit_of_measurement in NUMERIC_UNITS_OF_MEASUREMENT: - state = int(state) + elif self.native_unit_of_measurement in NUMERIC_UNITS_OF_MEASUREMENT: + state = int(state) - if self.device_class == SensorDeviceClass.DATE: - state = datetime.fromisoformat(state) + if self.device_class == SensorDeviceClass.DATE: + state = datetime.fromisoformat(state) + + elif self.device_class == SensorDeviceClass.TIMESTAMP: + tz = datetime.now().astimezone().tzinfo + ts = datetime.fromisoformat(state).timestamp() + state = datetime.fromtimestamp(ts, tz=tz) self._attr_native_value = state From 53c0350d53d6b87b286251b948f11c677ded02cb Mon Sep 17 00:00:00 2001 From: Elad Bar Date: Fri, 17 May 2024 15:26:54 +0300 Subject: [PATCH 23/25] Add fallback mechanism for consumables, fix hassfest error --- CHANGELOG.md | 6 +++++ .../hpprinter/managers/rest_api.py | 22 +++++++++++++--- custom_components/hpprinter/manifest.json | 2 +- .../hpprinter/parameters/data_points.json | 26 ++++++++++++++++--- custom_components/hpprinter/sensor.py | 3 +++ custom_components/hpprinter/strings.json | 2 +- .../hpprinter/translations/de.json | 2 +- .../hpprinter/translations/dk.json | 2 +- .../hpprinter/translations/en.json | 2 +- .../hpprinter/translations/es.json | 2 +- .../hpprinter/translations/fr.json | 2 +- .../hpprinter/translations/nb.json | 2 +- .../hpprinter/translations/nl.json | 2 +- .../hpprinter/translations/pl.json | 2 +- .../hpprinter/translations/pt-BR.json | 2 +- 15 files changed, 61 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ab7fa9..6927cd7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,15 @@ # Changelog +## 2.0.0b9 + +- Add fallback mechanism for consumables, if station is not available, will use color mapping +- Fix hassfest failure caused by invalid enums values for translation + ## 2.0.0b8 - Fix async dispatcher send - Change all sensors with date device class to timestamp [#127](https://github.com/elad-bar/ha-hpprinter/issues/127) +- Add fallback mechanism for consumables, if station is not available, will use color mapping ## 2.0.0b7 diff --git a/custom_components/hpprinter/managers/rest_api.py b/custom_components/hpprinter/managers/rest_api.py index eb63468..9e929c0 100644 --- a/custom_components/hpprinter/managers/rest_api.py +++ b/custom_components/hpprinter/managers/rest_api.py @@ -182,18 +182,34 @@ def _extract_data(self, devices: list[dict]): device_key = device_type if identifier is not None: - device_id = item_data.get(identifier) + identifier_key = identifier.get("key") + + device_id = item_data.get(identifier_key) + + if device_id is None: + fallback = identifier.get("fallback") + if fallback is not None: + fallback_key = fallback.get("key") + fallback_mapping = fallback.get("mapping") + + fallback_key_data = item_data.get(fallback_key) + device_id = ( + fallback_key_data + if fallback_mapping is None + else fallback_mapping.get(fallback_key_data) + ) + if flat: new_items_data = { slugify(f"{device_id}_{key}"): item_data[key] for key in item_data - if key != identifier + if key != identifier_key } new_properties = { slugify(f"{device_id}_{key}"): properties[key] for key in properties - if key != identifier + if key != identifier_key } item_data = new_items_data diff --git a/custom_components/hpprinter/manifest.json b/custom_components/hpprinter/manifest.json index ba1a6ab..cdd80ed 100644 --- a/custom_components/hpprinter/manifest.json +++ b/custom_components/hpprinter/manifest.json @@ -9,5 +9,5 @@ "iot_class": "local_polling", "issue_tracker": "https://github.com/elad-bar/ha-hpprinter/issues", "requirements": ["xmltodict~=0.13.0", "flatten_json", "defusedxml"], - "version": "2.0.0b8" + "version": "2.0.0b9" } diff --git a/custom_components/hpprinter/parameters/data_points.json b/custom_components/hpprinter/parameters/data_points.json index e87da34..cdc1043 100644 --- a/custom_components/hpprinter/parameters/data_points.json +++ b/custom_components/hpprinter/parameters/data_points.json @@ -35,7 +35,12 @@ "endpoint": "/DevMgmt/ConsumableConfigDyn.xml", "path": "ConsumableConfigDyn.ConsumableInfo", "device_type": "Consumable", - "identifier": "consumable_station", + "identifier": { + "key": "consumable_station", + "fallback": { + "key": "consumable_label_code" + } + }, "properties": { "consumable_label_code": { "path": "ConsumableLabelCode" @@ -57,7 +62,7 @@ "path": "ConsumableTypeEnum", "platform": "sensor", "device_class": "enum", - "options": ["ink", "inkCartridge", "printhead", "toner"] + "options": ["ink", "inkcartridge", "printhead", "toner"] }, "installation_date": { "path": "Installation.Date", @@ -113,7 +118,18 @@ "endpoint": "/DevMgmt/ProductUsageDyn.xml", "path": "ProductUsageDyn.ConsumableSubunit.Consumable", "device_type": "Consumable", - "identifier": "consumable_station", + "identifier": { + "key": "consumable_station", + "fallback": { + "key": "marker_color", + "mapping": { + "Cyan": "C", + "Yellow": "Y", + "Magenta": "M", + "Black": "K" + } + } + }, "properties": { "consumable_station": { "path": "ConsumableStation" @@ -303,7 +319,9 @@ "endpoint": "/IoMgmt/Adapters", "path": "Adapters.Adapter", "device_type": "Main", - "identifier": "hardware_config_name", + "identifier": { + "key": "hardware_config_name" + }, "flat": true, "properties": { "hardware_config_name": { diff --git a/custom_components/hpprinter/sensor.py b/custom_components/hpprinter/sensor.py index 6be494d..b3c0cea 100644 --- a/custom_components/hpprinter/sensor.py +++ b/custom_components/hpprinter/sensor.py @@ -62,6 +62,9 @@ def _set_value(self): ts = datetime.fromisoformat(state).timestamp() state = datetime.fromtimestamp(ts, tz=tz) + elif self.device_class == SensorDeviceClass.ENUM: + state = state.lower() + self._attr_native_value = state def _handle_coordinator_update(self) -> None: diff --git a/custom_components/hpprinter/strings.json b/custom_components/hpprinter/strings.json index 5be1608..bac87c5 100644 --- a/custom_components/hpprinter/strings.json +++ b/custom_components/hpprinter/strings.json @@ -45,7 +45,7 @@ "name": "Type", "state": { "ink": "Ink", - "inkCartridge": "Ink", + "inkcartridge": "Ink", "printhead": "Printhead", "toner": "Toner" } diff --git a/custom_components/hpprinter/translations/de.json b/custom_components/hpprinter/translations/de.json index d84ecc5..2e9dbc0 100644 --- a/custom_components/hpprinter/translations/de.json +++ b/custom_components/hpprinter/translations/de.json @@ -51,7 +51,7 @@ "name": "Typ", "state": { "ink": "Tinte", - "inkCartridge": "Tinte", + "inkcartridge": "Tinte", "printhead": "Druckkopf", "toner": "Toner" } diff --git a/custom_components/hpprinter/translations/dk.json b/custom_components/hpprinter/translations/dk.json index b83c377..437c2bb 100644 --- a/custom_components/hpprinter/translations/dk.json +++ b/custom_components/hpprinter/translations/dk.json @@ -51,7 +51,7 @@ "name": "Type", "state": { "ink": "Bl\u00e6k", - "inkCartridge": "Bl\u00e6k", + "inkcartridge": "Bl\u00e6k", "printhead": "Printhead", "toner": "Toner" } diff --git a/custom_components/hpprinter/translations/en.json b/custom_components/hpprinter/translations/en.json index 6ba6737..b7f3090 100644 --- a/custom_components/hpprinter/translations/en.json +++ b/custom_components/hpprinter/translations/en.json @@ -51,7 +51,7 @@ "name": "Type", "state": { "ink": "Ink", - "inkCartridge": "Ink", + "inkcartridge": "Ink", "printhead": "Printhead", "toner": "Toner" } diff --git a/custom_components/hpprinter/translations/es.json b/custom_components/hpprinter/translations/es.json index a278203..536ab21 100644 --- a/custom_components/hpprinter/translations/es.json +++ b/custom_components/hpprinter/translations/es.json @@ -51,7 +51,7 @@ "name": "Tipo", "state": { "ink": "Tinta", - "inkCartridge": "Tinta", + "inkcartridge": "Tinta", "printhead": "Cabezal", "toner": "Virador" } diff --git a/custom_components/hpprinter/translations/fr.json b/custom_components/hpprinter/translations/fr.json index 52ee58d..51619d3 100644 --- a/custom_components/hpprinter/translations/fr.json +++ b/custom_components/hpprinter/translations/fr.json @@ -51,7 +51,7 @@ "name": "Taper", "state": { "ink": "Encre", - "inkCartridge": "Encre", + "inkcartridge": "Encre", "printhead": "T\u00eate d'impression", "toner": "Toner" } diff --git a/custom_components/hpprinter/translations/nb.json b/custom_components/hpprinter/translations/nb.json index 5fdb47f..9da6742 100644 --- a/custom_components/hpprinter/translations/nb.json +++ b/custom_components/hpprinter/translations/nb.json @@ -51,7 +51,7 @@ "name": "Type", "state": { "ink": "Blekk", - "inkCartridge": "Blekk", + "inkcartridge": "Blekk", "printhead": "Skrivehode", "toner": "Toner" } diff --git a/custom_components/hpprinter/translations/nl.json b/custom_components/hpprinter/translations/nl.json index 8539ee4..3697915 100644 --- a/custom_components/hpprinter/translations/nl.json +++ b/custom_components/hpprinter/translations/nl.json @@ -51,7 +51,7 @@ "name": "Type", "state": { "ink": "Inkt", - "inkCartridge": "Inkt", + "inkcartridge": "Inkt", "printhead": "Printhead", "toner": "Toner" } diff --git a/custom_components/hpprinter/translations/pl.json b/custom_components/hpprinter/translations/pl.json index be8d1ee..f184312 100644 --- a/custom_components/hpprinter/translations/pl.json +++ b/custom_components/hpprinter/translations/pl.json @@ -51,7 +51,7 @@ "name": "Typ", "state": { "ink": "Atrament", - "inkCartridge": "Atrament", + "inkcartridge": "Atrament", "printhead": "Printhead", "toner": "Toner" } diff --git a/custom_components/hpprinter/translations/pt-BR.json b/custom_components/hpprinter/translations/pt-BR.json index 482dcd3..5a8db2e 100644 --- a/custom_components/hpprinter/translations/pt-BR.json +++ b/custom_components/hpprinter/translations/pt-BR.json @@ -51,7 +51,7 @@ "name": "Tipo", "state": { "ink": "Tinta", - "inkCartridge": "Tinta", + "inkcartridge": "Tinta", "printhead": "Cabe\u00e7ote de impress\u00e3o", "toner": "Toner" } From a8623af40439a61fd6d83b80df55e38101782969 Mon Sep 17 00:00:00 2001 From: Elad Bar Date: Sat, 18 May 2024 09:01:02 +0300 Subject: [PATCH 24/25] Change the matching of consumable to its details by marker color instead of station --- CHANGELOG.md | 5 +++++ .../hpprinter/managers/rest_api.py | 20 +++++++----------- custom_components/hpprinter/manifest.json | 2 +- .../hpprinter/parameters/data_points.json | 21 +++++++------------ 4 files changed, 21 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6927cd7..32ed772 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## 2.0.0b10 + +- Add support for mapping of multicolor consumable (CyanMagentaYellow) +- Change the matching of consumable to its details by marker color instead of station + ## 2.0.0b9 - Add fallback mechanism for consumables, if station is not available, will use color mapping diff --git a/custom_components/hpprinter/managers/rest_api.py b/custom_components/hpprinter/managers/rest_api.py index 9e929c0..1752970 100644 --- a/custom_components/hpprinter/managers/rest_api.py +++ b/custom_components/hpprinter/managers/rest_api.py @@ -183,21 +183,15 @@ def _extract_data(self, devices: list[dict]): if identifier is not None: identifier_key = identifier.get("key") + identifier_mapping = identifier.get("mapping") - device_id = item_data.get(identifier_key) + key_data = item_data.get(identifier_key) - if device_id is None: - fallback = identifier.get("fallback") - if fallback is not None: - fallback_key = fallback.get("key") - fallback_mapping = fallback.get("mapping") - - fallback_key_data = item_data.get(fallback_key) - device_id = ( - fallback_key_data - if fallback_mapping is None - else fallback_mapping.get(fallback_key_data) - ) + device_id = ( + item_data.get(identifier_key) + if identifier_mapping is None + else identifier_mapping.get(key_data) + ) if flat: new_items_data = { diff --git a/custom_components/hpprinter/manifest.json b/custom_components/hpprinter/manifest.json index cdd80ed..f1daca5 100644 --- a/custom_components/hpprinter/manifest.json +++ b/custom_components/hpprinter/manifest.json @@ -9,5 +9,5 @@ "iot_class": "local_polling", "issue_tracker": "https://github.com/elad-bar/ha-hpprinter/issues", "requirements": ["xmltodict~=0.13.0", "flatten_json", "defusedxml"], - "version": "2.0.0b9" + "version": "2.0.0b10" } diff --git a/custom_components/hpprinter/parameters/data_points.json b/custom_components/hpprinter/parameters/data_points.json index cdc1043..29fe500 100644 --- a/custom_components/hpprinter/parameters/data_points.json +++ b/custom_components/hpprinter/parameters/data_points.json @@ -36,10 +36,7 @@ "path": "ConsumableConfigDyn.ConsumableInfo", "device_type": "Consumable", "identifier": { - "key": "consumable_station", - "fallback": { - "key": "consumable_label_code" - } + "key": "consumable_label_code" }, "properties": { "consumable_label_code": { @@ -119,15 +116,13 @@ "path": "ProductUsageDyn.ConsumableSubunit.Consumable", "device_type": "Consumable", "identifier": { - "key": "consumable_station", - "fallback": { - "key": "marker_color", - "mapping": { - "Cyan": "C", - "Yellow": "Y", - "Magenta": "M", - "Black": "K" - } + "key": "marker_color", + "mapping": { + "Cyan": "C", + "Yellow": "Y", + "Magenta": "M", + "CyanMagentaYellow": "CMY", + "Black": "K" } }, "properties": { From 786a37b0cf63e977a701be8e0dc6aca0143831f9 Mon Sep 17 00:00:00 2001 From: Elad Bar Date: Thu, 6 Jun 2024 17:29:54 +0300 Subject: [PATCH 25/25] fix blocking call --- CHANGELOG.md | 4 +++ .../hpprinter/managers/ha_config_manager.py | 25 +++++++++++-------- custom_components/hpprinter/manifest.json | 2 +- 3 files changed, 20 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 32ed772..f07b9bf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 2.0.0 + +- Fix "detected blocking call to open inside the event loop by custom integration" error + ## 2.0.0b10 - Add support for mapping of multicolor consumable (CyanMagentaYellow) diff --git a/custom_components/hpprinter/managers/ha_config_manager.py b/custom_components/hpprinter/managers/ha_config_manager.py index de00d75..58b991a 100644 --- a/custom_components/hpprinter/managers/ha_config_manager.py +++ b/custom_components/hpprinter/managers/ha_config_manager.py @@ -4,6 +4,8 @@ import os from pathlib import Path +import aiofiles + from homeassistant.config_entries import STORAGE_VERSION, ConfigEntry from homeassistant.const import Platform from homeassistant.core import HomeAssistant @@ -122,8 +124,8 @@ async def initialize(self, entry_config: dict): self._config_data.update(entry_config) - self._load_exclude_endpoints_configuration() - self._load_data_points_configuration() + await self._load_exclude_endpoints_configuration() + await self._load_data_points_configuration() self._load_entity_descriptions() @@ -374,10 +376,10 @@ def _is_valid_entity( return is_valid - def _load_data_points_configuration(self): + async def _load_data_points_configuration(self): self._endpoints = [] - self._data_points = self._get_parameters(ParameterType.DATA_POINTS) + self._data_points = await self._get_parameters(ParameterType.DATA_POINTS) endpoint_objects = self._data_points @@ -390,23 +392,26 @@ def _load_data_points_configuration(self): ): self._endpoints.append(endpoint_uri) - def _load_exclude_endpoints_configuration(self): - endpoints = self._get_parameters(ParameterType.ENDPOINT_VALIDATIONS) + async def _load_exclude_endpoints_configuration(self): + endpoints = await self._get_parameters(ParameterType.ENDPOINT_VALIDATIONS) self._exclude_uri_list = endpoints.get("exclude_uri") self._exclude_type_list = endpoints.get("exclude_type") @staticmethod - def _get_parameters(parameter_type: ParameterType) -> dict: + async def _get_parameters(parameter_type: ParameterType) -> dict: config_file = f"{parameter_type}.json" current_path = Path(__file__) parent_directory = current_path.parents[1] file_path = os.path.join(parent_directory, "parameters", config_file) - with open(file_path) as f: - data = json.load(f) + file = await aiofiles.open(file_path) + content = await file.read() + await file.close() + + data = json.loads(content) - return data + return data def is_valid_endpoint(self, endpoint: dict): endpoint_type = endpoint.get("type") diff --git a/custom_components/hpprinter/manifest.json b/custom_components/hpprinter/manifest.json index f1daca5..b633132 100644 --- a/custom_components/hpprinter/manifest.json +++ b/custom_components/hpprinter/manifest.json @@ -9,5 +9,5 @@ "iot_class": "local_polling", "issue_tracker": "https://github.com/elad-bar/ha-hpprinter/issues", "requirements": ["xmltodict~=0.13.0", "flatten_json", "defusedxml"], - "version": "2.0.0b10" + "version": "2.0.0" }