diff --git a/.coveragerc b/.coveragerc index 680cd996d28..63763cb9231 100644 --- a/.coveragerc +++ b/.coveragerc @@ -62,6 +62,7 @@ omit = homeassistant/components/sensor/mysensors.py homeassistant/components/sensor/openweathermap.py homeassistant/components/sensor/rfxtrx.py + homeassistant/components/sensor/rpi_gpio.py homeassistant/components/sensor/sabnzbd.py homeassistant/components/sensor/swiss_public_transport.py homeassistant/components/sensor/systemmonitor.py diff --git a/homeassistant/components/sensor/rpi_gpio.py b/homeassistant/components/sensor/rpi_gpio.py new file mode 100644 index 00000000000..c57cf31b397 --- /dev/null +++ b/homeassistant/components/sensor/rpi_gpio.py @@ -0,0 +1,133 @@ +# -*- coding: utf-8 -*- +""" +homeassistant.components.sensor.rpi_gpio +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Allows to configure a binary state sensor using RPi GPIO. +Note: To use RPi GPIO, Home Assistant must be run as root. + +sensor: + platform: rpi_gpio + pull_mode: "UP" + value_high: "Active" + value_low: "Inactive" + ports: + 11: PIR Office + 12: PIR Bedroom + +Variables: + +pull_mode +*Optional +The internal pull to use (UP or DOWN). Default is UP. + +value_high +*Optional +The value of the sensor when the port is HIGH. Default is "HIGH". + +value_low +*Optional +The value of the sensor when the port is LOW. Default is "LOW". + +bouncetime +*Optional +The time in milliseconds for port debouncing. Default is 50ms. + +ports +*Required +An array specifying the GPIO ports to use and the name to use in the frontend. + +""" + +import logging +from homeassistant.helpers.entity import Entity +try: + import RPi.GPIO as GPIO +except ImportError: + GPIO = None +from homeassistant.const import (DEVICE_DEFAULT_NAME, + EVENT_HOMEASSISTANT_START, + EVENT_HOMEASSISTANT_STOP) + +DEFAULT_PULL_MODE = "UP" +DEFAULT_VALUE_HIGH = "HIGH" +DEFAULT_VALUE_LOW = "LOW" +DEFAULT_BOUNCETIME = 50 + +REQUIREMENTS = ['RPi.GPIO>=0.5.11'] +_LOGGER = logging.getLogger(__name__) + + +# pylint: disable=unused-argument +def setup_platform(hass, config, add_devices, discovery_info=None): + """ Sets up the Raspberry PI GPIO ports. """ + if GPIO is None: + _LOGGER.error('RPi.GPIO not available. rpi_gpio ports ignored.') + return + # pylint: disable=no-member + GPIO.setmode(GPIO.BCM) + + sensors = [] + pull_mode = config.get('pull_mode', DEFAULT_PULL_MODE) + value_high = config.get('value_high', DEFAULT_VALUE_HIGH) + value_low = config.get('value_low', DEFAULT_VALUE_LOW) + bouncetime = config.get('bouncetime', DEFAULT_BOUNCETIME) + ports = config.get('ports') + for port_num, port_name in ports.items(): + sensors.append(RPiGPIOSensor( + port_name, port_num, pull_mode, + value_high, value_low, bouncetime)) + add_devices(sensors) + + def cleanup_gpio(event): + """ Stuff to do before stop home assistant. """ + # pylint: disable=no-member + GPIO.cleanup() + + def prepare_gpio(event): + """ Stuff to do when home assistant starts. """ + hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, cleanup_gpio) + + hass.bus.listen_once(EVENT_HOMEASSISTANT_START, prepare_gpio) + + +# pylint: disable=too-many-arguments, too-many-instance-attributes +class RPiGPIOSensor(Entity): + """ Sets up the Raspberry PI GPIO ports. """ + def __init__(self, port_name, port_num, pull_mode, + value_high, value_low, bouncetime): + # pylint: disable=no-member + self._name = port_name or DEVICE_DEFAULT_NAME + self._port = port_num + self._pull = GPIO.PUD_DOWN if pull_mode == "DOWN" else GPIO.PUD_UP + self._vhigh = value_high + self._vlow = value_low + self._bouncetime = bouncetime + GPIO.setup(self._port, GPIO.IN, pull_up_down=self._pull) + self._state = self._vhigh if GPIO.input(self._port) else self._vlow + + def edge_callback(channel): + """ port changed state """ + # pylint: disable=no-member + self._state = self._vhigh if GPIO.input(channel) else self._vlow + self.update_ha_state() + + GPIO.add_event_detect( + self._port, + GPIO.BOTH, + callback=edge_callback, + bouncetime=self._bouncetime) + + @property + def should_poll(self): + """ No polling needed """ + return False + + @property + def name(self): + """ The name of the sensor """ + return self._name + + @property + def state(self): + """ Returns the state of the entity. """ + return self._state diff --git a/homeassistant/components/switch/rpi_gpio.py b/homeassistant/components/switch/rpi_gpio.py index 127d02a121d..bb9cf13e3ed 100644 --- a/homeassistant/components/switch/rpi_gpio.py +++ b/homeassistant/components/switch/rpi_gpio.py @@ -2,21 +2,28 @@ homeassistant.components.switch.rpi_gpio ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Allows to control the GPIO pins of a Raspberry Pi. +Note: To use RPi GPIO, Home Assistant must be run as root. Configuration: switch: platform: rpi_gpio + invert_logic: false ports: 11: Fan Office 12: Light Desk Variables: +invert_logic +*Optional +If true, inverts the output logic to ACTIVE LOW. Default is false (ACTIVE HIGH) + ports *Required An array specifying the GPIO ports to use and the name to use in the frontend. """ + import logging try: import RPi.GPIO as GPIO @@ -27,6 +34,8 @@ from homeassistant.const import (DEVICE_DEFAULT_NAME, EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP) +DEFAULT_INVERT_LOGIC = False + REQUIREMENTS = ['RPi.GPIO>=0.5.11'] _LOGGER = logging.getLogger(__name__) @@ -37,11 +46,14 @@ def setup_platform(hass, config, add_devices, discovery_info=None): if GPIO is None: _LOGGER.error('RPi.GPIO not available. rpi_gpio ports ignored.') return + # pylint: disable=no-member + GPIO.setmode(GPIO.BCM) switches = [] + invert_logic = config.get('invert_logic', DEFAULT_INVERT_LOGIC) ports = config.get('ports') for port_num, port_name in ports.items(): - switches.append(RPiGPIOSwitch(port_name, port_num)) + switches.append(RPiGPIOSwitch(port_name, port_num, invert_logic)) add_devices(switches) def cleanup_gpio(event): @@ -59,10 +71,11 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class RPiGPIOSwitch(ToggleEntity): """ Represents a port that can be toggled using Raspberry Pi GPIO. """ - def __init__(self, name, gpio): + def __init__(self, name, gpio, invert_logic): self._name = name or DEVICE_DEFAULT_NAME - self._state = False self._gpio = gpio + self._active_state = not invert_logic + self._state = not self._active_state # pylint: disable=no-member GPIO.setup(gpio, GPIO.OUT) @@ -83,13 +96,13 @@ class RPiGPIOSwitch(ToggleEntity): def turn_on(self, **kwargs): """ Turn the device on. """ - if self._switch(True): + if self._switch(self._active_state): self._state = True self.update_ha_state() def turn_off(self, **kwargs): """ Turn the device off. """ - if self._switch(False): + if self._switch(not self._active_state): self._state = False self.update_ha_state()