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 <glm.net@gmail.com>

* Update esphome/components/i2c/i2c.cpp

Co-authored-by: Guillermo Ruffino <glm.net@gmail.com>

* 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 <andreas.hergert@otrs.com>
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
Co-authored-by: Guillermo Ruffino <glm.net@gmail.com>
This commit is contained in:
Andreas Hergert 2021-03-29 21:50:30 +02:00 committed by GitHub
parent ad76312f66
commit 9e23987db8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 178 additions and 3 deletions

View file

@ -95,6 +95,7 @@ esphome/components/st7789v/* @kbx81
esphome/components/substitutions/* @esphome/core esphome/components/substitutions/* @esphome/core
esphome/components/sun/* @OttoWinter esphome/components/sun/* @OttoWinter
esphome/components/switch/* @esphome/core esphome/components/switch/* @esphome/core
esphome/components/tca9548a/* @andreashergert1984
esphome/components/tcl112/* @glmnet esphome/components/tcl112/* @glmnet
esphome/components/teleinfo/* @0hax esphome/components/teleinfo/* @0hax
esphome/components/thermostat/* @kbx81 esphome/components/thermostat/* @kbx81

View file

@ -2,6 +2,7 @@ import esphome.codegen as cg
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome import pins from esphome import pins
from esphome.const import ( from esphome.const import (
CONF_CHANNEL,
CONF_FREQUENCY, CONF_FREQUENCY,
CONF_ID, CONF_ID,
CONF_SCAN, CONF_SCAN,
@ -9,6 +10,7 @@ from esphome.const import (
CONF_SDA, CONF_SDA,
CONF_ADDRESS, CONF_ADDRESS,
CONF_I2C_ID, CONF_I2C_ID,
CONF_MULTIPLEXER,
) )
from esphome.core import coroutine, coroutine_with_priority from esphome.core import coroutine, coroutine_with_priority
@ -16,6 +18,7 @@ CODEOWNERS = ["@esphome/core"]
i2c_ns = cg.esphome_ns.namespace("i2c") i2c_ns = cg.esphome_ns.namespace("i2c")
I2CComponent = i2c_ns.class_("I2CComponent", cg.Component) I2CComponent = i2c_ns.class_("I2CComponent", cg.Component)
I2CDevice = i2c_ns.class_("I2CDevice") I2CDevice = i2c_ns.class_("I2CDevice")
I2CMultiplexer = i2c_ns.class_("I2CMultiplexer", I2CDevice)
MULTI_CONF = True MULTI_CONF = True
CONFIG_SCHEMA = cv.Schema( CONFIG_SCHEMA = cv.Schema(
@ -30,6 +33,13 @@ CONFIG_SCHEMA = cv.Schema(
} }
).extend(cv.COMPONENT_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) @coroutine_with_priority(1.0)
def to_code(config): def to_code(config):
@ -53,6 +63,7 @@ def i2c_device_schema(default_address):
""" """
schema = { schema = {
cv.GenerateID(CONF_I2C_ID): cv.use_id(I2CComponent), cv.GenerateID(CONF_I2C_ID): cv.use_id(I2CComponent),
cv.Optional(CONF_MULTIPLEXER): I2CMULTIPLEXER_SCHEMA,
} }
if default_address is None: if default_address is None:
schema[cv.Required(CONF_ADDRESS)] = cv.i2c_address 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]) parent = yield cg.get_variable(config[CONF_I2C_ID])
cg.add(var.set_i2c_parent(parent)) cg.add(var.set_i2c_parent(parent))
cg.add(var.set_i2c_address(config[CONF_ADDRESS])) 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])
)

View file

@ -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; } 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 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); 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 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); 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 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); return this->parent_->write_bytes(this->address_, a_register, data, len);
} }
bool I2CDevice::write_byte(uint8_t a_register, uint8_t data) { // NOLINT 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); 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 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); 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 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); 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 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); 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 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); return this->parent_->write_byte_16(this->address_, a_register, data);
} }
void I2CDevice::set_i2c_parent(I2CComponent *parent) { this->parent_ = parent; } void I2CDevice::set_i2c_parent(I2CComponent *parent) { this->parent_ = parent; }

View file

@ -1,6 +1,7 @@
#pragma once #pragma once
#include <Wire.h> #include <Wire.h>
#include "esphome/core/defines.h"
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/core/helpers.h" #include "esphome/core/helpers.h"
@ -135,7 +136,7 @@ extern uint8_t next_i2c_bus_num_;
#endif #endif
class I2CDevice; class I2CDevice;
class I2CMultiplexer;
class I2CRegister { class I2CRegister {
public: public:
I2CRegister(I2CDevice *parent, uint8_t a_register) : parent_(parent), register_(a_register) {} 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. /// Manually set the i2c address of this device.
void set_i2c_address(uint8_t address); 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. /// Manually set the parent i2c bus for this device.
void set_i2c_parent(I2CComponent *parent); void set_i2c_parent(I2CComponent *parent);
@ -280,9 +284,19 @@ class I2CDevice {
bool write_byte_16(uint8_t a_register, uint16_t data); bool write_byte_16(uint8_t a_register, uint16_t data);
protected: protected:
// Checks for multiplexer set and set channel
void check_multiplexer_();
uint8_t address_{0x00}; uint8_t address_{0x00};
I2CComponent *parent_{nullptr}; 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 i2c
} // namespace esphome } // namespace esphome

View file

@ -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]))

View file

@ -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

View file

@ -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

View file

@ -1993,6 +1993,18 @@ cover:
debug: debug:
tca9548a:
- address: 0x70
id: multiplex0
scan: True
- address: 0x71
id: multiplex1
scan: True
multiplexer:
id: multiplex0
channel: 0
pcf8574: pcf8574:
- id: 'pcf8574_hub' - id: 'pcf8574_hub'
address: 0x21 address: 0x21