From 9e23987db896ecadf67ce3467ebf7e3f7084a5cb Mon Sep 17 00:00:00 2001 From: Andreas Hergert <36455093+andreashergert1984@users.noreply.github.com> Date: Mon, 29 Mar 2021 21:50:30 +0200 Subject: [PATCH] Add I2CMultiplexer in generel and the TCA9548A in special (#1410) * Added I2CMultiplexer in generel and the TCA9548A in special * cleanup * tidy * tidy * tidy * tidy * Update CODEOWNERS * Update CODEOWNERS * added CODEOWNERS * Fix CODEOWNERS * protected function * fixed scan * fixed style * added to test1.yaml * Update esphome/components/tca9548a/__init__.py * Update esphome/components/i2c/__init__.py Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> * Update esphome/components/i2c/i2c.cpp Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> * Update esphome/components/i2c/__init__.py * Update esphome/components/i2c/__init__.py Co-authored-by: Guillermo Ruffino * Update esphome/components/i2c/i2c.cpp Co-authored-by: Guillermo Ruffino * added define statements for I2C Multiplexer * fix * try to tidy * bug fix * tidy * override fix * only change channel if different * tidy * added test * testfix * added defines * tidy * fix dep * like recommended Co-authored-by: Andreas Hergert Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Co-authored-by: Guillermo Ruffino --- CODEOWNERS | 1 + esphome/components/i2c/__init__.py | 16 +++++++++ esphome/components/i2c/i2c.cpp | 39 ++++++++++++++++++++++ esphome/components/i2c/i2c.h | 20 ++++++++++-- esphome/components/tca9548a/__init__.py | 30 +++++++++++++++++ esphome/components/tca9548a/tca9548a.cpp | 41 ++++++++++++++++++++++++ esphome/components/tca9548a/tca9548a.h | 22 +++++++++++++ tests/test1.yaml | 12 +++++++ 8 files changed, 178 insertions(+), 3 deletions(-) create mode 100644 esphome/components/tca9548a/__init__.py create mode 100644 esphome/components/tca9548a/tca9548a.cpp create mode 100644 esphome/components/tca9548a/tca9548a.h diff --git a/CODEOWNERS b/CODEOWNERS index 0a1f2b3ed2..a726f85cf1 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -95,6 +95,7 @@ esphome/components/st7789v/* @kbx81 esphome/components/substitutions/* @esphome/core esphome/components/sun/* @OttoWinter esphome/components/switch/* @esphome/core +esphome/components/tca9548a/* @andreashergert1984 esphome/components/tcl112/* @glmnet esphome/components/teleinfo/* @0hax esphome/components/thermostat/* @kbx81 diff --git a/esphome/components/i2c/__init__.py b/esphome/components/i2c/__init__.py index 1446835904..59f90842e1 100644 --- a/esphome/components/i2c/__init__.py +++ b/esphome/components/i2c/__init__.py @@ -2,6 +2,7 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome import pins from esphome.const import ( + CONF_CHANNEL, CONF_FREQUENCY, CONF_ID, CONF_SCAN, @@ -9,6 +10,7 @@ from esphome.const import ( CONF_SDA, CONF_ADDRESS, CONF_I2C_ID, + CONF_MULTIPLEXER, ) from esphome.core import coroutine, coroutine_with_priority @@ -16,6 +18,7 @@ CODEOWNERS = ["@esphome/core"] i2c_ns = cg.esphome_ns.namespace("i2c") I2CComponent = i2c_ns.class_("I2CComponent", cg.Component) I2CDevice = i2c_ns.class_("I2CDevice") +I2CMultiplexer = i2c_ns.class_("I2CMultiplexer", I2CDevice) MULTI_CONF = True CONFIG_SCHEMA = cv.Schema( @@ -30,6 +33,13 @@ CONFIG_SCHEMA = cv.Schema( } ).extend(cv.COMPONENT_SCHEMA) +I2CMULTIPLEXER_SCHEMA = cv.Schema( + { + cv.Required(CONF_ID): cv.use_id(I2CMultiplexer), + cv.Required(CONF_CHANNEL): cv.uint8_t, + } +) + @coroutine_with_priority(1.0) def to_code(config): @@ -53,6 +63,7 @@ def i2c_device_schema(default_address): """ schema = { cv.GenerateID(CONF_I2C_ID): cv.use_id(I2CComponent), + cv.Optional(CONF_MULTIPLEXER): I2CMULTIPLEXER_SCHEMA, } if default_address is None: schema[cv.Required(CONF_ADDRESS)] = cv.i2c_address @@ -72,3 +83,8 @@ def register_i2c_device(var, config): parent = yield cg.get_variable(config[CONF_I2C_ID]) cg.add(var.set_i2c_parent(parent)) cg.add(var.set_i2c_address(config[CONF_ADDRESS])) + if CONF_MULTIPLEXER in config: + multiplexer = yield cg.get_variable(config[CONF_MULTIPLEXER][CONF_ID]) + cg.add( + var.set_i2c_multiplexer(multiplexer, config[CONF_MULTIPLEXER][CONF_CHANNEL]) + ) diff --git a/esphome/components/i2c/i2c.cpp b/esphome/components/i2c/i2c.cpp index 8e6d9f32fa..e9a7e72932 100644 --- a/esphome/components/i2c/i2c.cpp +++ b/esphome/components/i2c/i2c.cpp @@ -178,28 +178,67 @@ bool I2CComponent::write_byte_16(uint8_t address, uint8_t a_register, uint16_t d } void I2CDevice::set_i2c_address(uint8_t address) { this->address_ = address; } +#ifdef USE_I2C_MULTIPLEXER +void I2CDevice::set_i2c_multiplexer(I2CMultiplexer *multiplexer, uint8_t channel) { + ESP_LOGVV(TAG, " Setting Multiplexer %p for channel %d", multiplexer, channel); + this->multiplexer_ = multiplexer; + this->channel_ = channel; +} + +void I2CDevice::check_multiplexer_() { + if (this->multiplexer_ != nullptr) { + ESP_LOGVV(TAG, "Multiplexer setting channel to %d", this->channel_); + this->multiplexer_->set_channel(this->channel_); + } +} +#endif + bool I2CDevice::read_bytes(uint8_t a_register, uint8_t *data, uint8_t len, uint32_t conversion) { // NOLINT +#ifdef USE_I2C_MULTIPLEXER + this->check_multiplexer_(); +#endif return this->parent_->read_bytes(this->address_, a_register, data, len, conversion); } bool I2CDevice::read_byte(uint8_t a_register, uint8_t *data, uint32_t conversion) { // NOLINT +#ifdef USE_I2C_MULTIPLEXER + this->check_multiplexer_(); +#endif return this->parent_->read_byte(this->address_, a_register, data, conversion); } bool I2CDevice::write_bytes(uint8_t a_register, const uint8_t *data, uint8_t len) { // NOLINT +#ifdef USE_I2C_MULTIPLEXER + this->check_multiplexer_(); +#endif return this->parent_->write_bytes(this->address_, a_register, data, len); } bool I2CDevice::write_byte(uint8_t a_register, uint8_t data) { // NOLINT +#ifdef USE_I2C_MULTIPLEXER + this->check_multiplexer_(); +#endif return this->parent_->write_byte(this->address_, a_register, data); } bool I2CDevice::read_bytes_16(uint8_t a_register, uint16_t *data, uint8_t len, uint32_t conversion) { // NOLINT +#ifdef USE_I2C_MULTIPLEXER + this->check_multiplexer_(); +#endif return this->parent_->read_bytes_16(this->address_, a_register, data, len, conversion); } bool I2CDevice::read_byte_16(uint8_t a_register, uint16_t *data, uint32_t conversion) { // NOLINT +#ifdef USE_I2C_MULTIPLEXER + this->check_multiplexer_(); +#endif return this->parent_->read_byte_16(this->address_, a_register, data, conversion); } bool I2CDevice::write_bytes_16(uint8_t a_register, const uint16_t *data, uint8_t len) { // NOLINT +#ifdef USE_I2C_MULTIPLEXER + this->check_multiplexer_(); +#endif return this->parent_->write_bytes_16(this->address_, a_register, data, len); } bool I2CDevice::write_byte_16(uint8_t a_register, uint16_t data) { // NOLINT +#ifdef USE_I2C_MULTIPLEXER + this->check_multiplexer_(); +#endif return this->parent_->write_byte_16(this->address_, a_register, data); } void I2CDevice::set_i2c_parent(I2CComponent *parent) { this->parent_ = parent; } diff --git a/esphome/components/i2c/i2c.h b/esphome/components/i2c/i2c.h index 72777f8eb0..56da64c218 100644 --- a/esphome/components/i2c/i2c.h +++ b/esphome/components/i2c/i2c.h @@ -1,6 +1,7 @@ #pragma once #include +#include "esphome/core/defines.h" #include "esphome/core/component.h" #include "esphome/core/helpers.h" @@ -135,7 +136,7 @@ extern uint8_t next_i2c_bus_num_; #endif class I2CDevice; - +class I2CMultiplexer; class I2CRegister { public: I2CRegister(I2CDevice *parent, uint8_t a_register) : parent_(parent), register_(a_register) {} @@ -167,7 +168,10 @@ class I2CDevice { /// Manually set the i2c address of this device. void set_i2c_address(uint8_t address); - +#ifdef USE_I2C_MULTIPLEXER + /// Manually set the i2c multiplexer of this device. + void set_i2c_multiplexer(I2CMultiplexer *multiplexer, uint8_t channel); +#endif /// Manually set the parent i2c bus for this device. void set_i2c_parent(I2CComponent *parent); @@ -280,9 +284,19 @@ class I2CDevice { bool write_byte_16(uint8_t a_register, uint16_t data); protected: + // Checks for multiplexer set and set channel + void check_multiplexer_(); uint8_t address_{0x00}; I2CComponent *parent_{nullptr}; +#ifdef USE_I2C_MULTIPLEXER + I2CMultiplexer *multiplexer_{nullptr}; + uint8_t channel_; +#endif +}; +class I2CMultiplexer : public I2CDevice { + public: + I2CMultiplexer() = default; + virtual void set_channel(uint8_t channelno); }; - } // namespace i2c } // namespace esphome diff --git a/esphome/components/tca9548a/__init__.py b/esphome/components/tca9548a/__init__.py new file mode 100644 index 0000000000..aedd751086 --- /dev/null +++ b/esphome/components/tca9548a/__init__.py @@ -0,0 +1,30 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import i2c +from esphome.const import CONF_ID, CONF_SCAN + +CODEOWNERS = ["@andreashergert1984"] + +DEPENDENCIES = ["i2c"] + +tca9548a_ns = cg.esphome_ns.namespace("tca9548a") +TCA9548AComponent = tca9548a_ns.class_( + "TCA9548AComponent", cg.PollingComponent, i2c.I2CMultiplexer +) + +MULTI_CONF = True + +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(TCA9548AComponent), + cv.Optional(CONF_SCAN, default=True): cv.boolean, + } +).extend(i2c.i2c_device_schema(0x70)) + + +def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + cg.add_define("USE_I2C_MULTIPLEXER") + yield cg.register_component(var, config) + yield i2c.register_i2c_device(var, config) + cg.add(var.set_scan(config[CONF_SCAN])) diff --git a/esphome/components/tca9548a/tca9548a.cpp b/esphome/components/tca9548a/tca9548a.cpp new file mode 100644 index 0000000000..0df60d6dd2 --- /dev/null +++ b/esphome/components/tca9548a/tca9548a.cpp @@ -0,0 +1,41 @@ +#include "tca9548a.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace tca9548a { + +static const char *TAG = "tca9548a"; + +void TCA9548AComponent::setup() { + ESP_LOGCONFIG(TAG, "Setting up TCA9548A..."); + uint8_t status = 0; + if (!this->read_byte(0x00, &status)) { + ESP_LOGI(TAG, "TCA9548A failed"); + return; + } + // out of range to make sure on first set_channel a new one will be set + this->current_channelno_ = 8; + ESP_LOGCONFIG(TAG, "Channels currently open: %d", status); +} +void TCA9548AComponent::dump_config() { + ESP_LOGCONFIG(TAG, "TCA9548A:"); + LOG_I2C_DEVICE(this); + if (this->scan_) { + for (uint8_t i = 0; i < 8; i++) { + ESP_LOGCONFIG(TAG, "Activating channel: %d", i); + this->set_channel(i); + this->parent_->dump_config(); + } + } +} + +void TCA9548AComponent::set_channel(uint8_t channelno) { + if (this->current_channelno_ != channelno) { + this->current_channelno_ = channelno; + uint8_t channelbyte = 1 << channelno; + this->write_byte(0x70, channelbyte); + } +} + +} // namespace tca9548a +} // namespace esphome diff --git a/esphome/components/tca9548a/tca9548a.h b/esphome/components/tca9548a/tca9548a.h new file mode 100644 index 0000000000..50b1eb8b56 --- /dev/null +++ b/esphome/components/tca9548a/tca9548a.h @@ -0,0 +1,22 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/i2c/i2c.h" + +namespace esphome { +namespace tca9548a { + +class TCA9548AComponent : public Component, public i2c::I2CMultiplexer { + public: + void set_scan(bool scan) { scan_ = scan; } + void setup() override; + void dump_config() override; + void update(); + void set_channel(uint8_t channelno) override; + + protected: + bool scan_; + uint8_t current_channelno_; +}; +} // namespace tca9548a +} // namespace esphome diff --git a/tests/test1.yaml b/tests/test1.yaml index bcd4460d3c..e9b4cdf67c 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -1993,6 +1993,18 @@ cover: debug: +tca9548a: + - address: 0x70 + id: multiplex0 + scan: True + - address: 0x71 + id: multiplex1 + scan: True + multiplexer: + id: multiplex0 + channel: 0 + + pcf8574: - id: 'pcf8574_hub' address: 0x21