diff --git a/.coveragerc b/.coveragerc index 20bbc0474eb..8f32fcdbe37 100644 --- a/.coveragerc +++ b/.coveragerc @@ -343,6 +343,7 @@ omit = homeassistant/components/mastodon/notify.py homeassistant/components/matrix/* homeassistant/components/maxcube/* + homeassistant/components/mcp23017/* homeassistant/components/media_extractor/* homeassistant/components/mediaroom/media_player.py homeassistant/components/message_bird/notify.py diff --git a/CODEOWNERS b/CODEOWNERS index cadf6a2e841..00e05b98778 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -143,6 +143,7 @@ homeassistant/components/luci/* @fbradyirl homeassistant/components/luftdaten/* @fabaff homeassistant/components/mastodon/* @fabaff homeassistant/components/matrix/* @tinloaf +homeassistant/components/mcp23017/* @jardiamj homeassistant/components/mediaroom/* @dgomes homeassistant/components/melissa/* @kennedyshead homeassistant/components/met/* @danielhiversen diff --git a/homeassistant/components/mcp23017/__init__.py b/homeassistant/components/mcp23017/__init__.py new file mode 100644 index 00000000000..350ebc7f71d --- /dev/null +++ b/homeassistant/components/mcp23017/__init__.py @@ -0,0 +1,3 @@ +"""Support for I2C MCP23017 chip.""" + +DOMAIN = 'mcp23017' diff --git a/homeassistant/components/mcp23017/binary_sensor.py b/homeassistant/components/mcp23017/binary_sensor.py new file mode 100644 index 00000000000..6934468ec1c --- /dev/null +++ b/homeassistant/components/mcp23017/binary_sensor.py @@ -0,0 +1,89 @@ +"""Support for binary sensor using I2C MCP23017 chip.""" +import logging + +import voluptuous as vol + +from homeassistant.components.binary_sensor import ( + BinarySensorDevice, PLATFORM_SCHEMA) +from homeassistant.const import DEVICE_DEFAULT_NAME +import homeassistant.helpers.config_validation as cv + +_LOGGER = logging.getLogger(__name__) + +CONF_INVERT_LOGIC = 'invert_logic' +CONF_I2C_ADDRESS = 'i2c_address' +CONF_PINS = 'pins' +CONF_PULL_MODE = 'pull_mode' + +MODE_UP = 'UP' +MODE_DOWN = 'DOWN' + +DEFAULT_INVERT_LOGIC = False +DEFAULT_I2C_ADDRESS = 0x20 +DEFAULT_PULL_MODE = MODE_UP + +_SENSORS_SCHEMA = vol.Schema({ + cv.positive_int: cv.string, +}) + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Required(CONF_PINS): _SENSORS_SCHEMA, + vol.Optional(CONF_INVERT_LOGIC, default=DEFAULT_INVERT_LOGIC): cv.boolean, + vol.Optional(CONF_PULL_MODE, default=DEFAULT_PULL_MODE): + vol.All(vol.Upper, vol.In([MODE_UP, MODE_DOWN])), + vol.Optional(CONF_I2C_ADDRESS, default=DEFAULT_I2C_ADDRESS): + vol.Coerce(int), +}) + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """Set up the MCP23017 binary sensors.""" + import board + import busio + import adafruit_mcp230xx + + pull_mode = config[CONF_PULL_MODE] + invert_logic = config[CONF_INVERT_LOGIC] + i2c_address = config[CONF_I2C_ADDRESS] + + i2c = busio.I2C(board.SCL, board.SDA) + mcp = adafruit_mcp230xx.MCP23017(i2c, address=i2c_address) + + binary_sensors = [] + pins = config[CONF_PINS] + + for pin_num, pin_name in pins.items(): + pin = mcp.get_pin(pin_num) + binary_sensors.append(MCP23017BinarySensor( + pin_name, pin, pull_mode, invert_logic)) + + add_devices(binary_sensors, True) + + +class MCP23017BinarySensor(BinarySensorDevice): + """Represent a binary sensor that uses MCP23017.""" + + def __init__(self, name, pin, pull_mode, invert_logic): + """Initialize the MCP23017 binary sensor.""" + import digitalio + self._name = name or DEVICE_DEFAULT_NAME + self._pin = pin + self._pull_mode = pull_mode + self._invert_logic = invert_logic + self._state = None + self._pin.direction = digitalio.Direction.INPUT + self._pin.pull = digitalio.Pull.UP + + @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 + + def update(self): + """Update the GPIO state.""" + self._state = self._pin.value diff --git a/homeassistant/components/mcp23017/manifest.json b/homeassistant/components/mcp23017/manifest.json new file mode 100644 index 00000000000..41048683c92 --- /dev/null +++ b/homeassistant/components/mcp23017/manifest.json @@ -0,0 +1,12 @@ +{ + "domain": "mcp23017", + "name": "MCP23017 I/O Expander", + "documentation": "https://www.home-assistant.io/components/mcp23017", + "requirements": [ + "RPi.GPIO==0.6.5", + "adafruit-blinka==1.2.1", + "adafruit-circuitpython-mcp230xx==1.1.2" + ], + "dependencies": [], + "codeowners": ["@jardiamj"] +} diff --git a/homeassistant/components/mcp23017/switch.py b/homeassistant/components/mcp23017/switch.py new file mode 100644 index 00000000000..caa183543ac --- /dev/null +++ b/homeassistant/components/mcp23017/switch.py @@ -0,0 +1,92 @@ +"""Support for switch sensor using I2C MCP23017 chip.""" +import logging + +import voluptuous as vol + +from homeassistant.components.switch import PLATFORM_SCHEMA +from homeassistant.const import DEVICE_DEFAULT_NAME +from homeassistant.helpers.entity import ToggleEntity +import homeassistant.helpers.config_validation as cv + +_LOGGER = logging.getLogger(__name__) + +CONF_INVERT_LOGIC = 'invert_logic' +CONF_I2C_ADDRESS = 'i2c_address' +CONF_PINS = 'pins' +CONF_PULL_MODE = 'pull_mode' + +DEFAULT_INVERT_LOGIC = False +DEFAULT_I2C_ADDRESS = 0x20 + +_SWITCHES_SCHEMA = vol.Schema({ + cv.positive_int: cv.string, +}) + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Required(CONF_PINS): _SWITCHES_SCHEMA, + vol.Optional(CONF_INVERT_LOGIC, default=DEFAULT_INVERT_LOGIC): cv.boolean, + vol.Optional(CONF_I2C_ADDRESS, default=DEFAULT_I2C_ADDRESS): + vol.Coerce(int), +}) + + +def setup_platform(hass, config, add_entities, discovery_info=None): + """Set up the MCP23017 devices.""" + import board + import busio + import adafruit_mcp230xx + + invert_logic = config.get(CONF_INVERT_LOGIC) + i2c_address = config.get(CONF_I2C_ADDRESS) + + i2c = busio.I2C(board.SCL, board.SDA) + mcp = adafruit_mcp230xx.MCP23017(i2c, address=i2c_address) + + switches = [] + pins = config.get(CONF_PINS) + for pin_num, pin_name in pins.items(): + pin = mcp.get_pin(pin_num) + switches.append(MCP23017Switch(pin_name, pin, invert_logic)) + add_entities(switches) + + +class MCP23017Switch(ToggleEntity): + """Representation of a MCP23017 output pin.""" + + def __init__(self, name, pin, invert_logic): + """Initialize the pin.""" + import digitalio + self._name = name or DEVICE_DEFAULT_NAME + self._pin = pin + self._invert_logic = invert_logic + self._state = False + + self._pin.direction = digitalio.Direction.OUTPUT + self._pin.value = self._invert_logic + + @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 is_on(self): + """Return true if device is on.""" + return self._state + + def turn_on(self, **kwargs): + """Turn the device on.""" + self._pin.value = not self._invert_logic + self._state = True + self.schedule_update_ha_state() + + def turn_off(self, **kwargs): + """Turn the device off.""" + self._pin.value = self._invert_logic + self._state = False + self.schedule_update_ha_state() diff --git a/requirements_all.txt b/requirements_all.txt index b1ab4f872a3..dbaa52d6baf 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -75,6 +75,7 @@ PyTransportNSW==0.1.1 # homeassistant.components.xiaomi_aqara PyXiaomiGateway==0.12.3 +# homeassistant.components.mcp23017 # homeassistant.components.rpi_gpio # RPi.GPIO==0.6.5 @@ -99,6 +100,12 @@ YesssSMS==0.2.3 # homeassistant.components.abode abodepy==0.15.0 +# homeassistant.components.mcp23017 +adafruit-blinka==1.2.1 + +# homeassistant.components.mcp23017 +adafruit-circuitpython-mcp230xx==1.1.2 + # homeassistant.components.frontier_silicon afsapi==0.0.4