From 28e43235fab54be9bc3c9ed801a360291cd1eb3a Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 18 Oct 2026 16:27:01 -1000 Subject: [PATCH] [gpio_expander][pcf8574][mpr121][ch422g][ch423][pcm5122][weikai] Move the GPIO expanders and pin providers onto the shared pin helpers (#20868) Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> --- esphome/components/ch422g/__init__.py | 15 +++---- esphome/components/ch422g/ch422g.cpp | 14 +----- esphome/components/ch422g/ch422g.h | 25 ++--------- esphome/components/ch423/__init__.py | 15 +++---- esphome/components/ch423/ch423.cpp | 14 +----- esphome/components/ch423/ch423.h | 25 ++--------- esphome/components/max6956/__init__.py | 34 ++------------ esphome/components/max6956/max6956.cpp | 13 +----- esphome/components/max6956/max6956.h | 25 ++--------- esphome/components/mcp23016/__init__.py | 31 ++----------- esphome/components/mcp23016/mcp23016.cpp | 10 +---- esphome/components/mcp23016/mcp23016.h | 25 ++--------- esphome/components/mcp23xxx_base/__init__.py | 28 ++---------- esphome/components/mpr121/__init__.py | 26 +++-------- esphome/components/mpr121/mpr121.cpp | 31 +++---------- esphome/components/mpr121/mpr121.h | 33 ++++---------- esphome/components/pca6416a/__init__.py | 42 +++-------------- esphome/components/pca6416a/pca6416a.cpp | 10 +---- esphome/components/pca6416a/pca6416a.h | 26 ++--------- esphome/components/pca9505/__init__.py | 28 ++---------- esphome/components/pca9505/pca9505.cpp | 14 +----- esphome/components/pca9505/pca9505.h | 23 ++-------- esphome/components/pca9554/__init__.py | 23 +--------- esphome/components/pca9554/pca9554.cpp | 10 +---- esphome/components/pca9554/pca9554.h | 29 ++---------- esphome/components/pcf8574/__init__.py | 28 ++++-------- esphome/components/pcf8574/pcf8574.cpp | 15 +------ esphome/components/pcf8574/pcf8574.h | 30 ++----------- esphome/components/pcm5122/audio_dac.py | 20 +-------- esphome/components/pi4ioe5v6408/__init__.py | 21 +-------- .../components/pi4ioe5v6408/pi4ioe5v6408.cpp | 12 +---- .../components/pi4ioe5v6408/pi4ioe5v6408.h | 23 ++-------- esphome/components/sx1509/__init__.py | 41 ++++------------- esphome/components/sx1509/sx1509.cpp | 2 + esphome/components/sx1509/sx1509.h | 6 ++- esphome/components/sx1509/sx1509_gpio_pin.cpp | 18 -------- esphome/components/sx1509/sx1509_gpio_pin.h | 31 ------------- esphome/components/tca9555/__init__.py | 28 ++---------- esphome/components/tca9555/tca9555.cpp | 10 +---- esphome/components/tca9555/tca9555.h | 24 ++-------- .../waveshare_io_ch32v003/__init__.py | 30 ++----------- .../waveshare_io_ch32v003.cpp | 14 +----- .../waveshare_io_ch32v003.h | 26 ++--------- esphome/components/weikai/__init__.py | 6 +-- esphome/components/wk2168_i2c/__init__.py | 11 +---- esphome/components/wk2168_spi/__init__.py | 11 +---- esphome/components/wk2212_i2c/__init__.py | 11 +---- esphome/components/wk2212_spi/__init__.py | 11 +---- esphome/components/xl9535/__init__.py | 45 ++++--------------- esphome/components/xl9535/xl9535.cpp | 12 +---- esphome/components/xl9535/xl9535.h | 26 ++--------- tests/components/pcf8574/test_pcf8574.cpp | 6 +-- tests/unit_tests/components/test_pcf8574.py | 39 +++++++++++++++- 53 files changed, 220 insertions(+), 906 deletions(-) delete mode 100644 esphome/components/sx1509/sx1509_gpio_pin.cpp delete mode 100644 esphome/components/sx1509/sx1509_gpio_pin.h diff --git a/esphome/components/ch422g/__init__.py b/esphome/components/ch422g/__init__.py index 60801e0c8dd..107a7bb871d 100644 --- a/esphome/components/ch422g/__init__.py +++ b/esphome/components/ch422g/__init__.py @@ -7,7 +7,6 @@ from esphome.const import ( CONF_I2C_ID, CONF_ID, CONF_INPUT, - CONF_INVERTED, CONF_MODE, CONF_NUMBER, CONF_OPEN_DRAIN, @@ -18,6 +17,7 @@ from esphome.types import ConfigType CODEOWNERS = ["@jesterret", "@clydebarrow"] DEPENDENCIES = ["i2c"] +AUTO_LOAD = ["gpio_expander"] DOMAIN = "ch422g" MULTI_CONF = True ch422g_ns = cg.esphome_ns.namespace("ch422g") @@ -66,13 +66,10 @@ CH422G_PIN_SCHEMA = pins.gpio_base_schema( @pins.PIN_SCHEMA_REGISTRY.register(CONF_CH422G, CH422G_PIN_SCHEMA, pin_mode_check) async def ch422g_pin_to_code(config: ConfigType) -> MockObj: - var = cg.new_Pvariable(config[CONF_ID]) + var = await pins.new_parented_pin(config, CONF_CH422G) + # The parent collects every pin's mode before its setup() writes the mode register parent = await cg.get_variable(config[CONF_CH422G]) - - cg.add(var.set_parent(parent)) - - num = config[CONF_NUMBER] - cg.add(var.set_pin(num)) - cg.add(var.set_inverted(config[CONF_INVERTED])) - cg.add(var.set_flags(pins.gpio_flags_expr(config[CONF_MODE]))) + cg.add( + parent.pin_mode(config[CONF_NUMBER], pins.gpio_flags_expr(config[CONF_MODE])) + ) return var diff --git a/esphome/components/ch422g/ch422g.cpp b/esphome/components/ch422g/ch422g.cpp index e76801ff77a..b6496ffa2c7 100644 --- a/esphome/components/ch422g/ch422g.cpp +++ b/esphome/components/ch422g/ch422g.cpp @@ -120,16 +120,6 @@ bool CH422GComponent::write_outputs_() { float CH422GComponent::get_setup_priority() const { return setup_priority::IO; } -void CH422GGPIOPin::pin_mode(gpio::Flags flags) { this->parent_->pin_mode(this->pin_, flags); } -bool CH422GGPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) ^ this->inverted_; } - -void CH422GGPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_, value ^ this->inverted_); } -size_t CH422GGPIOPin::dump_summary(char *buffer, size_t len) const { - return buf_append_printf(buffer, len, 0, "EXIO%u via CH422G", this->pin_); -} -void CH422GGPIOPin::set_flags(gpio::Flags flags) { - flags_ = flags; - this->parent_->pin_mode(this->pin_, flags); -} - } // namespace esphome::ch422g + +ESPHOME_EXPANDER_PIN_SUMMARY(esphome::ch422g::CH422GComponent, "EXIO%u via CH422G") diff --git a/esphome/components/ch422g/ch422g.h b/esphome/components/ch422g/ch422g.h index a8729225e80..53b0d035e1a 100644 --- a/esphome/components/ch422g/ch422g.h +++ b/esphome/components/ch422g/ch422g.h @@ -1,5 +1,6 @@ #pragma once +#include "esphome/components/gpio_expander/expander_gpio_pin.h" #include "esphome/core/component.h" #include "esphome/core/hal.h" #include "esphome/components/i2c/i2c.h" @@ -42,26 +43,8 @@ class CH422GComponent final : public Component, public i2c::I2CDevice { }; /// Helper class to expose a CH422G pin as a GPIO pin. -class CH422GGPIOPin final : public GPIOPin { - 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_parent(CH422GComponent *parent) { parent_ = parent; } - void set_pin(uint8_t pin) { pin_ = pin; } - void set_inverted(bool inverted) { inverted_ = inverted; } - void set_flags(gpio::Flags flags); - - gpio::Flags get_flags() const override { return this->flags_; } - - protected: - CH422GComponent *parent_{}; - uint8_t pin_{}; - bool inverted_{}; - gpio::Flags flags_{}; -}; +using CH422GGPIOPin = gpio_expander::ExpanderGPIOPin; } // namespace esphome::ch422g + +ESPHOME_EXPANDER_PIN_SUMMARY_DECL(esphome::ch422g::CH422GComponent); diff --git a/esphome/components/ch423/__init__.py b/esphome/components/ch423/__init__.py index 42dc8bb2841..2e51d18d075 100644 --- a/esphome/components/ch423/__init__.py +++ b/esphome/components/ch423/__init__.py @@ -7,7 +7,6 @@ from esphome.const import ( CONF_I2C_ID, CONF_ID, CONF_INPUT, - CONF_INVERTED, CONF_MODE, CONF_NUMBER, CONF_OPEN_DRAIN, @@ -19,6 +18,7 @@ from esphome.types import ConfigType CODEOWNERS = ["@dwmw2"] DEPENDENCIES = ["i2c"] +AUTO_LOAD = ["gpio_expander"] DOMAIN = "ch423" MULTI_CONF = True ch423_ns = cg.esphome_ns.namespace("ch423") @@ -93,13 +93,10 @@ CH423_PIN_SCHEMA = pins.gpio_base_schema( @pins.PIN_SCHEMA_REGISTRY.register(CONF_CH423, CH423_PIN_SCHEMA, pin_mode_check) async def ch423_pin_to_code(config: ConfigType) -> MockObj: - var = cg.new_Pvariable(config[CONF_ID]) + var = await pins.new_parented_pin(config, CONF_CH423) + # The parent collects every pin's mode before its setup() writes the mode register parent = await cg.get_variable(config[CONF_CH423]) - - cg.add(var.set_parent(parent)) - - num = config[CONF_NUMBER] - cg.add(var.set_pin(num)) - cg.add(var.set_inverted(config[CONF_INVERTED])) - cg.add(var.set_flags(pins.gpio_flags_expr(config[CONF_MODE]))) + cg.add( + parent.pin_mode(config[CONF_NUMBER], pins.gpio_flags_expr(config[CONF_MODE])) + ) return var diff --git a/esphome/components/ch423/ch423.cpp b/esphome/components/ch423/ch423.cpp index 3a57f646d4e..2ad859f9b37 100644 --- a/esphome/components/ch423/ch423.cpp +++ b/esphome/components/ch423/ch423.cpp @@ -123,16 +123,6 @@ bool CH423Component::write_outputs_() { float CH423Component::get_setup_priority() const { return setup_priority::IO; } -void CH423GPIOPin::pin_mode(gpio::Flags flags) { this->parent_->pin_mode(this->pin_, flags); } -bool CH423GPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) ^ this->inverted_; } - -void CH423GPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_, value ^ this->inverted_); } -size_t CH423GPIOPin::dump_summary(char *buffer, size_t len) const { - return snprintf(buffer, len, "EXIO%u via CH423", this->pin_); -} -void CH423GPIOPin::set_flags(gpio::Flags flags) { - flags_ = flags; - this->parent_->pin_mode(this->pin_, flags); -} - } // namespace esphome::ch423 + +ESPHOME_EXPANDER_PIN_SUMMARY(esphome::ch423::CH423Component, "EXIO%u via CH423") diff --git a/esphome/components/ch423/ch423.h b/esphome/components/ch423/ch423.h index fbfffb521d2..38fcb1ed495 100644 --- a/esphome/components/ch423/ch423.h +++ b/esphome/components/ch423/ch423.h @@ -1,5 +1,6 @@ #pragma once +#include "esphome/components/gpio_expander/expander_gpio_pin.h" #include "esphome/core/component.h" #include "esphome/core/hal.h" #include "esphome/components/i2c/i2c.h" @@ -41,26 +42,8 @@ class CH423Component final : public Component, public i2c::I2CDevice { }; /// Helper class to expose a CH423 pin as a GPIO pin. -class CH423GPIOPin final : public GPIOPin { - 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_parent(CH423Component *parent) { parent_ = parent; } - void set_pin(uint8_t pin) { pin_ = pin; } - void set_inverted(bool inverted) { inverted_ = inverted; } - void set_flags(gpio::Flags flags); - - gpio::Flags get_flags() const override { return this->flags_; } - - protected: - CH423Component *parent_{}; - uint8_t pin_{}; - bool inverted_{}; - gpio::Flags flags_{}; -}; +using CH423GPIOPin = gpio_expander::ExpanderGPIOPin; } // namespace esphome::ch423 + +ESPHOME_EXPANDER_PIN_SUMMARY_DECL(esphome::ch423::CH423Component); diff --git a/esphome/components/max6956/__init__.py b/esphome/components/max6956/__init__.py index 6dd9dc0b09f..c2e5f71d4f5 100644 --- a/esphome/components/max6956/__init__.py +++ b/esphome/components/max6956/__init__.py @@ -2,21 +2,14 @@ from esphome import automation, pins import esphome.codegen as cg from esphome.components import i2c import esphome.config_validation as cv -from esphome.const import ( - CONF_ID, - CONF_INPUT, - CONF_INVERTED, - CONF_MODE, - CONF_NUMBER, - CONF_OUTPUT, - CONF_PULLUP, -) +from esphome.const import CONF_ID, CONF_INPUT, CONF_OUTPUT, CONF_PULLUP from esphome.cpp_generator import MockObj from esphome.types import ConfigType CODEOWNERS = ["@looping40"] DOMAIN = "max6956" +AUTO_LOAD = ["gpio_expander"] DEPENDENCIES = ["i2c"] MULTI_CONF = True @@ -56,23 +49,13 @@ async def to_code(config: ConfigType) -> None: cg.add(var.set_brightness_global(config[CONF_BRIGHTNESS_GLOBAL])) -def validate_mode(value: ConfigType) -> ConfigType: - if not (value[CONF_INPUT] or value[CONF_OUTPUT]): - raise cv.Invalid("Mode must be either input or output") - if value[CONF_INPUT] and value[CONF_OUTPUT]: - raise cv.Invalid("Mode must be either input or output") - if value[CONF_PULLUP] and not value[CONF_INPUT]: - raise cv.Invalid("Pullup only available with input") - return value - - CONF_MAX6956 = "max6956" MAX6956_PIN_SCHEMA = pins.gpio_base_schema( MAX6956GPIOPin, cv.int_range(min=4, max=31), modes=[CONF_INPUT, CONF_PULLUP, CONF_OUTPUT], - mode_validator=validate_mode, + mode_validator=pins.gpio_validate_input_xor_output_pullup, ).extend( { cv.Required(CONF_MAX6956): pins.use_id_or_address(MAX6956), @@ -82,16 +65,7 @@ MAX6956_PIN_SCHEMA = pins.gpio_base_schema( @pins.PIN_SCHEMA_REGISTRY.register(CONF_MAX6956, MAX6956_PIN_SCHEMA) async def max6956_pin_to_code(config: ConfigType) -> MockObj: - var = cg.new_Pvariable(config[CONF_ID]) - parent = await cg.get_variable(config[CONF_MAX6956]) - - cg.add(var.set_parent(parent)) - - num = config[CONF_NUMBER] - cg.add(var.set_pin(num)) - cg.add(var.set_inverted(config[CONF_INVERTED])) - cg.add(var.set_flags(pins.gpio_flags_expr(config[CONF_MODE]))) - return var + return await pins.new_parented_pin(config, CONF_MAX6956) automation.register_apply_action( diff --git a/esphome/components/max6956/max6956.cpp b/esphome/components/max6956/max6956.cpp index 7060a8c76b7..05814dcc46d 100644 --- a/esphome/components/max6956/max6956.cpp +++ b/esphome/components/max6956/max6956.cpp @@ -155,15 +155,6 @@ void MAX6956::dump_config() { } } -/************************************** - * MAX6956GPIOPin * - **************************************/ -void MAX6956GPIOPin::setup() { pin_mode(flags_); } -void MAX6956GPIOPin::pin_mode(gpio::Flags flags) { this->parent_->pin_mode(this->pin_, flags); } -bool MAX6956GPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) != this->inverted_; } -void MAX6956GPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_, value != this->inverted_); } -size_t MAX6956GPIOPin::dump_summary(char *buffer, size_t len) const { - return buf_append_printf(buffer, len, 0, "%u via Max6956", this->pin_); -} - } // namespace esphome::max6956 + +ESPHOME_EXPANDER_PIN_SUMMARY(esphome::max6956::MAX6956, "%u via Max6956") diff --git a/esphome/components/max6956/max6956.h b/esphome/components/max6956/max6956.h index 4dbee165284..956a2bdbcfc 100644 --- a/esphome/components/max6956/max6956.h +++ b/esphome/components/max6956/max6956.h @@ -3,6 +3,7 @@ #include "esphome/core/component.h" #include "esphome/core/hal.h" #include "esphome/components/i2c/i2c.h" +#include "esphome/components/gpio_expander/expander_gpio_pin.h" namespace esphome::max6956 { @@ -69,26 +70,8 @@ class MAX6956 final : public Component, public i2c::I2CDevice { int8_t prev_bright_[28] = {0}; }; -class MAX6956GPIOPin final : public GPIOPin { - 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_parent(MAX6956 *parent) { parent_ = parent; } - void set_pin(uint8_t pin) { pin_ = pin; } - void set_inverted(bool inverted) { inverted_ = inverted; } - void set_flags(gpio::Flags flags) { flags_ = flags; } - - gpio::Flags get_flags() const override { return this->flags_; } - - protected: - MAX6956 *parent_; - uint8_t pin_; - bool inverted_; - gpio::Flags flags_; -}; +using MAX6956GPIOPin = gpio_expander::ExpanderGPIOPin; } // namespace esphome::max6956 + +ESPHOME_EXPANDER_PIN_SUMMARY_DECL(esphome::max6956::MAX6956); diff --git a/esphome/components/mcp23016/__init__.py b/esphome/components/mcp23016/__init__.py index 67b302d1c51..d33b1e43ae4 100644 --- a/esphome/components/mcp23016/__init__.py +++ b/esphome/components/mcp23016/__init__.py @@ -2,15 +2,7 @@ from esphome import pins import esphome.codegen as cg from esphome.components import gpio_expander, i2c import esphome.config_validation as cv -from esphome.const import ( - CONF_ID, - CONF_INPUT, - CONF_INTERRUPT_PIN, - CONF_INVERTED, - CONF_MODE, - CONF_NUMBER, - CONF_OUTPUT, -) +from esphome.const import CONF_ID, CONF_INPUT, CONF_INTERRUPT_PIN, CONF_OUTPUT from esphome.cpp_generator import MockObj from esphome.types import ConfigType @@ -41,20 +33,12 @@ async def to_code(config: ConfigType) -> None: await cg.add_pin(var.set_interrupt_pin, config.get(CONF_INTERRUPT_PIN)) -def validate_mode(value: ConfigType) -> ConfigType: - if not (value[CONF_INPUT] or value[CONF_OUTPUT]): - raise cv.Invalid("Mode must be either input or output") - if value[CONF_INPUT] and value[CONF_OUTPUT]: - raise cv.Invalid("Mode must be either input or output") - return value - - CONF_MCP23016 = "mcp23016" MCP23016_PIN_SCHEMA = pins.gpio_base_schema( MCP23016GPIOPin, cv.int_range(min=0, max=15), modes=[CONF_INPUT, CONF_OUTPUT], - mode_validator=validate_mode, + mode_validator=pins.gpio_validate_input_xor_output, invertible=True, ).extend( { @@ -65,13 +49,4 @@ MCP23016_PIN_SCHEMA = pins.gpio_base_schema( @pins.PIN_SCHEMA_REGISTRY.register(CONF_MCP23016, MCP23016_PIN_SCHEMA) async def mcp23016_pin_to_code(config: ConfigType) -> MockObj: - var = cg.new_Pvariable(config[CONF_ID]) - parent = await cg.get_variable(config[CONF_MCP23016]) - - cg.add(var.set_parent(parent)) - - num = config[CONF_NUMBER] - cg.add(var.set_pin(num)) - cg.add(var.set_inverted(config[CONF_INVERTED])) - cg.add(var.set_flags(pins.gpio_flags_expr(config[CONF_MODE]))) - return var + return await pins.new_parented_pin(config, CONF_MCP23016) diff --git a/esphome/components/mcp23016/mcp23016.cpp b/esphome/components/mcp23016/mcp23016.cpp index 13d930fd470..d5a16f9964c 100644 --- a/esphome/components/mcp23016/mcp23016.cpp +++ b/esphome/components/mcp23016/mcp23016.cpp @@ -92,12 +92,6 @@ void MCP23016::update_reg_(uint8_t pin, bool pin_value, uint8_t reg_addr) { } } -void MCP23016GPIOPin::setup() { pin_mode(flags_); } -void MCP23016GPIOPin::pin_mode(gpio::Flags flags) { this->parent_->pin_mode(this->pin_, flags); } -bool MCP23016GPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) != this->inverted_; } -void MCP23016GPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_, value != this->inverted_); } -size_t MCP23016GPIOPin::dump_summary(char *buffer, size_t len) const { - return buf_append_printf(buffer, len, 0, "%u via MCP23016", this->pin_); -} - } // namespace esphome::mcp23016 + +ESPHOME_EXPANDER_PIN_SUMMARY(esphome::mcp23016::MCP23016, "%u via MCP23016") diff --git a/esphome/components/mcp23016/mcp23016.h b/esphome/components/mcp23016/mcp23016.h index 14c0c9a2fc4..a55d4dc6187 100644 --- a/esphome/components/mcp23016/mcp23016.h +++ b/esphome/components/mcp23016/mcp23016.h @@ -4,6 +4,7 @@ #include "esphome/core/hal.h" #include "esphome/components/i2c/i2c.h" #include "esphome/components/gpio_expander/cached_gpio.h" +#include "esphome/components/gpio_expander/expander_gpio_pin.h" namespace esphome::mcp23016 { @@ -56,26 +57,8 @@ class MCP23016 final : public Component, public i2c::I2CDevice, public gpio_expa InternalGPIOPin *interrupt_pin_{nullptr}; }; -class MCP23016GPIOPin final : public GPIOPin { - 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_parent(MCP23016 *parent) { parent_ = parent; } - void set_pin(uint8_t pin) { pin_ = pin; } - void set_inverted(bool inverted) { inverted_ = inverted; } - void set_flags(gpio::Flags flags) { flags_ = flags; } - - gpio::Flags get_flags() const override { return this->flags_; } - - protected: - MCP23016 *parent_; - uint8_t pin_; - bool inverted_; - gpio::Flags flags_; -}; +using MCP23016GPIOPin = gpio_expander::ExpanderGPIOPin; } // namespace esphome::mcp23016 + +ESPHOME_EXPANDER_PIN_SUMMARY_DECL(esphome::mcp23016::MCP23016); diff --git a/esphome/components/mcp23xxx_base/__init__.py b/esphome/components/mcp23xxx_base/__init__.py index c7aae9d10ff..5f018412b66 100644 --- a/esphome/components/mcp23xxx_base/__init__.py +++ b/esphome/components/mcp23xxx_base/__init__.py @@ -7,9 +7,6 @@ from esphome.const import ( CONF_INPUT, CONF_INTERRUPT, CONF_INTERRUPT_PIN, - CONF_INVERTED, - CONF_MODE, - CONF_NUMBER, CONF_OPEN_DRAIN_INTERRUPT, CONF_OUTPUT, CONF_PULLUP, @@ -51,23 +48,13 @@ async def register_mcp23xxx(config: ConfigType, num_pins: int) -> MockObj: return var -def validate_mode(value: ConfigType) -> ConfigType: - if not (value[CONF_INPUT] or value[CONF_OUTPUT]): - raise cv.Invalid("Mode must be either input or output") - if value[CONF_INPUT] and value[CONF_OUTPUT]: - raise cv.Invalid("Mode must be either input or output") - if value[CONF_PULLUP] and not value[CONF_INPUT]: - raise cv.Invalid("Pullup only available with input") - return value - - CONF_MCP23XXX = "mcp23xxx" MCP23XXX_PIN_SCHEMA = pins.gpio_base_schema( MCP23XXXGPIOPin, cv.int_range(min=0, max=15), modes=[CONF_INPUT, CONF_OUTPUT, CONF_PULLUP], - mode_validator=validate_mode, + mode_validator=pins.gpio_validate_input_xor_output_pullup, invertible=True, ).extend( { @@ -82,16 +69,9 @@ MCP23XXX_PIN_SCHEMA = pins.gpio_base_schema( @pins.PIN_SCHEMA_REGISTRY.register(CONF_MCP23XXX, MCP23XXX_PIN_SCHEMA) async def mcp23xxx_pin_to_code(config: ConfigType) -> MockObj: parent_id: ID = config[CONF_MCP23XXX] - parent = await cg.get_variable(parent_id) - + # Wait for the hub so its to_code has recorded the pin count + await cg.get_variable(parent_id) num_pins = cg.TemplateArguments(CORE.data[CONF_MCP23XXX][parent_id.id]) - - var = cg.new_Pvariable(config[CONF_ID], num_pins) - cg.add(var.set_parent(parent)) - - num = config[CONF_NUMBER] - cg.add(var.set_pin(num)) - cg.add(var.set_inverted(config[CONF_INVERTED])) - cg.add(var.set_flags(pins.gpio_flags_expr(config[CONF_MODE]))) + var = await pins.new_parented_pin(config, CONF_MCP23XXX, num_pins) cg.add(var.set_interrupt_mode(config[CONF_INTERRUPT])) return var diff --git a/esphome/components/mpr121/__init__.py b/esphome/components/mpr121/__init__.py index 1764ff9c054..3d75451ef82 100644 --- a/esphome/components/mpr121/__init__.py +++ b/esphome/components/mpr121/__init__.py @@ -7,8 +7,6 @@ from esphome.const import ( CONF_CHANNEL, CONF_ID, CONF_INPUT, - CONF_INVERTED, - CONF_MODE, CONF_NUMBER, CONF_OUTPUT, ) @@ -24,12 +22,15 @@ CONF_MAX_TOUCH_CHANNEL = "max_touch_channel" CONF_MPR121 = "mpr121" CONF_MPR121_ID = "mpr121_id" +AUTO_LOAD = ["gpio_expander"] DEPENDENCIES = ["i2c"] DOMAIN = "mpr121" mpr121_ns = cg.esphome_ns.namespace("mpr121") MPR121Component = mpr121_ns.class_("MPR121Component", cg.Component, i2c.I2CDevice) -MPR121GPIOPin = mpr121_ns.class_("MPR121GPIOPin", cg.GPIOPin) +MPR121GPIOPin = mpr121_ns.class_( + "MPR121GPIOPin", cg.GPIOPin, cg.Parented.template(MPR121Component) +) MULTI_CONF = True CONFIG_SCHEMA = ( @@ -85,12 +86,6 @@ async def to_code(config: ConfigType) -> None: await i2c.register_i2c_device(var, config) -def validate_mode(value: ConfigType) -> ConfigType: - if bool(value[CONF_INPUT]) == bool(value[CONF_OUTPUT]): - raise cv.Invalid("Mode must be either input or output") - return value - - # https://www.nxp.com/docs/en/data-sheet/MPR121.pdf, page 4 # # Among the 12 electrode inputs, 8 inputs are designed as multifunctional pins. When these pins are @@ -100,7 +95,7 @@ MPR121_GPIO_PIN_SCHEMA = pins.gpio_base_schema( MPR121GPIOPin, cv.int_range(min=4, max=11), modes=[CONF_INPUT, CONF_OUTPUT], - mode_validator=validate_mode, + mode_validator=pins.gpio_validate_input_xor_output, ).extend( { cv.Required(CONF_MPR121): cv.use_id(MPR121Component), @@ -121,13 +116,4 @@ def mpr121_pin_final_validate( CONF_MPR121, MPR121_GPIO_PIN_SCHEMA, mpr121_pin_final_validate ) async def mpr121_gpio_pin_to_code(config: ConfigType) -> MockObj: - var = cg.new_Pvariable(config[CONF_ID]) - parent = await cg.get_variable(config[CONF_MPR121]) - - cg.add(var.set_parent(parent)) - - num = config[CONF_NUMBER] - cg.add(var.set_pin(num)) - cg.add(var.set_inverted(config[CONF_INVERTED])) - cg.add(var.set_flags(pins.gpio_flags_expr(config[CONF_MODE]))) - return var + return await pins.new_parented_pin(config, CONF_MPR121) diff --git a/esphome/components/mpr121/mpr121.cpp b/esphome/components/mpr121/mpr121.cpp index 781028e7f5c..2dd8614a323 100644 --- a/esphome/components/mpr121/mpr121.cpp +++ b/esphome/components/mpr121/mpr121.cpp @@ -80,9 +80,10 @@ void MPR121Component::loop() { this->read_byte(MPR121_GPIODATA, &this->gpio_input_); } -bool MPR121Component::digital_read(uint8_t ionum) { return (this->gpio_input_ & (1 << ionum)) != 0; } +bool MPR121Component::digital_read(uint8_t pin) { return (this->gpio_input_ & (1 << (pin - 4))) != 0; } -void MPR121Component::digital_write(uint8_t ionum, bool value) { +void MPR121Component::digital_write(uint8_t pin, bool value) { + const uint8_t ionum = pin - 4; if (value) { this->gpio_output_ |= (1 << ionum); } else { @@ -91,7 +92,8 @@ void MPR121Component::digital_write(uint8_t ionum, bool value) { this->flush_gpio_(); } -void MPR121Component::pin_mode(uint8_t ionum, gpio::Flags flags) { +void MPR121Component::pin_mode(uint8_t pin, gpio::Flags flags) { + const uint8_t ionum = pin - 4; this->gpio_enable_ |= (1 << ionum); if (flags & gpio::FLAG_INPUT) { this->gpio_direction_ &= ~(1 << ionum); @@ -115,25 +117,6 @@ bool MPR121Component::flush_gpio_() { return this->status_comm_result(this->write_byte(MPR121_GPIODATA, this->gpio_output_)); } -void MPR121GPIOPin::setup() { this->pin_mode(this->flags_); } - -void MPR121GPIOPin::pin_mode(gpio::Flags flags) { - assert(this->pin_ >= 4); - this->parent_->pin_mode(this->pin_ - 4, flags); -} - -bool MPR121GPIOPin::digital_read() { - assert(this->pin_ >= 4); - return this->parent_->digital_read(this->pin_ - 4) != this->inverted_; -} - -void MPR121GPIOPin::digital_write(bool value) { - assert(this->pin_ >= 4); - this->parent_->digital_write(this->pin_ - 4, value != this->inverted_); -} - -size_t MPR121GPIOPin::dump_summary(char *buffer, size_t len) const { - return buf_append_printf(buffer, len, 0, "ELE%u on MPR121", this->pin_); -} - } // namespace esphome::mpr121 + +ESPHOME_EXPANDER_PIN_SUMMARY(esphome::mpr121::MPR121Component, "ELE%u on MPR121") diff --git a/esphome/components/mpr121/mpr121.h b/esphome/components/mpr121/mpr121.h index 4d945ded872..f998f015831 100644 --- a/esphome/components/mpr121/mpr121.h +++ b/esphome/components/mpr121/mpr121.h @@ -4,6 +4,7 @@ #include "esphome/core/defines.h" #include "esphome/core/hal.h" +#include "esphome/components/gpio_expander/expander_gpio_pin.h" #include "esphome/components/i2c/i2c.h" #include @@ -73,10 +74,10 @@ class MPR121Component final : public Component, public i2c::I2CDevice { void set_max_touch_channel(uint8_t max_touch_channel) { this->max_touch_channel_ = max_touch_channel; } - // GPIO helper functions. - bool digital_read(uint8_t ionum); - void digital_write(uint8_t ionum, bool value); - void pin_mode(uint8_t ionum, gpio::Flags flags); + // GPIO helper functions; `pin` is the electrode number, 4 to 11, of the shared GPIO pins. + bool digital_read(uint8_t pin); + void digital_write(uint8_t pin, bool value); + void pin_mode(uint8_t pin, gpio::Flags flags); protected: std::vector channels_{}; @@ -98,26 +99,8 @@ class MPR121Component final : public Component, public i2c::I2CDevice { }; /// Helper class to expose a MPR121 pin as an internal input GPIO pin. -class MPR121GPIOPin final : public GPIOPin { - 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_parent(MPR121Component *parent) { this->parent_ = parent; } - void set_pin(uint8_t pin) { this->pin_ = pin; } - void set_inverted(bool inverted) { this->inverted_ = inverted; } - void set_flags(gpio::Flags flags) { this->flags_ = flags; } - - gpio::Flags get_flags() const override { return this->flags_; } - - protected: - MPR121Component *parent_; - uint8_t pin_; - bool inverted_; - gpio::Flags flags_; -}; +using MPR121GPIOPin = gpio_expander::ExpanderGPIOPin; } // namespace esphome::mpr121 + +ESPHOME_EXPANDER_PIN_SUMMARY_DECL(esphome::mpr121::MPR121Component); diff --git a/esphome/components/pca6416a/__init__.py b/esphome/components/pca6416a/__init__.py index 144088d48f1..92fb8f8eeed 100644 --- a/esphome/components/pca6416a/__init__.py +++ b/esphome/components/pca6416a/__init__.py @@ -6,9 +6,6 @@ from esphome.const import ( CONF_ID, CONF_INPUT, CONF_INTERRUPT_PIN, - CONF_INVERTED, - CONF_MODE, - CONF_NUMBER, CONF_OUTPUT, CONF_PULLUP, ) @@ -45,43 +42,18 @@ async def to_code(config: ConfigType) -> None: await cg.add_pin(var.set_interrupt_pin, config.get(CONF_INTERRUPT_PIN)) -def validate_mode(value: ConfigType) -> ConfigType: - if not (value[CONF_INPUT] or value[CONF_OUTPUT]): - raise cv.Invalid("Mode must be either input or output") - if value[CONF_INPUT] and value[CONF_OUTPUT]: - raise cv.Invalid("Mode must be either input or output") - if value[CONF_PULLUP] and not value[CONF_INPUT]: - raise cv.Invalid("Pullup only available with input") - return value - - -PCA6416A_PIN_SCHEMA = cv.All( +PCA6416A_PIN_SCHEMA = pins.gpio_base_schema( + PCA6416AGPIOPin, + cv.int_range(min=0, max=15), + modes=[CONF_INPUT, CONF_PULLUP, CONF_OUTPUT], + mode_validator=pins.gpio_validate_input_xor_output_pullup, +).extend( { - cv.GenerateID(): cv.declare_id(PCA6416AGPIOPin), cv.Required(CONF_PCA6416A): pins.use_id_or_address(PCA6416AComponent), - cv.Required(CONF_NUMBER): cv.int_range(min=0, max=15), - cv.Optional(CONF_MODE, default={}): cv.All( - { - cv.Optional(CONF_INPUT, default=False): cv.boolean, - cv.Optional(CONF_PULLUP, default=False): cv.boolean, - cv.Optional(CONF_OUTPUT, default=False): cv.boolean, - }, - validate_mode, - ), - cv.Optional(CONF_INVERTED, default=False): cv.boolean, } ) @pins.PIN_SCHEMA_REGISTRY.register(CONF_PCA6416A, PCA6416A_PIN_SCHEMA) async def pca6416a_pin_to_code(config: ConfigType) -> MockObj: - var = cg.new_Pvariable(config[CONF_ID]) - parent = await cg.get_variable(config[CONF_PCA6416A]) - - cg.add(var.set_parent(parent)) - - num = config[CONF_NUMBER] - cg.add(var.set_pin(num)) - cg.add(var.set_inverted(config[CONF_INVERTED])) - cg.add(var.set_flags(pins.gpio_flags_expr(config[CONF_MODE]))) - return var + return await pins.new_parented_pin(config, CONF_PCA6416A) diff --git a/esphome/components/pca6416a/pca6416a.cpp b/esphome/components/pca6416a/pca6416a.cpp index 5f5b4ea4a10..1b643c6241d 100644 --- a/esphome/components/pca6416a/pca6416a.cpp +++ b/esphome/components/pca6416a/pca6416a.cpp @@ -187,12 +187,6 @@ void PCA6416AComponent::update_register_(uint8_t pin, bool pin_value, uint8_t re float PCA6416AComponent::get_setup_priority() const { return setup_priority::IO; } -void PCA6416AGPIOPin::setup() { pin_mode(flags_); } -void PCA6416AGPIOPin::pin_mode(gpio::Flags flags) { this->parent_->pin_mode(this->pin_, flags); } -bool PCA6416AGPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) != this->inverted_; } -void PCA6416AGPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_, value != this->inverted_); } -size_t PCA6416AGPIOPin::dump_summary(char *buffer, size_t len) const { - return buf_append_printf(buffer, len, 0, "%u via PCA6416A", this->pin_); -} - } // namespace esphome::pca6416a + +ESPHOME_EXPANDER_PIN_SUMMARY(esphome::pca6416a::PCA6416AComponent, "%u via PCA6416A") diff --git a/esphome/components/pca6416a/pca6416a.h b/esphome/components/pca6416a/pca6416a.h index 39011d53ab7..61b65501cfa 100644 --- a/esphome/components/pca6416a/pca6416a.h +++ b/esphome/components/pca6416a/pca6416a.h @@ -4,6 +4,7 @@ #include "esphome/core/hal.h" #include "esphome/components/i2c/i2c.h" #include "esphome/components/gpio_expander/cached_gpio.h" +#include "esphome/components/gpio_expander/expander_gpio_pin.h" namespace esphome::pca6416a { @@ -48,27 +49,8 @@ class PCA6416AComponent final : public Component, InternalGPIOPin *interrupt_pin_{nullptr}; }; -/// Helper class to expose a PCA6416A pin as an internal input GPIO pin. -class PCA6416AGPIOPin final : public GPIOPin { - 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_parent(PCA6416AComponent *parent) { parent_ = parent; } - void set_pin(uint8_t pin) { pin_ = pin; } - void set_inverted(bool inverted) { inverted_ = inverted; } - void set_flags(gpio::Flags flags) { flags_ = flags; } - - gpio::Flags get_flags() const override { return this->flags_; } - - protected: - PCA6416AComponent *parent_; - uint8_t pin_; - bool inverted_; - gpio::Flags flags_; -}; +using PCA6416AGPIOPin = gpio_expander::ExpanderGPIOPin; } // namespace esphome::pca6416a + +ESPHOME_EXPANDER_PIN_SUMMARY_DECL(esphome::pca6416a::PCA6416AComponent); diff --git a/esphome/components/pca9505/__init__.py b/esphome/components/pca9505/__init__.py index 91521820b30..f1c34b1eaad 100644 --- a/esphome/components/pca9505/__init__.py +++ b/esphome/components/pca9505/__init__.py @@ -2,15 +2,7 @@ from esphome import pins import esphome.codegen as cg from esphome.components import gpio_expander, i2c import esphome.config_validation as cv -from esphome.const import ( - CONF_ID, - CONF_INPUT, - CONF_INTERRUPT_PIN, - CONF_INVERTED, - CONF_MODE, - CONF_NUMBER, - CONF_OUTPUT, -) +from esphome.const import CONF_ID, CONF_INPUT, CONF_INTERRUPT_PIN, CONF_OUTPUT from esphome.cpp_generator import MockObj from esphome.types import ConfigType @@ -45,19 +37,11 @@ async def to_code(config: ConfigType) -> None: await cg.add_pin(var.set_interrupt_pin, config.get(CONF_INTERRUPT_PIN)) -def validate_mode(value: ConfigType) -> ConfigType: - if not (value[CONF_INPUT] or value[CONF_OUTPUT]): - raise cv.Invalid("Mode must be either input or output") - if value[CONF_INPUT] and value[CONF_OUTPUT]: - raise cv.Invalid("Mode must be either input or output") - return value - - PCA9505_PIN_SCHEMA = pins.gpio_base_schema( PCA9505GPIOPin, cv.int_range(min=0, max=39), modes=[CONF_INPUT, CONF_OUTPUT], - mode_validator=validate_mode, + mode_validator=pins.gpio_validate_input_xor_output, ).extend( { cv.Required(CONF_PCA9505): cv.use_id(PCA9505Component), @@ -67,10 +51,4 @@ PCA9505_PIN_SCHEMA = pins.gpio_base_schema( @pins.PIN_SCHEMA_REGISTRY.register(CONF_PCA9505, PCA9505_PIN_SCHEMA) async def pca9505_pin_to_code(config: ConfigType) -> MockObj: - var = cg.new_Pvariable(config[CONF_ID]) - await cg.register_parented(var, config[CONF_PCA9505]) - - cg.add(var.set_pin(config[CONF_NUMBER])) - cg.add(var.set_inverted(config[CONF_INVERTED])) - cg.add(var.set_flags(pins.gpio_flags_expr(config[CONF_MODE]))) - return var + return await pins.new_parented_pin(config, CONF_PCA9505) diff --git a/esphome/components/pca9505/pca9505.cpp b/esphome/components/pca9505/pca9505.cpp index 15c26a078f1..567b480b1b4 100644 --- a/esphome/components/pca9505/pca9505.cpp +++ b/esphome/components/pca9505/pca9505.cpp @@ -135,16 +135,6 @@ bool PCA9505Component::write_all_banks_(uint8_t reg, uint8_t value) { float PCA9505Component::get_setup_priority() const { return setup_priority::IO; } -void PCA9505GPIOPin::setup() { this->pin_mode(this->flags_); } - -void PCA9505GPIOPin::pin_mode(gpio::Flags flags) { this->parent_->pin_mode(this->pin_, flags); } - -bool PCA9505GPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) != this->inverted_; } - -void PCA9505GPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_, value != this->inverted_); } - -size_t PCA9505GPIOPin::dump_summary(char *buffer, size_t len) const { - return buf_append_printf(buffer, len, 0, "%u via PCA9505", this->pin_); -} - } // namespace esphome::pca9505 + +ESPHOME_EXPANDER_PIN_SUMMARY(esphome::pca9505::PCA9505Component, "%u via PCA9505") diff --git a/esphome/components/pca9505/pca9505.h b/esphome/components/pca9505/pca9505.h index 29c4f18361c..a8ce6f1a324 100644 --- a/esphome/components/pca9505/pca9505.h +++ b/esphome/components/pca9505/pca9505.h @@ -4,6 +4,7 @@ #include "esphome/core/hal.h" #include "esphome/core/helpers.h" #include "esphome/components/gpio_expander/cached_gpio.h" +#include "esphome/components/gpio_expander/expander_gpio_pin.h" #include "esphome/components/i2c/i2c.h" namespace esphome::pca9505 { @@ -49,24 +50,8 @@ class PCA9505Component final : public Component, InternalGPIOPin *interrupt_pin_{nullptr}; }; -class PCA9505GPIOPin final : public 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) { this->pin_ = pin; } - void set_inverted(bool inverted) { this->inverted_ = inverted; } - void set_flags(gpio::Flags flags) { this->flags_ = flags; } - - gpio::Flags get_flags() const override { return this->flags_; } - - protected: - uint8_t pin_{0}; - bool inverted_{false}; - gpio::Flags flags_{}; -}; +using PCA9505GPIOPin = gpio_expander::ExpanderGPIOPin; } // namespace esphome::pca9505 + +ESPHOME_EXPANDER_PIN_SUMMARY_DECL(esphome::pca9505::PCA9505Component); diff --git a/esphome/components/pca9554/__init__.py b/esphome/components/pca9554/__init__.py index 6e9478b1fd4..4e30c47bdae 100644 --- a/esphome/components/pca9554/__init__.py +++ b/esphome/components/pca9554/__init__.py @@ -6,8 +6,6 @@ from esphome.const import ( CONF_ID, CONF_INPUT, CONF_INTERRUPT_PIN, - CONF_INVERTED, - CONF_MODE, CONF_NUMBER, CONF_OUTPUT, ) @@ -51,19 +49,11 @@ async def to_code(config: ConfigType) -> None: await cg.add_pin(var.set_interrupt_pin, config.get(CONF_INTERRUPT_PIN)) -def validate_mode(value: ConfigType) -> ConfigType: - if not (value[CONF_INPUT] or value[CONF_OUTPUT]): - raise cv.Invalid("Mode must be either input or output") - if value[CONF_INPUT] and value[CONF_OUTPUT]: - raise cv.Invalid("Mode must be either input or output") - return value - - PCA9554_PIN_SCHEMA = pins.gpio_base_schema( PCA9554GPIOPin, cv.int_range(min=0, max=15), modes=[CONF_INPUT, CONF_OUTPUT], - mode_validator=validate_mode, + mode_validator=pins.gpio_validate_input_xor_output, ).extend( { cv.Required(CONF_PCA9554): pins.use_id_or_address(PCA9554Component), @@ -83,13 +73,4 @@ def pca9554_pin_final_validate( CONF_PCA9554, PCA9554_PIN_SCHEMA, pca9554_pin_final_validate ) async def pca9554_pin_to_code(config: ConfigType) -> MockObj: - var = cg.new_Pvariable(config[CONF_ID]) - parent = await cg.get_variable(config[CONF_PCA9554]) - - cg.add(var.set_parent(parent)) - - num = config[CONF_NUMBER] - cg.add(var.set_pin(num)) - cg.add(var.set_inverted(config[CONF_INVERTED])) - cg.add(var.set_flags(pins.gpio_flags_expr(config[CONF_MODE]))) - return var + return await pins.new_parented_pin(config, CONF_PCA9554) diff --git a/esphome/components/pca9554/pca9554.cpp b/esphome/components/pca9554/pca9554.cpp index 4f44143c547..add9c8d0109 100644 --- a/esphome/components/pca9554/pca9554.cpp +++ b/esphome/components/pca9554/pca9554.cpp @@ -135,12 +135,6 @@ bool PCA9554Component::write_register_(uint8_t reg, uint16_t value) { float PCA9554Component::get_setup_priority() const { return setup_priority::IO; } -void PCA9554GPIOPin::setup() { pin_mode(flags_); } -void PCA9554GPIOPin::pin_mode(gpio::Flags flags) { this->parent_->pin_mode(this->pin_, flags); } -bool PCA9554GPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) != this->inverted_; } -void PCA9554GPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_, value != this->inverted_); } -size_t PCA9554GPIOPin::dump_summary(char *buffer, size_t len) const { - return buf_append_printf(buffer, len, 0, "%u via PCA9554", this->pin_); -} - } // namespace esphome::pca9554 + +ESPHOME_EXPANDER_PIN_SUMMARY(esphome::pca9554::PCA9554Component, "%u via PCA9554") diff --git a/esphome/components/pca9554/pca9554.h b/esphome/components/pca9554/pca9554.h index cc95f147ac5..b8d34e328eb 100644 --- a/esphome/components/pca9554/pca9554.h +++ b/esphome/components/pca9554/pca9554.h @@ -4,6 +4,7 @@ #include "esphome/core/hal.h" #include "esphome/components/i2c/i2c.h" #include "esphome/components/gpio_expander/cached_gpio.h" +#include "esphome/components/gpio_expander/expander_gpio_pin.h" namespace esphome::pca9554 { @@ -52,30 +53,8 @@ class PCA9554Component final : public Component, InternalGPIOPin *interrupt_pin_{nullptr}; }; -/// Helper class to expose a PCA9554 pin as an internal input GPIO pin. -class PCA9554GPIOPin final : public GPIOPin { - public: - // User provided, not "= default": `new(p) PCA9554GPIOPin()` would zero-fill .bss that is already zero. - PCA9554GPIOPin() {} - - 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_parent(PCA9554Component *parent) { parent_ = parent; } - void set_pin(uint8_t pin) { pin_ = pin; } - void set_inverted(bool inverted) { inverted_ = inverted; } - void set_flags(gpio::Flags flags) { flags_ = flags; } - - gpio::Flags get_flags() const override { return this->flags_; } - - protected: - PCA9554Component *parent_{nullptr}; - uint8_t pin_{0}; - bool inverted_{false}; - gpio::Flags flags_{}; -}; +using PCA9554GPIOPin = gpio_expander::ExpanderGPIOPin; } // namespace esphome::pca9554 + +ESPHOME_EXPANDER_PIN_SUMMARY_DECL(esphome::pca9554::PCA9554Component); diff --git a/esphome/components/pcf8574/__init__.py b/esphome/components/pcf8574/__init__.py index b491dae5047..21e9e7b007e 100644 --- a/esphome/components/pcf8574/__init__.py +++ b/esphome/components/pcf8574/__init__.py @@ -6,7 +6,6 @@ from esphome.const import ( CONF_ID, CONF_INPUT, CONF_INTERRUPT_PIN, - CONF_INVERTED, CONF_MODE, CONF_NUMBER, CONF_OUTPUT, @@ -23,7 +22,9 @@ MULTI_CONF = True pcf8574_ns = cg.esphome_ns.namespace("pcf8574") PCF8574Component = pcf8574_ns.class_("PCF8574Component", cg.Component, i2c.I2CDevice) -PCF8574GPIOPin = pcf8574_ns.class_("PCF8574GPIOPin", cg.GPIOPin) +PCF8574GPIOPin = pcf8574_ns.class_( + "PCF8574GPIOPin", cg.GPIOPin, cg.Parented.template(PCF8574Component) +) CONF_PCF8574 = "pcf8574" CONF_PCF8575 = "pcf8575" @@ -51,19 +52,11 @@ async def to_code(config: ConfigType) -> None: await cg.add_pin(var.set_interrupt_pin, config.get(CONF_INTERRUPT_PIN)) -def validate_mode(value: ConfigType) -> ConfigType: - if not (value[CONF_INPUT] or value[CONF_OUTPUT]): - raise cv.Invalid("Mode must be either input or output") - if value[CONF_INPUT] and value[CONF_OUTPUT]: - raise cv.Invalid("Mode must be either input or output") - return value - - PCF8574_PIN_SCHEMA = pins.gpio_base_schema( PCF8574GPIOPin, cv.int_range(min=0, max=15), modes=[CONF_INPUT, CONF_OUTPUT], - mode_validator=validate_mode, + mode_validator=pins.gpio_validate_input_xor_output, invertible=True, ).extend( { @@ -74,13 +67,8 @@ PCF8574_PIN_SCHEMA = pins.gpio_base_schema( @pins.PIN_SCHEMA_REGISTRY.register(CONF_PCF8574, PCF8574_PIN_SCHEMA) async def pcf8574_pin_to_code(config: ConfigType) -> MockObj: - var = cg.new_Pvariable(config[CONF_ID]) - parent = await cg.get_variable(config[CONF_PCF8574]) - - cg.add(var.set_parent(parent)) - - num = config[CONF_NUMBER] - cg.add(var.set_pin(num)) - cg.add(var.set_inverted(config[CONF_INVERTED])) - cg.add(var.set_flags(pins.gpio_flags_expr(config[CONF_MODE]))) + var = await pins.new_parented_pin(config, CONF_PCF8574) + if config[CONF_MODE][CONF_INPUT]: + parent = await cg.get_variable(config[CONF_PCF8574]) + cg.add(parent.register_input_pin(config[CONF_NUMBER])) return var diff --git a/esphome/components/pcf8574/pcf8574.cpp b/esphome/components/pcf8574/pcf8574.cpp index 5674b6a53e8..ddf1985db45 100644 --- a/esphome/components/pcf8574/pcf8574.cpp +++ b/esphome/components/pcf8574/pcf8574.cpp @@ -109,17 +109,6 @@ bool PCF8574Component::write_gpio_() { } float PCF8574Component::get_setup_priority() const { return setup_priority::IO; } -void PCF8574GPIOPin::setup() { pin_mode(flags_); } -void PCF8574GPIOPin::set_flags(gpio::Flags flags) { - this->flags_ = flags; - if (flags & gpio::FLAG_INPUT) - this->parent_->register_input_pin(this->pin_); -} -void PCF8574GPIOPin::pin_mode(gpio::Flags flags) { this->parent_->pin_mode(this->pin_, flags); } -bool PCF8574GPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) != this->inverted_; } -void PCF8574GPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_, value != this->inverted_); } -size_t PCF8574GPIOPin::dump_summary(char *buffer, size_t len) const { - return buf_append_printf(buffer, len, 0, "%u via PCF8574", this->pin_); -} - } // namespace esphome::pcf8574 + +ESPHOME_EXPANDER_PIN_SUMMARY(esphome::pcf8574::PCF8574Component, "%u via PCF8574") diff --git a/esphome/components/pcf8574/pcf8574.h b/esphome/components/pcf8574/pcf8574.h index 465bbd7a918..4bc19d204fa 100644 --- a/esphome/components/pcf8574/pcf8574.h +++ b/esphome/components/pcf8574/pcf8574.h @@ -4,6 +4,7 @@ #include "esphome/core/hal.h" #include "esphome/components/i2c/i2c.h" #include "esphome/components/gpio_expander/cached_gpio.h" +#include "esphome/components/gpio_expander/expander_gpio_pin.h" namespace esphome::pcf8574 { @@ -56,31 +57,8 @@ class PCF8574Component final : public Component, InternalGPIOPin *interrupt_pin_{nullptr}; }; -/// Helper class to expose a PCF8574 pin as an internal input GPIO pin. -class PCF8574GPIOPin final : public GPIOPin { - public: - // User provided, not "= default": `new(p) PCF8574GPIOPin()` would zero-fill .bss that is already zero. - PCF8574GPIOPin() {} - - 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_parent(PCF8574Component *parent) { parent_ = parent; } - void set_pin(uint8_t pin) { pin_ = pin; } - void set_inverted(bool inverted) { inverted_ = inverted; } - // Call after set_parent() and set_pin(): input flags register the pin with the parent. - void set_flags(gpio::Flags flags); - - gpio::Flags get_flags() const override { return this->flags_; } - - protected: - PCF8574Component *parent_{nullptr}; - uint8_t pin_{0}; - bool inverted_{false}; - gpio::Flags flags_{}; -}; +using PCF8574GPIOPin = gpio_expander::ExpanderGPIOPin; } // namespace esphome::pcf8574 + +ESPHOME_EXPANDER_PIN_SUMMARY_DECL(esphome::pcf8574::PCF8574Component); diff --git a/esphome/components/pcm5122/audio_dac.py b/esphome/components/pcm5122/audio_dac.py index fbfc998bb9f..6b7bfd47c3a 100644 --- a/esphome/components/pcm5122/audio_dac.py +++ b/esphome/components/pcm5122/audio_dac.py @@ -6,9 +6,7 @@ import esphome.config_validation as cv from esphome.const import ( CONF_BITS_PER_SAMPLE, CONF_ENABLE_PIN, - CONF_ID, CONF_INPUT, - CONF_INVERTED, CONF_MODE, CONF_NUMBER, CONF_OUTPUT, @@ -92,14 +90,6 @@ CONFIG_SCHEMA = cv.All( ) -def _validate_pin_mode(value: ConfigType) -> ConfigType: - if not (value[CONF_INPUT] or value[CONF_OUTPUT]): - raise cv.Invalid("Mode must be either input or output") - if value[CONF_INPUT] and value[CONF_OUTPUT]: - raise cv.Invalid("Mode must be either input or output, not both") - return value - - def _validate_pin(value: ConfigType) -> ConfigType: if value[CONF_MODE][CONF_INPUT] and value[CONF_NUMBER] == 6: raise cv.Invalid("GPIO6 cannot be used as input on the PCM5122") @@ -111,7 +101,7 @@ PIN_SCHEMA = cv.All( PCM5122GPIOPin, cv.int_range(min=3, max=6), modes=[CONF_INPUT, CONF_OUTPUT], - mode_validator=_validate_pin_mode, + mode_validator=pins.gpio_validate_input_xor_output, ).extend( { cv.Required(CONF_PCM5122): cv.use_id(PCM5122), @@ -123,13 +113,7 @@ PIN_SCHEMA = cv.All( @pins.PIN_SCHEMA_REGISTRY.register(CONF_PCM5122, PIN_SCHEMA) async def pcm5122_pin_to_code(config: ConfigType) -> MockObj: - var = cg.new_Pvariable(config[CONF_ID]) - await cg.register_parented(var, config[CONF_PCM5122]) - - cg.add(var.set_pin(config[CONF_NUMBER])) - cg.add(var.set_inverted(config[CONF_INVERTED])) - cg.add(var.set_flags(pins.gpio_flags_expr(config[CONF_MODE]))) - return var + return await pins.new_parented_pin(config, CONF_PCM5122) async def to_code(config: ConfigType) -> None: diff --git a/esphome/components/pi4ioe5v6408/__init__.py b/esphome/components/pi4ioe5v6408/__init__.py index 458ea1f2728..d9d1fd31f51 100644 --- a/esphome/components/pi4ioe5v6408/__init__.py +++ b/esphome/components/pi4ioe5v6408/__init__.py @@ -6,9 +6,6 @@ from esphome.const import ( CONF_ID, CONF_INPUT, CONF_INTERRUPT_PIN, - CONF_INVERTED, - CONF_MODE, - CONF_NUMBER, CONF_OUTPUT, CONF_PULLDOWN, CONF_PULLUP, @@ -52,14 +49,6 @@ async def to_code(config: ConfigType) -> None: await cg.add_pin(var.set_interrupt_pin, config.get(CONF_INTERRUPT_PIN)) -def validate_mode(value: ConfigType) -> ConfigType: - if not (value[CONF_INPUT] or value[CONF_OUTPUT]): - raise cv.Invalid("Mode must be either input or output") - if value[CONF_INPUT] and value[CONF_OUTPUT]: - raise cv.Invalid("Mode must be either input or output") - return value - - PI4IOE5V6408_PIN_SCHEMA = pins.gpio_base_schema( PI4IOE5V6408GPIOPin, cv.int_range(min=0, max=7), @@ -69,7 +58,7 @@ PI4IOE5V6408_PIN_SCHEMA = pins.gpio_base_schema( CONF_PULLUP, CONF_PULLDOWN, ], - mode_validator=validate_mode, + mode_validator=pins.gpio_validate_input_xor_output, ).extend( { cv.Required(CONF_PI4IOE5V6408): pins.use_id_or_address(PI4IOE5V6408Component), @@ -79,10 +68,4 @@ PI4IOE5V6408_PIN_SCHEMA = pins.gpio_base_schema( @pins.PIN_SCHEMA_REGISTRY.register(CONF_PI4IOE5V6408, PI4IOE5V6408_PIN_SCHEMA) async def pi4ioe5v6408_pin_schema(config: ConfigType) -> MockObj: - var = cg.new_Pvariable(config[CONF_ID]) - await cg.register_parented(var, config[CONF_PI4IOE5V6408]) - - cg.add(var.set_pin(config[CONF_NUMBER])) - cg.add(var.set_inverted(config[CONF_INVERTED])) - cg.add(var.set_flags(pins.gpio_flags_expr(config[CONF_MODE]))) - return var + return await pins.new_parented_pin(config, CONF_PI4IOE5V6408) diff --git a/esphome/components/pi4ioe5v6408/pi4ioe5v6408.cpp b/esphome/components/pi4ioe5v6408/pi4ioe5v6408.cpp index e6a423de7cf..63fa1c249b4 100644 --- a/esphome/components/pi4ioe5v6408/pi4ioe5v6408.cpp +++ b/esphome/components/pi4ioe5v6408/pi4ioe5v6408.cpp @@ -182,14 +182,6 @@ bool PI4IOE5V6408Component::digital_read_cache(uint8_t pin) { return (this->inpu float PI4IOE5V6408Component::get_setup_priority() const { return setup_priority::IO; } -void PI4IOE5V6408GPIOPin::setup() { this->pin_mode(this->flags_); } -void PI4IOE5V6408GPIOPin::pin_mode(gpio::Flags flags) { this->parent_->pin_mode(this->pin_, flags); } -bool PI4IOE5V6408GPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) != this->inverted_; } -void PI4IOE5V6408GPIOPin::digital_write(bool value) { - this->parent_->digital_write(this->pin_, value != this->inverted_); -} -size_t PI4IOE5V6408GPIOPin::dump_summary(char *buffer, size_t len) const { - return buf_append_printf(buffer, len, 0, "%u via PI4IOE5V6408", this->pin_); -} - } // namespace esphome::pi4ioe5v6408 + +ESPHOME_EXPANDER_PIN_SUMMARY(esphome::pi4ioe5v6408::PI4IOE5V6408Component, "%u via PI4IOE5V6408") diff --git a/esphome/components/pi4ioe5v6408/pi4ioe5v6408.h b/esphome/components/pi4ioe5v6408/pi4ioe5v6408.h index 9909dc2217b..556404a3743 100644 --- a/esphome/components/pi4ioe5v6408/pi4ioe5v6408.h +++ b/esphome/components/pi4ioe5v6408/pi4ioe5v6408.h @@ -1,6 +1,7 @@ #pragma once #include "esphome/components/gpio_expander/cached_gpio.h" +#include "esphome/components/gpio_expander/expander_gpio_pin.h" #include "esphome/components/i2c/i2c.h" #include "esphome/core/component.h" #include "esphome/core/hal.h" @@ -49,24 +50,8 @@ class PI4IOE5V6408Component final : public Component, bool read_gpio_outputs_(); }; -class PI4IOE5V6408GPIOPin final : public 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) { this->pin_ = pin; } - void set_inverted(bool inverted) { this->inverted_ = inverted; } - void set_flags(gpio::Flags flags) { this->flags_ = flags; } - - gpio::Flags get_flags() const override { return this->flags_; } - - protected: - uint8_t pin_; - bool inverted_; - gpio::Flags flags_; -}; +using PI4IOE5V6408GPIOPin = gpio_expander::ExpanderGPIOPin; } // namespace esphome::pi4ioe5v6408 + +ESPHOME_EXPANDER_PIN_SUMMARY_DECL(esphome::pi4ioe5v6408::PI4IOE5V6408Component); diff --git a/esphome/components/sx1509/__init__.py b/esphome/components/sx1509/__init__.py index c44c52a6fc6..c98f94d4b55 100644 --- a/esphome/components/sx1509/__init__.py +++ b/esphome/components/sx1509/__init__.py @@ -4,11 +4,7 @@ from esphome.components import i2c, key_provider from esphome.components.const import CONF_KEYS import esphome.config_validation as cv from esphome.const import ( - CONF_ID, CONF_INPUT, - CONF_INVERTED, - CONF_MODE, - CONF_NUMBER, CONF_ON_KEY, CONF_OPEN_DRAIN, CONF_OUTPUT, @@ -113,12 +109,7 @@ async def to_code(config: ConfigType) -> None: def validate_mode(value: ConfigType) -> ConfigType: - if not (value[CONF_INPUT] or value[CONF_OUTPUT]): - raise cv.Invalid("Mode must be either input or output") - if value[CONF_INPUT] and value[CONF_OUTPUT]: - raise cv.Invalid("Mode must be either input or output") - if value[CONF_PULLUP] and not value[CONF_INPUT]: - raise cv.Invalid("Pullup only available with input") + pins.gpio_validate_input_xor_output_pullup(value) if value[CONF_PULLDOWN] and not value[CONF_INPUT]: raise cv.Invalid("Pulldown only available with input") if value[CONF_PULLUP] and value[CONF_PULLDOWN]: @@ -129,34 +120,18 @@ def validate_mode(value: ConfigType) -> ConfigType: CONF_SX1509 = "sx1509" -SX1509_PIN_SCHEMA = cv.All( +SX1509_PIN_SCHEMA = pins.gpio_base_schema( + SX1509GPIOPin, + cv.int_range(min=0, max=15), + modes=[CONF_INPUT, CONF_PULLUP, CONF_PULLDOWN, CONF_OUTPUT, CONF_OPEN_DRAIN], + mode_validator=validate_mode, +).extend( { - cv.GenerateID(): cv.declare_id(SX1509GPIOPin), cv.Required(CONF_SX1509): pins.use_id_or_address(SX1509Component), - cv.Required(CONF_NUMBER): cv.int_range(min=0, max=15), - cv.Optional(CONF_MODE, default={}): cv.All( - { - cv.Optional(CONF_INPUT, default=False): cv.boolean, - cv.Optional(CONF_PULLUP, default=False): cv.boolean, - cv.Optional(CONF_PULLDOWN, default=False): cv.boolean, - cv.Optional(CONF_OUTPUT, default=False): cv.boolean, - cv.Optional(CONF_OPEN_DRAIN, default=False): cv.boolean, - }, - validate_mode, - ), - cv.Optional(CONF_INVERTED, default=False): cv.boolean, } ) @pins.PIN_SCHEMA_REGISTRY.register(CONF_SX1509, SX1509_PIN_SCHEMA) async def sx1509_pin_to_code(config: ConfigType) -> MockObj: - var = cg.new_Pvariable(config[CONF_ID]) - parent = await cg.get_variable(config[CONF_SX1509]) - cg.add(var.set_parent(parent)) - - num = config[CONF_NUMBER] - cg.add(var.set_pin(num)) - cg.add(var.set_inverted(config[CONF_INVERTED])) - cg.add(var.set_flags(pins.gpio_flags_expr(config[CONF_MODE]))) - return var + return await pins.new_parented_pin(config, CONF_SX1509) diff --git a/esphome/components/sx1509/sx1509.cpp b/esphome/components/sx1509/sx1509.cpp index d883982bcd5..bbb38198459 100644 --- a/esphome/components/sx1509/sx1509.cpp +++ b/esphome/components/sx1509/sx1509.cpp @@ -312,3 +312,5 @@ void SX1509Component::set_debounce_keypad_(uint8_t time, uint8_t num_rows, uint8 } } // namespace esphome::sx1509 + +ESPHOME_EXPANDER_PIN_SUMMARY(esphome::sx1509::SX1509Component, "%u via sx1509") diff --git a/esphome/components/sx1509/sx1509.h b/esphome/components/sx1509/sx1509.h index ef6ee6ec5e6..5b163cbd6e5 100644 --- a/esphome/components/sx1509/sx1509.h +++ b/esphome/components/sx1509/sx1509.h @@ -5,7 +5,7 @@ #include "esphome/components/gpio_expander/cached_gpio.h" #include "esphome/core/component.h" #include "esphome/core/hal.h" -#include "sx1509_gpio_pin.h" +#include "esphome/components/gpio_expander/expander_gpio_pin.h" #include "sx1509_registers.h" #include @@ -97,4 +97,8 @@ class SX1509Component final : public Component, void clock_(uint8_t osc_source = 2, uint8_t osc_pin_function = 1, uint8_t osc_freq_out = 0, uint8_t osc_divider = 0); }; +using SX1509GPIOPin = gpio_expander::ExpanderGPIOPin; + } // namespace esphome::sx1509 + +ESPHOME_EXPANDER_PIN_SUMMARY_DECL(esphome::sx1509::SX1509Component); diff --git a/esphome/components/sx1509/sx1509_gpio_pin.cpp b/esphome/components/sx1509/sx1509_gpio_pin.cpp deleted file mode 100644 index 41f967b6672..00000000000 --- a/esphome/components/sx1509/sx1509_gpio_pin.cpp +++ /dev/null @@ -1,18 +0,0 @@ -#include "esphome/core/helpers.h" -#include "esphome/core/log.h" -#include "sx1509.h" -#include "sx1509_gpio_pin.h" - -namespace esphome::sx1509 { - -ESPHOME_LOG_TAG(TAG, "sx1509_gpio_pin"); - -void SX1509GPIOPin::setup() { pin_mode(flags_); } -void SX1509GPIOPin::pin_mode(gpio::Flags flags) { this->parent_->pin_mode(this->pin_, flags); } -bool SX1509GPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) != this->inverted_; } -void SX1509GPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_, value != this->inverted_); } -size_t SX1509GPIOPin::dump_summary(char *buffer, size_t len) const { - return buf_append_printf(buffer, len, 0, "%u via sx1509", this->pin_); -} - -} // namespace esphome::sx1509 diff --git a/esphome/components/sx1509/sx1509_gpio_pin.h b/esphome/components/sx1509/sx1509_gpio_pin.h deleted file mode 100644 index 3bd3d90bd93..00000000000 --- a/esphome/components/sx1509/sx1509_gpio_pin.h +++ /dev/null @@ -1,31 +0,0 @@ -#pragma once - -#include "esphome/core/gpio.h" - -namespace esphome::sx1509 { - -class SX1509Component; - -class SX1509GPIOPin final : public GPIOPin { - 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_parent(SX1509Component *parent) { this->parent_ = parent; } - void set_pin(uint8_t pin) { this->pin_ = pin; } - void set_inverted(bool inverted) { this->inverted_ = inverted; } - void set_flags(gpio::Flags flags) { this->flags_ = flags; } - - gpio::Flags get_flags() const override { return this->flags_; } - - protected: - SX1509Component *parent_; - uint8_t pin_; - bool inverted_; - gpio::Flags flags_; -}; - -} // namespace esphome::sx1509 diff --git a/esphome/components/tca9555/__init__.py b/esphome/components/tca9555/__init__.py index a3992b7c88e..5b7f58253e8 100644 --- a/esphome/components/tca9555/__init__.py +++ b/esphome/components/tca9555/__init__.py @@ -2,15 +2,7 @@ from esphome import pins import esphome.codegen as cg from esphome.components import gpio_expander, i2c import esphome.config_validation as cv -from esphome.const import ( - CONF_ID, - CONF_INPUT, - CONF_INTERRUPT_PIN, - CONF_INVERTED, - CONF_MODE, - CONF_NUMBER, - CONF_OUTPUT, -) +from esphome.const import CONF_ID, CONF_INPUT, CONF_INTERRUPT_PIN, CONF_OUTPUT from esphome.cpp_generator import MockObj from esphome.types import ConfigType @@ -44,19 +36,11 @@ async def to_code(config: ConfigType) -> None: await cg.add_pin(var.set_interrupt_pin, config.get(CONF_INTERRUPT_PIN)) -def validate_mode(value: ConfigType) -> ConfigType: - if not (value[CONF_INPUT] or value[CONF_OUTPUT]): - raise cv.Invalid("Mode must be either input or output") - if value[CONF_INPUT] and value[CONF_OUTPUT]: - raise cv.Invalid("Mode must be either input or output") - return value - - TCA9555_PIN_SCHEMA = pins.gpio_base_schema( TCA9555GPIOPin, cv.int_range(min=0, max=15), modes=[CONF_INPUT, CONF_OUTPUT], - mode_validator=validate_mode, + mode_validator=pins.gpio_validate_input_xor_output, invertible=True, ).extend( { @@ -67,10 +51,4 @@ TCA9555_PIN_SCHEMA = pins.gpio_base_schema( @pins.PIN_SCHEMA_REGISTRY.register(CONF_TCA9555, TCA9555_PIN_SCHEMA) async def tca9555_pin_to_code(config: ConfigType) -> MockObj: - var = cg.new_Pvariable(config[CONF_ID]) - await cg.register_parented(var, config[CONF_TCA9555]) - - cg.add(var.set_pin(config[CONF_NUMBER])) - cg.add(var.set_inverted(config[CONF_INVERTED])) - cg.add(var.set_flags(pins.gpio_flags_expr(config[CONF_MODE]))) - return var + return await pins.new_parented_pin(config, CONF_TCA9555) diff --git a/esphome/components/tca9555/tca9555.cpp b/esphome/components/tca9555/tca9555.cpp index 448836e6609..bfb837e243c 100644 --- a/esphome/components/tca9555/tca9555.cpp +++ b/esphome/components/tca9555/tca9555.cpp @@ -129,12 +129,6 @@ bool TCA9555Component::digital_read_cache(uint8_t pin) { return this->input_mask float TCA9555Component::get_setup_priority() const { return setup_priority::IO; } -void TCA9555GPIOPin::setup() { this->pin_mode(this->flags_); } -void TCA9555GPIOPin::pin_mode(gpio::Flags flags) { this->parent_->pin_mode(this->pin_, flags); } -bool TCA9555GPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) != this->inverted_; } -void TCA9555GPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_, value != this->inverted_); } -size_t TCA9555GPIOPin::dump_summary(char *buffer, size_t len) const { - return buf_append_printf(buffer, len, 0, "%u via TCA9555", this->pin_); -} - } // namespace esphome::tca9555 + +ESPHOME_EXPANDER_PIN_SUMMARY(esphome::tca9555::TCA9555Component, "%u via TCA9555") diff --git a/esphome/components/tca9555/tca9555.h b/esphome/components/tca9555/tca9555.h index 50037cbe92d..fe4f6c21264 100644 --- a/esphome/components/tca9555/tca9555.h +++ b/esphome/components/tca9555/tca9555.h @@ -1,6 +1,7 @@ #pragma once #include "esphome/components/gpio_expander/cached_gpio.h" +#include "esphome/components/gpio_expander/expander_gpio_pin.h" #include "esphome/components/i2c/i2c.h" #include "esphome/core/component.h" #include "esphome/core/hal.h" @@ -46,25 +47,8 @@ class TCA9555Component final : public Component, InternalGPIOPin *interrupt_pin_{nullptr}; }; -/// Helper class to expose a TCA9555 pin as an internal input GPIO pin. -class TCA9555GPIOPin final : public 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) { this->pin_ = pin; } - void set_inverted(bool inverted) { this->inverted_ = inverted; } - void set_flags(gpio::Flags flags) { this->flags_ = flags; } - - gpio::Flags get_flags() const override { return this->flags_; } - - protected: - uint8_t pin_; - bool inverted_; - gpio::Flags flags_; -}; +using TCA9555GPIOPin = gpio_expander::ExpanderGPIOPin; } // namespace esphome::tca9555 + +ESPHOME_EXPANDER_PIN_SUMMARY_DECL(esphome::tca9555::TCA9555Component); diff --git a/esphome/components/waveshare_io_ch32v003/__init__.py b/esphome/components/waveshare_io_ch32v003/__init__.py index 0b89ca8b561..e01fd924e7b 100644 --- a/esphome/components/waveshare_io_ch32v003/__init__.py +++ b/esphome/components/waveshare_io_ch32v003/__init__.py @@ -2,14 +2,7 @@ from esphome import pins import esphome.codegen as cg from esphome.components import i2c import esphome.config_validation as cv -from esphome.const import ( - CONF_ID, - CONF_INPUT, - CONF_INVERTED, - CONF_MODE, - CONF_NUMBER, - CONF_OUTPUT, -) +from esphome.const import CONF_ID, CONF_INPUT, CONF_OUTPUT from esphome.cpp_generator import MockObj from esphome.types import ConfigType @@ -48,19 +41,11 @@ async def to_code(config: ConfigType) -> None: await i2c.new_i2c_component(config) -def validate_mode(value: ConfigType) -> ConfigType: - if not (value[CONF_INPUT] or value[CONF_OUTPUT]): - raise cv.Invalid("Mode must be either input or output") - if value[CONF_INPUT] and value[CONF_OUTPUT]: - raise cv.Invalid("Mode must be either input or output") - return value - - WAVESHARE_IO_PIN_SCHEMA = pins.gpio_base_schema( WaveshareIOCH32V003GPIOPin, cv.int_range(min=0, max=7), modes=[CONF_INPUT, CONF_OUTPUT], - mode_validator=validate_mode, + mode_validator=pins.gpio_validate_input_xor_output, invertible=True, ).extend( { @@ -73,13 +58,4 @@ WAVESHARE_IO_PIN_SCHEMA = pins.gpio_base_schema( @pins.PIN_SCHEMA_REGISTRY.register(CONF_WAVESHARE_IO_CH32V003, WAVESHARE_IO_PIN_SCHEMA) async def waveshare_io_pin_to_code(config: ConfigType) -> MockObj: - var = cg.new_Pvariable(config[CONF_ID]) - parent = await cg.get_variable(config[CONF_WAVESHARE_IO_CH32V003]) - - cg.add(var.set_parent(parent)) - - num = config[CONF_NUMBER] - cg.add(var.set_pin(num)) - cg.add(var.set_inverted(config[CONF_INVERTED])) - cg.add(var.set_flags(pins.gpio_flags_expr(config[CONF_MODE]))) - return var + return await pins.new_parented_pin(config, CONF_WAVESHARE_IO_CH32V003) diff --git a/esphome/components/waveshare_io_ch32v003/waveshare_io_ch32v003.cpp b/esphome/components/waveshare_io_ch32v003/waveshare_io_ch32v003.cpp index 9ff7eb7f382..86571363257 100644 --- a/esphome/components/waveshare_io_ch32v003/waveshare_io_ch32v003.cpp +++ b/esphome/components/waveshare_io_ch32v003/waveshare_io_ch32v003.cpp @@ -124,16 +124,6 @@ void WaveshareIOCH32V003Component::digital_write_hw(uint8_t pin, bool value) { bool WaveshareIOCH32V003Component::digital_read_cache(uint8_t pin) { return this->input_mask_ & (1 << pin); } float WaveshareIOCH32V003Component::get_setup_priority() const { return setup_priority::IO; } -void WaveshareIOCH32V003GPIOPin::setup() { this->pin_mode(this->flags_); } -void WaveshareIOCH32V003GPIOPin::pin_mode(gpio::Flags flags) { this->parent_->pin_mode(this->pin_, flags); } -bool WaveshareIOCH32V003GPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) ^ this->inverted_; } - -void WaveshareIOCH32V003GPIOPin::digital_write(bool value) { - this->parent_->digital_write(this->pin_, value ^ this->inverted_); -} - -size_t WaveshareIOCH32V003GPIOPin::dump_summary(char *buffer, size_t len) const { - return buf_append_printf(buffer, len, 0, "EXIO%u via WaveshareIO", this->pin_); -} - } // namespace esphome::waveshare_io_ch32v003 + +ESPHOME_EXPANDER_PIN_SUMMARY(esphome::waveshare_io_ch32v003::WaveshareIOCH32V003Component, "EXIO%u via WaveshareIO") diff --git a/esphome/components/waveshare_io_ch32v003/waveshare_io_ch32v003.h b/esphome/components/waveshare_io_ch32v003/waveshare_io_ch32v003.h index 4a31602fa41..adecaa681cc 100644 --- a/esphome/components/waveshare_io_ch32v003/waveshare_io_ch32v003.h +++ b/esphome/components/waveshare_io_ch32v003/waveshare_io_ch32v003.h @@ -4,6 +4,7 @@ #include "esphome/components/i2c/i2c.h" #include "esphome/core/component.h" #include "esphome/core/hal.h" +#include "esphome/components/gpio_expander/expander_gpio_pin.h" namespace esphome::waveshare_io_ch32v003 { @@ -27,8 +28,6 @@ class WaveshareIOCH32V003Component : public Component, void set_pwm_value(uint8_t value); // 0 - 255 protected: - friend class WaveshareIOCH32V003GPIOPin; - 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; @@ -41,25 +40,8 @@ class WaveshareIOCH32V003Component : public Component, bool write_gpio_outputs_(); }; -/// Helper class to expose a WaveshareIO pin as a GPIO pin. -class WaveshareIOCH32V003GPIOPin : public 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) { this->pin_ = pin; } - void set_inverted(bool inverted) { this->inverted_ = inverted; } - void set_flags(gpio::Flags flags) { this->flags_ = flags; } - - gpio::Flags get_flags() const override { return this->flags_; } - - protected: - uint8_t pin_{}; - bool inverted_{}; - gpio::Flags flags_{}; -}; +using WaveshareIOCH32V003GPIOPin = gpio_expander::ExpanderGPIOPin; } // namespace esphome::waveshare_io_ch32v003 + +ESPHOME_EXPANDER_PIN_SUMMARY_DECL(esphome::waveshare_io_ch32v003::WaveshareIOCH32V003Component); diff --git a/esphome/components/weikai/__init__.py b/esphome/components/weikai/__init__.py index 58bfc6f815d..5fb54a906aa 100644 --- a/esphome/components/weikai/__init__.py +++ b/esphome/components/weikai/__init__.py @@ -1,3 +1,4 @@ +from esphome import pins import esphome.codegen as cg from esphome.components import uart from esphome.components.const import CONF_DATA_BITS, CONF_PARITY, CONF_STOP_BITS @@ -90,10 +91,7 @@ async def register_weikai(var: MockObj, config: ConfigType) -> None: def validate_pin_mode(value: ConfigType) -> ConfigType: """Checks input/output mode inconsistency""" - if not (value[CONF_MODE][CONF_INPUT] or value[CONF_MODE][CONF_OUTPUT]): - raise cv.Invalid("Mode must be either input or output") - if value[CONF_MODE][CONF_INPUT] and value[CONF_MODE][CONF_OUTPUT]: - raise cv.Invalid("Mode must be either input or output") + pins.gpio_validate_input_xor_output(value[CONF_MODE]) return value diff --git a/esphome/components/wk2168_i2c/__init__.py b/esphome/components/wk2168_i2c/__init__.py index c450717ea2c..9c2b074e503 100644 --- a/esphome/components/wk2168_i2c/__init__.py +++ b/esphome/components/wk2168_i2c/__init__.py @@ -2,7 +2,7 @@ from esphome import pins import esphome.codegen as cg from esphome.components import i2c, weikai import esphome.config_validation as cv -from esphome.const import CONF_ID, CONF_INVERTED, CONF_MODE, CONF_NUMBER +from esphome.const import CONF_ID from esphome.cpp_generator import MockObj from esphome.types import ConfigType @@ -52,11 +52,4 @@ WK2168_PIN_SCHEMA = cv.All( @pins.PIN_SCHEMA_REGISTRY.register(CONF_WK2168_I2C, WK2168_PIN_SCHEMA) async def sc16is75x_pin_to_code(config: ConfigType) -> MockObj: - var = cg.new_Pvariable(config[CONF_ID]) - parent = await cg.get_variable(config[CONF_WK2168_I2C]) - cg.add(var.set_parent(parent)) - num = config[CONF_NUMBER] - cg.add(var.set_pin(num)) - cg.add(var.set_inverted(config[CONF_INVERTED])) - cg.add(var.set_flags(pins.gpio_flags_expr(config[CONF_MODE]))) - return var + return await pins.new_parented_pin(config, CONF_WK2168_I2C) diff --git a/esphome/components/wk2168_spi/__init__.py b/esphome/components/wk2168_spi/__init__.py index ef12117a2c7..12192e71db0 100644 --- a/esphome/components/wk2168_spi/__init__.py +++ b/esphome/components/wk2168_spi/__init__.py @@ -2,7 +2,7 @@ from esphome import pins import esphome.codegen as cg from esphome.components import spi, weikai import esphome.config_validation as cv -from esphome.const import CONF_ID, CONF_INVERTED, CONF_MODE, CONF_NUMBER +from esphome.const import CONF_ID from esphome.cpp_generator import MockObj from esphome.types import ConfigType @@ -50,11 +50,4 @@ WK2168_PIN_SCHEMA = cv.All( @pins.PIN_SCHEMA_REGISTRY.register(CONF_WK2168_SPI, WK2168_PIN_SCHEMA) async def sc16is75x_pin_to_code(config: ConfigType) -> MockObj: - var = cg.new_Pvariable(config[CONF_ID]) - parent = await cg.get_variable(config[CONF_WK2168_SPI]) - cg.add(var.set_parent(parent)) - num = config[CONF_NUMBER] - cg.add(var.set_pin(num)) - cg.add(var.set_inverted(config[CONF_INVERTED])) - cg.add(var.set_flags(pins.gpio_flags_expr(config[CONF_MODE]))) - return var + return await pins.new_parented_pin(config, CONF_WK2168_SPI) diff --git a/esphome/components/wk2212_i2c/__init__.py b/esphome/components/wk2212_i2c/__init__.py index 1f51fea3145..73c41b36504 100644 --- a/esphome/components/wk2212_i2c/__init__.py +++ b/esphome/components/wk2212_i2c/__init__.py @@ -2,7 +2,7 @@ from esphome import pins import esphome.codegen as cg from esphome.components import i2c, weikai import esphome.config_validation as cv -from esphome.const import CONF_ID, CONF_INVERTED, CONF_MODE, CONF_NUMBER +from esphome.const import CONF_ID from esphome.cpp_generator import MockObj from esphome.types import ConfigType @@ -52,11 +52,4 @@ WK2212_PIN_SCHEMA = cv.All( @pins.PIN_SCHEMA_REGISTRY.register(CONF_WK2212_I2C, WK2212_PIN_SCHEMA) async def sc16is75x_pin_to_code(config: ConfigType) -> MockObj: - var = cg.new_Pvariable(config[CONF_ID]) - parent = await cg.get_variable(config[CONF_WK2212_I2C]) - cg.add(var.set_parent(parent)) - num = config[CONF_NUMBER] - cg.add(var.set_pin(num)) - cg.add(var.set_inverted(config[CONF_INVERTED])) - cg.add(var.set_flags(pins.gpio_flags_expr(config[CONF_MODE]))) - return var + return await pins.new_parented_pin(config, CONF_WK2212_I2C) diff --git a/esphome/components/wk2212_spi/__init__.py b/esphome/components/wk2212_spi/__init__.py index a0cce3331ad..cc4c358f284 100644 --- a/esphome/components/wk2212_spi/__init__.py +++ b/esphome/components/wk2212_spi/__init__.py @@ -2,7 +2,7 @@ from esphome import pins import esphome.codegen as cg from esphome.components import spi, weikai import esphome.config_validation as cv -from esphome.const import CONF_ID, CONF_INVERTED, CONF_MODE, CONF_NUMBER +from esphome.const import CONF_ID from esphome.cpp_generator import MockObj from esphome.types import ConfigType @@ -50,11 +50,4 @@ WK2212_PIN_SCHEMA = cv.All( @pins.PIN_SCHEMA_REGISTRY.register(CONF_WK2212_SPI, WK2212_PIN_SCHEMA) async def sc16is75x_pin_to_code(config: ConfigType) -> MockObj: - var = cg.new_Pvariable(config[CONF_ID]) - parent = await cg.get_variable(config[CONF_WK2212_SPI]) - cg.add(var.set_parent(parent)) - num = config[CONF_NUMBER] - cg.add(var.set_pin(num)) - cg.add(var.set_inverted(config[CONF_INVERTED])) - cg.add(var.set_flags(pins.gpio_flags_expr(config[CONF_MODE]))) - return var + return await pins.new_parented_pin(config, CONF_WK2212_SPI) diff --git a/esphome/components/xl9535/__init__.py b/esphome/components/xl9535/__init__.py index 85330357d2d..8d1e03ff156 100644 --- a/esphome/components/xl9535/__init__.py +++ b/esphome/components/xl9535/__init__.py @@ -2,19 +2,13 @@ from esphome import pins import esphome.codegen as cg from esphome.components import i2c import esphome.config_validation as cv -from esphome.const import ( - CONF_ID, - CONF_INPUT, - CONF_INVERTED, - CONF_MODE, - CONF_NUMBER, - CONF_OUTPUT, -) +from esphome.const import CONF_ID, CONF_INPUT, CONF_OUTPUT from esphome.cpp_generator import MockObj from esphome.types import ConfigType CONF_XL9535 = "xl9535" +AUTO_LOAD = ["gpio_expander"] DEPENDENCIES = ["i2c"] CODEOWNERS = ["@mreditor97"] DOMAIN = "xl9535" @@ -36,45 +30,24 @@ async def to_code(config: ConfigType) -> None: await i2c.new_i2c_component(config) -def validate_mode(mode: ConfigType) -> ConfigType: - if not (mode[CONF_INPUT] or mode[CONF_OUTPUT]) or ( - mode[CONF_INPUT] and mode[CONF_OUTPUT] - ): - raise cv.Invalid("Mode must be either a input or a output") - return mode - - def validate_pin(pin: int) -> int: if pin in (8, 9): raise cv.Invalid(f"pin {pin} doesn't exist") return pin -XL9535_PIN_SCHEMA = cv.All( +XL9535_PIN_SCHEMA = pins.gpio_base_schema( + XL9535GPIOPin, + cv.All(cv.int_range(min=0, max=17), validate_pin), + modes=[CONF_INPUT, CONF_OUTPUT], + mode_validator=pins.gpio_validate_input_xor_output, +).extend( { - cv.GenerateID(): cv.declare_id(XL9535GPIOPin), cv.Required(CONF_XL9535): pins.use_id_or_address(XL9535Component), - cv.Required(CONF_NUMBER): cv.All(cv.int_range(min=0, max=17), validate_pin), - cv.Optional(CONF_MODE, default={}): cv.All( - { - cv.Optional(CONF_INPUT, default=False): cv.boolean, - cv.Optional(CONF_OUTPUT, default=False): cv.boolean, - }, - validate_mode, - ), - cv.Optional(CONF_INVERTED, default=False): cv.boolean, } ) @pins.PIN_SCHEMA_REGISTRY.register(CONF_XL9535, XL9535_PIN_SCHEMA) async def xl9535_pin_to_code(config: ConfigType) -> MockObj: - var = cg.new_Pvariable(config[CONF_ID]) - parent = await cg.get_variable(config[CONF_XL9535]) - - cg.add(var.set_parent(parent)) - cg.add(var.set_pin(config[CONF_NUMBER])) - cg.add(var.set_inverted(config[CONF_INVERTED])) - cg.add(var.set_flags(pins.gpio_flags_expr(config[CONF_MODE]))) - - return var + return await pins.new_parented_pin(config, CONF_XL9535) diff --git a/esphome/components/xl9535/xl9535.cpp b/esphome/components/xl9535/xl9535.cpp index 9be536353c8..d26641c3afa 100644 --- a/esphome/components/xl9535/xl9535.cpp +++ b/esphome/components/xl9535/xl9535.cpp @@ -103,14 +103,6 @@ void XL9535Component::pin_mode(uint8_t pin, gpio::Flags mode) { } } -void XL9535GPIOPin::setup() { this->pin_mode(this->flags_); } - -size_t XL9535GPIOPin::dump_summary(char *buffer, size_t len) const { - return buf_append_printf(buffer, len, 0, "%u via XL9535", this->pin_); -} - -void XL9535GPIOPin::pin_mode(gpio::Flags flags) { this->parent_->pin_mode(this->pin_, flags); } -bool XL9535GPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) != this->inverted_; } -void XL9535GPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_, value != this->inverted_); } - } // namespace esphome::xl9535 + +ESPHOME_EXPANDER_PIN_SUMMARY(esphome::xl9535::XL9535Component, "%u via XL9535") diff --git a/esphome/components/xl9535/xl9535.h b/esphome/components/xl9535/xl9535.h index 11fb3acc8d0..8f883d36df7 100644 --- a/esphome/components/xl9535/xl9535.h +++ b/esphome/components/xl9535/xl9535.h @@ -3,6 +3,7 @@ #include "esphome/core/component.h" #include "esphome/components/i2c/i2c.h" #include "esphome/core/hal.h" +#include "esphome/components/gpio_expander/expander_gpio_pin.h" namespace esphome::xl9535 { @@ -28,27 +29,8 @@ class XL9535Component final : public Component, public i2c::I2CDevice { float get_setup_priority() const override { return setup_priority::IO; } }; -class XL9535GPIOPin final : public GPIOPin { - public: - void set_parent(XL9535Component *parent) { this->parent_ = parent; } - void set_pin(uint8_t pin) { this->pin_ = pin; } - void set_inverted(bool inverted) { this->inverted_ = inverted; } - void set_flags(gpio::Flags flags) { this->flags_ = flags; } - - gpio::Flags get_flags() const override { return this->flags_; } - - void setup() override; - size_t dump_summary(char *buffer, size_t len) const override; - void pin_mode(gpio::Flags flags) override; - bool digital_read() override; - void digital_write(bool value) override; - - protected: - XL9535Component *parent_; - - uint8_t pin_; - bool inverted_; - gpio::Flags flags_; -}; +using XL9535GPIOPin = gpio_expander::ExpanderGPIOPin; } // namespace esphome::xl9535 + +ESPHOME_EXPANDER_PIN_SUMMARY_DECL(esphome::xl9535::XL9535Component); diff --git a/tests/components/pcf8574/test_pcf8574.cpp b/tests/components/pcf8574/test_pcf8574.cpp index 3500069ba10..1caf86febaf 100644 --- a/tests/components/pcf8574/test_pcf8574.cpp +++ b/tests/components/pcf8574/test_pcf8574.cpp @@ -50,10 +50,8 @@ class PCF8574ResetTest : public ::testing::TestWithParam { } void configure_input_(uint8_t number) { - PCF8574GPIOPin pin; - pin.set_parent(&this->component_); - pin.set_pin(number); - pin.set_flags(gpio::FLAG_INPUT); + // Codegen registers input pins with the parent before setup() + this->component_.register_input_pin(number); EXPECT_EQ(this->bus_.writes, 0); EXPECT_EQ(this->bus_.reads, 0); } diff --git a/tests/unit_tests/components/test_pcf8574.py b/tests/unit_tests/components/test_pcf8574.py index e5b6519700d..250eb5be9bc 100644 --- a/tests/unit_tests/components/test_pcf8574.py +++ b/tests/unit_tests/components/test_pcf8574.py @@ -5,7 +5,14 @@ from unittest.mock import AsyncMock, Mock, patch import pytest from esphome.components import pcf8574 -from esphome.const import CONF_ID, CONF_RESET +from esphome.const import ( + CONF_ID, + CONF_INPUT, + CONF_MODE, + CONF_NUMBER, + CONF_OUTPUT, + CONF_RESET, +) @pytest.mark.asyncio @@ -31,3 +38,33 @@ async def test_reset_configuration(options: dict, expected: bool) -> None: var.set_reset.assert_not_called() else: var.set_reset.assert_called_once_with(False) + + +@pytest.mark.asyncio +@pytest.mark.parametrize( + "number, is_input", + [ + (0, True), + (15, True), + (0, False), + (15, False), + ], +) +async def test_pin_registers_inputs_only(number: int, is_input: bool) -> None: + config = { + CONF_NUMBER: number, + CONF_MODE: {CONF_INPUT: is_input, CONF_OUTPUT: not is_input}, + pcf8574.CONF_PCF8574: "hub", + } + parent = Mock() + with ( + patch.object(pcf8574.pins, "new_parented_pin", new_callable=AsyncMock), + patch.object(pcf8574.cg, "get_variable", AsyncMock(return_value=parent)), + patch.object(pcf8574.cg, "add"), + ): + await pcf8574.pcf8574_pin_to_code(config) + + if is_input: + parent.register_input_pin.assert_called_once_with(number) + else: + parent.register_input_pin.assert_not_called()