mirror of
https://github.com/home-assistant/core.git
synced 2025-07-27 15:17:35 +00:00
Add new raspihats component (#7392)
* Add new raspihats component * added raspihats to COMMENT_REQUIREMENTS in gen_requirements_all.py * disabled pylint import errors * using hass.data for storing i2c-hats manager
This commit is contained in:
parent
526abdd329
commit
92411cdc18
@ -83,6 +83,8 @@ omit =
|
|||||||
homeassistant/components/qwikswitch.py
|
homeassistant/components/qwikswitch.py
|
||||||
homeassistant/components/*/qwikswitch.py
|
homeassistant/components/*/qwikswitch.py
|
||||||
|
|
||||||
|
homeassistant/components/raspihats.py
|
||||||
|
|
||||||
homeassistant/components/rfxtrx.py
|
homeassistant/components/rfxtrx.py
|
||||||
homeassistant/components/*/rfxtrx.py
|
homeassistant/components/*/rfxtrx.py
|
||||||
|
|
||||||
|
249
homeassistant/components/raspihats.py
Normal file
249
homeassistant/components/raspihats.py
Normal file
@ -0,0 +1,249 @@
|
|||||||
|
"""
|
||||||
|
Support for controlling raspihats boards.
|
||||||
|
|
||||||
|
For more details about this component, please refer to the documentation at
|
||||||
|
https://home-assistant.io/components/raspihats/
|
||||||
|
"""
|
||||||
|
import logging
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
|
||||||
|
from homeassistant.const import (
|
||||||
|
EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP
|
||||||
|
)
|
||||||
|
|
||||||
|
REQUIREMENTS = ['raspihats==2.2.1']
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
DOMAIN = 'raspihats'
|
||||||
|
|
||||||
|
CONF_I2C_HATS = 'i2c_hats'
|
||||||
|
CONF_BOARD = 'board'
|
||||||
|
CONF_ADDRESS = 'address'
|
||||||
|
CONF_CHANNELS = 'channels'
|
||||||
|
CONF_INDEX = 'index'
|
||||||
|
CONF_INVERT_LOGIC = 'invert_logic'
|
||||||
|
CONF_INITIAL_STATE = 'initial_state'
|
||||||
|
|
||||||
|
I2C_HAT_NAMES = [
|
||||||
|
'Di16', 'Rly10', 'Di6Rly6',
|
||||||
|
'DI16ac', 'DQ10rly', 'DQ16oc', 'DI6acDQ6rly'
|
||||||
|
]
|
||||||
|
|
||||||
|
I2C_HATS_MANAGER = 'I2CH_MNG'
|
||||||
|
|
||||||
|
|
||||||
|
# pylint: disable=unused-argument
|
||||||
|
def setup(hass, config):
|
||||||
|
"""Setup the raspihats component."""
|
||||||
|
hass.data[I2C_HATS_MANAGER] = I2CHatsManager()
|
||||||
|
|
||||||
|
def start_i2c_hats_keep_alive(event):
|
||||||
|
"""Start I2C-HATs keep alive."""
|
||||||
|
hass.data[I2C_HATS_MANAGER].start_keep_alive()
|
||||||
|
|
||||||
|
def stop_i2c_hats_keep_alive(event):
|
||||||
|
"""Stop I2C-HATs keep alive."""
|
||||||
|
hass.data[I2C_HATS_MANAGER].stop_keep_alive()
|
||||||
|
|
||||||
|
hass.bus.listen_once(EVENT_HOMEASSISTANT_START, start_i2c_hats_keep_alive)
|
||||||
|
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_i2c_hats_keep_alive)
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def log_message(source, *parts):
|
||||||
|
"""Build log message."""
|
||||||
|
message = source.__class__.__name__
|
||||||
|
for part in parts:
|
||||||
|
message += ": " + str(part)
|
||||||
|
return message
|
||||||
|
|
||||||
|
|
||||||
|
class I2CHatsException(Exception):
|
||||||
|
"""I2C-HATs exception."""
|
||||||
|
|
||||||
|
|
||||||
|
class I2CHatsDIScanner(object):
|
||||||
|
"""Scan Digital Inputs and fire callbacks."""
|
||||||
|
|
||||||
|
_DIGITAL_INPUTS = "di"
|
||||||
|
_OLD_VALUE = "old_value"
|
||||||
|
_CALLBACKS = "callbacks"
|
||||||
|
|
||||||
|
def setup(self, i2c_hat):
|
||||||
|
"""Setup I2C-HAT instance for digital inputs scanner."""
|
||||||
|
if hasattr(i2c_hat, self._DIGITAL_INPUTS):
|
||||||
|
digital_inputs = getattr(i2c_hat, self._DIGITAL_INPUTS)
|
||||||
|
old_value = None
|
||||||
|
# add old value attribute
|
||||||
|
setattr(digital_inputs, self._OLD_VALUE, old_value)
|
||||||
|
# add callbacks dict attribute {channel: callback}
|
||||||
|
setattr(digital_inputs, self._CALLBACKS, {})
|
||||||
|
|
||||||
|
def register_callback(self, i2c_hat, channel, callback):
|
||||||
|
"""Register edge callback."""
|
||||||
|
if hasattr(i2c_hat, self._DIGITAL_INPUTS):
|
||||||
|
digital_inputs = getattr(i2c_hat, self._DIGITAL_INPUTS)
|
||||||
|
callbacks = getattr(digital_inputs, self._CALLBACKS)
|
||||||
|
callbacks[channel] = callback
|
||||||
|
setattr(digital_inputs, self._CALLBACKS, callbacks)
|
||||||
|
|
||||||
|
def scan(self, i2c_hat):
|
||||||
|
"""Scan I2C-HATs digital inputs and fire callbacks."""
|
||||||
|
if hasattr(i2c_hat, self._DIGITAL_INPUTS):
|
||||||
|
digital_inputs = getattr(i2c_hat, self._DIGITAL_INPUTS)
|
||||||
|
callbacks = getattr(digital_inputs, self._CALLBACKS)
|
||||||
|
old_value = getattr(digital_inputs, self._OLD_VALUE)
|
||||||
|
value = digital_inputs.value # i2c data transfer
|
||||||
|
if old_value is not None and value != old_value:
|
||||||
|
for channel in range(0, len(digital_inputs.channels)):
|
||||||
|
state = (value >> channel) & 0x01
|
||||||
|
old_state = (old_value >> channel) & 0x01
|
||||||
|
if state != old_state:
|
||||||
|
callback = callbacks.get(channel, None)
|
||||||
|
if callback is not None:
|
||||||
|
callback(state)
|
||||||
|
setattr(digital_inputs, self._OLD_VALUE, value)
|
||||||
|
|
||||||
|
|
||||||
|
class I2CHatsManager(threading.Thread):
|
||||||
|
"""Manages all I2C-HATs instances."""
|
||||||
|
|
||||||
|
_EXCEPTION = "exception"
|
||||||
|
_CALLBACKS = "callbacks"
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
"""Init I2C-HATs Manager."""
|
||||||
|
threading.Thread.__init__(self)
|
||||||
|
self._lock = threading.Lock()
|
||||||
|
self._i2c_hats = {}
|
||||||
|
self._run = False
|
||||||
|
self._di_scanner = I2CHatsDIScanner()
|
||||||
|
|
||||||
|
def register_board(self, board, address):
|
||||||
|
"""Register I2C-HAT."""
|
||||||
|
with self._lock:
|
||||||
|
i2c_hat = self._i2c_hats.get(address)
|
||||||
|
if i2c_hat is None:
|
||||||
|
# pylint: disable=import-error
|
||||||
|
import raspihats.i2c_hats as module
|
||||||
|
constructor = getattr(module, board)
|
||||||
|
i2c_hat = constructor(address)
|
||||||
|
setattr(i2c_hat, self._CALLBACKS, {})
|
||||||
|
|
||||||
|
# Setting exception attribute will trigger online callbacks
|
||||||
|
# when keep alive thread starts.
|
||||||
|
setattr(i2c_hat, self._EXCEPTION, None)
|
||||||
|
|
||||||
|
self._di_scanner.setup(i2c_hat)
|
||||||
|
self._i2c_hats[address] = i2c_hat
|
||||||
|
status_word = i2c_hat.status # read status_word to reset bits
|
||||||
|
_LOGGER.info(
|
||||||
|
log_message(self, i2c_hat, "registered", status_word)
|
||||||
|
)
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
"""Keep alive for I2C-HATs."""
|
||||||
|
# pylint: disable=import-error
|
||||||
|
from raspihats.i2c_hats import ResponseException
|
||||||
|
|
||||||
|
_LOGGER.info(
|
||||||
|
log_message(self, "starting")
|
||||||
|
)
|
||||||
|
while self._run:
|
||||||
|
with self._lock:
|
||||||
|
for i2c_hat in list(self._i2c_hats.values()):
|
||||||
|
try:
|
||||||
|
self._di_scanner.scan(i2c_hat)
|
||||||
|
self._read_status(i2c_hat)
|
||||||
|
|
||||||
|
if hasattr(i2c_hat, self._EXCEPTION):
|
||||||
|
if getattr(i2c_hat, self._EXCEPTION) is not None:
|
||||||
|
_LOGGER.warning(
|
||||||
|
log_message(self, i2c_hat, "online again")
|
||||||
|
)
|
||||||
|
delattr(i2c_hat, self._EXCEPTION)
|
||||||
|
# trigger online callbacks
|
||||||
|
callbacks = getattr(i2c_hat, self._CALLBACKS)
|
||||||
|
for callback in list(callbacks.values()):
|
||||||
|
callback()
|
||||||
|
except ResponseException as ex:
|
||||||
|
if not hasattr(i2c_hat, self._EXCEPTION):
|
||||||
|
_LOGGER.error(
|
||||||
|
log_message(self, i2c_hat, ex)
|
||||||
|
)
|
||||||
|
setattr(i2c_hat, self._EXCEPTION, ex)
|
||||||
|
time.sleep(0.05)
|
||||||
|
_LOGGER.info(
|
||||||
|
log_message(self, "exiting")
|
||||||
|
)
|
||||||
|
|
||||||
|
def _read_status(self, i2c_hat):
|
||||||
|
"""Read I2C-HATs status."""
|
||||||
|
status_word = i2c_hat.status
|
||||||
|
if status_word.value != 0x00:
|
||||||
|
_LOGGER.error(
|
||||||
|
log_message(self, i2c_hat, status_word)
|
||||||
|
)
|
||||||
|
|
||||||
|
def start_keep_alive(self):
|
||||||
|
"""Start keep alive mechanism."""
|
||||||
|
self._run = True
|
||||||
|
threading.Thread.start(self)
|
||||||
|
|
||||||
|
def stop_keep_alive(self):
|
||||||
|
"""Stop keep alive mechanism."""
|
||||||
|
self._run = False
|
||||||
|
self.join()
|
||||||
|
|
||||||
|
def register_di_callback(self, address, channel, callback):
|
||||||
|
"""Register I2C-HAT digital input edge callback."""
|
||||||
|
with self._lock:
|
||||||
|
i2c_hat = self._i2c_hats[address]
|
||||||
|
self._di_scanner.register_callback(i2c_hat, channel, callback)
|
||||||
|
|
||||||
|
def register_online_callback(self, address, channel, callback):
|
||||||
|
"""Register I2C-HAT online callback."""
|
||||||
|
with self._lock:
|
||||||
|
i2c_hat = self._i2c_hats[address]
|
||||||
|
callbacks = getattr(i2c_hat, self._CALLBACKS)
|
||||||
|
callbacks[channel] = callback
|
||||||
|
setattr(i2c_hat, self._CALLBACKS, callbacks)
|
||||||
|
|
||||||
|
def read_di(self, address, channel):
|
||||||
|
"""Read a value from a I2C-HAT digital input."""
|
||||||
|
# pylint: disable=import-error
|
||||||
|
from raspihats.i2c_hats import ResponseException
|
||||||
|
|
||||||
|
with self._lock:
|
||||||
|
i2c_hat = self._i2c_hats[address]
|
||||||
|
try:
|
||||||
|
value = i2c_hat.di.value
|
||||||
|
return (value >> channel) & 0x01
|
||||||
|
except ResponseException as ex:
|
||||||
|
raise I2CHatsException(str(ex))
|
||||||
|
|
||||||
|
def write_dq(self, address, channel, value):
|
||||||
|
"""Write a value to a I2C-HAT digital output."""
|
||||||
|
# pylint: disable=import-error
|
||||||
|
from raspihats.i2c_hats import ResponseException
|
||||||
|
|
||||||
|
with self._lock:
|
||||||
|
i2c_hat = self._i2c_hats[address]
|
||||||
|
try:
|
||||||
|
i2c_hat.dq.channels[channel] = value
|
||||||
|
except ResponseException as ex:
|
||||||
|
raise I2CHatsException(str(ex))
|
||||||
|
|
||||||
|
def read_dq(self, address, channel):
|
||||||
|
"""Read a value from a I2C-HAT digital output."""
|
||||||
|
# pylint: disable=import-error
|
||||||
|
from raspihats.i2c_hats import ResponseException
|
||||||
|
|
||||||
|
with self._lock:
|
||||||
|
i2c_hat = self._i2c_hats[address]
|
||||||
|
try:
|
||||||
|
return i2c_hat.dq.channels[channel]
|
||||||
|
except ResponseException as ex:
|
||||||
|
raise I2CHatsException(str(ex))
|
@ -721,6 +721,9 @@ qnapstats==0.2.4
|
|||||||
# homeassistant.components.climate.radiotherm
|
# homeassistant.components.climate.radiotherm
|
||||||
radiotherm==1.2
|
radiotherm==1.2
|
||||||
|
|
||||||
|
# homeassistant.components.raspihats
|
||||||
|
# raspihats==2.2.1
|
||||||
|
|
||||||
# homeassistant.components.rflink
|
# homeassistant.components.rflink
|
||||||
rflink==0.0.31
|
rflink==0.0.31
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@ import sys
|
|||||||
|
|
||||||
COMMENT_REQUIREMENTS = (
|
COMMENT_REQUIREMENTS = (
|
||||||
'RPi.GPIO',
|
'RPi.GPIO',
|
||||||
|
'raspihats',
|
||||||
'rpi-rf',
|
'rpi-rf',
|
||||||
'Adafruit_Python_DHT',
|
'Adafruit_Python_DHT',
|
||||||
'Adafruit_BBIO',
|
'Adafruit_BBIO',
|
||||||
|
Loading…
x
Reference in New Issue
Block a user