diff --git a/CODEOWNERS b/CODEOWNERS index c77a9bd683..50bb216bd5 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -40,6 +40,8 @@ esphome/components/json/* @OttoWinter esphome/components/ledc/* @OttoWinter esphome/components/light/* @esphome/core esphome/components/logger/* @esphome/core +esphome/components/mcp23s08/* @SenexCrenshaw +esphome/components/mcp23s17/* @SenexCrenshaw esphome/components/mcp9808/* @k7hpn esphome/components/network/* @esphome/core esphome/components/ota/* @esphome/core diff --git a/esphome/components/mcp23s08/__init__.py b/esphome/components/mcp23s08/__init__.py new file mode 100644 index 0000000000..1440f74f56 --- /dev/null +++ b/esphome/components/mcp23s08/__init__.py @@ -0,0 +1,58 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome import pins +from esphome.components import spi +from esphome.const import CONF_ID, CONF_NUMBER, CONF_MODE, CONF_INVERTED + +CODEOWNERS = ['@SenexCrenshaw'] + +DEPENDENCIES = ['spi'] +MULTI_CONF = True + +CONF_DEVICEADDRESS = "deviceaddress" + +mcp23S08_ns = cg.esphome_ns.namespace('mcp23s08') +mcp23S08GPIOMode = mcp23S08_ns.enum('MCP23S08GPIOMode') +mcp23S08_GPIO_MODES = { + 'INPUT': mcp23S08GPIOMode.MCP23S08_INPUT, + 'INPUT_PULLUP': mcp23S08GPIOMode.MCP23S08_INPUT_PULLUP, + 'OUTPUT': mcp23S08GPIOMode.MCP23S08_OUTPUT, +} + +mcp23S08 = mcp23S08_ns.class_('MCP23S08', cg.Component, spi.SPIDevice) +mcp23S08GPIOPin = mcp23S08_ns.class_('MCP23S08GPIOPin', cg.GPIOPin) + +CONFIG_SCHEMA = cv.Schema({ + cv.Required(CONF_ID): cv.declare_id(mcp23S08), + cv.Optional(CONF_DEVICEADDRESS, default=0): cv.uint8_t, +}).extend(cv.COMPONENT_SCHEMA).extend(spi.spi_device_schema()) + + +def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + cg.add(var.set_device_address(config[CONF_DEVICEADDRESS])) + yield cg.register_component(var, config) + yield spi.register_spi_device(var, config) + + +CONF_MCP23S08 = 'mcp23s08' + +mcp23S08_OUTPUT_PIN_SCHEMA = cv.Schema({ + cv.GenerateID(CONF_MCP23S08): cv.use_id(mcp23S08), + cv.Required(CONF_NUMBER): cv.int_, + cv.Optional(CONF_MODE, default="OUTPUT"): cv.enum(mcp23S08_GPIO_MODES, upper=True), + cv.Optional(CONF_INVERTED, default=False): cv.boolean, +}) +mcp23S08_INPUT_PIN_SCHEMA = cv.Schema({ + cv.GenerateID(CONF_MCP23S08): cv.use_id(mcp23S08), + cv.Required(CONF_NUMBER): cv.int_range(0, 7), + cv.Optional(CONF_MODE, default="INPUT"): cv.enum(mcp23S08_GPIO_MODES, upper=True), + cv.Optional(CONF_INVERTED, default=False): cv.boolean, +}) + + +@pins.PIN_SCHEMA_REGISTRY.register(CONF_MCP23S08, + (mcp23S08_OUTPUT_PIN_SCHEMA, mcp23S08_INPUT_PIN_SCHEMA)) +def mcp23S08_pin_to_code(config): + parent = yield cg.get_variable(config[CONF_MCP23S08]) + yield mcp23S08GPIOPin.new(parent, config[CONF_NUMBER], config[CONF_MODE], config[CONF_INVERTED]) diff --git a/esphome/components/mcp23s08/mcp23s08.cpp b/esphome/components/mcp23s08/mcp23s08.cpp new file mode 100644 index 0000000000..07e1808485 --- /dev/null +++ b/esphome/components/mcp23s08/mcp23s08.cpp @@ -0,0 +1,121 @@ +#include "mcp23s08.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace mcp23s08 { + +static const char *TAG = "mcp23s08"; + +void MCP23S08::set_device_address(uint8_t device_addr) { + if (device_addr != 0) { + this->device_opcode_ |= ((device_addr & 0x03) << 1); + } +} + +void MCP23S08::setup() { + ESP_LOGCONFIG(TAG, "Setting up MCP23S08..."); + this->spi_setup(); + this->enable(); + + this->transfer_byte(MCP23S08_IODIR); + this->transfer_byte(0xFF); + for (uint8_t i = 0; i < MCP23S08_OLAT; i++) { + this->transfer_byte(0x00); + } + this->disable(); +} + +void MCP23S08::dump_config() { + ESP_LOGCONFIG(TAG, "MCP23S08:"); + LOG_PIN(" CS Pin: ", this->cs_); +} + +float MCP23S08::get_setup_priority() const { return setup_priority::HARDWARE; } + +bool MCP23S08::digital_read(uint8_t pin) { + if (pin > 7) { + return false; + } + uint8_t bit = pin % 8; + uint8_t reg_addr = MCP23S08_GPIO; + uint8_t value = 0; + this->read_reg(reg_addr, &value); + return value & (1 << bit); +} + +void MCP23S08::digital_write(uint8_t pin, bool value) { + if (pin > 7) { + return; + } + uint8_t reg_addr = MCP23S08_OLAT; + this->update_reg(pin, value, reg_addr); +} + +void MCP23S08::pin_mode(uint8_t pin, uint8_t mode) { + uint8_t iodir = MCP23S08_IODIR; + uint8_t gppu = MCP23S08_GPPU; + switch (mode) { + case MCP23S08_INPUT: + this->update_reg(pin, true, iodir); + break; + case MCP23S08_INPUT_PULLUP: + this->update_reg(pin, true, iodir); + this->update_reg(pin, true, gppu); + break; + case MCP23S08_OUTPUT: + this->update_reg(pin, false, iodir); + break; + default: + break; + } +} + +void MCP23S08::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 == MCP23S08_OLAT) { + reg_value = this->olat_; + } else { + this->read_reg(reg_addr, ®_value); + } + + if (pin_value) + reg_value |= 1 << bit; + else + reg_value &= ~(1 << bit); + + this->write_reg(reg_addr, reg_value); + + if (reg_addr == MCP23S08_OLAT) { + this->olat_ = reg_value; + } +} + +bool MCP23S08::write_reg(uint8_t reg, uint8_t value) { + this->enable(); + this->transfer_byte(this->device_opcode_); + this->transfer_byte(reg); + this->transfer_byte(value); + this->disable(); + return true; +} + +bool MCP23S08::read_reg(uint8_t reg, uint8_t *value) { + uint8_t data; + this->enable(); + this->transfer_byte(this->device_opcode_ | 1); + this->transfer_byte(reg); + *value = this->transfer_byte(0); + this->disable(); + return true; +} + +MCP23S08GPIOPin::MCP23S08GPIOPin(MCP23S08 *parent, uint8_t pin, uint8_t mode, bool inverted) + : GPIOPin(pin, mode, inverted), parent_(parent) {} +void MCP23S08GPIOPin::setup() { this->pin_mode(this->mode_); } +void MCP23S08GPIOPin::pin_mode(uint8_t mode) { this->parent_->pin_mode(this->pin_, mode); } +bool MCP23S08GPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) != this->inverted_; } +void MCP23S08GPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_, value != this->inverted_); } + +} // namespace mcp23s08 +} // namespace esphome diff --git a/esphome/components/mcp23s08/mcp23s08.h b/esphome/components/mcp23s08/mcp23s08.h new file mode 100644 index 0000000000..a90f89ba23 --- /dev/null +++ b/esphome/components/mcp23s08/mcp23s08.h @@ -0,0 +1,74 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/esphal.h" +#include "esphome/components/spi/spi.h" + +namespace esphome { +namespace mcp23s08 { + +/// Modes for MCP23S08 pins +enum MCP23S08GPIOMode : uint8_t { + MCP23S08_INPUT = INPUT, // 0x00 + MCP23S08_INPUT_PULLUP = INPUT_PULLUP, // 0x02 + MCP23S08_OUTPUT = OUTPUT // 0x01 +}; + +enum MCP23S08GPIORegisters { + // A side + MCP23S08_IODIR = 0x00, + MCP23S08_IPOL = 0x01, + MCP23S08_GPINTEN = 0x02, + MCP23S08_DEFVAL = 0x03, + MCP23S08_INTCON = 0x04, + MCP23S08_IOCON = 0x05, + MCP23S08_GPPU = 0x06, + MCP23S08_INTF = 0x07, + MCP23S08_INTCAP = 0x08, + MCP23S08_GPIO = 0x09, + MCP23S08_OLAT = 0x0A, +}; + +class MCP23S08 : public Component, + public spi::SPIDevice { + public: + MCP23S08() = default; + + void setup() override; + void dump_config() override; + bool digital_read(uint8_t pin); + void digital_write(uint8_t pin, bool value); + void pin_mode(uint8_t pin, uint8_t mode); + + void set_device_address(uint8_t device_addr); + + float get_setup_priority() const override; + + // 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); + + protected: + uint8_t device_opcode_ = 0x40; + uint8_t olat_{0x00}; +}; + +class MCP23S08GPIOPin : public GPIOPin { + public: + MCP23S08GPIOPin(MCP23S08 *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: + MCP23S08 *parent_; +}; + +} // namespace mcp23s08 +} // namespace esphome diff --git a/esphome/components/mcp23s17/__init__.py b/esphome/components/mcp23s17/__init__.py new file mode 100644 index 0000000000..c0c06d495c --- /dev/null +++ b/esphome/components/mcp23s17/__init__.py @@ -0,0 +1,58 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome import pins +from esphome.components import spi +from esphome.const import CONF_ID, CONF_NUMBER, CONF_MODE, CONF_INVERTED + +CODEOWNERS = ['@SenexCrenshaw'] + +DEPENDENCIES = ['spi'] +MULTI_CONF = True + +CONF_DEVICEADDRESS = "deviceaddress" + +mcp23S17_ns = cg.esphome_ns.namespace('mcp23s17') +mcp23S17GPIOMode = mcp23S17_ns.enum('MCP23S17GPIOMode') +mcp23S17_GPIO_MODES = { + 'INPUT': mcp23S17GPIOMode.MCP23S17_INPUT, + 'INPUT_PULLUP': mcp23S17GPIOMode.MCP23S17_INPUT_PULLUP, + 'OUTPUT': mcp23S17GPIOMode.MCP23S17_OUTPUT, +} + +mcp23S17 = mcp23S17_ns.class_('MCP23S17', cg.Component, spi.SPIDevice) +mcp23S17GPIOPin = mcp23S17_ns.class_('MCP23S17GPIOPin', cg.GPIOPin) + +CONFIG_SCHEMA = cv.Schema({ + cv.Required(CONF_ID): cv.declare_id(mcp23S17), + cv.Optional(CONF_DEVICEADDRESS, default=0): cv.uint8_t, +}).extend(cv.COMPONENT_SCHEMA).extend(spi.spi_device_schema()) + + +def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + cg.add(var.set_device_address(config[CONF_DEVICEADDRESS])) + yield cg.register_component(var, config) + yield spi.register_spi_device(var, config) + + +CONF_MCP23S17 = 'mcp23s17' + +mcp23S17_OUTPUT_PIN_SCHEMA = cv.Schema({ + cv.GenerateID(CONF_MCP23S17): cv.use_id(mcp23S17), + cv.Required(CONF_NUMBER): cv.int_, + cv.Optional(CONF_MODE, default="OUTPUT"): cv.enum(mcp23S17_GPIO_MODES, upper=True), + cv.Optional(CONF_INVERTED, default=False): cv.boolean, +}) +mcp23S17_INPUT_PIN_SCHEMA = cv.Schema({ + cv.Required(CONF_MCP23S17): cv.use_id(mcp23S17), + cv.Required(CONF_NUMBER): cv.int_range(0, 15), + cv.Optional(CONF_MODE, default="INPUT"): cv.enum(mcp23S17_GPIO_MODES, upper=True), + cv.Optional(CONF_INVERTED, default=False): cv.boolean, +}) + + +@pins.PIN_SCHEMA_REGISTRY.register(CONF_MCP23S17, + (mcp23S17_OUTPUT_PIN_SCHEMA, mcp23S17_INPUT_PIN_SCHEMA)) +def mcp23S17_pin_to_code(config): + parent = yield cg.get_variable(config[CONF_MCP23S17]) + yield mcp23S17GPIOPin.new(parent, config[CONF_NUMBER], config[CONF_MODE], config[CONF_INVERTED]) diff --git a/esphome/components/mcp23s17/mcp23s17.cpp b/esphome/components/mcp23s17/mcp23s17.cpp new file mode 100644 index 0000000000..30e4f63953 --- /dev/null +++ b/esphome/components/mcp23s17/mcp23s17.cpp @@ -0,0 +1,126 @@ +#include "mcp23s17.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace mcp23s17 { + +static const char *TAG = "mcp23s17"; + +void MCP23S17::set_device_address(uint8_t device_addr) { + if (device_addr != 0) { + this->device_opcode_ |= ((device_addr & 0b111) << 1); + } +} + +void MCP23S17::setup() { + ESP_LOGCONFIG(TAG, "Setting up MCP23S17..."); + this->spi_setup(); + + this->enable(); + uint8_t cmd = 0b01000000; + this->transfer_byte(cmd); + this->transfer_byte(0x18); + this->transfer_byte(0x0A); + this->transfer_byte(this->device_opcode_); + this->transfer_byte(0); + this->transfer_byte(0xFF); + this->transfer_byte(0xFF); + + for (uint8_t i = 0; i < 20; i++) { + this->transfer_byte(0); + } + this->disable(); +} + +void MCP23S17::dump_config() { + ESP_LOGCONFIG(TAG, "MCP23S17:"); + LOG_PIN(" CS Pin: ", this->cs_); +} + +float MCP23S17::get_setup_priority() const { return setup_priority::HARDWARE; } + +bool MCP23S17::digital_read(uint8_t pin) { + uint8_t bit = pin % 8; + uint8_t reg_addr = pin < 8 ? MCP23S17_GPIOA : MCP23S17_GPIOB; + uint8_t value = 0; + this->read_reg(reg_addr, &value); + return value & (1 << bit); +} + +void MCP23S17::digital_write(uint8_t pin, bool value) { + uint8_t reg_addr = pin < 8 ? MCP23S17_OLATA : MCP23S17_OLATB; + this->update_reg(pin, value, reg_addr); +} + +void MCP23S17::pin_mode(uint8_t pin, uint8_t mode) { + uint8_t iodir = pin < 8 ? MCP23S17_IODIRA : MCP23S17_IODIRB; + uint8_t gppu = pin < 8 ? MCP23S17_GPPUA : MCP23S17_GPPUB; + switch (mode) { + case MCP23S17_INPUT: + this->update_reg(pin, true, iodir); + break; + case MCP23S17_INPUT_PULLUP: + this->update_reg(pin, true, iodir); + this->update_reg(pin, true, gppu); + break; + case MCP23S17_OUTPUT: + this->update_reg(pin, false, iodir); + break; + default: + break; + } +} + +void MCP23S17::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 == MCP23S17_OLATA) { + reg_value = this->olat_a_; + } else if (reg_addr == MCP23S17_OLATB) { + reg_value = this->olat_b_; + } else { + this->read_reg(reg_addr, ®_value); + } + + if (pin_value) + reg_value |= 1 << bit; + else + reg_value &= ~(1 << bit); + + this->write_reg(reg_addr, reg_value); + + if (reg_addr == MCP23S17_OLATA) { + this->olat_a_ = reg_value; + } else if (reg_addr == MCP23S17_OLATB) { + this->olat_b_ = reg_value; + } +} + +bool MCP23S17::read_reg(uint8_t reg, uint8_t *value) { + this->enable(); + this->transfer_byte(this->device_opcode_ | 1); + this->transfer_byte(reg); + *value = this->transfer_byte(0xFF); + this->disable(); + return true; +} + +bool MCP23S17::write_reg(uint8_t reg, uint8_t value) { + this->enable(); + this->transfer_byte(this->device_opcode_); + this->transfer_byte(reg); + this->transfer_byte(value); + + this->disable(); + return true; +} + +MCP23S17GPIOPin::MCP23S17GPIOPin(MCP23S17 *parent, uint8_t pin, uint8_t mode, bool inverted) + : GPIOPin(pin, mode, inverted), parent_(parent) {} +void MCP23S17GPIOPin::setup() { this->pin_mode(this->mode_); } +void MCP23S17GPIOPin::pin_mode(uint8_t mode) { this->parent_->pin_mode(this->pin_, mode); } +bool MCP23S17GPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) != this->inverted_; } +void MCP23S17GPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_, value != this->inverted_); } + +} // namespace mcp23s17 +} // namespace esphome diff --git a/esphome/components/mcp23s17/mcp23s17.h b/esphome/components/mcp23s17/mcp23s17.h new file mode 100644 index 0000000000..8e27ff447f --- /dev/null +++ b/esphome/components/mcp23s17/mcp23s17.h @@ -0,0 +1,87 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/esphal.h" +#include "esphome/components/spi/spi.h" + +namespace esphome { +namespace mcp23s17 { + +/// Modes for MCP23S17 pins +enum MCP23S17GPIOMode : uint8_t { + MCP23S17_INPUT = INPUT, // 0x00 + MCP23S17_INPUT_PULLUP = INPUT_PULLUP, // 0x02 + MCP23S17_OUTPUT = OUTPUT // 0x01 +}; + +enum MCP23S17GPIORegisters { + // A side + MCP23S17_IODIRA = 0x00, + MCP23S17_IPOLA = 0x02, + MCP23S17_GPINTENA = 0x04, + MCP23S17_DEFVALA = 0x06, + MCP23S17_INTCONA = 0x08, + MCP23S17_IOCONA = 0x0A, + MCP23S17_GPPUA = 0x0C, + MCP23S17_INTFA = 0x0E, + MCP23S17_INTCAPA = 0x10, + MCP23S17_GPIOA = 0x12, + MCP23S17_OLATA = 0x14, + // B side + MCP23S17_IODIRB = 0x01, + MCP23S17_IPOLB = 0x03, + MCP23S17_GPINTENB = 0x05, + MCP23S17_DEFVALB = 0x07, + MCP23S17_INTCONB = 0x09, + MCP23S17_IOCONB = 0x0B, + MCP23S17_GPPUB = 0x0D, + MCP23S17_INTFB = 0x0F, + MCP23S17_INTCAPB = 0x11, + MCP23S17_GPIOB = 0x13, + MCP23S17_OLATB = 0x15, +}; + +class MCP23S17 : public Component, + public spi::SPIDevice { + public: + MCP23S17() = default; + + void setup() override; + void dump_config() override; + void set_device_address(uint8_t device_addr); + + 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; + + // 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); + + protected: + uint8_t device_opcode_ = 0x40; + uint8_t olat_a_{0x00}; + uint8_t olat_b_{0x00}; +}; + +class MCP23S17GPIOPin : public GPIOPin { + public: + MCP23S17GPIOPin(MCP23S17 *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: + MCP23S17 *parent_; +}; + +} // namespace mcp23s17 +} // namespace esphome diff --git a/esphome/components/spi/spi.h b/esphome/components/spi/spi.h index 986f85a13b..38ed9fb1d4 100644 --- a/esphome/components/spi/spi.h +++ b/esphome/components/spi/spi.h @@ -55,6 +55,7 @@ enum SPIDataRate : uint32_t { DATA_RATE_2MHZ = 2000000, DATA_RATE_4MHZ = 4000000, DATA_RATE_8MHZ = 8000000, + DATA_RATE_10MHZ = 10000000, DATA_RATE_20MHZ = 20000000, DATA_RATE_40MHZ = 40000000, }; diff --git a/tests/test1.yaml b/tests/test1.yaml index dff679115c..3982f228e7 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -197,6 +197,16 @@ wled: adalight: +mcp23s08: + - id: 'mcp23s08_hub' + cs_pin: GPIO12 + deviceaddress: 0 + +mcp23s17: + - id: 'mcp23s17_hub' + cs_pin: GPIO12 + deviceaddress: 1 + sensor: - platform: adc pin: A0 @@ -802,6 +812,24 @@ esp32_touch: voltage_attenuation: 1.5V binary_sensor: + - platform: gpio + name: "MCP23S08 Pin #1" + pin: + mcp23s08: mcp23s08_hub + # Use pin number 1 + number: 1 + # One of INPUT or INPUT_PULLUP + mode: INPUT_PULLUP + inverted: False + - platform: gpio + name: "MCP23S17 Pin #1" + pin: + mcp23s17: mcp23s17_hub + # Use pin number 1 + number: 1 + # One of INPUT or INPUT_PULLUP + mode: INPUT_PULLUP + inverted: False - platform: gpio pin: GPIO9 name: 'Living Room Window' @@ -1362,6 +1390,22 @@ climate: name: Hitachi Climate switch: + - platform: gpio + name: "MCP23S08 Pin #0" + pin: + mcp23s08: mcp23s08_hub + # Use pin number 0 + number: 0 + mode: OUTPUT + inverted: False + - platform: gpio + name: "MCP23S17 Pin #0" + pin: + mcp23s17: mcp23s17_hub + # Use pin number 0 + number: 1 + mode: OUTPUT + inverted: False - platform: gpio pin: GPIO25 name: 'Living Room Dehumidifier'