diff --git a/esphome/components/mcp23017.py b/esphome/components/mcp23017.py new file mode 100644 index 0000000000..0db97591f6 --- /dev/null +++ b/esphome/components/mcp23017.py @@ -0,0 +1,35 @@ +import voluptuous as vol + +from esphome import pins +import esphome.config_validation as cv +from esphome.const import CONF_ADDRESS, CONF_ID +from esphome.cpp_generator import Pvariable +from esphome.cpp_helpers import setup_component +from esphome.cpp_types import App, GPIOInputPin, GPIOOutputPin, io_ns + +DEPENDENCIES = ['i2c'] +MULTI_CONF = True + +MCP23017GPIOMode = io_ns.enum('MCP23017GPIOMode') +MCP23017_GPIO_MODES = { + 'INPUT': MCP23017GPIOMode.MCP23017_INPUT, + 'INPUT_PULLUP': MCP23017GPIOMode.MCP23017_INPUT_PULLUP, + 'OUTPUT': MCP23017GPIOMode.MCP23017_OUTPUT, +} + +MCP23017GPIOInputPin = io_ns.class_('MCP23017GPIOInputPin', GPIOInputPin) +MCP23017GPIOOutputPin = io_ns.class_('MCP23017GPIOOutputPin', GPIOOutputPin) + +CONFIG_SCHEMA = cv.Schema({ + vol.Required(CONF_ID): cv.declare_variable_id(pins.MCP23017), + vol.Optional(CONF_ADDRESS, default=0x20): cv.i2c_address, +}).extend(cv.COMPONENT_SCHEMA.schema) + + +def to_code(config): + rhs = App.make_mcp23017_component(config[CONF_ADDRESS]) + var = Pvariable(config[CONF_ID], rhs) + setup_component(var, config) + + +BUILD_FLAGS = '-DUSE_MCP23017' diff --git a/esphome/const.py b/esphome/const.py index b5af7f6b67..11678e0bd8 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -199,6 +199,7 @@ CONF_CSS_URL = 'css_url' CONF_JS_URL = 'js_url' CONF_SSL_FINGERPRINTS = 'ssl_fingerprints' CONF_PCF8574 = 'pcf8574' +CONF_MCP23017 = 'mcp23017' CONF_PCF8575 = 'pcf8575' CONF_SCAN = 'scan' CONF_KEEPALIVE = 'keepalive' diff --git a/esphome/cpp_helpers.py b/esphome/cpp_helpers.py index 4b3a628d89..1d7e1b7036 100644 --- a/esphome/cpp_helpers.py +++ b/esphome/cpp_helpers.py @@ -1,5 +1,5 @@ from esphome.const import CONF_INVERTED, CONF_MODE, CONF_NUMBER, CONF_PCF8574, \ - CONF_SETUP_PRIORITY + CONF_SETUP_PRIORITY, CONF_MCP23017 from esphome.core import CORE, EsphomeError from esphome.cpp_generator import IntLiteral, RawExpression from esphome.cpp_types import GPIOInputPin, GPIOOutputPin @@ -24,6 +24,21 @@ def generic_gpio_pin_expression_(conf, mock_obj, default_mode): yield hub.make_output_pin(number, inverted) return + raise EsphomeError(u"Unknown default mode {}".format(default_mode)) + if CONF_MCP23017 in conf: + from esphome.components import mcp23017 + + for hub in CORE.get_variable(conf[CONF_MCP23017]): + yield None + + if default_mode == u'INPUT': + mode = mcp23017.MCP23017_GPIO_MODES[conf.get(CONF_MODE, u'INPUT')] + yield hub.make_input_pin(number, mode, inverted) + return + if default_mode == u'OUTPUT': + yield hub.make_output_pin(number, inverted) + return + raise EsphomeError(u"Unknown default mode {}".format(default_mode)) if len(conf) == 1: yield IntLiteral(number) diff --git a/esphome/pins.py b/esphome/pins.py index 06b7fdd52f..39027608bd 100644 --- a/esphome/pins.py +++ b/esphome/pins.py @@ -5,7 +5,7 @@ import logging import voluptuous as vol import esphome.config_validation as cv -from esphome.const import CONF_INVERTED, CONF_MODE, CONF_NUMBER, CONF_PCF8574 +from esphome.const import CONF_INVERTED, CONF_MODE, CONF_NUMBER, CONF_PCF8574, CONF_MCP23017 from esphome.core import CORE from esphome.cpp_types import Component, esphome_ns, io_ns @@ -358,6 +358,7 @@ def validate_has_interrupt(value): I2CDevice = esphome_ns.class_('I2CDevice') PCF8574Component = io_ns.class_('PCF8574Component', Component, I2CDevice) +MCP23017 = io_ns.class_('MCP23017', Component, I2CDevice) PCF8574_OUTPUT_PIN_SCHEMA = cv.Schema({ vol.Required(CONF_PCF8574): cv.use_variable_id(PCF8574Component), @@ -370,6 +371,17 @@ PCF8574_INPUT_PIN_SCHEMA = PCF8574_OUTPUT_PIN_SCHEMA.extend({ vol.Optional(CONF_MODE): cv.one_of("INPUT", "INPUT_PULLUP", upper=True), }) +MCP23017_OUTPUT_PIN_SCHEMA = cv.Schema({ + vol.Required(CONF_MCP23017): cv.use_variable_id(MCP23017), + vol.Required(CONF_NUMBER): vol.All(vol.Coerce(int), vol.Range(min=0, max=15)), + vol.Optional(CONF_MODE): cv.one_of("OUTPUT", upper=True), + vol.Optional(CONF_INVERTED, default=False): cv.boolean, +}) + +MCP23017_INPUT_PIN_SCHEMA = MCP23017_OUTPUT_PIN_SCHEMA.extend({ + vol.Optional(CONF_MODE): cv.one_of("INPUT", "INPUT_PULLUP", upper=True), +}) + def internal_gpio_output_pin_schema(value): if isinstance(value, dict): @@ -380,6 +392,8 @@ def internal_gpio_output_pin_schema(value): def gpio_output_pin_schema(value): if isinstance(value, dict) and CONF_PCF8574 in value: return PCF8574_OUTPUT_PIN_SCHEMA(value) + if isinstance(value, dict) and CONF_MCP23017 in value: + return MCP23017_OUTPUT_PIN_SCHEMA(value) return internal_gpio_output_pin_schema(value) @@ -392,6 +406,8 @@ def internal_gpio_input_pin_schema(value): def gpio_input_pin_schema(value): if isinstance(value, dict) and CONF_PCF8574 in value: return PCF8574_INPUT_PIN_SCHEMA(value) + if isinstance(value, dict) and CONF_MCP23017 in value: + return MCP23017_INPUT_PIN_SCHEMA(value) return internal_gpio_input_pin_schema(value) @@ -404,4 +420,6 @@ def internal_gpio_input_pullup_pin_schema(value): def gpio_input_pullup_pin_schema(value): if isinstance(value, dict) and CONF_PCF8574 in value: return PCF8574_INPUT_PIN_SCHEMA(value) + if isinstance(value, dict) and CONF_MCP23017 in value: + return MCP23017_INPUT_PIN_SCHEMA(value) return internal_gpio_input_pin_schema(value) diff --git a/tests/test1.yaml b/tests/test1.yaml index a1f26f7573..c899e48afa 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -613,6 +613,13 @@ binary_sensor: number: 1 mode: INPUT inverted: True + - platform: gpio + name: "MCP binary sensor" + pin: + mcp23017: mcp23017_hub + number: 1 + mode: INPUT + inverted: True pca9685: frequency: 500 @@ -669,6 +676,13 @@ output: number: 0 mode: OUTPUT inverted: False + - platform: gpio + id: id22 + pin: + mcp23017: mcp23017_hub + number: 0 + mode: OUTPUT + inverted: False - platform: my9231 id: my_0 channel: 0 @@ -1140,6 +1154,9 @@ pcf8574: address: 0x21 pcf8575: False +mcp23017: + - id: 'mcp23017_hub' + stepper: - platform: a4988 id: my_stepper