Skip to content

Commit

Permalink
Multiple fixes
Browse files Browse the repository at this point in the history
#8 - added icon per sensor
#7 - support entities in the integration section
#4 - load is being done after 5 seconds of loading the custom component, running in async mode
  • Loading branch information
elad-bar committed Feb 12, 2020
1 parent a7fbebe commit 44539cd
Show file tree
Hide file tree
Showing 10 changed files with 357 additions and 101 deletions.
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
# Integration to HP Printers

#### Integration configuration of the HP Printer (Support multiple)
```
name: custom name
host: hostname or ip
```
#### Configuration
Configuration support multiple HP Printer devices through Configuration -> Integrations

\* Custom component doesn't support YAML configuration!, in case you have used it via configuration.yaml, please remove it <br/>
\* In case labels in Configuration -> Integrations -> Add new are note being displayed, please delete the custom component and re-download it

#### Components:
###### Device status - Binary Sensor
Expand Down
2 changes: 1 addition & 1 deletion __main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ def data_provider(data_type):

hostname = "192.168.1.30"

device_data = HPDeviceData(hostname, "HP7740")
device_data = HPDeviceData(None, hostname, "HP7740")
data = device_data.get_data() #_LOGGER.store_data)

json_data = json.dumps(data)
Expand Down
35 changes: 27 additions & 8 deletions custom_components/hpprinter/HPDeviceData.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
from .HPPrinterData import *
from .HPPrinterAPI import *

_LOGGER = logging.getLogger(__name__)


class HPDeviceData:
def __init__(self, host, name, external_data_provider=None):
self._usage_data_manager = ProductUsageDynPrinterData(host, external_data_provider=external_data_provider)
self._consumable_data_manager = ConsumableConfigDynPrinterData(host, external_data_provider=external_data_provider)
def __init__(self, hass, host, name, reader=None):
self._usage_data_manager = ProductUsageDynPrinterDataAPI(hass, host, reader=reader)
self._consumable_data_manager = ConsumableConfigDynPrinterDataAPI(hass, host, reader=reader)
self._product_config_manager = ProductConfigDynDataAPI(hass, host, reader=reader)

self._name = name
self._host = host

self._usage_data = None
self._consumable_data = None
self._product_config_data = None

self._device_data = {
"Name": name,
Expand All @@ -24,16 +26,20 @@ def update(self):

return data

def get_data(self, store=None):
async def get_data(self, store=None):
try:
self._usage_data = self._usage_data_manager.get_data(store)
self._consumable_data = self._consumable_data_manager.get_data(store)
self._usage_data = await self._usage_data_manager.get_data(store)
self._consumable_data = await self._consumable_data_manager.get_data(store)
self._product_config_data = await self._product_config_manager.get_data(store)

is_online = self._usage_data is not None and self._consumable_data is not None
is_online = self._usage_data is not None and \
self._consumable_data is not None and \
self._product_config_data is not None

if is_online:
self.set_usage_data()
self.set_consumable_data()
self.set_product_config_data()

self._device_data[HP_DEVICE_IS_ONLINE] = is_online

Expand Down Expand Up @@ -70,6 +76,19 @@ def set_consumable_data(self):

_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_usage_data(self):
try:
if self._usage_data is not None:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,38 +2,57 @@
import logging
import json

import requests
import aiohttp
import xmltodict

from homeassistant.helpers.aiohttp_client import async_create_clientsession

from .const import *

_LOGGER = logging.getLogger(__name__)


class HPPrinterData:
def __init__(self, host, port=80, is_ssl=False, data_type=None, external_data_provider=None):
class HPPrinterAPI:
def __init__(self, hass, host, port=80, is_ssl=False, data_type=None, reader=None):
self._hass = hass
self._host = host
self._port = port
self._protocol = "https" if is_ssl else "http"
self._data_type = data_type
self._data = None
self._external_data_provider = external_data_provider
self._reader = reader
self._session = None

self._url = f'{self._protocol}://{self._host}:{self._port}/DevMgmt/{self._data_type}.xml'

self.initialize()

@property
def data(self):
return self._data

def get_data(self, store=None):
def initialize(self):
try:
timeout = aiohttp.ClientTimeout(total=10)

self._session = async_create_clientsession(hass=self._hass, timeout=timeout)

except Exception as ex:
exc_type, exc_obj, tb = sys.exc_info()
line_number = tb.tb_lineno

_LOGGER.error(f"Failed to initialize BlueIris API, error: {ex}, line: {line_number}")

async def get_data(self, store=None):
try:
self._data = None

_LOGGER.debug(f"Updating {self._data_type} from {self._host}")

if self._external_data_provider is None:
printer_data = self.get_data_from_printer(store)
if self._reader is None:
printer_data = await self.async_get(store)
else:
printer_data = self._external_data_provider(self._data_type)
printer_data = self._reader(self._data_type)

result = {}

Expand Down Expand Up @@ -61,26 +80,26 @@ def get_data(self, store=None):

return self._data

def get_data_from_printer(self, store=None):
async def async_get(self, store=None):
result = None

try:
_LOGGER.debug(f"Retrieving {self._data_type} from {self._host}")

response = requests.get(self._url, timeout=10)
response.raise_for_status()
async with self._session.get(self._url, ssl=False) as response:
response.raise_for_status()

content = response.text
content = await response.text()

if store is not None:
store(f"{self._data_type}.xml", content)
if store is not None:
store(f"{self._data_type}.xml", content)

for ns in NAMESPACES_TO_REMOVE:
content = content.replace(f'{ns}:', '')
for ns in NAMESPACES_TO_REMOVE:
content = content.replace(f'{ns}:', '')

json_data = xmltodict.parse(content)
json_data = xmltodict.parse(content)

result = json_data
result = json_data

except Exception as ex:
exc_type, exc_obj, tb = sys.exc_info()
Expand Down Expand Up @@ -179,22 +198,29 @@ def clean_parameter(data_item, data_key, default_value="N/A"):
return result


class ConsumableConfigDynPrinterData(HPPrinterData):
def __init__(self, host, port=80, is_ssl=False, external_data_provider=None):
class ConsumableConfigDynPrinterDataAPI(HPPrinterAPI):
def __init__(self, hass, host, port=80, is_ssl=False, reader=None):
data_type = "ConsumableConfigDyn"

super().__init__(host, port, is_ssl, data_type, external_data_provider)
super().__init__(hass, host, port, is_ssl, data_type, reader)


class ProductUsageDynPrinterData(HPPrinterData):
def __init__(self, host, port=80, is_ssl=False, external_data_provider=None):
class ProductUsageDynPrinterDataAPI(HPPrinterAPI):
def __init__(self, hass, host, port=80, is_ssl=False, reader=None):
data_type = "ProductUsageDyn"

super().__init__(host, port, is_ssl, data_type, external_data_provider)
super().__init__(hass, host, port, is_ssl, data_type, reader)


class ProductStatusDynData(HPPrinterData):
def __init__(self, host, port=80, is_ssl=False, external_data_provider=None):
class ProductStatusDynDataAPI(HPPrinterAPI):
def __init__(self, hass, host, port=80, is_ssl=False, reader=None):
data_type = "ProductStatusDyn"

super().__init__(host, port, is_ssl, data_type, external_data_provider)
super().__init__(hass, host, port, is_ssl, data_type, reader)


class ProductConfigDynDataAPI(HPPrinterAPI):
def __init__(self, hass, host, port=80, is_ssl=False, reader=None):
data_type = "ProductConfigDyn"

super().__init__(hass, host, port, is_ssl, data_type, reader)
19 changes: 8 additions & 11 deletions custom_components/hpprinter/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

from .const import *
from .HPDeviceData import *
from .home_assistant import HPPrinterHomeAssistant
from .home_assistant import HPPrinterHomeAssistant, _get_printers

_LOGGER = logging.getLogger(__name__)

Expand All @@ -27,26 +27,23 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
entry_data = entry.data

host = entry_data.get(CONF_HOST)
data = {}

if DATA_HP_PRINTER not in hass.data:
hass.data[DATA_HP_PRINTER] = data
data = _get_printers(hass)

name = entry_data.get(CONF_NAME, f"{DEFAULT_NAME} #{len(data) + 1}")

if host is None:
_LOGGER.info("Invalid hostname")
return False

if name in data:
_LOGGER.info(f"Printer {name} already defined")
return False

hp_data = HPDeviceData(host, name)
for printer in data:
if printer.name == name:
_LOGGER.info(f"Printer {name} already defined")
return False

ha = HPPrinterHomeAssistant(hass, SCAN_INTERVAL, name, hp_data)
ha = HPPrinterHomeAssistant(hass, name, host, entry)
ha.initialize()

hass.data[DATA_HP_PRINTER][name] = ha
hass.data[DATA_HP_PRINTER].append(ha)

return True
File renamed without changes.
35 changes: 31 additions & 4 deletions custom_components/hpprinter/const.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,40 @@
from datetime import timedelta

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'

SCAN_INTERVAL = timedelta(minutes=60)
# SCAN_INTERVAL = timedelta(minutes=60)
SCAN_INTERVAL = timedelta(seconds=30)

SENSOR_ENTITY_ID = 'sensor.{}_{}'
BINARY_SENSOR_ENTITY_ID = 'binary_sensor.{}_{}'

NAMESPACES_TO_REMOVE = ["ccdyn", "ad", "dd", "dd2", "pudyn", "psdyn", "xsd", "pscat", "locid"]
NAMESPACES_TO_REMOVE = ["ccdyn", "ad", "dd", "dd2", "pudyn", "psdyn", "xsd", "pscat", "locid", "prdcfgdyn2", "prdcfgdyn"]

ENTITY_ICON = 'icon'
ENTITY_STATE = 'state'
ENTITY_ATTRIBUTES = 'attributes'
ENTITY_NAME = 'name'
ENTITY_MODEL = 'model'

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",
Expand All @@ -26,16 +48,21 @@
"@xmlns:psdyn",
"@xmlns:pscat",
"@xmlns:locid",
"@xmlns:locid",
"@xmlns:prdcfgdyn",
"@xmlns:prdcfgdyn2",
"@xmlns:pudyn",
"PECounter"
]

ARRAY_KEYS = {
"UsageByMedia": [],
"SupportedConsumable": ["ConsumableTypeEnum", "ConsumableLabelCode"],
"SupportedConsumableInfo": ["ConsumableUsageType"]
"SupportedConsumableInfo": ["ConsumableUsageType"],
"EmailAlertCategories": ["AlertCategory"]
}

ARRAY_AS_DEFAULT = ["AlertDetailsUserAction", "ConsumableStateAction"]
ARRAY_AS_DEFAULT = ["AlertDetailsUserAction", "ConsumableStateAction", "AlertCategory", "ResourceURI", "Language"]

HP_DEVICE_STATUS = "Status"
HP_DEVICE_PRINTER = "Printer"
Expand Down
Loading

0 comments on commit 44539cd

Please sign in to comment.