From 40bc49aaae926b970827962931ea5880423260de Mon Sep 17 00:00:00 2001 From: ntouran Date: Sun, 22 May 2016 14:15:09 -0700 Subject: [PATCH 1/9] Added LIRC component for responding to IR remote commands --- homeassistant/components/sensor/lirc.py | 109 ++++++++++++++++++++++++ requirements_all.txt | 3 + 2 files changed, 112 insertions(+) create mode 100644 homeassistant/components/sensor/lirc.py diff --git a/homeassistant/components/sensor/lirc.py b/homeassistant/components/sensor/lirc.py new file mode 100644 index 00000000000..e3096fd8762 --- /dev/null +++ b/homeassistant/components/sensor/lirc.py @@ -0,0 +1,109 @@ +""" +LIRC interface to receive signals from a infrared remote control. + +This sensor will momentarily set state to various values as defined +in the .lintrc file which can be interpreted in home-assistant to +trigger various actions. + +Sending signals to other IR receivers can be accomplished with the +shell_command component and the irsend command. +""" + +import threading +import time +import logging + +from homeassistant.helpers.entity import Entity +from homeassistant.const import EVENT_HOMEASSISTANT_STOP + +LIRC = None + +REQUIREMENTS = ['python-lirc==1.2.1'] +_LOGGER = logging.getLogger(__name__) +ICON = 'mdi:remote' + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """Setup LIRC capability.""" + # Perform safe import of third-party python-lirc module + try: + import lirc + global LIRC + LIRC = lirc + except ImportError: + _LOGGER.error("You are missing a required dependency: python-lirc.") + return False + + LIRC.init('home-assistant', blocking=False) + sensor = LircSensor() + add_devices([sensor]) + + hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, sensor.stop) + + +class LircSensor(Entity): + """Sensor entity for LIRC.""" + + def __init__(self, *args, **kwargs): + """Contruct a LircSensor entity.""" + _LOGGER.info('Initializing LIRC sensor') + Entity.__init__(self, *args, **kwargs) + self.last_key_pressed = '' + self._lirc_interface = LircInterface(self) + self._lirc_interface.start() + + @property + def name(self): + """Name of lirc sensor.""" + return 'lirc' + + @property + def state(self): + """State of LIRC sensor.""" + return self.last_key_pressed + + def update_state(self, new_state): + """Inform system of update when they occur.""" + self.last_key_pressed = new_state + self.update_ha_state() + + def stop(self, event): + """Kill the helper thread on stop.""" + _LOGGER.info('Ending LIRC interface thread') + self._lirc_interface.stopped.set() + + +class LircInterface(threading.Thread): + """ + This interfaces with the lirc daemon to read IR commands. + + When using lirc in blocking mode, sometimes repeated commands get produced + in the next read of a command so we use a thread here to just wait + around until a non-empty response is obtained from lirc. + """ + + def __init__(self, parent): + """Construct a LIRC interface object.""" + threading.Thread.__init__(self) + self.stopped = threading.Event() + self._parent = parent + + def run(self): + """Main loop of LIRC interface thread.""" + while not self.stopped.isSet(): + code = LIRC.nextcode() # list; empty if no buttons pressed + + # interpret result from python-lirc + if code: + code = code[0] + else: + code = '' + + # update if changed. + if code != self._parent.state: + _LOGGER.info('Got new LIRC code %s', code) + self._parent.update_state(code) + else: + time.sleep(0.1) # avoid high CPU in this thread + + _LOGGER.info('LIRC interface thread stopped') diff --git a/requirements_all.txt b/requirements_all.txt index 92c9b735b2c..bca754e110e 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -262,6 +262,9 @@ pysnmp==4.2.5 # homeassistant.components.sensor.forecast python-forecastio==1.3.4 +# homeassistant.components.sensor.lirc +python-lirc==1.2.1 + # homeassistant.components.media_player.mpd python-mpd2==0.5.5 From b3e9e1dfcdb56dbf10eaed13a0ce8f073c44d258 Mon Sep 17 00:00:00 2001 From: ntouran Date: Sun, 22 May 2016 16:11:26 -0700 Subject: [PATCH 2/9] added LIRC component to .coveragerc --- .coveragerc | 1 + 1 file changed, 1 insertion(+) diff --git a/.coveragerc b/.coveragerc index 734a5c7b78d..d6af8e558c0 100644 --- a/.coveragerc +++ b/.coveragerc @@ -164,6 +164,7 @@ omit = homeassistant/components/sensor/google_travel_time.py homeassistant/components/sensor/gtfs.py homeassistant/components/sensor/lastfm.py + homeassistant/components/sensor/lirc.py homeassistant/components/sensor/loopenergy.py homeassistant/components/sensor/netatmo.py homeassistant/components/sensor/neurio_energy.py From 80e60efd8f77ca768b4e1ffae43850246d6cd524 Mon Sep 17 00:00:00 2001 From: ntouran Date: Sun, 22 May 2016 16:28:20 -0700 Subject: [PATCH 3/9] Removed LIRC dependency from requirements due to "complex" compliation User will have to install lirc and python-lirc manually. --- homeassistant/components/sensor/lirc.py | 1 - requirements_all.txt | 3 --- 2 files changed, 4 deletions(-) diff --git a/homeassistant/components/sensor/lirc.py b/homeassistant/components/sensor/lirc.py index e3096fd8762..e6872e029eb 100644 --- a/homeassistant/components/sensor/lirc.py +++ b/homeassistant/components/sensor/lirc.py @@ -18,7 +18,6 @@ from homeassistant.const import EVENT_HOMEASSISTANT_STOP LIRC = None -REQUIREMENTS = ['python-lirc==1.2.1'] _LOGGER = logging.getLogger(__name__) ICON = 'mdi:remote' diff --git a/requirements_all.txt b/requirements_all.txt index bca754e110e..92c9b735b2c 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -262,9 +262,6 @@ pysnmp==4.2.5 # homeassistant.components.sensor.forecast python-forecastio==1.3.4 -# homeassistant.components.sensor.lirc -python-lirc==1.2.1 - # homeassistant.components.media_player.mpd python-mpd2==0.5.5 From 4e5b5f22048ac769c5bfe01f62b104ebd963643b Mon Sep 17 00:00:00 2001 From: ntouran Date: Sun, 22 May 2016 22:19:10 -0700 Subject: [PATCH 4/9] LIRC: Responded to some code review requests but not the big one --- homeassistant/components/sensor/lirc.py | 19 +++++++++---------- requirements_all.txt | 3 +++ script/gen_requirements_all.py | 1 + 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/sensor/lirc.py b/homeassistant/components/sensor/lirc.py index e6872e029eb..9d96d6bcd7d 100644 --- a/homeassistant/components/sensor/lirc.py +++ b/homeassistant/components/sensor/lirc.py @@ -6,9 +6,9 @@ in the .lintrc file which can be interpreted in home-assistant to trigger various actions. Sending signals to other IR receivers can be accomplished with the -shell_command component and the irsend command. +shell_command component and the irsend command for now. """ - +# pylint: disable=import-error import threading import time import logging @@ -16,8 +16,7 @@ import logging from homeassistant.helpers.entity import Entity from homeassistant.const import EVENT_HOMEASSISTANT_STOP -LIRC = None - +REQUIREMENTS = ['python-lirc>=1.2.1'] _LOGGER = logging.getLogger(__name__) ICON = 'mdi:remote' @@ -27,13 +26,12 @@ def setup_platform(hass, config, add_devices, discovery_info=None): # Perform safe import of third-party python-lirc module try: import lirc - global LIRC - LIRC = lirc except ImportError: _LOGGER.error("You are missing a required dependency: python-lirc.") return False - LIRC.init('home-assistant', blocking=False) + # blocking=True gives unexpected behavior (multiple responses for 1 press) + lirc.init('home-assistant', blocking=False) sensor = LircSensor() add_devices([sensor]) @@ -44,7 +42,7 @@ class LircSensor(Entity): """Sensor entity for LIRC.""" def __init__(self, *args, **kwargs): - """Contruct a LircSensor entity.""" + """Construct a LircSensor entity.""" _LOGGER.info('Initializing LIRC sensor') Entity.__init__(self, *args, **kwargs) self.last_key_pressed = '' @@ -66,7 +64,7 @@ class LircSensor(Entity): self.last_key_pressed = new_state self.update_ha_state() - def stop(self, event): + def stop(self, _event): """Kill the helper thread on stop.""" _LOGGER.info('Ending LIRC interface thread') self._lirc_interface.stopped.set() @@ -89,8 +87,9 @@ class LircInterface(threading.Thread): def run(self): """Main loop of LIRC interface thread.""" + import lirc while not self.stopped.isSet(): - code = LIRC.nextcode() # list; empty if no buttons pressed + code = lirc.nextcode() # list; empty if no buttons pressed # interpret result from python-lirc if code: diff --git a/requirements_all.txt b/requirements_all.txt index 92c9b735b2c..b7c383f7ddc 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -262,6 +262,9 @@ pysnmp==4.2.5 # homeassistant.components.sensor.forecast python-forecastio==1.3.4 +# homeassistant.components.sensor.lirc +# python-lirc>=1.2.1 + # homeassistant.components.media_player.mpd python-mpd2==0.5.5 diff --git a/script/gen_requirements_all.py b/script/gen_requirements_all.py index 76ed3acba39..872d13bab75 100755 --- a/script/gen_requirements_all.py +++ b/script/gen_requirements_all.py @@ -13,6 +13,7 @@ COMMENT_REQUIREMENTS = [ 'fritzconnection', 'pybluez', 'bluepy', + 'python-lirc', ] From c1f96aabb09283884b832b6852bc588d5300c31a Mon Sep 17 00:00:00 2001 From: ntouran Date: Mon, 23 May 2016 21:26:49 -0700 Subject: [PATCH 5/9] Changed LIRC component so that it just fires events on the bus. --- homeassistant/components/sensor/lirc.py | 56 ++++++------------------- 1 file changed, 13 insertions(+), 43 deletions(-) diff --git a/homeassistant/components/sensor/lirc.py b/homeassistant/components/sensor/lirc.py index 9d96d6bcd7d..76d10d2e621 100644 --- a/homeassistant/components/sensor/lirc.py +++ b/homeassistant/components/sensor/lirc.py @@ -13,12 +13,13 @@ import threading import time import logging -from homeassistant.helpers.entity import Entity from homeassistant.const import EVENT_HOMEASSISTANT_STOP REQUIREMENTS = ['python-lirc>=1.2.1'] _LOGGER = logging.getLogger(__name__) ICON = 'mdi:remote' +EVENT_BUTTON_PRESSED = 'button_pressed' +BUTTON_NAME = 'button_name' def setup_platform(hass, config, add_devices, discovery_info=None): @@ -31,43 +32,18 @@ def setup_platform(hass, config, add_devices, discovery_info=None): return False # blocking=True gives unexpected behavior (multiple responses for 1 press) + # also by not blocking, we allow hass to shut down the thread gracefully + # on exit. lirc.init('home-assistant', blocking=False) - sensor = LircSensor() - add_devices([sensor]) + lirc_interface = LircInterface(hass) + lirc_interface.start() - hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, sensor.stop) + def _stop_lirc(_event): + lirc_interface.stopped.set() + hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, _stop_lirc) -class LircSensor(Entity): - """Sensor entity for LIRC.""" - - def __init__(self, *args, **kwargs): - """Construct a LircSensor entity.""" - _LOGGER.info('Initializing LIRC sensor') - Entity.__init__(self, *args, **kwargs) - self.last_key_pressed = '' - self._lirc_interface = LircInterface(self) - self._lirc_interface.start() - - @property - def name(self): - """Name of lirc sensor.""" - return 'lirc' - - @property - def state(self): - """State of LIRC sensor.""" - return self.last_key_pressed - - def update_state(self, new_state): - """Inform system of update when they occur.""" - self.last_key_pressed = new_state - self.update_ha_state() - - def stop(self, _event): - """Kill the helper thread on stop.""" - _LOGGER.info('Ending LIRC interface thread') - self._lirc_interface.stopped.set() + return True class LircInterface(threading.Thread): @@ -79,28 +55,22 @@ class LircInterface(threading.Thread): around until a non-empty response is obtained from lirc. """ - def __init__(self, parent): + def __init__(self, hass): """Construct a LIRC interface object.""" threading.Thread.__init__(self) self.stopped = threading.Event() - self._parent = parent + self.hass = hass def run(self): """Main loop of LIRC interface thread.""" import lirc while not self.stopped.isSet(): code = lirc.nextcode() # list; empty if no buttons pressed - # interpret result from python-lirc if code: code = code[0] - else: - code = '' - - # update if changed. - if code != self._parent.state: _LOGGER.info('Got new LIRC code %s', code) - self._parent.update_state(code) + self.hass.bus.fire(EVENT_BUTTON_PRESSED, {BUTTON_NAME: code}) else: time.sleep(0.1) # avoid high CPU in this thread From 09161ae61591201ca6a6ded2a6fb5401f35bbd09 Mon Sep 17 00:00:00 2001 From: ntouran Date: Mon, 23 May 2016 21:36:48 -0700 Subject: [PATCH 6/9] Moved lirc out of sensor package. --- .coveragerc | 2 +- homeassistant/components/{sensor => }/lirc.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) rename homeassistant/components/{sensor => }/lirc.py (97%) diff --git a/.coveragerc b/.coveragerc index d6af8e558c0..786f88535bd 100644 --- a/.coveragerc +++ b/.coveragerc @@ -111,6 +111,7 @@ omit = homeassistant/components/light/hyperion.py homeassistant/components/light/lifx.py homeassistant/components/light/limitlessled.py + homeassistant/components/lirc.py homeassistant/components/media_player/cast.py homeassistant/components/media_player/denon.py homeassistant/components/media_player/firetv.py @@ -164,7 +165,6 @@ omit = homeassistant/components/sensor/google_travel_time.py homeassistant/components/sensor/gtfs.py homeassistant/components/sensor/lastfm.py - homeassistant/components/sensor/lirc.py homeassistant/components/sensor/loopenergy.py homeassistant/components/sensor/netatmo.py homeassistant/components/sensor/neurio_energy.py diff --git a/homeassistant/components/sensor/lirc.py b/homeassistant/components/lirc.py similarity index 97% rename from homeassistant/components/sensor/lirc.py rename to homeassistant/components/lirc.py index 76d10d2e621..a34b4b6a584 100644 --- a/homeassistant/components/sensor/lirc.py +++ b/homeassistant/components/lirc.py @@ -15,6 +15,7 @@ import logging from homeassistant.const import EVENT_HOMEASSISTANT_STOP +DOMAIN = "lirc" REQUIREMENTS = ['python-lirc>=1.2.1'] _LOGGER = logging.getLogger(__name__) ICON = 'mdi:remote' @@ -22,7 +23,7 @@ EVENT_BUTTON_PRESSED = 'button_pressed' BUTTON_NAME = 'button_name' -def setup_platform(hass, config, add_devices, discovery_info=None): +def setup(hass, config): """Setup LIRC capability.""" # Perform safe import of third-party python-lirc module try: From 148b8c505559dea1332c0cdc6ec88848dee11faa Mon Sep 17 00:00:00 2001 From: ntouran Date: Mon, 23 May 2016 21:47:46 -0700 Subject: [PATCH 7/9] Updated requirements for LIRC --- requirements_all.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_all.txt b/requirements_all.txt index b7c383f7ddc..ea490d08194 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -262,7 +262,7 @@ pysnmp==4.2.5 # homeassistant.components.sensor.forecast python-forecastio==1.3.4 -# homeassistant.components.sensor.lirc +# homeassistant.components.lirc # python-lirc>=1.2.1 # homeassistant.components.media_player.mpd From e30f2bf91229357b7a8b0af241f5bc339769581a Mon Sep 17 00:00:00 2001 From: ntouran Date: Wed, 25 May 2016 22:26:00 -0700 Subject: [PATCH 8/9] Cleanups to LIRC module --- homeassistant/components/lirc.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/homeassistant/components/lirc.py b/homeassistant/components/lirc.py index a34b4b6a584..091f8342dbd 100644 --- a/homeassistant/components/lirc.py +++ b/homeassistant/components/lirc.py @@ -13,35 +13,34 @@ import threading import time import logging -from homeassistant.const import EVENT_HOMEASSISTANT_STOP +from homeassistant.const import (EVENT_HOMEASSISTANT_STOP, + EVENT_HOMEASSISTANT_START) DOMAIN = "lirc" REQUIREMENTS = ['python-lirc>=1.2.1'] _LOGGER = logging.getLogger(__name__) ICON = 'mdi:remote' -EVENT_BUTTON_PRESSED = 'button_pressed' +EVENT_IR_COMMAND_RECEIVED = 'ir_command_received' BUTTON_NAME = 'button_name' def setup(hass, config): """Setup LIRC capability.""" - # Perform safe import of third-party python-lirc module - try: - import lirc - except ImportError: - _LOGGER.error("You are missing a required dependency: python-lirc.") - return False + import lirc # blocking=True gives unexpected behavior (multiple responses for 1 press) # also by not blocking, we allow hass to shut down the thread gracefully # on exit. lirc.init('home-assistant', blocking=False) lirc_interface = LircInterface(hass) - lirc_interface.start() + + def _start_lirc(_event): + lirc_interface.start() def _stop_lirc(_event): lirc_interface.stopped.set() + hass.bus.listen_once(EVENT_HOMEASSISTANT_START, _start_lirc) hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, _stop_lirc) return True @@ -59,6 +58,7 @@ class LircInterface(threading.Thread): def __init__(self, hass): """Construct a LIRC interface object.""" threading.Thread.__init__(self) + self.daemon = True self.stopped = threading.Event() self.hass = hass @@ -71,8 +71,8 @@ class LircInterface(threading.Thread): if code: code = code[0] _LOGGER.info('Got new LIRC code %s', code) - self.hass.bus.fire(EVENT_BUTTON_PRESSED, {BUTTON_NAME: code}) + self.hass.bus.fire(EVENT_IR_COMMAND_RECEIVED, + {BUTTON_NAME: code}) else: - time.sleep(0.1) # avoid high CPU in this thread - + time.sleep(0.2) _LOGGER.info('LIRC interface thread stopped') From d5053989175502d764050e6039ea23516d16d587 Mon Sep 17 00:00:00 2001 From: ntouran Date: Thu, 26 May 2016 07:53:17 -0700 Subject: [PATCH 9/9] Locked in required version of python-lirc for LIRC component --- homeassistant/components/lirc.py | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/lirc.py b/homeassistant/components/lirc.py index 091f8342dbd..ec172d1b7f2 100644 --- a/homeassistant/components/lirc.py +++ b/homeassistant/components/lirc.py @@ -17,7 +17,7 @@ from homeassistant.const import (EVENT_HOMEASSISTANT_STOP, EVENT_HOMEASSISTANT_START) DOMAIN = "lirc" -REQUIREMENTS = ['python-lirc>=1.2.1'] +REQUIREMENTS = ['python-lirc==1.2.1'] _LOGGER = logging.getLogger(__name__) ICON = 'mdi:remote' EVENT_IR_COMMAND_RECEIVED = 'ir_command_received' diff --git a/requirements_all.txt b/requirements_all.txt index 5fb7916189e..db5d2e0903c 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -263,7 +263,7 @@ pysnmp==4.2.5 python-forecastio==1.3.4 # homeassistant.components.lirc -# python-lirc>=1.2.1 +# python-lirc==1.2.1 # homeassistant.components.media_player.mpd python-mpd2==0.5.5