TCA6408 Example for DevTest

This commit is contained in:
Dave Williams | DitroniX | G8PUO
2026-06-13 08:19:00 +01:00
parent 94a348d55e
commit 7e658807e8
7 changed files with 504 additions and 1 deletions
+4
View File
@@ -5,6 +5,10 @@ This repository is for integration of DitroniX boards into Home Assistant using
The development and integration repository is work in progress as various projects evolve. It will also expand on route.
[![DitroniX Wiki Pages](https://github.com/DitroniX/Home-Assistant-Dev/blob/main/Datasheets%20and%20Information/GitHub%20WiKi.png?raw=true)](https://github.com/DitroniX/Home-Assistant-Dev/wiki)
[![DitroniX Discussions](https://github.com/DitroniX/DitroniX/blob/main/Files/GitHub%20Discussions.png?raw=true)](https://github.com/DitroniX/Home-Assistant-Dev/discussions)
#### User contributions
User contributions to the HA integration is welcome and of course, acknowledgements would be included.
+105
View File
@@ -0,0 +1,105 @@
esphome:
name: tca6408-node
friendly_name: TCA6408 Node
esp32:
board: esp32-c5-devkitc-1 # Change to your board (or esp32dev for S3/C3)
framework:
type: esp-idf
# Enable logging
logger:
level: DEBUG
# WiFi & API (Home Assistant)
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
api:
ota:
# ====================== I2C & TCA6408 ======================
i2c:
sda: GPIO2 # Adjust pins for your board
scl: GPIO3
scan: true
frequency: 400kHz
external_components:
- source:
type: git
url: https://github.com/DitroniX/Home-Assistant-Dev
ref: main
refresh: 0s #optional, how often to download fresh files from source, defaults to 1 day, use 0 to force updates
components: [ TCA6408 ]
tca6408:
- id: tca6408_hub
address: 0x20 # Common default (0x20 - 0x27 depending on ADDR pins) DitroniX boards sometimes use 0x21 as default to avoid conflicts
interrupt_pin: GPIO6 # Recommended - connect to TCA6408 INT pin
# ====================== OUTPUTS (Switches) ======================
switch:
- platform: gpio
name: "TCA6408 Output 0"
id: tca_out0
pin:
tca6408: tca6408_hub
number: 0
mode: OUTPUT
- platform: gpio
name: "TCA6408 Output 1 (Relay)"
id: tca_out1
pin:
tca6408: tca6408_hub
number: 1
mode: OUTPUT
inverted: false
- platform: gpio
name: "TCA6408 Output 2"
id: tca_out2
pin:
tca6408: tca6408_hub
number: 2
mode: OUTPUT
# ====================== INPUTS (Binary Sensors) ======================
binary_sensor:
- platform: gpio
name: "TCA6408 Input 3 (Door Sensor)"
pin:
tca6408: tca6408_hub
number: 3
mode: INPUT
inverted: true # Use hardware polarity inversion if active-low
- platform: gpio
name: "TCA6408 Input 4 (Button)"
pin:
tca6408: tca6408_hub
number: 4
mode: INPUT
- platform: gpio
name: "TCA6408 Input 5"
pin:
tca6408: tca6408_hub
number: 5
mode: INPUT
inverted: false
- platform: gpio
name: "TCA6408 Input 6"
pin:
tca6408: tca6408_hub
number: 6
mode: INPUT
# Optional: Status LED or other outputs
status_led:
pin:
number: GPIO2
+121
View File
@@ -0,0 +1,121 @@
esphome:
name: tca6408-outputs
friendly_name: TCA6408 Outputs
esp32:
board: esp32-c5-devkitc-1
framework:
type: esp-idf
logger:
level: DEBUG
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
api:
ota:
# ====================== I2C & TCA6408 ======================
i2c:
sda: GPIO2 # Adjust pins for your board
scl: GPIO3
scan: true
frequency: 400kHz
external_components:
- source:
type: git
url: https://github.com/DitroniX/Home-Assistant-Dev
ref: main
refresh: 0s #optional, how often to download fresh files from source, defaults to 1 day, use 0 to force updates
components: [ TCA6408 ]
tca6408:
- id: tca6408_hub
address: 0x20 # Common default (0x20 - 0x27 depending on ADDR pins) DitroniX boards sometimes use 0x21 as default to avoid conflicts
# interrupt_pin: GPIO6 # Optional for outputs only. Interrupt pin is commented out as not needed when using only outputs.
# ====================== OUTPUTS (Switches) ======================
switch:
- platform: gpio
name: "TCA6408 Output 0"
id: tca_out0
pin:
tca6408: tca6408_hub
number: 0
mode: OUTPUT
# inverted: false # Default behavior
# inverted: true # Hardware/software inversion
- platform: gpio
name: "TCA6408 Output 1 (Relay)"
id: tca_out1
pin:
tca6408: tca6408_hub
number: 1
mode: OUTPUT
# inverted: false # Default behavior
# inverted: true # Hardware/software inversion
- platform: gpio
name: "TCA6408 Output 2"
id: tca_out2
pin:
tca6408: tca6408_hub
number: 2
mode: OUTPUT
# inverted: false # Default behavior
# inverted: true # Hardware/software inversion
- platform: gpio
name: "TCA6408 Output 3"
id: tca_out3
pin:
tca6408: tca6408_hub
number: 3
mode: OUTPUT
# inverted: false # Default behavior
# inverted: true # Hardware/software inversion
- platform: gpio
name: "TCA6408 Output 4"
id: tca_out4
pin:
tca6408: tca6408_hub
number: 4
mode: OUTPUT
# inverted: false # Default behavior
# inverted: true # Hardware/software inversion
- platform: gpio
name: "TCA6408 Output 5"
id: tca_out5
pin:
tca6408: tca6408_hub
number: 5
mode: OUTPUT
# inverted: false # Default behavior
# inverted: true # Hardware/software inversion
- platform: gpio
name: "TCA6408 Output 6"
id: tca_out6
pin:
tca6408: tca6408_hub
number: 6
mode: OUTPUT
# inverted: false # Default behavior
# inverted: true # Hardware/software inversion
- platform: gpio
name: "TCA6408 Output 7"
id: tca_out7
pin:
tca6408: tca6408_hub
number: 7
mode: OUTPUT
# inverted: false # Default behavior
# inverted: true # Hardware/software inversion
+34
View File
@@ -0,0 +1,34 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import i2c, gpio
from esphome.const import CONF_ID, CONF_ADDRESS, CONF_INTERRUPT_PIN
DEPENDENCIES = ["i2c"]
tca6408_ns = cg.esphome_ns.namespace("tca6408")
TCA6408Component = tca6408_ns.class_("TCA6408Component", cg.Component, i2c.I2CDevice)
CONFIG_SCHEMA = (
cv.Schema(
{
cv.GenerateID(): cv.declare_id(TCA6408Component),
cv.Optional(CONF_ADDRESS, default=0x20): cv.i2c_address,
cv.Optional(CONF_INTERRUPT_PIN): cv.All(
gpio.validate_gpio_pin("internal"),
),
}
)
.extend(cv.COMPONENT_SCHEMA)
.extend(i2c.i2c_device_schema())
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await i2c.register_i2c_device(var, config)
cg.add(var.set_address(config[CONF_ADDRESS]))
if CONF_INTERRUPT_PIN in config:
pin = await gpio.register_gpio_pin(var, config[CONF_INTERRUPT_PIN])
cg.add(var.set_interrupt_pin(pin))
+165
View File
@@ -0,0 +1,165 @@
#include "tca6408.h"
#include "esphome/core/log.h"
namespace esphome {
namespace tca6408 {
static const char *const TAG = "tca6408";
static const uint8_t TCA6408_INPUT_PORT = 0x00;
static const uint8_t TCA6408_OUTPUT_PORT = 0x01;
static const uint8_t TCA6408_POLARITY_PORT = 0x02;
static const uint8_t TCA6408_CONFIG_PORT = 0x03;
void TCA6408Component::setup() {
ESP_LOGCONFIG(TAG, "Setting up TCA6408...");
if (this->write_register(TCA6408_POLARITY_PORT, 0x00) != i2c::ERROR_OK ||
this->write_register(TCA6408_CONFIG_PORT, this->mode_mask_) != i2c::ERROR_OK ||
!this->read_gpio_outputs_()) {
this->mark_failed();
return;
}
if (this->interrupt_pin_ != nullptr) {
this->interrupt_pin_->setup();
this->interrupt_pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
this->interrupt_pin_->attach_interrupt(&TCA6408Component::gpio_intr, this, gpio::INTERRUPT_FALLING_EDGE);
this->set_invalidate_on_read_(false);
ESP_LOGCONFIG(TAG, " Interrupt mode enabled");
} else {
this->enable_loop();
ESP_LOGCONFIG(TAG, " Polling mode enabled");
}
}
void IRAM_ATTR TCA6408Component::gpio_intr(TCA6408Component *arg) {
arg->interrupt_triggered_ = true;
arg->enable_loop_soon_any_context();
}
void TCA6408Component::loop() {
if (this->interrupt_triggered_) {
this->interrupt_triggered_ = false;
this->digital_read_hw(0);
this->reset_pin_cache_();
if (this->interrupt_pin_ && this->interrupt_pin_->digital_read()) {
this->disable_loop();
}
}
}
void TCA6408Component::dump_config() {
ESP_LOGCONFIG(TAG, "TCA6408:");
LOG_I2C_DEVICE(this);
ESP_LOGCONFIG(TAG, " Address: 0x%02X", this->address_);
LOG_PIN(" Interrupt Pin: ", this->interrupt_pin_);
}
bool TCA6408Component::read_gpio_outputs_() {
if (this->is_failed()) return false;
uint8_t data;
if (this->read_register(TCA6408_OUTPUT_PORT, &data) != i2c::ERROR_OK) {
this->status_set_warning();
return false;
}
this->output_mask_ = data;
this->status_clear_warning();
return true;
}
bool TCA6408Component::digital_read_hw(uint8_t pin) {
if (this->is_failed()) return false;
uint8_t data = 0;
if (this->read_register(TCA6408_INPUT_PORT, &data) != i2c::ERROR_OK) {
this->status_set_warning();
return false;
}
this->input_mask_ = data;
this->status_clear_warning();
return true;
}
void TCA6408Component::digital_write_hw(uint8_t pin, bool value) {
if (this->is_failed()) return;
if (value) {
this->output_mask_ |= (1 << pin);
} else {
this->output_mask_ &= ~(1 << pin);
}
if (this->write_register(TCA6408_OUTPUT_PORT, this->output_mask_) != i2c::ERROR_OK) {
this->status_set_warning();
} else {
this->status_clear_warning();
}
}
void TCA6408Component::pin_mode(uint8_t pin, gpio::Flags flags) {
if (flags & gpio::FLAG_OUTPUT) {
this->mode_mask_ &= ~(1 << pin);
} else {
this->mode_mask_ |= (1 << pin);
if (this->interrupt_pin_ == nullptr) {
this->enable_loop();
}
}
this->write_register(TCA6408_CONFIG_PORT, this->mode_mask_);
}
bool TCA6408Component::digital_read_cache(uint8_t pin) {
bool value = (this->input_mask_ & (1 << pin)) != 0;
if (this->polarity_mask_ & (1 << pin)) {
value = !value;
}
return value;
}
void TCA6408Component::set_polarity_inversion(uint8_t pin, bool inverted) {
if (inverted) {
this->polarity_mask_ |= (1 << pin);
} else {
this->polarity_mask_ &= ~(1 << pin);
}
this->write_register(TCA6408_POLARITY_PORT, this->polarity_mask_);
}
// ==================== TCA6408GPIOPin ====================
void TCA6408GPIOPin::setup() {
this->pin_mode(this->flags_);
if (this->inverted_) {
this->parent_->set_polarity_inversion(this->pin_, true);
}
}
void TCA6408GPIOPin::set_inverted(bool inverted) {
this->inverted_ = inverted;
if (this->parent_ != nullptr) {
this->parent_->set_polarity_inversion(this->pin_, inverted);
}
}
void TCA6408GPIOPin::pin_mode(gpio::Flags flags) {
this->flags_ = flags;
this->parent_->pin_mode(this->pin_, flags);
}
bool TCA6408GPIOPin::digital_read() {
return this->parent_->digital_read(this->pin_);
}
void TCA6408GPIOPin::digital_write(bool value) {
this->parent_->digital_write(this->pin_, value);
}
size_t TCA6408GPIOPin::dump_summary(char *buffer, size_t len) const {
return snprintf(buffer, len, "%u via TCA6408", this->pin_);
}
} // namespace tca6408
} // namespace esphome
+62
View File
@@ -0,0 +1,62 @@
#pragma once
#include "esphome/components/gpio_expander/cached_gpio.h"
#include "esphome/components/i2c/i2c.h"
#include "esphome/core/component.h"
#include "esphome/core/hal.h"
namespace esphome {
namespace tca6408 {
class TCA6408Component : public Component,
public i2c::I2CDevice,
public gpio_expander::CachedGpioExpander<uint8_t, 8> {
public:
void setup() override;
void loop() override;
void dump_config() override;
float get_setup_priority() const override { return setup_priority::IO; }
void set_address(uint8_t address) { this->address_ = address; }
void set_interrupt_pin(InternalGPIOPin *pin) { this->interrupt_pin_ = pin; }
void set_polarity_inversion(uint8_t pin, bool inverted);
protected:
static void IRAM_ATTR gpio_intr(TCA6408Component *arg);
bool digital_read_hw(uint8_t pin) override;
bool digital_read_cache(uint8_t pin) override;
void digital_write_hw(uint8_t pin, bool value) override;
void pin_mode(uint8_t pin, gpio::Flags flags) override;
uint8_t mode_mask_{0xFF}; // 1 = input, 0 = output
uint8_t output_mask_{0x00};
uint8_t input_mask_{0x00};
uint8_t polarity_mask_{0x00}; // 1 = inverted
InternalGPIOPin *interrupt_pin_{nullptr};
bool interrupt_triggered_{false};
};
class TCA6408GPIOPin : public gpio::GPIOPin, public Parented<TCA6408Component> {
public:
void setup() override;
void pin_mode(gpio::Flags flags) override;
bool digital_read() override;
void digital_write(bool value) override;
size_t dump_summary(char *buffer, size_t len) const override;
void set_pin(uint8_t pin) { pin_ = pin; }
void set_inverted(bool inverted);
void set_flags(gpio::Flags flags) { flags_ = flags; }
gpio::Flags get_flags() const override { return flags_; }
protected:
uint8_t pin_{0};
bool inverted_{false};
gpio::Flags flags_{0};
};
} // namespace tca6408
} // namespace esphome
+13 -1
View File
@@ -1,4 +1,16 @@
## Home Assistant - ESPHome Components
These ESPHome Components are under development and are subject to change as feedback is provided from the community.
The components have been created in order to assist users of DitroniX boards and ESPHome development. Hopefully these will eventually be incorporated within HA/ESPHome.
[![DitroniX Wiki Pages](https://github.com/DitroniX/Home-Assistant-Dev/blob/main/Datasheets%20and%20Information/GitHub%20WiKi.png?raw=true)](https://github.com/DitroniX/Home-Assistant-Dev/wiki)
[![DitroniX Discussions](https://github.com/DitroniX/DitroniX/blob/main/Files/GitHub%20Discussions.png?raw=true)](https://github.com/DitroniX/Home-Assistant-Dev/discussions)
- [**ATM90E36**](https://github.com/DitroniX/Home-Assistant-Dev/tree/main/components/ATM90E36 "ATM90E36")
- This is for use on the [EPEM ATM90E36](https://github.com/DitroniX/EPEM-Ethernet-Power-Energy-Monitor) Ethernet Power Energy Monitor
- This is for use on the ATM90E36 based Mains Energy Monitors
- [EPEM](https://github.com/DitroniX/EPEM-Ethernet-Power-Energy-Monitor)
- [IPEM Plus](https://github.com/DitroniX/IPEM-Plus-ESP32-C5-LoRa-LoRaWAN-IoT-Mains-Power-Energy-Monitor)
- [**TCA6408**](https://github.com/DitroniX/Home-Assistant-Dev/tree/main/components/TCA6408"TCA6408")
- 260612 - This is a test component and is work in progress
- [WREN](https://github.com/DitroniX/WREN-Recliner-Linear-Motor-Controller-IoT-Home-Automation)