diff --git a/homeassistant/components/cups/sensor.py b/homeassistant/components/cups/sensor.py index cf0ba5f7f8d..c53d44f614d 100644 --- a/homeassistant/components/cups/sensor.py +++ b/homeassistant/components/cups/sensor.py @@ -8,6 +8,7 @@ import voluptuous as vol import homeassistant.helpers.config_validation as cv from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import CONF_HOST, CONF_PORT +from homeassistant.exceptions import PlatformNotReady from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -23,9 +24,11 @@ ATTR_PRINTER_TYPE = 'printer_type' ATTR_PRINTER_URI_SUPPORTED = 'printer_uri_supported' CONF_PRINTERS = 'printers' +CONF_IS_CUPS_SERVER = 'is_cups_server' DEFAULT_HOST = '127.0.0.1' DEFAULT_PORT = 631 +DEFAULT_IS_CUPS_SERVER = True ICON = 'mdi:printer' @@ -39,6 +42,8 @@ PRINTER_STATES = { PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Required(CONF_PRINTERS): vol.All(cv.ensure_list, [cv.string]), + vol.Optional(CONF_IS_CUPS_SERVER, + default=DEFAULT_IS_CUPS_SERVER): cv.boolean, vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string, vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, }) @@ -49,21 +54,36 @@ def setup_platform(hass, config, add_entities, discovery_info=None): host = config.get(CONF_HOST) port = config.get(CONF_PORT) printers = config.get(CONF_PRINTERS) + is_cups = config.get(CONF_IS_CUPS_SERVER) - try: - data = CupsData(host, port) + if is_cups: + data = CupsData(host, port, None) data.update() - except RuntimeError: - _LOGGER.error("Unable to connect to CUPS server: %s:%s", host, port) - return False + if data.available is False: + _LOGGER.error("Unable to connect to CUPS server: %s:%s", + host, port) + raise PlatformNotReady() + + dev = [] + for printer in printers: + if printer not in data.printers: + _LOGGER.error("Printer is not present: %s", printer) + continue + dev.append(CupsSensor(data, printer)) + + add_entities(dev, True) + return + + data = CupsData(host, port, printers) + data.update() + if data.available is False: + _LOGGER.error("Unable to connect to IPP printer: %s:%s", + host, port) + raise PlatformNotReady() dev = [] for printer in printers: - if printer in data.printers: - dev.append(CupsSensor(data, printer)) - else: - _LOGGER.error("Printer is not present: %s", printer) - continue + dev.append(IPPSensor(data, printer)) add_entities(dev, True) @@ -76,6 +96,7 @@ class CupsSensor(Entity): self.data = data self._name = printer self._printer = None + self._available = False @property def name(self): @@ -85,12 +106,16 @@ class CupsSensor(Entity): @property def state(self): """Return the state of the sensor.""" - if self._printer is not None: - try: - return next(v for k, v in PRINTER_STATES.items() - if self._printer['printer-state'] == k) - except StopIteration: - return self._printer['printer-state'] + if self._printer is None: + return None + + key = self._printer['printer-state'] + return PRINTER_STATES.get(key, key) + + @property + def available(self): + """Return True if entity is available.""" + return self._available @property def icon(self): @@ -100,41 +125,133 @@ class CupsSensor(Entity): @property def device_state_attributes(self): """Return the state attributes of the sensor.""" - if self._printer is not None: - return { - ATTR_DEVICE_URI: self._printer['device-uri'], - ATTR_PRINTER_INFO: self._printer['printer-info'], - ATTR_PRINTER_IS_SHARED: self._printer['printer-is-shared'], - ATTR_PRINTER_LOCATION: self._printer['printer-location'], - ATTR_PRINTER_MODEL: self._printer['printer-make-and-model'], - ATTR_PRINTER_STATE_MESSAGE: - self._printer['printer-state-message'], - ATTR_PRINTER_STATE_REASON: - self._printer['printer-state-reasons'], - ATTR_PRINTER_TYPE: self._printer['printer-type'], - ATTR_PRINTER_URI_SUPPORTED: - self._printer['printer-uri-supported'], - } + if self._printer is None: + return None + + return { + ATTR_DEVICE_URI: self._printer['device-uri'], + ATTR_PRINTER_INFO: self._printer['printer-info'], + ATTR_PRINTER_IS_SHARED: self._printer['printer-is-shared'], + ATTR_PRINTER_LOCATION: self._printer['printer-location'], + ATTR_PRINTER_MODEL: self._printer['printer-make-and-model'], + ATTR_PRINTER_STATE_MESSAGE: + self._printer['printer-state-message'], + ATTR_PRINTER_STATE_REASON: + self._printer['printer-state-reasons'], + ATTR_PRINTER_TYPE: self._printer['printer-type'], + ATTR_PRINTER_URI_SUPPORTED: + self._printer['printer-uri-supported'], + } def update(self): """Get the latest data and updates the states.""" self.data.update() self._printer = self.data.printers.get(self._name) + self._available = self.data.available + + +class IPPSensor(Entity): + """Implementation of the IPPSensor. + + This sensor represents the status of the printer. + """ + + def __init__(self, data, name): + """Initialize the sensor.""" + self.data = data + self._name = name + self._attributes = None + self._available = False + + @property + def name(self): + """Return the name of the sensor.""" + return self._attributes['printer-make-and-model'] + + @property + def icon(self): + """Return the icon to use in the frontend.""" + return ICON + + @property + def available(self): + """Return True if entity is available.""" + return self._available + + @property + def state(self): + """Return the state of the sensor.""" + if self._attributes is None: + return None + + key = self._attributes['printer-state'] + return PRINTER_STATES.get(key, key) + + @property + def device_state_attributes(self): + """Return the state attributes of the sensor.""" + if self._attributes is None: + return None + + state_attributes = {} + + if 'printer-info' in self._attributes: + state_attributes[ATTR_PRINTER_INFO] = \ + self._attributes['printer-info'] + + if 'printer-location' in self._attributes: + state_attributes[ATTR_PRINTER_LOCATION] = \ + self._attributes['printer-location'] + + if 'printer-state-message' in self._attributes: + state_attributes[ATTR_PRINTER_STATE_MESSAGE] = \ + self._attributes['printer-state-message'] + + if 'printer-state-reasons' in self._attributes: + state_attributes[ATTR_PRINTER_STATE_REASON] = \ + self._attributes['printer-state-reasons'] + + if 'printer-uri-supported' in self._attributes: + state_attributes[ATTR_PRINTER_URI_SUPPORTED] = \ + self._attributes['printer-uri-supported'] + + return state_attributes + + def update(self): + """Fetch new state data for the sensor.""" + self.data.update() + self._attributes = self.data.attributes.get(self._name) + self._available = self.data.available # pylint: disable=no-name-in-module class CupsData: """Get the latest data from CUPS and update the state.""" - def __init__(self, host, port): + def __init__(self, host, port, ipp_printers): """Initialize the data object.""" self._host = host self._port = port + self._ipp_printers = ipp_printers + self.is_cups = (ipp_printers is None) self.printers = None + self.attributes = {} + self.available = False def update(self): """Get the latest data from CUPS.""" cups = importlib.import_module('cups') - conn = cups.Connection(host=self._host, port=self._port) - self.printers = conn.getPrinters() + try: + conn = cups.Connection(host=self._host, port=self._port) + if self.is_cups: + self.printers = conn.getPrinters() + else: + for ipp_printer in self._ipp_printers: + self.attributes[ipp_printer] = conn.getPrinterAttributes( + uri="ipp://{}:{}/{}" + .format(self._host, self._port, ipp_printer)) + + self.available = True + except RuntimeError: + self.available = False