diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index cfa79e3cf1..aa4f78de69 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -15,7 +15,6 @@ from esphome.components.const import ( CONF_ENABLE_OTA_DOWNGRADE_PROTECTION, CONF_IGNORE_NOT_FOUND, ) -from esphome.config_helpers import filter_source_files_from_defines import esphome.config_validation as cv from esphome.const import ( CONF_ADVANCED, @@ -3967,10 +3966,3 @@ def process_stacktrace(config, line, backtrace_state): _decode_pc(config, addr.group()) return backtrace_state - - -# gpio.cpp only implements ESP32InternalGPIOPin and its ISR helpers, which -# are instantiated solely by the pin schema codegen (esp32_pin_to_code) -FILTER_SOURCE_FILES = filter_source_files_from_defines( - {"gpio.cpp": "USE_ESP32_INTERNAL_GPIO"} -) diff --git a/esphome/components/esp32/gpio.cpp b/esphome/components/esp32/gpio.cpp index 6e4de6aa51..d9c9b86185 100644 --- a/esphome/components/esp32/gpio.cpp +++ b/esphome/components/esp32/gpio.cpp @@ -1,9 +1,10 @@ #include "esphome/core/defines.h" -// Also defines the core ISRInternalGPIOPin methods; those are only reachable -// via ESP32InternalGPIOPin::to_isr(), so the same define gates both safely. -#if defined(USE_ESP32) && defined(USE_ESP32_INTERNAL_GPIO) +// Always built: InternalGPIOPin is this class, so components that merely hold a +// pointer to it reference these methods even in configs without a pin. +#ifdef USE_ESP32 #include "gpio.h" +#include "esphome/core/hal.h" #include "esphome/core/log.h" #include "driver/gpio.h" #include "driver/rtc_io.h" @@ -68,7 +69,7 @@ ISRInternalGPIOPin ESP32InternalGPIOPin::to_isr() const { return ISRInternalGPIOPin((void *) arg); } -void ESP32InternalGPIOPin::attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const { +void ESP32InternalGPIOPin::attach_interrupt_(void (*func)(void *), void *arg, gpio::InterruptType type) const { gpio_int_type_t idf_type = GPIO_INTR_ANYEDGE; switch (type) { case gpio::INTERRUPT_RISING_EDGE: @@ -207,4 +208,4 @@ void IRAM_ATTR ISRInternalGPIOPin::pin_mode(gpio::Flags flags) { } // namespace esphome -#endif // USE_ESP32 && USE_ESP32_INTERNAL_GPIO +#endif // USE_ESP32 diff --git a/esphome/components/esp32/gpio.h b/esphome/components/esp32/gpio.h index 9c661b4d9e..954b213d7f 100644 --- a/esphome/components/esp32/gpio.h +++ b/esphome/components/esp32/gpio.h @@ -1,7 +1,8 @@ #pragma once #ifdef USE_ESP32 -#include "esphome/core/hal.h" +#include "esphome/core/gpio_pin.h" +#include #include namespace esphome::esp32 { @@ -10,7 +11,7 @@ namespace esphome::esp32 { static_assert(GPIO_NUM_MAX <= 256, "gpio_num_t has too many values for uint8_t"); static_assert(GPIO_DRIVE_CAP_MAX <= 4, "gpio_drive_cap_t has too many values for 2-bit field"); -class ESP32InternalGPIOPin final : public InternalGPIOPin { +class ESP32InternalGPIOPin final : public GPIOPin { public: void set_pin(gpio_num_t pin) { this->pin_ = static_cast(pin); } void set_inverted(bool inverted) { this->pin_flags_.inverted = inverted; } @@ -24,11 +25,15 @@ class ESP32InternalGPIOPin final : public InternalGPIOPin { bool digital_read() override; void digital_write(bool value) override; size_t dump_summary(char *buffer, size_t len) const override; - void detach_interrupt() const override; - ISRInternalGPIOPin to_isr() const override; - uint8_t get_pin() const override { return this->pin_; } + void detach_interrupt() const; + template void attach_interrupt(void (*func)(T *), T *arg, gpio::InterruptType type) const { + this->attach_interrupt_(reinterpret_cast(func), arg, type); + } + ISRInternalGPIOPin to_isr() const; + uint8_t get_pin() const { return this->pin_; } gpio::Flags get_flags() const override { return this->flags_; } - bool is_inverted() const override { return this->pin_flags_.inverted; } + bool is_inverted() const { return this->pin_flags_.inverted; } + bool is_internal() override { return true; } gpio_num_t get_pin_num() const { return static_cast(this->pin_); } gpio_drive_cap_t get_drive_strength() const { return static_cast(this->pin_flags_.drive_strength); } #ifdef USE_GPIO_HOLD @@ -41,7 +46,7 @@ class ESP32InternalGPIOPin final : public InternalGPIOPin { #endif protected: - void attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const override; + void attach_interrupt_(void (*func)(void *), void *arg, gpio::InterruptType type) const; // Memory layout: 8 bytes total on 32-bit systems // - 3 bytes for members below diff --git a/esphome/components/esp32/gpio.py b/esphome/components/esp32/gpio.py index 98aac209ec..321dd3d498 100644 --- a/esphome/components/esp32/gpio.py +++ b/esphome/components/esp32/gpio.py @@ -257,7 +257,6 @@ ESP32_PIN_SCHEMA = cv.All( @pins.PIN_SCHEMA_REGISTRY.register(PLATFORM_ESP32, ESP32_PIN_SCHEMA) async def esp32_pin_to_code(config): - cg.add_define("USE_ESP32_INTERNAL_GPIO") var = cg.new_Pvariable(config[CONF_ID]) num = config[CONF_NUMBER] cg.add(var.set_pin(getattr(gpio_num_t, f"GPIO_NUM_{num}"))) diff --git a/esphome/components/esp8266/gpio.cpp b/esphome/components/esp8266/gpio.cpp index ae09bdb91c..1d97b2c1e5 100644 --- a/esphome/components/esp8266/gpio.cpp +++ b/esphome/components/esp8266/gpio.cpp @@ -1,6 +1,7 @@ #ifdef USE_ESP8266 #include "gpio.h" +#include "esphome/core/hal.h" #include "esphome/core/log.h" namespace esphome::esp8266 { @@ -74,7 +75,7 @@ ISRInternalGPIOPin ESP8266GPIOPin::to_isr() const { return ISRInternalGPIOPin((void *) arg); } -void ESP8266GPIOPin::attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const { +void ESP8266GPIOPin::attach_interrupt_(void (*func)(void *), void *arg, gpio::InterruptType type) const { uint8_t arduino_mode = 0; switch (type) { case gpio::INTERRUPT_RISING_EDGE: diff --git a/esphome/components/esp8266/gpio.h b/esphome/components/esp8266/gpio.h index 57ef06106a..84968d9752 100644 --- a/esphome/components/esp8266/gpio.h +++ b/esphome/components/esp8266/gpio.h @@ -2,12 +2,12 @@ #ifdef USE_ESP8266 -#include "esphome/core/hal.h" +#include "esphome/core/gpio_pin.h" #include namespace esphome::esp8266 { -class ESP8266GPIOPin final : public InternalGPIOPin { +class ESP8266GPIOPin final : public GPIOPin { public: void set_pin(uint8_t pin) { pin_ = pin; } void set_inverted(bool inverted) { inverted_ = inverted; } @@ -18,14 +18,18 @@ class ESP8266GPIOPin final : public InternalGPIOPin { bool digital_read() override; void digital_write(bool value) override; size_t dump_summary(char *buffer, size_t len) const override; - void detach_interrupt() const override; - ISRInternalGPIOPin to_isr() const override; - uint8_t get_pin() const override { return pin_; } + void detach_interrupt() const; + template void attach_interrupt(void (*func)(T *), T *arg, gpio::InterruptType type) const { + this->attach_interrupt_(reinterpret_cast(func), arg, type); + } + ISRInternalGPIOPin to_isr() const; + uint8_t get_pin() const { return pin_; } gpio::Flags get_flags() const override { return flags_; } - bool is_inverted() const override { return inverted_; } + bool is_inverted() const { return inverted_; } + bool is_internal() override { return true; } protected: - void attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const override; + void attach_interrupt_(void (*func)(void *), void *arg, gpio::InterruptType type) const; uint8_t pin_; bool inverted_{}; diff --git a/esphome/components/host/gpio.cpp b/esphome/components/host/gpio.cpp index c1fd56e945..b57bd41188 100644 --- a/esphome/components/host/gpio.cpp +++ b/esphome/components/host/gpio.cpp @@ -1,6 +1,7 @@ #ifdef USE_HOST #include "gpio.h" +#include "esphome/core/hal.h" #include "esphome/core/log.h" namespace esphome { @@ -20,7 +21,7 @@ ISRInternalGPIOPin HostGPIOPin::to_isr() const { return ISRInternalGPIOPin((void *) arg); } -void HostGPIOPin::attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const { +void HostGPIOPin::attach_interrupt_(void (*func)(void *), void *arg, gpio::InterruptType type) const { ESP_LOGD(TAG, "Attaching interrupt %p to pin %d and mode %d", func, pin_, (uint32_t) type); } void HostGPIOPin::pin_mode(gpio::Flags flags) { ESP_LOGD(TAG, "Setting pin %d mode to %02X", pin_, (uint32_t) flags); } diff --git a/esphome/components/host/gpio.h b/esphome/components/host/gpio.h index bd2d09257b..fd3ebefe1f 100644 --- a/esphome/components/host/gpio.h +++ b/esphome/components/host/gpio.h @@ -2,11 +2,11 @@ #ifdef USE_HOST -#include "esphome/core/hal.h" +#include "esphome/core/gpio_pin.h" namespace esphome::host { -class HostGPIOPin final : public InternalGPIOPin { +class HostGPIOPin final : public GPIOPin { public: void set_pin(uint8_t pin) { pin_ = pin; } void set_inverted(bool inverted) { inverted_ = inverted; } @@ -17,14 +17,18 @@ class HostGPIOPin final : public InternalGPIOPin { bool digital_read() override; void digital_write(bool value) override; size_t dump_summary(char *buffer, size_t len) const override; - void detach_interrupt() const override; - ISRInternalGPIOPin to_isr() const override; - uint8_t get_pin() const override { return pin_; } + void detach_interrupt() const; + template void attach_interrupt(void (*func)(T *), T *arg, gpio::InterruptType type) const { + this->attach_interrupt_(reinterpret_cast(func), arg, type); + } + ISRInternalGPIOPin to_isr() const; + uint8_t get_pin() const { return pin_; } gpio::Flags get_flags() const override { return flags_; } - bool is_inverted() const override { return inverted_; } + bool is_inverted() const { return inverted_; } + bool is_internal() override { return true; } protected: - void attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const override; + void attach_interrupt_(void (*func)(void *), void *arg, gpio::InterruptType type) const; uint8_t pin_; bool inverted_{}; diff --git a/esphome/components/libretiny/gpio_arduino.cpp b/esphome/components/libretiny/gpio_arduino.cpp index ff333aacef..9884c37b85 100644 --- a/esphome/components/libretiny/gpio_arduino.cpp +++ b/esphome/components/libretiny/gpio_arduino.cpp @@ -1,6 +1,7 @@ #ifdef USE_LIBRETINY #include "gpio_arduino.h" +#include "esphome/core/hal.h" #include "esphome/core/log.h" namespace esphome::libretiny { @@ -37,7 +38,7 @@ ISRInternalGPIOPin ArduinoInternalGPIOPin::to_isr() const { return ISRInternalGPIOPin((void *) arg); } -void ArduinoInternalGPIOPin::attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const { +void ArduinoInternalGPIOPin::attach_interrupt_(void (*func)(void *), void *arg, gpio::InterruptType type) const { PinStatus arduino_mode = (PinStatus) 255; switch (type) { case gpio::INTERRUPT_RISING_EDGE: diff --git a/esphome/components/libretiny/gpio_arduino.h b/esphome/components/libretiny/gpio_arduino.h index da477fde36..b5ccf544ba 100644 --- a/esphome/components/libretiny/gpio_arduino.h +++ b/esphome/components/libretiny/gpio_arduino.h @@ -1,11 +1,11 @@ #pragma once #ifdef USE_LIBRETINY -#include "esphome/core/hal.h" +#include "esphome/core/gpio_pin.h" namespace esphome::libretiny { -class ArduinoInternalGPIOPin final : public InternalGPIOPin { +class ArduinoInternalGPIOPin final : public GPIOPin { public: void set_pin(uint8_t pin) { pin_ = pin; } void set_inverted(bool inverted) { inverted_ = inverted; } @@ -16,14 +16,18 @@ class ArduinoInternalGPIOPin final : public InternalGPIOPin { bool digital_read() override; void digital_write(bool value) override; size_t dump_summary(char *buffer, size_t len) const override; - void detach_interrupt() const override; - ISRInternalGPIOPin to_isr() const override; - uint8_t get_pin() const override { return pin_; } + void detach_interrupt() const; + template void attach_interrupt(void (*func)(T *), T *arg, gpio::InterruptType type) const { + this->attach_interrupt_(reinterpret_cast(func), arg, type); + } + ISRInternalGPIOPin to_isr() const; + uint8_t get_pin() const { return pin_; } gpio::Flags get_flags() const override { return flags_; } - bool is_inverted() const override { return inverted_; } + bool is_inverted() const { return inverted_; } + bool is_internal() override { return true; } protected: - void attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const override; + void attach_interrupt_(void (*func)(void *), void *arg, gpio::InterruptType type) const; uint8_t pin_; bool inverted_{}; diff --git a/esphome/components/rp2/gpio.cpp b/esphome/components/rp2/gpio.cpp index 93bdba1fca..2eeb460e95 100644 --- a/esphome/components/rp2/gpio.cpp +++ b/esphome/components/rp2/gpio.cpp @@ -1,6 +1,7 @@ #ifdef USE_RP2 #include "gpio.h" +#include "esphome/core/hal.h" #include "esphome/core/log.h" namespace esphome { @@ -40,7 +41,7 @@ ISRInternalGPIOPin RP2GPIOPin::to_isr() const { return ISRInternalGPIOPin((void *) arg); } -void RP2GPIOPin::attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const { +void RP2GPIOPin::attach_interrupt_(void (*func)(void *), void *arg, gpio::InterruptType type) const { PinStatus arduino_mode = LOW; switch (type) { case gpio::INTERRUPT_RISING_EDGE: diff --git a/esphome/components/rp2/gpio.h b/esphome/components/rp2/gpio.h index 538fef619a..b1d4c9a8f6 100644 --- a/esphome/components/rp2/gpio.h +++ b/esphome/components/rp2/gpio.h @@ -3,11 +3,11 @@ #ifdef USE_RP2 #include -#include "esphome/core/hal.h" +#include "esphome/core/gpio_pin.h" namespace esphome::rp2 { -class RP2GPIOPin final : public InternalGPIOPin { +class RP2GPIOPin final : public GPIOPin { public: void set_pin(uint8_t pin) { pin_ = pin; } void set_inverted(bool inverted) { inverted_ = inverted; } @@ -18,14 +18,18 @@ class RP2GPIOPin final : public InternalGPIOPin { bool digital_read() override; void digital_write(bool value) override; size_t dump_summary(char *buffer, size_t len) const override; - void detach_interrupt() const override; - ISRInternalGPIOPin to_isr() const override; - uint8_t get_pin() const override { return pin_; } + void detach_interrupt() const; + template void attach_interrupt(void (*func)(T *), T *arg, gpio::InterruptType type) const { + this->attach_interrupt_(reinterpret_cast(func), arg, type); + } + ISRInternalGPIOPin to_isr() const; + uint8_t get_pin() const { return pin_; } gpio::Flags get_flags() const override { return flags_; } - bool is_inverted() const override { return inverted_; } + bool is_inverted() const { return inverted_; } + bool is_internal() override { return true; } protected: - void attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const override; + void attach_interrupt_(void (*func)(void *), void *arg, gpio::InterruptType type) const; uint8_t pin_; bool inverted_{}; diff --git a/esphome/components/zephyr/gpio.cpp b/esphome/components/zephyr/gpio.cpp index 4400f0a0c5..57a31e302e 100644 --- a/esphome/components/zephyr/gpio.cpp +++ b/esphome/components/zephyr/gpio.cpp @@ -1,5 +1,6 @@ #ifdef USE_ZEPHYR #include "gpio.h" +#include "esphome/core/hal.h" #include #include #include "esphome/core/log.h" @@ -79,7 +80,7 @@ ISRInternalGPIOPin ZephyrGPIOPin::to_isr() const { return ISRInternalGPIOPin((void *) arg); } -void ZephyrGPIOPin::attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const { +void ZephyrGPIOPin::attach_interrupt_(void (*func)(void *), void *arg, gpio::InterruptType type) const { if (!device_is_ready(this->gpio_)) { ESP_LOGE(TAG, "Cannot attach interrupt: GPIO device not ready"); return; diff --git a/esphome/components/zephyr/gpio.h b/esphome/components/zephyr/gpio.h index 71d1620a67..dd26e34eb8 100644 --- a/esphome/components/zephyr/gpio.h +++ b/esphome/components/zephyr/gpio.h @@ -1,7 +1,7 @@ #pragma once #ifdef USE_ZEPHYR -#include "esphome/core/hal.h" +#include "esphome/core/gpio_pin.h" #include #include namespace esphome::zephyr { @@ -16,7 +16,7 @@ struct ZephyrGPIOInterrupt { void *arg{nullptr}; }; -class ZephyrGPIOPin final : public InternalGPIOPin { +class ZephyrGPIOPin final : public GPIOPin { public: ZephyrGPIOPin(const device *gpio, int gpio_size, const char *pin_name_prefix) { this->gpio_ = gpio; @@ -32,14 +32,18 @@ class ZephyrGPIOPin final : public InternalGPIOPin { bool digital_read() override; void digital_write(bool value) override; size_t dump_summary(char *buffer, size_t len) const override; - void detach_interrupt() const override; - ISRInternalGPIOPin to_isr() const override; - uint8_t get_pin() const override { return this->pin_; } - bool is_inverted() const override { return this->inverted_; } + void detach_interrupt() const; + template void attach_interrupt(void (*func)(T *), T *arg, gpio::InterruptType type) const { + this->attach_interrupt_(reinterpret_cast(func), arg, type); + } + ISRInternalGPIOPin to_isr() const; + uint8_t get_pin() const { return this->pin_; } + bool is_inverted() const { return this->inverted_; } + bool is_internal() override { return true; } gpio::Flags get_flags() const override { return flags_; } protected: - void attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const override; + void attach_interrupt_(void (*func)(void *), void *arg, gpio::InterruptType type) const; const device *gpio_{nullptr}; const char *pin_name_prefix_{nullptr}; gpio::Flags flags_{}; @@ -48,8 +52,7 @@ class ZephyrGPIOPin final : public InternalGPIOPin { bool inverted_{}; bool value_{false}; - // attach_interrupt()/detach_interrupt() are const (matching the base class), so - // the interrupt state they manage has to be mutable. + // attach_interrupt_()/detach_interrupt() are const, so their interrupt state is mutable. mutable ZephyrGPIOInterrupt interrupt_{}; }; diff --git a/esphome/core/defines.h b/esphome/core/defines.h index d4932ae749..731c0d2017 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -364,7 +364,6 @@ #ifdef USE_ESP32 #define USE_ESP32_CRASH_HANDLER #define USE_ESP32_VASPRINTF_STUB -#define USE_ESP32_INTERNAL_GPIO #define USE_MQTT_IDF_ENQUEUE #define USE_ESPHOME_TASK_LOG_BUFFER #define ESPHOME_TASK_LOG_BUFFER_SIZE 768 diff --git a/esphome/core/gpio.h b/esphome/core/gpio.h index 7daf1917a6..8ed0d70f6b 100644 --- a/esphome/core/gpio.h +++ b/esphome/core/gpio.h @@ -1,150 +1,44 @@ #pragma once -#include -#include +#include "esphome/core/gpio_pin.h" -#include "esphome/core/defines.h" -#include "esphome/core/helpers.h" -#include "esphome/core/log.h" +// Each platform has exactly one internal pin class, so InternalGPIOPin is an alias for +// it instead of an abstract base class: calls through the alias are direct and can be inlined. +#if defined(USE_ESP32) +#include "esphome/components/esp32/gpio.h" +namespace esphome { +using InternalGPIOPin = esp32::ESP32InternalGPIOPin; +} +#elif defined(USE_ESP8266) +#include "esphome/components/esp8266/gpio.h" +namespace esphome { +using InternalGPIOPin = esp8266::ESP8266GPIOPin; +} +#elif defined(USE_LIBRETINY) +#include "esphome/components/libretiny/gpio_arduino.h" +namespace esphome { +using InternalGPIOPin = libretiny::ArduinoInternalGPIOPin; +} +#elif defined(USE_RP2) +#include "esphome/components/rp2/gpio.h" +namespace esphome { +using InternalGPIOPin = rp2::RP2GPIOPin; +} +#elif defined(USE_HOST) +#include "esphome/components/host/gpio.h" +namespace esphome { +using InternalGPIOPin = host::HostGPIOPin; +} +#elif defined(USE_ZEPHYR) +#include "esphome/components/zephyr/gpio.h" +namespace esphome { +using InternalGPIOPin = zephyr::ZephyrGPIOPin; +} +#else +#error "gpio.h: no internal pin class for this platform" +#endif namespace esphome { - -/// Maximum buffer size for dump_summary output -inline constexpr size_t GPIO_SUMMARY_MAX_LEN = 48; - -#ifdef USE_ESP8266 -#define LOG_PIN(prefix, pin) log_pin(TAG, F(prefix), pin) -#else -#define LOG_PIN(prefix, pin) log_pin(TAG, prefix, pin) -#endif - -// put GPIO flags in a namespace to not pollute esphome namespace -namespace gpio { - -enum Flags : uint8_t { - // Can't name these just INPUT because of Arduino defines :( - FLAG_NONE = 0x00, - FLAG_INPUT = 0x01, - FLAG_OUTPUT = 0x02, - FLAG_OPEN_DRAIN = 0x04, - FLAG_PULLUP = 0x08, - FLAG_PULLDOWN = 0x10, - FLAG_HOLD = 0x20, -}; - -class FlagsHelper { - public: - constexpr FlagsHelper(Flags val) : val_(val) {} - constexpr operator Flags() const { return val_; } - - protected: - Flags val_; -}; -constexpr FlagsHelper operator&(Flags lhs, Flags rhs) { - return static_cast(static_cast(lhs) & static_cast(rhs)); -} -constexpr FlagsHelper operator|(Flags lhs, Flags rhs) { - return static_cast(static_cast(lhs) | static_cast(rhs)); -} - -enum InterruptType : uint8_t { - INTERRUPT_RISING_EDGE = 1, - INTERRUPT_FALLING_EDGE = 2, - INTERRUPT_ANY_EDGE = 3, - INTERRUPT_LOW_LEVEL = 4, - INTERRUPT_HIGH_LEVEL = 5, -}; - -} // namespace gpio - -class GPIOPin { - public: - virtual void setup() = 0; - - virtual void pin_mode(gpio::Flags flags) = 0; - - /** - * @brief Retrieve GPIO pin flags. - * - * @return The GPIO flags describing the pin mode and properties. - */ - virtual gpio::Flags get_flags() const = 0; - - virtual bool digital_read() = 0; - - virtual void digital_write(bool value) = 0; - - /// Write a summary of this pin to the provided buffer. - /// @param buffer The buffer to write to - /// @param len The size of the buffer (must be > 0) - /// @return The number of characters that would be written (excluding null terminator), - /// which may exceed len-1 if truncation occurred (snprintf semantics) - virtual size_t dump_summary(char *buffer, size_t len) const; - - virtual bool is_internal() { return false; } -#ifdef USE_GPIO_HOLD - bool get_hold() const { return this->get_flags() & gpio::FLAG_HOLD; } - virtual bool is_held() const { return false; } -#else - bool get_hold() const { return false; } - bool is_held() const { return false; } -#endif -}; - -/// Copy of GPIOPin that is safe to use from ISRs (with no virtual functions) -class ISRInternalGPIOPin { - public: - ISRInternalGPIOPin() = default; - ISRInternalGPIOPin(void *arg) : arg_(arg) {} - bool digital_read(); - void digital_write(bool value); - void clear_interrupt(); - void pin_mode(gpio::Flags flags); - - protected: - void *arg_{nullptr}; -}; - -class InternalGPIOPin : public GPIOPin { - public: - template void attach_interrupt(void (*func)(T *), T *arg, gpio::InterruptType type) const { - this->attach_interrupt(reinterpret_cast(func), arg, type); - } - - virtual void detach_interrupt() const = 0; - - virtual ISRInternalGPIOPin to_isr() const = 0; - - virtual uint8_t get_pin() const = 0; - - bool is_internal() override { return true; } - - virtual bool is_inverted() const = 0; - - protected: - virtual void attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const = 0; -}; - -// Inline default implementation for GPIOPin::dump_summary. -// Writes an empty summary; subclasses override to provide pin details. -inline size_t GPIOPin::dump_summary(char *buffer, size_t len) const { - if (len > 0) - buffer[0] = '\0'; - return 0; -} - -// Inline helper for log_pin - allows compiler to inline into log_pin in gpio.cpp -inline void log_pin_with_prefix(const char *tag, const char *prefix, GPIOPin *pin) { - char buffer[GPIO_SUMMARY_MAX_LEN]; - size_t len = pin->dump_summary(buffer, sizeof(buffer)); - len = std::min(len, sizeof(buffer) - 1); - esp_log_printf_(ESPHOME_LOG_LEVEL_CONFIG, tag, __LINE__, "%s%.*s", prefix, (int) len, buffer); -} - -// log_pin function declarations - implementation in gpio.cpp -#ifdef USE_ESP8266 -void log_pin(const char *tag, const __FlashStringHelper *prefix, GPIOPin *pin); -#else -void log_pin(const char *tag, const char *prefix, GPIOPin *pin); -#endif - +static_assert(InternalGPIOPinContract, + "The platform's internal pin class is missing part of the InternalGPIOPin surface " + "(esphome/core/gpio_pin.h)"); } // namespace esphome diff --git a/esphome/core/gpio_pin.h b/esphome/core/gpio_pin.h new file mode 100644 index 0000000000..6c9acfa8d6 --- /dev/null +++ b/esphome/core/gpio_pin.h @@ -0,0 +1,147 @@ +#pragma once +#include +#include +#include +#include + +#include "esphome/core/defines.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" + +namespace esphome { + +/// Maximum buffer size for dump_summary output +inline constexpr size_t GPIO_SUMMARY_MAX_LEN = 48; + +#ifdef USE_ESP8266 +#define LOG_PIN(prefix, pin) log_pin(TAG, F(prefix), pin) +#else +#define LOG_PIN(prefix, pin) log_pin(TAG, prefix, pin) +#endif + +// put GPIO flags in a namespace to not pollute esphome namespace +namespace gpio { + +enum Flags : uint8_t { + // Can't name these just INPUT because of Arduino defines :( + FLAG_NONE = 0x00, + FLAG_INPUT = 0x01, + FLAG_OUTPUT = 0x02, + FLAG_OPEN_DRAIN = 0x04, + FLAG_PULLUP = 0x08, + FLAG_PULLDOWN = 0x10, + FLAG_HOLD = 0x20, +}; + +class FlagsHelper { + public: + constexpr FlagsHelper(Flags val) : val_(val) {} + constexpr operator Flags() const { return val_; } + + protected: + Flags val_; +}; +constexpr FlagsHelper operator&(Flags lhs, Flags rhs) { + return static_cast(static_cast(lhs) & static_cast(rhs)); +} +constexpr FlagsHelper operator|(Flags lhs, Flags rhs) { + return static_cast(static_cast(lhs) | static_cast(rhs)); +} + +enum InterruptType : uint8_t { + INTERRUPT_RISING_EDGE = 1, + INTERRUPT_FALLING_EDGE = 2, + INTERRUPT_ANY_EDGE = 3, + INTERRUPT_LOW_LEVEL = 4, + INTERRUPT_HIGH_LEVEL = 5, +}; + +} // namespace gpio + +class GPIOPin { + public: + virtual void setup() = 0; + + virtual void pin_mode(gpio::Flags flags) = 0; + + /** + * @brief Retrieve GPIO pin flags. + * + * @return The GPIO flags describing the pin mode and properties. + */ + virtual gpio::Flags get_flags() const = 0; + + virtual bool digital_read() = 0; + + virtual void digital_write(bool value) = 0; + + /// Write a summary of this pin to the provided buffer. + /// @param buffer The buffer to write to + /// @param len The size of the buffer (must be > 0) + /// @return The number of characters that would be written (excluding null terminator), + /// which may exceed len-1 if truncation occurred (snprintf semantics) + virtual size_t dump_summary(char *buffer, size_t len) const; + + /// Only the platform pin class behind the InternalGPIOPin alias may return true: + /// callers static_cast to InternalGPIOPin on it. + virtual bool is_internal() { return false; } +#ifdef USE_GPIO_HOLD + bool get_hold() const { return this->get_flags() & gpio::FLAG_HOLD; } + virtual bool is_held() const { return false; } +#else + bool get_hold() const { return false; } + bool is_held() const { return false; } +#endif +}; + +/// Copy of GPIOPin that is safe to use from ISRs (with no virtual functions) +class ISRInternalGPIOPin { + public: + ISRInternalGPIOPin() = default; + ISRInternalGPIOPin(void *arg) : arg_(arg) {} + bool digital_read(); + void digital_write(bool value); + void clear_interrupt(); + void pin_mode(gpio::Flags flags); + + protected: + void *arg_{nullptr}; +}; + +// What callers of the InternalGPIOPin alias (bound in esphome/core/gpio.h) rely on beyond +// GPIOPin. The class must be final so calls to the GPIOPin virtuals through the alias are direct. +// int is arbitrary: any T * exercises the public attach_interrupt template. +template +concept InternalGPIOPinContract = std::derived_from && std::is_final_v && + requires(const T pin, void (*func)(int *), int *arg, gpio::InterruptType type) { + { pin.get_pin() } -> std::same_as; + { pin.is_inverted() } -> std::same_as; + { pin.to_isr() } -> std::same_as; + { pin.detach_interrupt() } -> std::same_as; + { pin.attach_interrupt(func, arg, type) } -> std::same_as; +}; + +// Inline default implementation for GPIOPin::dump_summary. +// Writes an empty summary; subclasses override to provide pin details. +inline size_t GPIOPin::dump_summary(char *buffer, size_t len) const { + if (len > 0) + buffer[0] = '\0'; + return 0; +} + +// Inline helper for log_pin - allows compiler to inline into log_pin in gpio.cpp +inline void log_pin_with_prefix(const char *tag, const char *prefix, GPIOPin *pin) { + char buffer[GPIO_SUMMARY_MAX_LEN]; + size_t len = pin->dump_summary(buffer, sizeof(buffer)); + len = std::min(len, sizeof(buffer) - 1); + esp_log_printf_(ESPHOME_LOG_LEVEL_CONFIG, tag, __LINE__, "%s%.*s", prefix, (int) len, buffer); +} + +// log_pin function declarations - implementation in gpio.cpp +#ifdef USE_ESP8266 +void log_pin(const char *tag, const __FlashStringHelper *prefix, GPIOPin *pin); +#else +void log_pin(const char *tag, const char *prefix, GPIOPin *pin); +#endif + +} // namespace esphome diff --git a/tests/components/deep_sleep/test-timer-only.esp32-idf.yaml b/tests/components/deep_sleep/test-timer-only.esp32-idf.yaml new file mode 100644 index 0000000000..ad93ea225f --- /dev/null +++ b/tests/components/deep_sleep/test-timer-only.esp32-idf.yaml @@ -0,0 +1,4 @@ +# No pin anywhere in the config: the pin class methods deep_sleep calls must still link +deep_sleep: + run_duration: 10s + sleep_duration: 50s diff --git a/tests/components/power_supply/test_setup_priority.cpp b/tests/components/power_supply/test_setup_priority.cpp index 401fc72654..736fdf99b0 100644 --- a/tests/components/power_supply/test_setup_priority.cpp +++ b/tests/components/power_supply/test_setup_priority.cpp @@ -1,3 +1,4 @@ +#ifdef USE_HOST #include #include "esphome/components/power_supply/power_supply.h" @@ -6,28 +7,9 @@ namespace esphome::power_supply::testing { -// Minimal dummy internal GPIO pin implementation for testing -class DummyInternalPin : public InternalGPIOPin { - public: - DummyInternalPin() = default; - void setup() override {} - void pin_mode(esphome::gpio::Flags) override {} - esphome::gpio::Flags get_flags() const override { return esphome::gpio::FLAG_NONE; } - bool digital_read() override { return false; } - void digital_write(bool) override {} - void detach_interrupt() const override {} - ISRInternalGPIOPin to_isr() const override { return ISRInternalGPIOPin(); } - uint8_t get_pin() const override { return 0; } - bool is_inverted() const override { return false; } - - protected: - // Implement protected attach_interrupt required by InternalGPIOPin - void attach_interrupt(void (*func)(void *), void *arg, esphome::gpio::InterruptType type) const override {} -}; - TEST(PowerSupply, HasHigherPriorityThanBusWhenInternalAndEnableOnBoot) { power_supply::PowerSupply ps; - DummyInternalPin pin; + InternalGPIOPin pin; ps.set_pin(&pin); ps.set_enable_on_boot(true); @@ -37,7 +19,7 @@ TEST(PowerSupply, HasHigherPriorityThanBusWhenInternalAndEnableOnBoot) { TEST(PowerSupply, FallsBackToIOWhenNotEnableOnBoot) { power_supply::PowerSupply ps; - DummyInternalPin pin; + InternalGPIOPin pin; ps.set_pin(&pin); ps.set_enable_on_boot(false); @@ -45,3 +27,4 @@ TEST(PowerSupply, FallsBackToIOWhenNotEnableOnBoot) { } } // namespace esphome::power_supply::testing +#endif // USE_HOST