diff --git a/components/TCA6408/README.md b/components/TCA6408/README.md deleted file mode 100644 index 217331b..0000000 --- a/components/TCA6408/README.md +++ /dev/null @@ -1,77 +0,0 @@ -# TCA6408 ESPHome Component - -Full-featured custom component for the **TCA6408** 8-bit I²C I/O Expander. - -**This is work in progress and under test** - -## Features - -- 8 bidirectional GPIO pins -- Hardware polarity inversion support (inputs) -- Interrupt-driven inputs (active-low INT pin) -- Efficient cached GPIO expander -- Polling fallback mode -- Compatible with ESP32 (including C3/C5/C6), ESP8266, etc. - -The INT pin on TCA6408 is open-drain active-low — connect a pull-up resistor (or use internal pull-up on ESP). - -## Installation - -1. Download or clone this component into your ESPHome project: - ```bash - mkdir -p custom_components - cp -r TCA6408/custom_components/tca6408 custom_components/ -## Basic Configuration - - i2c: - sda: GPIO4 - scl: GPIO5 - scan: true - frequency: 400kHz - - custom_components: - - source: - type: local - path: custom_components/tca6408 - - tca6408: - - id: tca6408_hub - address: 0x20 # Default: 0x20 (0x20–0x27 depending on ADDR pins) - interrupt_pin: GPIO6 # Recommended for fast input response - -## Example: Outputs (Switches) - - switch: - - platform: gpio - name: "TCA6408 Output 0" - pin: - tca6408: tca6408_hub - number: 0 - mode: OUTPUT - - - platform: gpio - name: "TCA6408 Relay (Active Low)" - pin: - tca6408: tca6408_hub - number: 1 - mode: OUTPUT - inverted: true - -## Example: Inputs (Binary Sensors) with Interrupt - - binary_sensor: - - platform: gpio - name: "Door Sensor" - pin: - tca6408: tca6408_hub - number: 3 - mode: INPUT - inverted: true # Uses hardware polarity inversion - - - platform: gpio - name: "Push Button" - pin: - tca6408: tca6408_hub - number: 4 - mode: INPUT - diff --git a/components/TCA6408/__init__.py b/components/TCA6408/__init__.py deleted file mode 100644 index 3cf72d7..0000000 --- a/components/TCA6408/__init__.py +++ /dev/null @@ -1,97 +0,0 @@ -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 - -# This component depends on the I2C bus -DEPENDENCIES = ["i2c"] - -# Namespace for the C++ component -tca6408_ns = cg.esphome_ns.namespace("tca6408") - -# Reference to the main C++ class -TCA6408Component = tca6408_ns.class_("TCA6408Component", cg.Component, i2c.I2CDevice) - -# Configuration schema for the component -CONFIG_SCHEMA = ( - cv.Schema( - { - # Unique ID for this component instance - cv.GenerateID(): cv.declare_id(TCA6408Component), - - # I²C address of the TCA6408 (default 0x20 when ADDR pins are grounded) - cv.Optional(CONF_ADDRESS, default=0x20): cv.i2c_address, - - # Optional ESP GPIO connected to the TCA6408 INT pin (recommended) - # cv.Optional(CONF_INTERRUPT_PIN): gpio.validate_gpio_pin("internal"), - } - ) - .extend(cv.COMPONENT_SCHEMA) - .extend(i2c.i2c_device_schema()) -) - -async def to_code(config): - """Generate C++ code from the YAML configuration.""" - - 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)) - - -# ================================================ -# YAML USAGE EXAMPLES -# ================================================ - -""" -# Example 1: Basic Outputs Only -tca6408: - - id: tca6408_hub - address: 0x20 - -switch: - - platform: gpio - name: "TCA6408 Output 0" - pin: - tca6408: tca6408_hub - number: 0 - mode: OUTPUT - - -# Example 2: Full Configuration with Interrupt (Recommended) -i2c: - sda: GPIO4 - scl: GPIO5 - scan: true - -tca6408: - - id: tca6408_hub - address: 0x20 - interrupt_pin: GPIO6 - -# Outputs -switch: - - platform: gpio - name: "Relay (Active Low)" - pin: - tca6408: tca6408_hub - number: 1 - mode: OUTPUT - inverted: true - -# Inputs -binary_sensor: - - platform: gpio - name: "Door Sensor" - pin: - tca6408: tca6408_hub - number: 3 - mode: INPUT - inverted: true -""" diff --git a/components/TCA6408/tca6408.cpp b/components/TCA6408/tca6408.cpp deleted file mode 100644 index fc27923..0000000 --- a/components/TCA6408/tca6408.cpp +++ /dev/null @@ -1,203 +0,0 @@ -#include "tca6408.h" -#include "esphome/core/log.h" - -namespace esphome { -namespace tca6408 { - -static const char *const TAG = "tca6408"; - -// TCA6408 Register Addresses (from datasheet) -static const uint8_t TCA6408_INPUT_PORT = 0x00; // Input Port Register -static const uint8_t TCA6408_OUTPUT_PORT = 0x01; // Output Port Register -static const uint8_t TCA6408_POLARITY_PORT = 0x02; // Polarity Inversion Register -static const uint8_t TCA6408_CONFIG_PORT = 0x03; // Configuration Register (1 = input, 0 = output) - -void TCA6408Component::setup() { - ESP_LOGCONFIG(TAG, "Setting up TCA6408 at address 0x%02X...", this->address_); - - // Initialize chip to a known safe state - if (this->write_register(TCA6408_POLARITY_PORT, 0x00) != i2c::ERROR_OK) { - ESP_LOGE(TAG, "Failed to write polarity register"); - this->mark_failed(); - return; - } - - if (this->write_register(TCA6408_CONFIG_PORT, this->mode_mask_) != i2c::ERROR_OK) { - ESP_LOGE(TAG, "Failed to write config register"); - this->mark_failed(); - return; - } - - if (!this->read_gpio_outputs_()) { - ESP_LOGE(TAG, "Failed to read initial output state"); - this->mark_failed(); - return; - } - - // Configure interrupt mode if requested - if (this->interrupt_pin_ != nullptr) { - this->interrupt_pin_->setup(); - this->interrupt_pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP); // Required for open-drain INT - this->interrupt_pin_->attach_interrupt(&TCA6408Component::gpio_intr, this, - gpio::INTERRUPT_FALLING_EDGE); - this->set_invalidate_on_read_(false); // We manage cache manually for efficiency - ESP_LOGCONFIG(TAG, " Interrupt mode enabled (active-low INT pin)"); - } else { - this->enable_loop(); // Fallback to polling - ESP_LOGCONFIG(TAG, " Polling mode enabled"); - } - - ESP_LOGCONFIG(TAG, " TCA6408 setup complete"); -} - -/** - * Interrupt Service Routine (ISR) - * Runs in interrupt context - must be very lightweight - */ -void IRAM_ATTR TCA6408Component::gpio_intr(TCA6408Component *arg) { - arg->interrupt_triggered_ = true; - arg->enable_loop_soon_any_context(); // Schedule loop() to run soon -} - -/** - * Main loop - handles interrupt processing - * Only runs when an interrupt occurs (in interrupt mode) - */ -void TCA6408Component::loop() { - if (this->interrupt_triggered_) { - this->interrupt_triggered_ = false; - - // Reading Input Port automatically clears the INT pin on TCA6408 - this->digital_read_hw(0); // pin argument is ignored - reads all 8 bits - this->reset_pin_cache_(); // Notify ESPHome that inputs may have changed - - // Check if INT line has returned high (no more pending interrupts) - if (this->interrupt_pin_ && this->interrupt_pin_->digital_read()) { - this->disable_loop(); // Stop looping until next interrupt - } - } -} - -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_); - ESP_LOGCONFIG(TAG, " Mode Mask: 0x%02X (1 = input)", this->mode_mask_); - ESP_LOGCONFIG(TAG, " Polarity Mask: 0x%02X", this->polarity_mask_); -} - -bool TCA6408Component::read_gpio_outputs_() { - if (this->is_failed()) return false; - uint8_t data = 0; - 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; -} - -/** Read current state of all input pins from hardware */ -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; -} - -/** Write to a specific output pin */ -void TCA6408Component::digital_write_hw(uint8_t pin, bool value) { - if (this->is_failed()) return; - - if (value) { - this->output_mask_ |= (1U << pin); - } else { - this->output_mask_ &= ~(1U << pin); - } - - if (this->write_register(TCA6408_OUTPUT_PORT, this->output_mask_) != i2c::ERROR_OK) { - this->status_set_warning(); - } else { - this->status_clear_warning(); - } -} - -/** Configure pin direction (input/output) */ -void TCA6408Component::pin_mode(uint8_t pin, gpio::Flags flags) { - if (flags & gpio::FLAG_OUTPUT) { - this->mode_mask_ &= ~(1U << pin); // 0 = output - } else { - this->mode_mask_ |= (1U << pin); // 1 = input - if (this->interrupt_pin_ == nullptr) { - this->enable_loop(); // Enable polling if no interrupt pin - } - } - this->write_register(TCA6408_CONFIG_PORT, this->mode_mask_); -} - -/** Set hardware polarity inversion for inputs */ -void TCA6408Component::set_polarity_inversion(uint8_t pin, bool inverted) { - if (inverted) { - this->polarity_mask_ |= (1U << pin); - } else { - this->polarity_mask_ &= ~(1U << pin); - } - this->write_register(TCA6408_POLARITY_PORT, this->polarity_mask_); -} - -/** Read cached value with polarity inversion applied */ -bool TCA6408Component::digital_read_cache(uint8_t pin) { - bool value = (this->input_mask_ & (1U << pin)) != 0; - - // Apply hardware polarity inversion if enabled for this pin - if (this->polarity_mask_ & (1U << pin)) { - value = !value; - } - return value; -} - -// ====================== TCA6408GPIOPin Wrapper ====================== - -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_) { - 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() { - // Note: inversion handled in digital_read_cache() for inputs - return this->parent_->digital_read(this->pin_) != this->inverted_; -} - -void TCA6408GPIOPin::digital_write(bool value) { - this->parent_->digital_write(this->pin_, value != this->inverted_); -} - -size_t TCA6408GPIOPin::dump_summary(char *buffer, size_t len) const { - return snprintf(buffer, len, "%u via TCA6408", this->pin_); -} - -} // namespace tca6408 -} // namespace esphome diff --git a/components/TCA6408/tca6408.h b/components/TCA6408/tca6408.h deleted file mode 100644 index eae3cfd..0000000 --- a/components/TCA6408/tca6408.h +++ /dev/null @@ -1,74 +0,0 @@ -#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 { - -/** - * TCA6408 8-bit I²C I/O Expander - * - * Supports: - * - 8 bidirectional pins - * - Hardware polarity inversion for inputs - * - Interrupt-driven inputs (active-low open-drain INT) - * - Efficient cached access - */ -class TCA6408Component : public Component, - public i2c::I2CDevice, - public gpio_expander::CachedGpioExpander { - 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}; // Hardware polarity inversion - - InternalGPIOPin *interrupt_pin_{nullptr}; - bool interrupt_triggered_{false}; - - private: - bool read_gpio_outputs_(); -}; - -class TCA6408GPIOPin : public gpio::GPIOPin, public Parented { - 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