[core] Bind InternalGPIOPin to the platform pin class instead of a virtual base (#20455)

This commit is contained in:
J. Nick Koston
2026-10-09 15:37:10 -10:00
committed by GitHub
parent 5d2433f69a
commit 79fd5edc7f
19 changed files with 278 additions and 230 deletions
-8
View File
@@ -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"}
)
+6 -5
View File
@@ -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
+12 -7
View File
@@ -1,7 +1,8 @@
#pragma once
#ifdef USE_ESP32
#include "esphome/core/hal.h"
#include "esphome/core/gpio_pin.h"
#include <esp_system.h>
#include <driver/gpio.h>
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<uint8_t>(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<typename T> void attach_interrupt(void (*func)(T *), T *arg, gpio::InterruptType type) const {
this->attach_interrupt_(reinterpret_cast<void (*)(void *)>(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<gpio_num_t>(this->pin_); }
gpio_drive_cap_t get_drive_strength() const { return static_cast<gpio_drive_cap_t>(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
-1
View File
@@ -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}")))
+2 -1
View File
@@ -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:
+11 -7
View File
@@ -2,12 +2,12 @@
#ifdef USE_ESP8266
#include "esphome/core/hal.h"
#include "esphome/core/gpio_pin.h"
#include <Arduino.h>
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<typename T> void attach_interrupt(void (*func)(T *), T *arg, gpio::InterruptType type) const {
this->attach_interrupt_(reinterpret_cast<void (*)(void *)>(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_{};
+2 -1
View File
@@ -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); }
+11 -7
View File
@@ -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<typename T> void attach_interrupt(void (*func)(T *), T *arg, gpio::InterruptType type) const {
this->attach_interrupt_(reinterpret_cast<void (*)(void *)>(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_{};
@@ -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:
+11 -7
View File
@@ -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<typename T> void attach_interrupt(void (*func)(T *), T *arg, gpio::InterruptType type) const {
this->attach_interrupt_(reinterpret_cast<void (*)(void *)>(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_{};
+2 -1
View File
@@ -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:
+11 -7
View File
@@ -3,11 +3,11 @@
#ifdef USE_RP2
#include <Arduino.h>
#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<typename T> void attach_interrupt(void (*func)(T *), T *arg, gpio::InterruptType type) const {
this->attach_interrupt_(reinterpret_cast<void (*)(void *)>(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_{};
+2 -1
View File
@@ -1,5 +1,6 @@
#ifdef USE_ZEPHYR
#include "gpio.h"
#include "esphome/core/hal.h"
#include <zephyr/drivers/gpio.h>
#include <zephyr/sys/util.h>
#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;
+12 -9
View File
@@ -1,7 +1,7 @@
#pragma once
#ifdef USE_ZEPHYR
#include "esphome/core/hal.h"
#include "esphome/core/gpio_pin.h"
#include <zephyr/device.h>
#include <zephyr/drivers/gpio.h>
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<typename T> void attach_interrupt(void (*func)(T *), T *arg, gpio::InterruptType type) const {
this->attach_interrupt_(reinterpret_cast<void (*)(void *)>(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_{};
};
-1
View File
@@ -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
+39 -145
View File
@@ -1,150 +1,44 @@
#pragma once
#include <algorithm>
#include <cstdint>
#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<Flags>(static_cast<uint8_t>(lhs) & static_cast<uint8_t>(rhs));
}
constexpr FlagsHelper operator|(Flags lhs, Flags rhs) {
return static_cast<Flags>(static_cast<uint8_t>(lhs) | static_cast<uint8_t>(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<typename T> void attach_interrupt(void (*func)(T *), T *arg, gpio::InterruptType type) const {
this->attach_interrupt(reinterpret_cast<void (*)(void *)>(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<InternalGPIOPin>,
"The platform's internal pin class is missing part of the InternalGPIOPin surface "
"(esphome/core/gpio_pin.h)");
} // namespace esphome
+147
View File
@@ -0,0 +1,147 @@
#pragma once
#include <algorithm>
#include <concepts>
#include <cstdint>
#include <type_traits>
#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<Flags>(static_cast<uint8_t>(lhs) & static_cast<uint8_t>(rhs));
}
constexpr FlagsHelper operator|(Flags lhs, Flags rhs) {
return static_cast<Flags>(static_cast<uint8_t>(lhs) | static_cast<uint8_t>(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<T> template.
template<typename T>
concept InternalGPIOPinContract = std::derived_from<T, GPIOPin> && std::is_final_v<T> &&
requires(const T pin, void (*func)(int *), int *arg, gpio::InterruptType type) {
{ pin.get_pin() } -> std::same_as<uint8_t>;
{ pin.is_inverted() } -> std::same_as<bool>;
{ pin.to_isr() } -> std::same_as<ISRInternalGPIOPin>;
{ pin.detach_interrupt() } -> std::same_as<void>;
{ pin.attach_interrupt(func, arg, type) } -> std::same_as<void>;
};
// 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
@@ -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
@@ -1,3 +1,4 @@
#ifdef USE_HOST
#include <gtest/gtest.h>
#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