From d87e96967192278b6d50a93c208bcbc495d7b4bd Mon Sep 17 00:00:00 2001 From: happyleavesaoc Date: Mon, 20 Jun 2016 01:22:29 +0000 Subject: [PATCH 1/4] add cec platform --- .coveragerc | 1 + homeassistant/components/cec.py | 120 ++++++++++++++++++++++++++++++++ 2 files changed, 121 insertions(+) create mode 100644 homeassistant/components/cec.py diff --git a/.coveragerc b/.coveragerc index 265c653d636..6416ad3e2b4 100644 --- a/.coveragerc +++ b/.coveragerc @@ -94,6 +94,7 @@ omit = homeassistant/components/camera/generic.py homeassistant/components/camera/mjpeg.py homeassistant/components/camera/rpi_camera.py + homeassistant/components/cec.py homeassistant/components/device_tracker/actiontec.py homeassistant/components/device_tracker/aruba.py homeassistant/components/device_tracker/asuswrt.py diff --git a/homeassistant/components/cec.py b/homeassistant/components/cec.py new file mode 100644 index 00000000000..0034eb131b6 --- /dev/null +++ b/homeassistant/components/cec.py @@ -0,0 +1,120 @@ +""" +CEC platform. + +Requires libcec + Python bindings. +""" + +import logging +import voluptuous as vol +from homeassistant.const import EVENT_HOMEASSISTANT_START +import homeassistant.helpers.config_validation as cv + + +_LOGGER = logging.getLogger(__name__) +_CEC = None +DOMAIN = 'cec' +SERVICE_SELECT_DEVICE = 'select_device' +SERVICE_POWER_ON = 'power_on' +SERVICE_STANDBY = 'standby' +CONF_DEVICES = 'devices' +ATTR_DEVICE = 'device' +MAX_DEPTH = 4 + + +# pylint: disable=unnecessary-lambda +DEVICE_SCHEMA = vol.Schema({ + vol.All(cv.positive_int): vol.Any(lambda devices: DEVICE_SCHEMA(devices), + cv.string) +}) + + +PLATFORM_SCHEMA = vol.Schema({ + vol.Required(CONF_DEVICES): DEVICE_SCHEMA +}) + + +def parse_mapping(mapping, parents=None): + """Parse configuration device mapping.""" + if parents is None: + parents = [] + for addr, val in mapping.items(): + cur = parents + [str(addr)] + if isinstance(val, dict): + yield from parse_mapping(val, cur) + elif isinstance(val, str): + yield (val, cur) + + +def pad_physical_address(addr): + """Right-pad a physical address""" + return addr + ['0'] * (MAX_DEPTH - len(addr)) + + +def setup(hass, config): + """Setup CEC capability.""" + global _CEC + + # cec is only available if libcec is properly installed + # and the Python bindings are accessible. + try: + import cec + except ImportError: + _LOGGER.error("libcec must be installed") + return False + + # Parse configuration into a dict of device name + # to physical address represented as a list of + # four elements. + flat = {} + for pair in parse_mapping(config[DOMAIN][0].get(CONF_DEVICES, {})): + flat[pair[0]] = pad_physical_address(pair[1]) + + # Configure libcec. + cfg = cec.libcec_configuration() + cfg.strDeviceName = 'HASS' + cfg.bActivateSource = 0 + cfg.bMonitorOnly = 1 + cfg.clientVersion = cec.LIBCEC_VERSION_CURRENT + + # Set up CEC adapter. + _CEC = cec.ICECAdapter.Create(cfg) + + def _power_on(call): + """Power on all devices.""" + _CEC.PowerOnDevices() + + def _standby(call): + """Standby all devices.""" + _CEC.StandbyDevices() + + def _select_device(call): + """Select the active device.""" + path = flat.get(call.data[ATTR_DEVICE]) + if not path: + _LOGGER.error("Device not found: %s", call.data[ATTR_DEVICE]) + cmds = [] + for i in range(1, MAX_DEPTH - 1): + addr = pad_physical_address(path[:i]) + cmds.append('1f:82:{}{}:{}{}'.format(*addr)) + cmds.append('1f:86:{}{}:{}{}'.format(*addr)) + for cmd in cmds: + _CEC.Transmit(_CEC.CommandFromString(cmd)) + _LOGGER.info("Selected %s", call.data[ATTR_DEVICE]) + + def _start_cec(event): + """Open CEC adapter.""" + adapters = _CEC.DetectAdapters() + if len(adapters) == 0: + _LOGGER.error("No CEC adapter found") + return + + if _CEC.Open(adapters[0].strComName): + hass.services.register(DOMAIN, SERVICE_POWER_ON, _power_on) + hass.services.register(DOMAIN, SERVICE_STANDBY, _standby) + hass.services.register(DOMAIN, SERVICE_SELECT_DEVICE, + _select_device) + else: + _LOGGER.error("Failed to open adapter") + + hass.bus.listen_once(EVENT_HOMEASSISTANT_START, _start_cec) + return True From 7fc9fa4b0c186633c2609cfef923407f97c831ca Mon Sep 17 00:00:00 2001 From: happyleaves Date: Tue, 21 Jun 2016 19:31:40 -0400 Subject: [PATCH 2/4] satisfy farcy --- homeassistant/components/cec.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/cec.py b/homeassistant/components/cec.py index 0034eb131b6..0c7c40c1214 100644 --- a/homeassistant/components/cec.py +++ b/homeassistant/components/cec.py @@ -46,7 +46,7 @@ def parse_mapping(mapping, parents=None): def pad_physical_address(addr): - """Right-pad a physical address""" + """Right-pad a physical address.""" return addr + ['0'] * (MAX_DEPTH - len(addr)) From 94b47d8bc361ce1485443b2d42bacc85e13cd876 Mon Sep 17 00:00:00 2001 From: happyleaves Date: Wed, 22 Jun 2016 17:07:46 -0400 Subject: [PATCH 3/4] addressed review --- homeassistant/components/{cec.py => hdmi_cec.py} | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) rename homeassistant/components/{cec.py => hdmi_cec.py} (95%) diff --git a/homeassistant/components/cec.py b/homeassistant/components/hdmi_cec.py similarity index 95% rename from homeassistant/components/cec.py rename to homeassistant/components/hdmi_cec.py index 0c7c40c1214..f5a64b909af 100644 --- a/homeassistant/components/cec.py +++ b/homeassistant/components/hdmi_cec.py @@ -1,5 +1,5 @@ """ -CEC platform. +CEC component. Requires libcec + Python bindings. """ @@ -12,7 +12,7 @@ import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) _CEC = None -DOMAIN = 'cec' +DOMAIN = 'hdmi_cec' SERVICE_SELECT_DEVICE = 'select_device' SERVICE_POWER_ON = 'power_on' SERVICE_STANDBY = 'standby' @@ -28,8 +28,10 @@ DEVICE_SCHEMA = vol.Schema({ }) -PLATFORM_SCHEMA = vol.Schema({ - vol.Required(CONF_DEVICES): DEVICE_SCHEMA +CONFIG_SCHEMA = vol.Schema({ + DOMAIN: vol.Schema({ + vol.Required(CONF_DEVICES): DEVICE_SCHEMA + }) }) From d0ee8abcb839ec13409fc66d0aca24a46eaac932 Mon Sep 17 00:00:00 2001 From: happyleaves Date: Wed, 22 Jun 2016 17:29:22 -0400 Subject: [PATCH 4/4] couple fixes --- .coveragerc | 2 +- homeassistant/components/hdmi_cec.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.coveragerc b/.coveragerc index 6416ad3e2b4..0f9904d80a2 100644 --- a/.coveragerc +++ b/.coveragerc @@ -94,7 +94,6 @@ omit = homeassistant/components/camera/generic.py homeassistant/components/camera/mjpeg.py homeassistant/components/camera/rpi_camera.py - homeassistant/components/cec.py homeassistant/components/device_tracker/actiontec.py homeassistant/components/device_tracker/aruba.py homeassistant/components/device_tracker/asuswrt.py @@ -115,6 +114,7 @@ omit = homeassistant/components/downloader.py homeassistant/components/feedreader.py homeassistant/components/garage_door/wink.py + homeassistant/components/hdmi_cec.py homeassistant/components/ifttt.py homeassistant/components/keyboard.py homeassistant/components/light/blinksticklight.py diff --git a/homeassistant/components/hdmi_cec.py b/homeassistant/components/hdmi_cec.py index f5a64b909af..89cbe789c58 100644 --- a/homeassistant/components/hdmi_cec.py +++ b/homeassistant/components/hdmi_cec.py @@ -32,7 +32,7 @@ CONFIG_SCHEMA = vol.Schema({ DOMAIN: vol.Schema({ vol.Required(CONF_DEVICES): DEVICE_SCHEMA }) -}) +}, extra=vol.ALLOW_EXTRA) def parse_mapping(mapping, parents=None): @@ -68,7 +68,7 @@ def setup(hass, config): # to physical address represented as a list of # four elements. flat = {} - for pair in parse_mapping(config[DOMAIN][0].get(CONF_DEVICES, {})): + for pair in parse_mapping(config[DOMAIN].get(CONF_DEVICES, {})): flat[pair[0]] = pad_physical_address(pair[1]) # Configure libcec.