Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add mcp23008 support #649

Merged
merged 11 commits into from Oct 17, 2019
51 changes: 51 additions & 0 deletions esphome/components/mcp23008/__init__.py
@@ -0,0 +1,51 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import pins
from esphome.components import i2c
from esphome.const import CONF_ID, CONF_NUMBER, CONF_MODE, CONF_INVERTED

DEPENDENCIES = ['i2c']
MULTI_CONF = True

mcp23008_ns = cg.esphome_ns.namespace('mcp23008')
MCP23008GPIOMode = mcp23008_ns.enum('MCP23008GPIOMode')
MCP23008_GPIO_MODES = {
'INPUT': MCP23008GPIOMode.MCP23008_INPUT,
'INPUT_PULLUP': MCP23008GPIOMode.MCP23008_INPUT_PULLUP,
'OUTPUT': MCP23008GPIOMode.MCP23008_OUTPUT,
}

MCP23008 = mcp23008_ns.class_('MCP23008', cg.Component, i2c.I2CDevice)
MCP23008GPIOPin = mcp23008_ns.class_('MCP23008GPIOPin', cg.GPIOPin)

CONFIG_SCHEMA = cv.Schema({
cv.Required(CONF_ID): cv.declare_id(MCP23008),
}).extend(cv.COMPONENT_SCHEMA).extend(i2c.i2c_device_schema(0x20))


def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
yield cg.register_component(var, config)
yield i2c.register_i2c_device(var, config)


CONF_MCP23008 = 'mcp23008'
MCP23008_OUTPUT_PIN_SCHEMA = cv.Schema({
cv.Required(CONF_MCP23008): cv.use_id(MCP23008),
cv.Required(CONF_NUMBER): cv.int_,
cv.Optional(CONF_MODE, default="OUTPUT"): cv.enum(MCP23008_GPIO_MODES, upper=True),
cv.Optional(CONF_INVERTED, default=False): cv.boolean,
})
MCP23008_INPUT_PIN_SCHEMA = cv.Schema({
cv.Required(CONF_MCP23008): cv.use_id(MCP23008),
cv.Required(CONF_NUMBER): cv.int_,
cv.Optional(CONF_MODE, default="INPUT"): cv.enum(MCP23008_GPIO_MODES, upper=True),
cv.Optional(CONF_INVERTED, default=False): cv.boolean,
})


@pins.PIN_SCHEMA_REGISTRY.register(CONF_MCP23008,
(MCP23008_OUTPUT_PIN_SCHEMA, MCP23008_INPUT_PIN_SCHEMA))
def mcp23008_pin_to_code(config):
parent = yield cg.get_variable(config[CONF_MCP23008])
yield MCP23008GPIOPin.new(parent, config[CONF_NUMBER], config[CONF_MODE], config[CONF_INVERTED])
91 changes: 91 additions & 0 deletions esphome/components/mcp23008/mcp23008.cpp
@@ -0,0 +1,91 @@
#include "mcp23008.h"
#include "esphome/core/log.h"

namespace esphome {
namespace mcp23008 {

static const char *TAG = "mcp23008";

void MCP23008::setup() {
ESP_LOGCONFIG(TAG, "Setting up MCP23008...");
uint8_t iocon;
if (!this->read_reg_(MCP23008_IOCON, &iocon)) {
this->mark_failed();
return;
}

// all pins input
this->write_reg_(MCP23008_IODIR, 0xFF);
}
bool MCP23008::digital_read(uint8_t pin) {
uint8_t bit = pin % 8;
uint8_t reg_addr = MCP23008_GPIO;
uint8_t value = 0;
this->read_reg_(reg_addr, &value);
return value & (1 << bit);
}
void MCP23008::digital_write(uint8_t pin, bool value) {
uint8_t reg_addr = MCP23008_OLAT;
this->update_reg_(pin, value, reg_addr);
}
void MCP23008::pin_mode(uint8_t pin, uint8_t mode) {
uint8_t iodir = MCP23008_IODIR;
uint8_t gppu = MCP23008_GPPU;
switch (mode) {
case MCP23008_INPUT:
this->update_reg_(pin, true, iodir);
break;
case MCP23008_INPUT_PULLUP:
this->update_reg_(pin, true, iodir);
this->update_reg_(pin, true, gppu);
break;
case MCP23008_OUTPUT:
this->update_reg_(pin, false, iodir);
break;
default:
break;
}
}
float MCP23008::get_setup_priority() const { return setup_priority::HARDWARE; }
bool MCP23008::read_reg_(uint8_t reg, uint8_t *value) {
if (this->is_failed())
return false;

return this->read_byte(reg, value);
}
bool MCP23008::write_reg_(uint8_t reg, uint8_t value) {
if (this->is_failed())
return false;

return this->write_byte(reg, value);
}
void MCP23008::update_reg_(uint8_t pin, bool pin_value, uint8_t reg_addr) {
uint8_t bit = pin % 8;
uint8_t reg_value = 0;
if (reg_addr == MCP23008_OLAT) {
reg_value = this->olat_;
} else {
this->read_reg_(reg_addr, &reg_value);
}

if (pin_value)
reg_value |= 1 << bit;
else
reg_value &= ~(1 << bit);

this->write_reg_(reg_addr, reg_value);

if (reg_addr == MCP23008_OLAT) {
this->olat_ = reg_value;
}
}

MCP23008GPIOPin::MCP23008GPIOPin(MCP23008 *parent, uint8_t pin, uint8_t mode, bool inverted)
: GPIOPin(pin, mode, inverted), parent_(parent) {}
void MCP23008GPIOPin::setup() { this->pin_mode(this->mode_); }
void MCP23008GPIOPin::pin_mode(uint8_t mode) { this->parent_->pin_mode(this->pin_, mode); }
bool MCP23008GPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) != this->inverted_; }
void MCP23008GPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_, value != this->inverted_); }

} // namespace mcp23008
} // namespace esphome
69 changes: 69 additions & 0 deletions esphome/components/mcp23008/mcp23008.h
@@ -0,0 +1,69 @@
#pragma once

#include "esphome/core/component.h"
#include "esphome/core/esphal.h"
#include "esphome/components/i2c/i2c.h"

namespace esphome {
namespace mcp23008 {

/// Modes for MCP23008 pins
enum MCP23008GPIOMode : uint8_t {
MCP23008_INPUT = INPUT, // 0x00
MCP23008_INPUT_PULLUP = INPUT_PULLUP, // 0x02
MCP23008_OUTPUT = OUTPUT // 0x01
};

enum MCP23008GPIORegisters {
// A side
MCP23008_IODIR = 0x00,
MCP23008_IPOL = 0x01,
MCP23008_GPINTEN = 0x02,
MCP23008_DEFVAL = 0x03,
MCP23008_INTCON = 0x04,
MCP23008_IOCON = 0x05,
MCP23008_GPPU = 0x06,
MCP23008_INTF = 0x07,
MCP23008_INTCAP = 0x08,
MCP23008_GPIO = 0x09,
MCP23008_OLAT = 0x0A,
};

class MCP23008 : public Component, public i2c::I2CDevice {
public:
MCP23008() = default;

void setup() override;

bool digital_read(uint8_t pin);
void digital_write(uint8_t pin, bool value);
void pin_mode(uint8_t pin, uint8_t mode);

float get_setup_priority() const override;

protected:
// read a given register
bool read_reg_(uint8_t reg, uint8_t *value);
// write a value to a given register
bool write_reg_(uint8_t reg, uint8_t value);
// update registers with given pin value.
void update_reg_(uint8_t pin, bool pin_value, uint8_t reg_a);

uint8_t olat_{0x00};
};

class MCP23008GPIOPin : public GPIOPin {
public:
MCP23008GPIOPin(MCP23008 *parent, uint8_t pin, uint8_t mode, bool inverted = false);

void setup() override;
void pin_mode(uint8_t mode) override;
bool digital_read() override;
void digital_write(bool value) override;

protected:
MCP23008 *parent_;
};

} // namespace mcp23008
} // namespace esphome
21 changes: 20 additions & 1 deletion tests/test1.yaml
Expand Up @@ -717,12 +717,20 @@ binary_sensor:
mode: INPUT
inverted: True
- platform: gpio
name: "MCP binary sensor"
name: "MCP21 binary sensor"
pin:
mcp23017: mcp23017_hub
number: 1
mode: INPUT
inverted: True
- platform: gpio
name: "MCP22 binary sensor"
pin:
mcp23008: mcp23008_hub
number: 7
mode: INPUT_PULLUP
inverted: False

- platform: remote_receiver
name: "Raw Remote Receiver Test"
raw:
Expand Down Expand Up @@ -793,6 +801,13 @@ output:
number: 0
mode: OUTPUT
inverted: False
- platform: gpio
id: id23
pin:
mcp23008: mcp23008_hub
number: 0
mode: OUTPUT
inverted: False
- platform: my9231
id: my_0
channel: 0
Expand Down Expand Up @@ -1318,6 +1333,10 @@ pcf8574:
mcp23017:
- id: 'mcp23017_hub'

mcp23008:
- id: 'mcp23008_hub'
address: 0x22

stepper:
- platform: a4988
id: my_stepper
Expand Down
16 changes: 13 additions & 3 deletions tests/test3.yaml
Expand Up @@ -331,12 +331,19 @@ switch:
- platform: gpio
id: gpio_switch1
pin:
mcp23017: mcp
mcp23017: mcp23017_hub
number: 0
mode: OUTPUT
interlock: &interlock [gpio_switch1, gpio_switch2]
interlock: &interlock [gpio_switch1, gpio_switch2, gpio_switch3]
- platform: gpio
id: gpio_switch2
pin:
mcp23008: mcp23008_hub
number: 0
mode: OUTPUT
interlock: *interlock
- platform: gpio
id: gpio_switch3
pin: GPIO1
interlock: *interlock
- platform: custom
Expand Down Expand Up @@ -479,7 +486,10 @@ output:
- id: custom_float

mcp23017:
id: mcp
id: mcp23017_hub

mcp23008:
id: mcp23008_hub

light:
- platform: neopixelbus
Expand Down