diff --git a/.coveragerc b/.coveragerc index 20111585c15..df371e1b683 100644 --- a/.coveragerc +++ b/.coveragerc @@ -487,6 +487,7 @@ omit = homeassistant/components/reddit/* homeassistant/components/rejseplanen/sensor.py homeassistant/components/remember_the_milk/__init__.py + homeassistant/components/remote_rpi_gpio/* homeassistant/components/rest/binary_sensor.py homeassistant/components/rest/notify.py homeassistant/components/rest/switch.py diff --git a/homeassistant/components/remote_rpi_gpio/__init__.py b/homeassistant/components/remote_rpi_gpio/__init__.py new file mode 100644 index 00000000000..82865b00cda --- /dev/null +++ b/homeassistant/components/remote_rpi_gpio/__init__.py @@ -0,0 +1,63 @@ +"""Support for controlling GPIO pins of a Raspberry Pi.""" +import logging + +_LOGGER = logging.getLogger(__name__) + +CONF_BOUNCETIME = 'bouncetime' +CONF_INVERT_LOGIC = 'invert_logic' +CONF_PULL_MODE = 'pull_mode' + +DEFAULT_BOUNCETIME = 50 +DEFAULT_INVERT_LOGIC = False +DEFAULT_PULL_MODE = "UP" + +DOMAIN = 'remote_rpi_gpio' + + +def setup(hass, config): + """Set up the Raspberry Pi Remote GPIO component.""" + return True + + +def setup_output(address, port, invert_logic): + """Set up a GPIO as output.""" + from gpiozero import LED + from gpiozero.pins.pigpio import PiGPIOFactory + + try: + return LED(port, active_high=invert_logic, + pin_factory=PiGPIOFactory(address)) + except (ValueError, IndexError, KeyError): + return None + + +def setup_input(address, port, pull_mode, bouncetime): + """Set up a GPIO as input.""" + from gpiozero import Button + from gpiozero.pins.pigpio import PiGPIOFactory + + if pull_mode == "UP": + pull_gpio_up = True + elif pull_mode == "DOWN": + pull_gpio_up = False + + try: + return Button(port, + pull_up=pull_gpio_up, + bounce_time=bouncetime, + pin_factory=PiGPIOFactory(address)) + except (ValueError, IndexError, KeyError, IOError): + return None + + +def write_output(switch, value): + """Write a value to a GPIO.""" + if value == 1: + switch.on() + if value == 0: + switch.off() + + +def read_input(button): + """Read a value from a GPIO.""" + return button.is_pressed diff --git a/homeassistant/components/remote_rpi_gpio/binary_sensor.py b/homeassistant/components/remote_rpi_gpio/binary_sensor.py new file mode 100644 index 00000000000..4c359163e56 --- /dev/null +++ b/homeassistant/components/remote_rpi_gpio/binary_sensor.py @@ -0,0 +1,106 @@ +"""Support for binary sensor using RPi GPIO.""" +import logging + +import voluptuous as vol + +import requests + +from homeassistant.const import CONF_HOST +from homeassistant.components.binary_sensor import ( + BinarySensorDevice, PLATFORM_SCHEMA) + +import homeassistant.helpers.config_validation as cv + +from . import (CONF_BOUNCETIME, CONF_PULL_MODE, CONF_INVERT_LOGIC, + DEFAULT_BOUNCETIME, DEFAULT_INVERT_LOGIC, DEFAULT_PULL_MODE) +from .. import remote_rpi_gpio + +_LOGGER = logging.getLogger(__name__) + +CONF_PORTS = 'ports' + +_SENSORS_SCHEMA = vol.Schema({ + cv.positive_int: cv.string, +}) + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Required(CONF_HOST): cv.string, + vol.Required(CONF_PORTS): _SENSORS_SCHEMA, + vol.Optional(CONF_INVERT_LOGIC, + default=DEFAULT_INVERT_LOGIC): cv.boolean, + vol.Optional(CONF_BOUNCETIME, + default=DEFAULT_BOUNCETIME): cv.positive_int, + vol.Optional(CONF_PULL_MODE, + default=DEFAULT_PULL_MODE): cv.string, +}) + + +def setup_platform(hass, config, add_entities, discovery_info=None): + """Set up the Raspberry PI GPIO devices.""" + address = config['host'] + invert_logic = config[CONF_INVERT_LOGIC] + pull_mode = config[CONF_PULL_MODE] + ports = config['ports'] + bouncetime = config[CONF_BOUNCETIME]/1000 + + devices = [] + for port_num, port_name in ports.items(): + try: + button = remote_rpi_gpio.setup_input(address, + port_num, + pull_mode, + bouncetime) + except (ValueError, IndexError, KeyError, IOError): + return + new_sensor = RemoteRPiGPIOBinarySensor(port_name, button, invert_logic) + devices.append(new_sensor) + + add_entities(devices, True) + + +class RemoteRPiGPIOBinarySensor(BinarySensorDevice): + """Represent a binary sensor that uses a Remote Raspberry Pi GPIO.""" + + def __init__(self, name, button, invert_logic): + """Initialize the RPi binary sensor.""" + self._name = name + self._invert_logic = invert_logic + self._state = False + self._button = button + + async def async_added_to_hass(self): + """Run when entity about to be added to hass.""" + def read_gpio(): + """Read state from GPIO.""" + self._state = remote_rpi_gpio.read_input(self._button) + self.schedule_update_ha_state() + + self._button.when_released = read_gpio + self._button.when_pressed = read_gpio + + @property + def should_poll(self): + """No polling needed.""" + return False + + @property + def name(self): + """Return the name of the sensor.""" + return self._name + + @property + def is_on(self): + """Return the state of the entity.""" + return self._state != self._invert_logic + + @property + def device_class(self): + """Return the class of this sensor, from DEVICE_CLASSES.""" + return + + def update(self): + """Update the GPIO state.""" + try: + self._state = remote_rpi_gpio.read_input(self._button) + except requests.exceptions.ConnectionError: + return diff --git a/homeassistant/components/remote_rpi_gpio/manifest.json b/homeassistant/components/remote_rpi_gpio/manifest.json new file mode 100644 index 00000000000..f15defd63dc --- /dev/null +++ b/homeassistant/components/remote_rpi_gpio/manifest.json @@ -0,0 +1,10 @@ +{ + "domain": "remote_rpi_gpio", + "name": "remote_rpi_gpio", + "documentation": "https://www.home-assistant.io/components/remote_rpi_gpio", + "requirements": [ + "gpiozero==1.4.1" + ], + "dependencies": [], + "codeowners": [] +} diff --git a/homeassistant/components/remote_rpi_gpio/switch.py b/homeassistant/components/remote_rpi_gpio/switch.py new file mode 100644 index 00000000000..493ccf03c32 --- /dev/null +++ b/homeassistant/components/remote_rpi_gpio/switch.py @@ -0,0 +1,91 @@ +"""Allows to configure a switch using RPi GPIO.""" +import logging + +import voluptuous as vol + +from homeassistant.components.switch import SwitchDevice, PLATFORM_SCHEMA +from homeassistant.const import DEVICE_DEFAULT_NAME, CONF_HOST + +import homeassistant.helpers.config_validation as cv + +from . import CONF_INVERT_LOGIC, DEFAULT_INVERT_LOGIC +from .. import remote_rpi_gpio + +_LOGGER = logging.getLogger(__name__) + +CONF_PORTS = 'ports' + +_SENSORS_SCHEMA = vol.Schema({ + cv.positive_int: cv.string, +}) + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Required(CONF_HOST): cv.string, + vol.Required(CONF_PORTS): _SENSORS_SCHEMA, + vol.Optional(CONF_INVERT_LOGIC, + default=DEFAULT_INVERT_LOGIC): cv.boolean +}) + + +def setup_platform(hass, config, add_entities, discovery_info=None): + """Set up the Remote Raspberry PI GPIO devices.""" + address = config[CONF_HOST] + invert_logic = config[CONF_INVERT_LOGIC] + ports = config[CONF_PORTS] + + devices = [] + for port, name in ports.items(): + try: + led = remote_rpi_gpio.setup_output( + address, port, invert_logic) + except (ValueError, IndexError, KeyError, IOError): + return + new_switch = RemoteRPiGPIOSwitch(name, led, invert_logic) + devices.append(new_switch) + + add_entities(devices) + + +class RemoteRPiGPIOSwitch(SwitchDevice): + """Representation of a Remtoe Raspberry Pi GPIO.""" + + def __init__(self, name, led, invert_logic): + """Initialize the pin.""" + self._name = name or DEVICE_DEFAULT_NAME + self._state = False + self._invert_logic = invert_logic + self._switch = led + + @property + def name(self): + """Return the name of the switch.""" + return self._name + + @property + def should_poll(self): + """No polling needed.""" + return False + + @property + def assumed_state(self): + """If unable to access real state of the entity.""" + return True + + @property + def is_on(self): + """Return true if device is on.""" + return self._state + + def turn_on(self, **kwargs): + """Turn the device on.""" + remote_rpi_gpio.write_output(self._switch, + 0 if self._invert_logic else 1) + self._state = True + self.schedule_update_ha_state() + + def turn_off(self, **kwargs): + """Turn the device off.""" + remote_rpi_gpio.write_output(self._switch, + 1 if self._invert_logic else 0) + self._state = False + self.schedule_update_ha_state() diff --git a/requirements_all.txt b/requirements_all.txt index c20b212be88..545ff132bed 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -518,6 +518,9 @@ googledevices==1.0.2 # homeassistant.components.google_travel_time googlemaps==2.5.1 +# homeassistant.components.remote_rpi_gpio +gpiozero==1.4.1 + # homeassistant.components.gpsd gps3==0.33.3