From 8ce4635bd1fa8d89b1cc3235ebd36117f201d7ce Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Mon, 22 Jun 2015 17:57:09 +0200 Subject: [PATCH 01/21] add arduino component --- .coveragerc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.coveragerc b/.coveragerc index 45b67e14b84..756c82f755e 100644 --- a/.coveragerc +++ b/.coveragerc @@ -7,6 +7,9 @@ omit = homeassistant/external/* # omit pieces of code that rely on external devices being present + homeassistant/components/arduino.py + homeassistant/components/*/arduino.py + homeassistant/components/wink.py homeassistant/components/*/wink.py From c0721241e5e5721e0551fb739b2972f235d7a671 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Mon, 22 Jun 2015 17:57:49 +0200 Subject: [PATCH 02/21] add firmata bindings --- requirements.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/requirements.txt b/requirements.txt index e004acab60c..f0b7f74729a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -73,3 +73,6 @@ jsonrpc-requests>=0.1 # Forecast.io Bindings (sensor.forecast) python-forecastio>=1.3.3 + +# Firmata Bindings (*.arduino) +PyMata>=2.07 From 7a6a394bbfdd4bb38ed4276c18f84edf9c0f25dc Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Mon, 22 Jun 2015 17:58:27 +0200 Subject: [PATCH 03/21] add arduino component --- homeassistant/components/arduino.py | 134 ++++++++++++++++++++++++++++ 1 file changed, 134 insertions(+) create mode 100644 homeassistant/components/arduino.py diff --git a/homeassistant/components/arduino.py b/homeassistant/components/arduino.py new file mode 100644 index 00000000000..e7131f9c9e0 --- /dev/null +++ b/homeassistant/components/arduino.py @@ -0,0 +1,134 @@ +""" +components.arduino +~~~~~~~~~~~~~~~~~~ +Arduino component that connects to a directly attached Arduino board which +runs with the Firmata firmware. + +Configuration: + +To use the Arduino board you will need to add something like the following +to your config/configuration.yaml + +arduino: + port: /dev/ttyACM0 + +Variables: + +port +*Required +The port where is your board connected to your Home Assistant system. +If you are using an original Arduino the port will be named ttyACM*. The exact +number can be determined with 'ls /dev/ttyACM*' or check your 'dmesg'/ +'journalctl -f' output. Keep in mind that Arduino clones are often using a +different name for the port (e.g. '/dev/ttyUSB*'). + +A word of caution: The Arduino is not storing states. This means that with +every initialization the pins are set to off/low. +""" +import logging + +from PyMata.pymata import PyMata +import serial + +from homeassistant.helpers import validate_config +from homeassistant.const import (EVENT_HOMEASSISTANT_START, + EVENT_HOMEASSISTANT_STOP) + +DOMAIN = "arduino" +DEPENDENCIES = [] +BOARD = None +_LOGGER = logging.getLogger(__name__) + + +def setup(hass, config): + """ Setup the Arduino component. """ + + if not validate_config(config, + {DOMAIN: ['port']}, + _LOGGER): + return False + + # pylint: disable=global-statement + global BOARD + try: + BOARD = ArduinoBoard(config[DOMAIN]['port']) + except (serial.serialutil.SerialException, FileNotFoundError): + _LOGGER.exception("Your port is not accessible.") + return False + + if BOARD.get_firmata()[1] <= 2: + _LOGGER.error("The StandardFirmata sketch should be 2.2 or newer.") + return False + + def stop_arduino(event): + """ Stop the Arduino service. """ + BOARD.disconnect() + + def start_arduino(event): + """ Start the Arduino service. """ + hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_arduino) + + hass.bus.listen_once(EVENT_HOMEASSISTANT_START, start_arduino) + + return True + + +class ArduinoBoard(object): + """ Represents an Arduino board. """ + + def __init__(self, port): + self._port = port + self._board = PyMata(self._port, verbose=False) + + def set_mode(self, pin, direction, mode): + """ Sets the mode and the direction of a given pin. """ + if mode == 'analog' and direction == 'in': + self._board.set_pin_mode(pin, + self._board.INPUT, + self._board.ANALOG) + elif mode == 'analog' and direction == 'out': + self._board.set_pin_mode(pin, + self._board.OUTPUT, + self._board.ANALOG) + elif mode == 'digital' and direction == 'in': + self._board.set_pin_mode(pin, + self._board.OUTPUT, + self._board.DIGITAL) + elif mode == 'digital' and direction == 'out': + self._board.set_pin_mode(pin, + self._board.OUTPUT, + self._board.DIGITAL) + elif mode == 'pwm': + self._board.set_pin_mode(pin, + self._board.OUTPUT, + self._board.PWM) + + def get_analog_inputs(self): + """ Get the values from the pins. """ + self._board.capability_query() + return self._board.get_analog_response_table() + + def set_digital_out_high(self, pin): + """ Sets a given digital pin to high. """ + self._board.digital_write(pin, 1) + + def set_digital_out_low(self, pin): + """ Sets a given digital pin to low. """ + self._board.digital_write(pin, 0) + + def get_digital_in(self, pin): + """ Gets the value from a given digital pin. """ + self._board.digital_read(pin) + + def get_analog_in(self, pin): + """ Gets the value from a given analog pin. """ + self._board.analog_read(pin) + + def get_firmata(self): + """ Return the version of the Firmata firmware. """ + return self._board.get_firmata_version() + + def disconnect(self): + """ Disconnects the board and closes the serial connection. """ + self._board.reset() + self._board.close() From 1d5a03a624682b4c3862fd931c62d4ea27a60c39 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Mon, 22 Jun 2015 17:58:46 +0200 Subject: [PATCH 04/21] add arduino switch platform --- homeassistant/components/switch/arduino.py | 93 ++++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 homeassistant/components/switch/arduino.py diff --git a/homeassistant/components/switch/arduino.py b/homeassistant/components/switch/arduino.py new file mode 100644 index 00000000000..367e7378b27 --- /dev/null +++ b/homeassistant/components/switch/arduino.py @@ -0,0 +1,93 @@ +""" +homeassistant.components.switch.arduino +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Support for switching Arduino pins on and off. So fare only digital pins are +supported. + +Configuration: + +switch: + platform: arduino + pins: + 11: + name: Fan Office + type: digital + 12: + name: Light Desk + type: digital + +Variables: + +pins +*Required +An array specifying the digital pins to use on the Arduino board. + +These are the variables for the pins array: + +name +*Required +The name for the pin that will be used in the frontend. + +type +*Required +The type of the pin: 'digital'. +""" +import logging + +import homeassistant.components.arduino as arduino +from homeassistant.components.switch import SwitchDevice +from homeassistant.const import DEVICE_DEFAULT_NAME + +DEPENDENCIES = ['arduino'] + +_LOGGER = logging.getLogger(__name__) + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """ Sets up the Arduino platform. """ + + # Verify that Arduino board is present + if arduino.BOARD is None: + _LOGGER.error('A connection has not been made to the Arduino board.') + return False + + switches = [] + pins = config.get('pins') + for pinnum, pin in pins.items(): + if pin.get('name'): + switches.append(ArduinoSwitch(pin.get('name'), + pinnum, + pin.get('type'))) + add_devices(switches) + + +class ArduinoSwitch(SwitchDevice): + """ Represents an Arduino Switch. """ + def __init__(self, name, pin, pin_type): + self._pin = pin + self._name = name or DEVICE_DEFAULT_NAME + self.pin_type = pin_type + self.direction = 'out' + self._state = False + + arduino.BOARD.set_mode(self._pin, self.direction, self.pin_type) + + @property + def name(self): + """ Get the name of the pin. """ + return self._name + + @property + def is_on(self): + """ Returns True if pin is high/on. """ + return self._state + + def turn_on(self): + """ Turns the pin to high/on. """ + self._state = True + arduino.BOARD.set_digital_out_high(self._pin) + + def turn_off(self): + """ Turns the pin to low/off. """ + self._state = False + arduino.BOARD.set_digital_out_low(self._pin) From 8f8fdcdc826b7de22081a14936cc8f9a4a96e987 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Mon, 22 Jun 2015 17:59:02 +0200 Subject: [PATCH 05/21] add arduino sensor platform --- homeassistant/components/sensor/arduino.py | 87 ++++++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 homeassistant/components/sensor/arduino.py diff --git a/homeassistant/components/sensor/arduino.py b/homeassistant/components/sensor/arduino.py new file mode 100644 index 00000000000..4210a064d13 --- /dev/null +++ b/homeassistant/components/sensor/arduino.py @@ -0,0 +1,87 @@ +""" +homeassistant.components.sensor.arduino +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Support for getting information from Arduino pins. Only analog pins are +supported. + +Configuration: + +sensor: + platform: arduino + pins: + 7: + name: Door switch + type: analog + 0: + name: Brightness + type: analog + +Variables: + +pins +*Required +An array specifying the digital pins to use on the Arduino board. + +These are the variables for the pins array: + +name +*Required +The name for the pin that will be used in the frontend. + +type +*Required +The type of the pin: 'analog'. +""" +import logging + +import homeassistant.components.arduino as arduino +from homeassistant.helpers.entity import Entity +from homeassistant.const import DEVICE_DEFAULT_NAME + +DEPENDENCIES = ['arduino'] + +_LOGGER = logging.getLogger(__name__) + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """ Sets up the Arduino platform. """ + + # Verify that Arduino board is present + if arduino.BOARD is None: + _LOGGER.error('A connection has not been made to the Arduino board.') + return False + + sensors = [] + pins = config.get('pins') + for pinnum, pin in pins.items(): + if pin.get('name'): + sensors.append(ArduinoSensor(pin.get('name'), + pinnum, + 'analog')) + add_devices(sensors) + + +class ArduinoSensor(Entity): + """ Represents an Arduino Sensor. """ + def __init__(self, name, pin, pin_type): + self._pin = pin + self._name = name or DEVICE_DEFAULT_NAME + self.pin_type = pin_type + self.direction = 'in' + self._value = None + + arduino.BOARD.set_mode(self._pin, self.direction, self.pin_type) + + @property + def state(self): + """ Returns the state of the sensor. """ + return self._value + + @property + def name(self): + """ Get the name of the sensor. """ + return self._name + + def update(self): + """ Get the latest value from the pin. """ + self._value = arduino.BOARD.get_analog_inputs()[self._pin][1] From 1e4c4012570ed9ec5eb615e678167b6a193fe0bd Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Mon, 22 Jun 2015 19:00:38 +0200 Subject: [PATCH 06/21] add pyserial --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index f0b7f74729a..1d6089715f9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -76,3 +76,4 @@ python-forecastio>=1.3.3 # Firmata Bindings (*.arduino) PyMata>=2.07 +pyserial>=2.7 From 98a4f2fedc246ce431ed56c97d528fb82e1d8d6e Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Mon, 22 Jun 2015 17:57:09 +0200 Subject: [PATCH 07/21] add arduino component --- .coveragerc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.coveragerc b/.coveragerc index 45b67e14b84..756c82f755e 100644 --- a/.coveragerc +++ b/.coveragerc @@ -7,6 +7,9 @@ omit = homeassistant/external/* # omit pieces of code that rely on external devices being present + homeassistant/components/arduino.py + homeassistant/components/*/arduino.py + homeassistant/components/wink.py homeassistant/components/*/wink.py From 457bd2339bb5081aeb3aed46f20f7755cebf6144 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Mon, 22 Jun 2015 17:57:49 +0200 Subject: [PATCH 08/21] add firmata bindings --- requirements.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/requirements.txt b/requirements.txt index e004acab60c..f0b7f74729a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -73,3 +73,6 @@ jsonrpc-requests>=0.1 # Forecast.io Bindings (sensor.forecast) python-forecastio>=1.3.3 + +# Firmata Bindings (*.arduino) +PyMata>=2.07 From a6f975d79f95f4484435fe45553dbe0bf102445c Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Mon, 22 Jun 2015 17:58:27 +0200 Subject: [PATCH 09/21] add arduino component --- homeassistant/components/arduino.py | 134 ++++++++++++++++++++++++++++ 1 file changed, 134 insertions(+) create mode 100644 homeassistant/components/arduino.py diff --git a/homeassistant/components/arduino.py b/homeassistant/components/arduino.py new file mode 100644 index 00000000000..e7131f9c9e0 --- /dev/null +++ b/homeassistant/components/arduino.py @@ -0,0 +1,134 @@ +""" +components.arduino +~~~~~~~~~~~~~~~~~~ +Arduino component that connects to a directly attached Arduino board which +runs with the Firmata firmware. + +Configuration: + +To use the Arduino board you will need to add something like the following +to your config/configuration.yaml + +arduino: + port: /dev/ttyACM0 + +Variables: + +port +*Required +The port where is your board connected to your Home Assistant system. +If you are using an original Arduino the port will be named ttyACM*. The exact +number can be determined with 'ls /dev/ttyACM*' or check your 'dmesg'/ +'journalctl -f' output. Keep in mind that Arduino clones are often using a +different name for the port (e.g. '/dev/ttyUSB*'). + +A word of caution: The Arduino is not storing states. This means that with +every initialization the pins are set to off/low. +""" +import logging + +from PyMata.pymata import PyMata +import serial + +from homeassistant.helpers import validate_config +from homeassistant.const import (EVENT_HOMEASSISTANT_START, + EVENT_HOMEASSISTANT_STOP) + +DOMAIN = "arduino" +DEPENDENCIES = [] +BOARD = None +_LOGGER = logging.getLogger(__name__) + + +def setup(hass, config): + """ Setup the Arduino component. """ + + if not validate_config(config, + {DOMAIN: ['port']}, + _LOGGER): + return False + + # pylint: disable=global-statement + global BOARD + try: + BOARD = ArduinoBoard(config[DOMAIN]['port']) + except (serial.serialutil.SerialException, FileNotFoundError): + _LOGGER.exception("Your port is not accessible.") + return False + + if BOARD.get_firmata()[1] <= 2: + _LOGGER.error("The StandardFirmata sketch should be 2.2 or newer.") + return False + + def stop_arduino(event): + """ Stop the Arduino service. """ + BOARD.disconnect() + + def start_arduino(event): + """ Start the Arduino service. """ + hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_arduino) + + hass.bus.listen_once(EVENT_HOMEASSISTANT_START, start_arduino) + + return True + + +class ArduinoBoard(object): + """ Represents an Arduino board. """ + + def __init__(self, port): + self._port = port + self._board = PyMata(self._port, verbose=False) + + def set_mode(self, pin, direction, mode): + """ Sets the mode and the direction of a given pin. """ + if mode == 'analog' and direction == 'in': + self._board.set_pin_mode(pin, + self._board.INPUT, + self._board.ANALOG) + elif mode == 'analog' and direction == 'out': + self._board.set_pin_mode(pin, + self._board.OUTPUT, + self._board.ANALOG) + elif mode == 'digital' and direction == 'in': + self._board.set_pin_mode(pin, + self._board.OUTPUT, + self._board.DIGITAL) + elif mode == 'digital' and direction == 'out': + self._board.set_pin_mode(pin, + self._board.OUTPUT, + self._board.DIGITAL) + elif mode == 'pwm': + self._board.set_pin_mode(pin, + self._board.OUTPUT, + self._board.PWM) + + def get_analog_inputs(self): + """ Get the values from the pins. """ + self._board.capability_query() + return self._board.get_analog_response_table() + + def set_digital_out_high(self, pin): + """ Sets a given digital pin to high. """ + self._board.digital_write(pin, 1) + + def set_digital_out_low(self, pin): + """ Sets a given digital pin to low. """ + self._board.digital_write(pin, 0) + + def get_digital_in(self, pin): + """ Gets the value from a given digital pin. """ + self._board.digital_read(pin) + + def get_analog_in(self, pin): + """ Gets the value from a given analog pin. """ + self._board.analog_read(pin) + + def get_firmata(self): + """ Return the version of the Firmata firmware. """ + return self._board.get_firmata_version() + + def disconnect(self): + """ Disconnects the board and closes the serial connection. """ + self._board.reset() + self._board.close() From a01de8c90e57483db9df463e7338f208eeb1d18f Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Mon, 22 Jun 2015 17:58:46 +0200 Subject: [PATCH 10/21] add arduino switch platform --- homeassistant/components/switch/arduino.py | 93 ++++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 homeassistant/components/switch/arduino.py diff --git a/homeassistant/components/switch/arduino.py b/homeassistant/components/switch/arduino.py new file mode 100644 index 00000000000..367e7378b27 --- /dev/null +++ b/homeassistant/components/switch/arduino.py @@ -0,0 +1,93 @@ +""" +homeassistant.components.switch.arduino +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Support for switching Arduino pins on and off. So fare only digital pins are +supported. + +Configuration: + +switch: + platform: arduino + pins: + 11: + name: Fan Office + type: digital + 12: + name: Light Desk + type: digital + +Variables: + +pins +*Required +An array specifying the digital pins to use on the Arduino board. + +These are the variables for the pins array: + +name +*Required +The name for the pin that will be used in the frontend. + +type +*Required +The type of the pin: 'digital'. +""" +import logging + +import homeassistant.components.arduino as arduino +from homeassistant.components.switch import SwitchDevice +from homeassistant.const import DEVICE_DEFAULT_NAME + +DEPENDENCIES = ['arduino'] + +_LOGGER = logging.getLogger(__name__) + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """ Sets up the Arduino platform. """ + + # Verify that Arduino board is present + if arduino.BOARD is None: + _LOGGER.error('A connection has not been made to the Arduino board.') + return False + + switches = [] + pins = config.get('pins') + for pinnum, pin in pins.items(): + if pin.get('name'): + switches.append(ArduinoSwitch(pin.get('name'), + pinnum, + pin.get('type'))) + add_devices(switches) + + +class ArduinoSwitch(SwitchDevice): + """ Represents an Arduino Switch. """ + def __init__(self, name, pin, pin_type): + self._pin = pin + self._name = name or DEVICE_DEFAULT_NAME + self.pin_type = pin_type + self.direction = 'out' + self._state = False + + arduino.BOARD.set_mode(self._pin, self.direction, self.pin_type) + + @property + def name(self): + """ Get the name of the pin. """ + return self._name + + @property + def is_on(self): + """ Returns True if pin is high/on. """ + return self._state + + def turn_on(self): + """ Turns the pin to high/on. """ + self._state = True + arduino.BOARD.set_digital_out_high(self._pin) + + def turn_off(self): + """ Turns the pin to low/off. """ + self._state = False + arduino.BOARD.set_digital_out_low(self._pin) From 2c37fb639e91a96cc3260e34046a66485289a475 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Mon, 22 Jun 2015 17:59:02 +0200 Subject: [PATCH 11/21] add arduino sensor platform --- homeassistant/components/sensor/arduino.py | 87 ++++++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 homeassistant/components/sensor/arduino.py diff --git a/homeassistant/components/sensor/arduino.py b/homeassistant/components/sensor/arduino.py new file mode 100644 index 00000000000..4210a064d13 --- /dev/null +++ b/homeassistant/components/sensor/arduino.py @@ -0,0 +1,87 @@ +""" +homeassistant.components.sensor.arduino +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Support for getting information from Arduino pins. Only analog pins are +supported. + +Configuration: + +sensor: + platform: arduino + pins: + 7: + name: Door switch + type: analog + 0: + name: Brightness + type: analog + +Variables: + +pins +*Required +An array specifying the digital pins to use on the Arduino board. + +These are the variables for the pins array: + +name +*Required +The name for the pin that will be used in the frontend. + +type +*Required +The type of the pin: 'analog'. +""" +import logging + +import homeassistant.components.arduino as arduino +from homeassistant.helpers.entity import Entity +from homeassistant.const import DEVICE_DEFAULT_NAME + +DEPENDENCIES = ['arduino'] + +_LOGGER = logging.getLogger(__name__) + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """ Sets up the Arduino platform. """ + + # Verify that Arduino board is present + if arduino.BOARD is None: + _LOGGER.error('A connection has not been made to the Arduino board.') + return False + + sensors = [] + pins = config.get('pins') + for pinnum, pin in pins.items(): + if pin.get('name'): + sensors.append(ArduinoSensor(pin.get('name'), + pinnum, + 'analog')) + add_devices(sensors) + + +class ArduinoSensor(Entity): + """ Represents an Arduino Sensor. """ + def __init__(self, name, pin, pin_type): + self._pin = pin + self._name = name or DEVICE_DEFAULT_NAME + self.pin_type = pin_type + self.direction = 'in' + self._value = None + + arduino.BOARD.set_mode(self._pin, self.direction, self.pin_type) + + @property + def state(self): + """ Returns the state of the sensor. """ + return self._value + + @property + def name(self): + """ Get the name of the sensor. """ + return self._name + + def update(self): + """ Get the latest value from the pin. """ + self._value = arduino.BOARD.get_analog_inputs()[self._pin][1] From cda1374b49af48a34bf9d8f5f7507352ff3ad01c Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Mon, 22 Jun 2015 19:00:38 +0200 Subject: [PATCH 12/21] add pyserial --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index f0b7f74729a..1d6089715f9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -76,3 +76,4 @@ python-forecastio>=1.3.3 # Firmata Bindings (*.arduino) PyMata>=2.07 +pyserial>=2.7 From 34977b836a8e46922f93121349f022c7c990e70d Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Thu, 25 Jun 2015 11:42:19 +0200 Subject: [PATCH 13/21] update pymata --- requirements.txt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 1d6089715f9..7b0ce48be4d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -75,5 +75,4 @@ jsonrpc-requests>=0.1 python-forecastio>=1.3.3 # Firmata Bindings (*.arduino) -PyMata>=2.07 -pyserial>=2.7 +PyMata>=2.07a From 1eef88e85f130a14663a4c45766c37762e0c2428 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Mon, 22 Jun 2015 17:57:09 +0200 Subject: [PATCH 14/21] add arduino component --- .coveragerc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.coveragerc b/.coveragerc index 45b67e14b84..756c82f755e 100644 --- a/.coveragerc +++ b/.coveragerc @@ -7,6 +7,9 @@ omit = homeassistant/external/* # omit pieces of code that rely on external devices being present + homeassistant/components/arduino.py + homeassistant/components/*/arduino.py + homeassistant/components/wink.py homeassistant/components/*/wink.py From 0ebab8f6129f9dee642474b7621474e96baa085d Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Mon, 22 Jun 2015 17:57:49 +0200 Subject: [PATCH 15/21] add firmata bindings --- requirements.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/requirements.txt b/requirements.txt index e004acab60c..f0b7f74729a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -73,3 +73,6 @@ jsonrpc-requests>=0.1 # Forecast.io Bindings (sensor.forecast) python-forecastio>=1.3.3 + +# Firmata Bindings (*.arduino) +PyMata>=2.07 From b33ae47a4c7faadd1b2e9f2ff40d817c8c603ef4 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Mon, 22 Jun 2015 17:58:27 +0200 Subject: [PATCH 16/21] add arduino component --- homeassistant/components/arduino.py | 134 ++++++++++++++++++++++++++++ 1 file changed, 134 insertions(+) create mode 100644 homeassistant/components/arduino.py diff --git a/homeassistant/components/arduino.py b/homeassistant/components/arduino.py new file mode 100644 index 00000000000..e7131f9c9e0 --- /dev/null +++ b/homeassistant/components/arduino.py @@ -0,0 +1,134 @@ +""" +components.arduino +~~~~~~~~~~~~~~~~~~ +Arduino component that connects to a directly attached Arduino board which +runs with the Firmata firmware. + +Configuration: + +To use the Arduino board you will need to add something like the following +to your config/configuration.yaml + +arduino: + port: /dev/ttyACM0 + +Variables: + +port +*Required +The port where is your board connected to your Home Assistant system. +If you are using an original Arduino the port will be named ttyACM*. The exact +number can be determined with 'ls /dev/ttyACM*' or check your 'dmesg'/ +'journalctl -f' output. Keep in mind that Arduino clones are often using a +different name for the port (e.g. '/dev/ttyUSB*'). + +A word of caution: The Arduino is not storing states. This means that with +every initialization the pins are set to off/low. +""" +import logging + +from PyMata.pymata import PyMata +import serial + +from homeassistant.helpers import validate_config +from homeassistant.const import (EVENT_HOMEASSISTANT_START, + EVENT_HOMEASSISTANT_STOP) + +DOMAIN = "arduino" +DEPENDENCIES = [] +BOARD = None +_LOGGER = logging.getLogger(__name__) + + +def setup(hass, config): + """ Setup the Arduino component. """ + + if not validate_config(config, + {DOMAIN: ['port']}, + _LOGGER): + return False + + # pylint: disable=global-statement + global BOARD + try: + BOARD = ArduinoBoard(config[DOMAIN]['port']) + except (serial.serialutil.SerialException, FileNotFoundError): + _LOGGER.exception("Your port is not accessible.") + return False + + if BOARD.get_firmata()[1] <= 2: + _LOGGER.error("The StandardFirmata sketch should be 2.2 or newer.") + return False + + def stop_arduino(event): + """ Stop the Arduino service. """ + BOARD.disconnect() + + def start_arduino(event): + """ Start the Arduino service. """ + hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_arduino) + + hass.bus.listen_once(EVENT_HOMEASSISTANT_START, start_arduino) + + return True + + +class ArduinoBoard(object): + """ Represents an Arduino board. """ + + def __init__(self, port): + self._port = port + self._board = PyMata(self._port, verbose=False) + + def set_mode(self, pin, direction, mode): + """ Sets the mode and the direction of a given pin. """ + if mode == 'analog' and direction == 'in': + self._board.set_pin_mode(pin, + self._board.INPUT, + self._board.ANALOG) + elif mode == 'analog' and direction == 'out': + self._board.set_pin_mode(pin, + self._board.OUTPUT, + self._board.ANALOG) + elif mode == 'digital' and direction == 'in': + self._board.set_pin_mode(pin, + self._board.OUTPUT, + self._board.DIGITAL) + elif mode == 'digital' and direction == 'out': + self._board.set_pin_mode(pin, + self._board.OUTPUT, + self._board.DIGITAL) + elif mode == 'pwm': + self._board.set_pin_mode(pin, + self._board.OUTPUT, + self._board.PWM) + + def get_analog_inputs(self): + """ Get the values from the pins. """ + self._board.capability_query() + return self._board.get_analog_response_table() + + def set_digital_out_high(self, pin): + """ Sets a given digital pin to high. """ + self._board.digital_write(pin, 1) + + def set_digital_out_low(self, pin): + """ Sets a given digital pin to low. """ + self._board.digital_write(pin, 0) + + def get_digital_in(self, pin): + """ Gets the value from a given digital pin. """ + self._board.digital_read(pin) + + def get_analog_in(self, pin): + """ Gets the value from a given analog pin. """ + self._board.analog_read(pin) + + def get_firmata(self): + """ Return the version of the Firmata firmware. """ + return self._board.get_firmata_version() + + def disconnect(self): + """ Disconnects the board and closes the serial connection. """ + self._board.reset() + self._board.close() From 20fd4ecb9aba73a002e9722174c4dc73bae65c5b Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Mon, 22 Jun 2015 17:58:46 +0200 Subject: [PATCH 17/21] add arduino switch platform --- homeassistant/components/switch/arduino.py | 93 ++++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 homeassistant/components/switch/arduino.py diff --git a/homeassistant/components/switch/arduino.py b/homeassistant/components/switch/arduino.py new file mode 100644 index 00000000000..367e7378b27 --- /dev/null +++ b/homeassistant/components/switch/arduino.py @@ -0,0 +1,93 @@ +""" +homeassistant.components.switch.arduino +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Support for switching Arduino pins on and off. So fare only digital pins are +supported. + +Configuration: + +switch: + platform: arduino + pins: + 11: + name: Fan Office + type: digital + 12: + name: Light Desk + type: digital + +Variables: + +pins +*Required +An array specifying the digital pins to use on the Arduino board. + +These are the variables for the pins array: + +name +*Required +The name for the pin that will be used in the frontend. + +type +*Required +The type of the pin: 'digital'. +""" +import logging + +import homeassistant.components.arduino as arduino +from homeassistant.components.switch import SwitchDevice +from homeassistant.const import DEVICE_DEFAULT_NAME + +DEPENDENCIES = ['arduino'] + +_LOGGER = logging.getLogger(__name__) + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """ Sets up the Arduino platform. """ + + # Verify that Arduino board is present + if arduino.BOARD is None: + _LOGGER.error('A connection has not been made to the Arduino board.') + return False + + switches = [] + pins = config.get('pins') + for pinnum, pin in pins.items(): + if pin.get('name'): + switches.append(ArduinoSwitch(pin.get('name'), + pinnum, + pin.get('type'))) + add_devices(switches) + + +class ArduinoSwitch(SwitchDevice): + """ Represents an Arduino Switch. """ + def __init__(self, name, pin, pin_type): + self._pin = pin + self._name = name or DEVICE_DEFAULT_NAME + self.pin_type = pin_type + self.direction = 'out' + self._state = False + + arduino.BOARD.set_mode(self._pin, self.direction, self.pin_type) + + @property + def name(self): + """ Get the name of the pin. """ + return self._name + + @property + def is_on(self): + """ Returns True if pin is high/on. """ + return self._state + + def turn_on(self): + """ Turns the pin to high/on. """ + self._state = True + arduino.BOARD.set_digital_out_high(self._pin) + + def turn_off(self): + """ Turns the pin to low/off. """ + self._state = False + arduino.BOARD.set_digital_out_low(self._pin) From 636071a22ace2da9598cc7892e024dae4bb490f8 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Mon, 22 Jun 2015 17:59:02 +0200 Subject: [PATCH 18/21] add arduino sensor platform --- homeassistant/components/sensor/arduino.py | 87 ++++++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 homeassistant/components/sensor/arduino.py diff --git a/homeassistant/components/sensor/arduino.py b/homeassistant/components/sensor/arduino.py new file mode 100644 index 00000000000..4210a064d13 --- /dev/null +++ b/homeassistant/components/sensor/arduino.py @@ -0,0 +1,87 @@ +""" +homeassistant.components.sensor.arduino +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Support for getting information from Arduino pins. Only analog pins are +supported. + +Configuration: + +sensor: + platform: arduino + pins: + 7: + name: Door switch + type: analog + 0: + name: Brightness + type: analog + +Variables: + +pins +*Required +An array specifying the digital pins to use on the Arduino board. + +These are the variables for the pins array: + +name +*Required +The name for the pin that will be used in the frontend. + +type +*Required +The type of the pin: 'analog'. +""" +import logging + +import homeassistant.components.arduino as arduino +from homeassistant.helpers.entity import Entity +from homeassistant.const import DEVICE_DEFAULT_NAME + +DEPENDENCIES = ['arduino'] + +_LOGGER = logging.getLogger(__name__) + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """ Sets up the Arduino platform. """ + + # Verify that Arduino board is present + if arduino.BOARD is None: + _LOGGER.error('A connection has not been made to the Arduino board.') + return False + + sensors = [] + pins = config.get('pins') + for pinnum, pin in pins.items(): + if pin.get('name'): + sensors.append(ArduinoSensor(pin.get('name'), + pinnum, + 'analog')) + add_devices(sensors) + + +class ArduinoSensor(Entity): + """ Represents an Arduino Sensor. """ + def __init__(self, name, pin, pin_type): + self._pin = pin + self._name = name or DEVICE_DEFAULT_NAME + self.pin_type = pin_type + self.direction = 'in' + self._value = None + + arduino.BOARD.set_mode(self._pin, self.direction, self.pin_type) + + @property + def state(self): + """ Returns the state of the sensor. """ + return self._value + + @property + def name(self): + """ Get the name of the sensor. """ + return self._name + + def update(self): + """ Get the latest value from the pin. """ + self._value = arduino.BOARD.get_analog_inputs()[self._pin][1] From ad1227d655dd3e72e1ee804e5d027bee298546ef Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Mon, 22 Jun 2015 19:00:38 +0200 Subject: [PATCH 19/21] add pyserial --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index f0b7f74729a..1d6089715f9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -76,3 +76,4 @@ python-forecastio>=1.3.3 # Firmata Bindings (*.arduino) PyMata>=2.07 +pyserial>=2.7 From 169e7e9623b8e822e096d8fd015ce13078197c48 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Thu, 25 Jun 2015 13:12:15 +0200 Subject: [PATCH 20/21] again requirements.txt --- requirements.txt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 1d6089715f9..7b0ce48be4d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -75,5 +75,4 @@ jsonrpc-requests>=0.1 python-forecastio>=1.3.3 # Firmata Bindings (*.arduino) -PyMata>=2.07 -pyserial>=2.7 +PyMata>=2.07a From 0cd0d1ea97a1ff4272909a7a4eeacb898f393fed Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Thu, 25 Jun 2015 15:54:33 +0200 Subject: [PATCH 21/21] another try (input from PyMata developer) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 7b0ce48be4d..1bce0cf57c4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -75,4 +75,4 @@ jsonrpc-requests>=0.1 python-forecastio>=1.3.3 # Firmata Bindings (*.arduino) -PyMata>=2.07a +PyMata==2.07a