Compare commits

..
Author SHA1 Message Date
J. Nick Koston f0bf62622c Merge branch 'gpio-expander-component-base' into gpio-expander-component-base-migrate 2026-10-23 10:56:35 -10:00
J. Nick Koston 73fd6bade5 Merge branch 'dev' into gpio-expander-component-base 2026-10-23 10:56:01 -10:00
J. Nick Koston 9afeb8f94c [pca9505] Release INT in the test fixture 2026-10-23 09:29:27 -10:00
J. Nick Koston d5bded9a60 Merge remote-tracking branch 'origin/gpio-expander-component-base' into gpio-expander-component-base-migrate 2026-10-23 08:49:31 -10:00
J. Nick Koston 5545257549 [gpio_expander] Run the loop when INT is already low at attach 2026-10-23 08:49:15 -10:00
J. Nick Koston f11c9704bb Merge branch 'gpio-expander-component-base' into gpio-expander-component-base-migrate 2026-10-22 23:56:54 -10:00
J. Nick Koston 7754da334a [gpio_expander] Say what happens when INT is already low at attach 2026-10-22 23:56:26 -10:00
J. Nick Koston 60a2819ae4 [m5stack_4relay] Name the I2C bus in the test so it groups with multiplexers 2026-10-22 22:54:51 -10:00
J. Nick Koston e0bd0e2937 [seeed_multi_channel_relay] Name the I2C bus in the test so it groups with multiplexers 2026-10-22 22:45:42 -10:00
J. Nick Koston eafa59df51 [gpio_expander] Use the shared expander schema and set the interrupt pin inline 2026-10-22 22:26:14 -10:00
J. Nick Koston 84855c9828 Merge branch 'gpio-expander-component-base' into gpio-expander-component-base-migrate 2026-10-22 22:25:41 -10:00
J. Nick Koston d16685239f [gpio_expander] Fold the interrupt pin into one component schema and drop the one-line helpers 2026-10-22 22:25:37 -10:00
J. Nick Koston 4f7d47d87f [i2c] Put the device address after the bus pointer so derived classes reuse its padding 2026-10-22 22:16:36 -10:00
J. Nick Koston b1089b6a2b [gpio_expander] Tidy the migrated pin_mode input checks and comments 2026-10-22 22:16:36 -10:00
J. Nick Koston 142ccb6825 Merge branch 'gpio-expander-component-base' into gpio-expander-component-base-migrate 2026-10-22 22:16:29 -10:00
J. Nick Koston 757d9f29d5 [gpio_expander] Check for the interrupt pin inline in loop() 2026-10-22 22:16:19 -10:00
J. Nick Koston 5830af80d3 [gpio_expander] Move the interrupt pin and loop handling of eight expanders onto the shared base 2026-10-22 22:11:44 -10:00
J. Nick Koston 0686e4324b [gpio_expander] Add a shared component base for cached expanders with an interrupt pin 2026-10-22 22:05:10 -10:00
111 changed files with 331 additions and 2599 deletions

No files matched your search

-1
View File
@@ -436,7 +436,6 @@ esphome/components/modbus_controller/sensor/* @martgras
esphome/components/modbus_controller/switch/* @martgras
esphome/components/modbus_controller/text_sensor/* @martgras
esphome/components/modbus_server/* @exciton
esphome/components/modem/* @oarcher
esphome/components/mopeka_ble/* @Fabian-Schmidt @spbrogan
esphome/components/mopeka_pro_check/* @spbrogan
esphome/components/mopeka_std_check/* @Fabian-Schmidt
@@ -1,5 +1,5 @@
#include "esphome/core/defines.h"
#if defined(USE_ESP32) && defined(USE_FAN)
#ifdef USE_ESP32
#include "ecocomfort2_fan.h"
#include "esphome/core/log.h"
@@ -138,4 +138,4 @@ void Ecocomfort2Fan::on_status() {
} // namespace esphome::ecocomfort2
#endif // USE_ESP32 && USE_FAN
#endif // USE_ESP32
@@ -1,7 +1,7 @@
#pragma once
#include "esphome/core/defines.h"
#if defined(USE_ESP32) && defined(USE_FAN)
#ifdef USE_ESP32
#include "esphome/core/component.h"
#include "esphome/components/fan/fan.h"
@@ -23,4 +23,4 @@ class Ecocomfort2Fan : public fan::Fan, public Ecocomfort2Client, public Compone
} // namespace esphome::ecocomfort2
#endif // USE_ESP32 && USE_FAN
#endif // USE_ESP32
-46
View File
@@ -48,16 +48,12 @@ void EzoPMP::update() {
}
if (this->is_first_read_) {
#ifdef USE_TEXT_SENSOR
this->queue_command_(EZO_PMP_COMMAND_READ_CALIBRATION_STATUS, 0, 0, (bool) this->calibration_status_);
#endif
#ifdef USE_SENSOR
this->queue_command_(EZO_PMP_COMMAND_READ_MAX_FLOW_RATE, 0, 0, (bool) this->max_flow_rate_);
this->queue_command_(EZO_PMP_COMMAND_READ_SINGLE_REPORT, 0, 0, (bool) this->current_volume_dosed_);
this->queue_command_(EZO_PMP_COMMAND_READ_TOTAL_VOLUME_DOSED, 0, 0, (bool) this->total_volume_dosed_);
this->queue_command_(EZO_PMP_COMMAND_READ_ABSOLUTE_TOTAL_VOLUME_DOSED, 0, 0,
(bool) this->absolute_total_volume_dosed_);
#endif
this->queue_command_(EZO_PMP_COMMAND_READ_PAUSE_STATUS, 0, 0, true);
this->is_first_read_ = false;
}
@@ -65,7 +61,6 @@ void EzoPMP::update() {
if (!this->is_waiting_ && this->peek_next_command_() == EZO_PMP_COMMAND_NONE) {
this->queue_command_(EZO_PMP_COMMAND_READ_DOSING, 0, 0, true);
#ifdef USE_SENSOR
if (this->is_dosing_flag_) {
this->queue_command_(EZO_PMP_COMMAND_READ_SINGLE_REPORT, 0, 0, (bool) this->current_volume_dosed_);
this->queue_command_(EZO_PMP_COMMAND_READ_TOTAL_VOLUME_DOSED, 0, 0, (bool) this->total_volume_dosed_);
@@ -74,7 +69,6 @@ void EzoPMP::update() {
}
this->queue_command_(EZO_PMP_COMMAND_READ_PUMP_VOLTAGE, 0, 0, (bool) this->pump_voltage_);
#endif
} else {
ESP_LOGV(TAG, "Not Scheduling new Command during update()");
}
@@ -198,68 +192,51 @@ void EzoPMP::read_command_result_() {
if (parsed_third_parameter.has_value())
this->is_dosing_flag_ = parsed_third_parameter.value_or(0) == 1;
#ifdef USE_BINARY_SENSOR
if (this->is_dosing_)
this->is_dosing_->publish_state(this->is_dosing_flag_);
#endif
#ifdef USE_SENSOR
if (parsed_second_parameter.has_value() && this->last_volume_requested_) {
this->last_volume_requested_->publish_state(parsed_second_parameter.value_or(0));
}
#endif
#ifdef USE_TEXT_SENSOR
if (!this->is_dosing_flag_ && !this->is_paused_flag_) {
// If pump is not paused and not dispensing
if (this->dosing_mode_ && this->dosing_mode_->state != DOSING_MODE_NONE)
this->dosing_mode_->publish_state(DOSING_MODE_NONE);
}
#endif
break;
case EZO_PMP_COMMAND_READ_SINGLE_REPORT: // Single Report (page 53)
#ifdef USE_SENSOR
if (parsed_first_parameter.has_value() && (bool) this->current_volume_dosed_) {
this->current_volume_dosed_->publish_state(parsed_first_parameter.value_or(0));
}
#endif
break;
case EZO_PMP_COMMAND_READ_MAX_FLOW_RATE: // Constant Flow Rate (page 57)
#ifdef USE_SENSOR
if (parsed_second_parameter.has_value() && this->max_flow_rate_)
this->max_flow_rate_->publish_state(parsed_second_parameter.value_or(0));
#endif
break;
case EZO_PMP_COMMAND_READ_PAUSE_STATUS: // Pause (page 61)
if (parsed_second_parameter.has_value())
this->is_paused_flag_ = parsed_second_parameter.value_or(0) == 1;
#ifdef USE_BINARY_SENSOR
if (this->is_paused_)
this->is_paused_->publish_state(this->is_paused_flag_);
#endif
break;
case EZO_PMP_COMMAND_READ_TOTAL_VOLUME_DOSED: // Total Volume Dispensed (page 64)
#ifdef USE_SENSOR
if (parsed_second_parameter.has_value() && this->total_volume_dosed_)
this->total_volume_dosed_->publish_state(parsed_second_parameter.value_or(0));
#endif
break;
case EZO_PMP_COMMAND_READ_ABSOLUTE_TOTAL_VOLUME_DOSED: // Total Volume Dispensed (page 64)
#ifdef USE_SENSOR
if (parsed_second_parameter.has_value() && this->absolute_total_volume_dosed_)
this->absolute_total_volume_dosed_->publish_state(parsed_second_parameter.value_or(0));
#endif
break;
case EZO_PMP_COMMAND_READ_CALIBRATION_STATUS: // Calibration (page 65)
#ifdef USE_TEXT_SENSOR
if (parsed_second_parameter.has_value() && this->calibration_status_) {
if (parsed_second_parameter.value_or(0) == 1) {
this->calibration_status_->publish_state("Fixed Volume");
@@ -271,56 +248,41 @@ void EzoPMP::read_command_result_() {
this->calibration_status_->publish_state("Uncalibrated");
}
}
#endif
break;
case EZO_PMP_COMMAND_READ_PUMP_VOLTAGE: // Pump Voltage (page 67)
#ifdef USE_SENSOR
if (parsed_second_parameter.has_value() && this->pump_voltage_)
this->pump_voltage_->publish_state(parsed_second_parameter.value_or(0));
#endif
break;
// Non-Read Commands
case EZO_PMP_COMMAND_DOSE_VOLUME: // Volume Dispensing (page 55)
#ifdef USE_TEXT_SENSOR
if (this->dosing_mode_ && this->dosing_mode_->state != DOSING_MODE_VOLUME)
this->dosing_mode_->publish_state(DOSING_MODE_VOLUME);
#endif
break;
case EZO_PMP_COMMAND_DOSE_VOLUME_OVER_TIME: // Dose over time (page 56)
#ifdef USE_TEXT_SENSOR
if (this->dosing_mode_ && this->dosing_mode_->state != DOSING_MODE_VOLUME_OVER_TIME)
this->dosing_mode_->publish_state(DOSING_MODE_VOLUME_OVER_TIME);
#endif
break;
case EZO_PMP_COMMAND_DOSE_WITH_CONSTANT_FLOW_RATE: // Constant Flow Rate (page 57)
#ifdef USE_TEXT_SENSOR
if (this->dosing_mode_ && this->dosing_mode_->state != DOSING_MODE_CONSTANT_FLOW_RATE)
this->dosing_mode_->publish_state(DOSING_MODE_CONSTANT_FLOW_RATE);
#endif
break;
case EZO_PMP_COMMAND_DOSE_CONTINUOUSLY: // Continuous Dispensing (page 54)
#ifdef USE_TEXT_SENSOR
if (this->dosing_mode_ && this->dosing_mode_->state != DOSING_MODE_CONTINUOUS)
this->dosing_mode_->publish_state(DOSING_MODE_CONTINUOUS);
#endif
break;
case EZO_PMP_COMMAND_STOP_DOSING: // Stop (page 62)
this->is_paused_flag_ = false;
#ifdef USE_BINARY_SENSOR
if (this->is_paused_)
this->is_paused_->publish_state(this->is_paused_flag_);
#endif
#ifdef USE_TEXT_SENSOR
if (this->dosing_mode_ && this->dosing_mode_->state != DOSING_MODE_NONE)
this->dosing_mode_->publish_state(DOSING_MODE_NONE);
#endif
break;
case EZO_PMP_COMMAND_EXEC_ARBITRARY_COMMAND_ADDRESS:
@@ -527,33 +489,25 @@ void EzoPMP::find() { this->queue_command_(EZO_PMP_COMMAND_FIND, 0, 0, true); }
void EzoPMP::dose_continuously() {
this->queue_command_(EZO_PMP_COMMAND_DOSE_CONTINUOUSLY, 0, 0, true);
this->queue_command_(EZO_PMP_COMMAND_READ_DOSING, 0, 0, true);
#ifdef USE_SENSOR
this->queue_command_(EZO_PMP_COMMAND_READ_SINGLE_REPORT, 0, 0, (bool) this->current_volume_dosed_);
#endif
}
void EzoPMP::dose_volume(double volume) {
this->queue_command_(EZO_PMP_COMMAND_DOSE_VOLUME, volume, 0, true);
this->queue_command_(EZO_PMP_COMMAND_READ_DOSING, 0, 0, true);
#ifdef USE_SENSOR
this->queue_command_(EZO_PMP_COMMAND_READ_SINGLE_REPORT, 0, 0, (bool) this->current_volume_dosed_);
#endif
}
void EzoPMP::dose_volume_over_time(double volume, int duration) {
this->queue_command_(EZO_PMP_COMMAND_DOSE_VOLUME_OVER_TIME, volume, duration, true);
this->queue_command_(EZO_PMP_COMMAND_READ_DOSING, 0, 0, true);
#ifdef USE_SENSOR
this->queue_command_(EZO_PMP_COMMAND_READ_SINGLE_REPORT, 0, 0, (bool) this->current_volume_dosed_);
#endif
}
void EzoPMP::dose_with_constant_flow_rate(double volume, int duration) {
this->queue_command_(EZO_PMP_COMMAND_DOSE_WITH_CONSTANT_FLOW_RATE, volume, duration, true);
this->queue_command_(EZO_PMP_COMMAND_READ_DOSING, 0, 0, true);
#ifdef USE_SENSOR
this->queue_command_(EZO_PMP_COMMAND_READ_SINGLE_REPORT, 0, 0, (bool) this->current_volume_dosed_);
#endif
}
void EzoPMP::set_calibration_volume(double volume) {
@@ -22,3 +22,9 @@ def validate_interrupt_pin(value: ConfigType) -> ConfigType:
f"Remove the '{CONF_INTERRUPT_PIN}' to fall back to polling."
)
return value
# Schema of a CachedGpioExpanderComponent; to_code sets the pin with cg.add_pin().
INTERRUPT_EXPANDER_SCHEMA = cv.COMPONENT_SCHEMA.extend(
{cv.Optional(CONF_INTERRUPT_PIN): validate_interrupt_pin}
)
@@ -0,0 +1,29 @@
#include "cached_gpio_component.h"
namespace esphome::gpio_expander {
// Not a member of the template: GCC 10 (ESP8266) ignores section attributes on template instantiations.
static void IRAM_ATTR interrupt_pin_isr(Component *arg) { arg->enable_loop_soon_any_context(); }
bool InterruptExpanderComponent::attach_interrupt_pin_() {
// Polling mode enables the loop in pin_mode() for the first input; interrupt mode from the ISR.
this->disable_loop();
if (this->interrupt_pin_ == nullptr)
return false;
this->interrupt_pin_->setup();
this->interrupt_pin_->attach_interrupt(&interrupt_pin_isr, static_cast<Component *>(this),
gpio::INTERRUPT_FALLING_EDGE);
// An INT line already low at attach gives no falling edge, so read the inputs now
if (!this->interrupt_pin_->digital_read())
this->enable_loop();
return true;
}
// Input changes that straddle the read leave INT asserted without a new falling edge, so keep looping
// until the line is released or the expander would keep stale state forever.
void InterruptExpanderComponent::disable_loop_when_released_() {
if (this->interrupt_pin_->digital_read())
this->disable_loop();
}
} // namespace esphome::gpio_expander
@@ -0,0 +1,50 @@
#pragma once
#include "esphome/components/gpio_expander/cached_gpio.h"
#include "esphome/core/component.h"
#include "esphome/core/hal.h"
namespace esphome::gpio_expander {
/// Component side of a cached GPIO expander with an optional active-low INT line. Not a template, so the
/// ISR and the shared code are emitted once for every expander.
class InterruptExpanderComponent : public Component {
public:
void set_interrupt_pin(InternalGPIOPin *pin) { this->interrupt_pin_ = pin; }
float get_setup_priority() const override { return setup_priority::IO; }
protected:
/// Attach the falling-edge ISR when there is an interrupt pin, then disable the loop until an input
/// needs it. Returns whether the interrupt pin is in use.
bool attach_interrupt_pin_();
/// The loop runs again after the INT line falls; stop it once the line is released (HIGH).
/// Only called with an interrupt pin.
void disable_loop_when_released_();
/// Call from pin_mode() for an input pin: without an interrupt pin the loop polls instead.
void enable_polling_loop_() {
if (this->interrupt_pin_ == nullptr)
this->enable_loop();
}
InternalGPIOPin *interrupt_pin_{nullptr};
};
/// Base for expanders that cache reads per loop. With an interrupt pin the cache stays valid until INT fires.
template<typename T, uint16_t N>
class CachedGpioExpanderComponent : public InterruptExpanderComponent, public CachedGpioExpander<T, N> {
public:
void loop() override {
this->reset_pin_cache_();
if (this->interrupt_pin_ != nullptr)
this->disable_loop_when_released_();
}
protected:
/// Call at the end of setup().
void setup_interrupt_pin_() {
if (this->attach_interrupt_pin_())
this->set_invalidate_on_read_(false);
}
};
} // namespace esphome::gpio_expander
+2 -1
View File
@@ -322,8 +322,9 @@ class I2CDevice {
bool update_register(uint8_t a_register, uint8_t mask, uint8_t value);
protected:
I2CBus *bus_{nullptr}; ///< pointer to I2CBus instance
// Last so a derived class can place its byte-sized members in the padding after it
uint8_t address_{0x00}; ///< store the address of the device on the bus
I2CBus *bus_{nullptr}; ///< pointer to I2CBus instance
};
} // namespace esphome::i2c
+8 -12
View File
@@ -1,7 +1,6 @@
from esphome import automation, pins
import esphome.codegen as cg
from esphome.components import libretiny, output
from esphome.components.libretiny.const import FAMILY_LN882H
from esphome.components import output
import esphome.config_validation as cv
from esphome.const import CONF_FREQUENCY, CONF_ID, CONF_PIN
from esphome.types import ConfigType
@@ -13,16 +12,13 @@ LibreTinyPWM = libretinypwm_ns.class_("LibreTinyPWM", output.FloatOutput, cg.Com
validate_frequency = cv.All(cv.frequency, cv.float_range(min=0, min_included=False))
CONFIG_SCHEMA = cv.All(
libretiny.only_on_family(unsupported=FAMILY_LN882H),
output.FLOAT_OUTPUT_SCHEMA.extend(
{
cv.Required(CONF_ID): cv.declare_id(LibreTinyPWM),
cv.Required(CONF_PIN): pins.internal_gpio_output_pin_schema,
cv.Optional(CONF_FREQUENCY, default="1kHz"): validate_frequency,
}
).extend(cv.COMPONENT_SCHEMA),
)
CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend(
{
cv.Required(CONF_ID): cv.declare_id(LibreTinyPWM),
cv.Required(CONF_PIN): pins.internal_gpio_output_pin_schema,
cv.Optional(CONF_FREQUENCY, default="1kHz"): validate_frequency,
}
).extend(cv.COMPONENT_SCHEMA)
async def to_code(config: ConfigType) -> None:
+1 -2
View File
@@ -20,10 +20,9 @@ CONFIG_SCHEMA = (
cv.Schema(
{
cv.Required(CONF_ID): cv.declare_id(MCP23016),
cv.Optional(CONF_INTERRUPT_PIN): gpio_expander.validate_interrupt_pin,
}
)
.extend(cv.COMPONENT_SCHEMA)
.extend(gpio_expander.INTERRUPT_EXPANDER_SCHEMA)
.extend(i2c.i2c_device_schema(0x20))
)
+2 -21
View File
@@ -25,25 +25,9 @@ void MCP23016::setup() {
// all pins input
this->write_reg_(MCP23016_IODIR1, 0xFFFF);
if (this->interrupt_pin_ != nullptr) {
this->interrupt_pin_->setup();
this->interrupt_pin_->attach_interrupt(&MCP23016::gpio_intr, this, gpio::INTERRUPT_FALLING_EDGE);
this->set_invalidate_on_read_(false);
}
this->disable_loop();
this->setup_interrupt_pin_();
}
void IRAM_ATTR MCP23016::gpio_intr(MCP23016 *arg) { arg->enable_loop_soon_any_context(); }
void MCP23016::loop() {
// Invalidate cache at the start of each loop
this->reset_pin_cache_();
// Only disable the loop once INT has actually gone HIGH. Input transitions that straddle the
// I2C read leave INT asserted without re-firing a falling edge, which would strand us with
// stale state forever; keep looping until the line is released so we self-heal.
if (this->interrupt_pin_ != nullptr && this->interrupt_pin_->digital_read()) {
this->disable_loop();
}
}
bool MCP23016::digital_read_hw(uint8_t pin) { return this->read_reg_(MCP23016_GP1, &this->input_mask_); }
bool MCP23016::digital_read_cache(uint8_t pin) { return this->input_mask_ & (1 << pin); }
@@ -51,14 +35,11 @@ void MCP23016::digital_write_hw(uint8_t pin, bool value) { this->update_reg_(pin
void MCP23016::pin_mode(uint8_t pin, gpio::Flags flags) {
if (flags == gpio::FLAG_INPUT) {
this->update_reg_(pin, true, MCP23016_IODIR1);
if (this->interrupt_pin_ == nullptr) {
this->enable_loop();
}
this->enable_polling_loop_();
} else if (flags == gpio::FLAG_OUTPUT) {
this->update_reg_(pin, false, MCP23016_IODIR1);
}
}
float MCP23016::get_setup_priority() const { return setup_priority::IO; }
bool MCP23016::read_reg_(uint8_t reg, uint16_t *value) {
if (this->is_failed())
return false;
+2 -9
View File
@@ -3,7 +3,7 @@
#include "esphome/core/component.h"
#include "esphome/core/hal.h"
#include "esphome/components/i2c/i2c.h"
#include "esphome/components/gpio_expander/cached_gpio.h"
#include "esphome/components/gpio_expander/cached_gpio_component.h"
#include "esphome/components/gpio_expander/expander_gpio_pin.h"
namespace esphome::mcp23016 {
@@ -25,20 +25,14 @@ enum MCP23016GPIORegisters {
MCP23016_IOCON1 = 0x0B,
};
class MCP23016 final : public Component, public i2c::I2CDevice, public gpio_expander::CachedGpioExpander<uint16_t, 16> {
class MCP23016 final : public gpio_expander::CachedGpioExpanderComponent<uint16_t, 16>, public i2c::I2CDevice {
public:
MCP23016() = default;
void setup() override;
void loop() override;
void pin_mode(uint8_t pin, gpio::Flags flags);
float get_setup_priority() const override;
void set_interrupt_pin(InternalGPIOPin *pin) { this->interrupt_pin_ = pin; }
protected:
static void gpio_intr(MCP23016 *arg);
// Virtual methods from CachedGpioExpander
bool digital_read_hw(uint8_t pin) override;
bool digital_read_cache(uint8_t pin) override;
@@ -54,7 +48,6 @@ class MCP23016 final : public Component, public i2c::I2CDevice, public gpio_expa
uint16_t olat_{0x0000};
// Cache for input values (16-bit combined for both banks)
uint16_t input_mask_{0x0000};
InternalGPIOPin *interrupt_pin_{nullptr};
};
using MCP23016GPIOPin = gpio_expander::ExpanderGPIOPin<MCP23016>;
@@ -32,15 +32,11 @@ void MCP23X08Base::pin_mode(uint8_t pin, gpio::Flags flags) {
} else if (flags == gpio::FLAG_OUTPUT) {
this->update_reg(pin, false, iodir);
}
// When interrupt_pin is configured, auto-enable CHANGE interrupt for input pins
// so the chip's INT output fires on any input state change
if (this->interrupt_pin_ != nullptr && (flags & gpio::FLAG_INPUT)) {
this->pin_interrupt_mode(pin, mcp23xxx_base::MCP23XXX_CHANGE);
}
// Enable polling loop for input pins (not needed for interrupt-driven mode
// where the ISR handles re-enabling loop)
if (this->interrupt_pin_ == nullptr && (flags & gpio::FLAG_INPUT)) {
this->enable_loop();
if (flags & gpio::FLAG_INPUT) {
// With an interrupt pin, INT fires on any change of an input
if (this->interrupt_pin_ != nullptr)
this->pin_interrupt_mode(pin, mcp23xxx_base::MCP23XXX_CHANGE);
this->enable_polling_loop_();
}
}
@@ -43,15 +43,11 @@ void MCP23X17Base::pin_mode(uint8_t pin, gpio::Flags flags) {
} else if (flags == gpio::FLAG_OUTPUT) {
this->update_reg(pin, false, iodir);
}
// When interrupt_pin is configured, auto-enable CHANGE interrupt for input pins
// so the chip's INT output fires on any input state change
if (this->interrupt_pin_ != nullptr && (flags & gpio::FLAG_INPUT)) {
this->pin_interrupt_mode(pin, mcp23xxx_base::MCP23XXX_CHANGE);
}
// Enable polling loop for input pins (not needed for interrupt-driven mode
// where the ISR handles re-enabling loop)
if (this->interrupt_pin_ == nullptr && (flags & gpio::FLAG_INPUT)) {
this->enable_loop();
if (flags & gpio::FLAG_INPUT) {
// With an interrupt pin, INT fires on any change of an input
if (this->interrupt_pin_ != nullptr)
this->pin_interrupt_mode(pin, mcp23xxx_base::MCP23XXX_CHANGE);
this->enable_polling_loop_();
}
}
+1 -2
View File
@@ -32,9 +32,8 @@ MCP23XXX_INTERRUPT_MODES = MCP23XXXInterruptMode.options(
MCP23XXX_CONFIG_SCHEMA = cv.Schema(
{
cv.Optional(CONF_OPEN_DRAIN_INTERRUPT, default=False): cv.boolean,
cv.Optional(CONF_INTERRUPT_PIN): gpio_expander.validate_interrupt_pin,
}
).extend(cv.COMPONENT_SCHEMA)
).extend(gpio_expander.INTERRUPT_EXPANDER_SCHEMA)
@coroutine
@@ -4,8 +4,6 @@
namespace esphome::mcp23xxx_base {
void IRAM_ATTR mcp23xxx_gpio_intr(Component *arg) { arg->enable_loop_soon_any_context(); }
template<uint8_t N> void MCP23XXXGPIOPin<N>::setup() {
this->pin_mode(flags_);
// When interrupt_pin is configured, pin_mode() already auto-enables CHANGE
@@ -1,6 +1,6 @@
#pragma once
#include "esphome/components/gpio_expander/cached_gpio.h"
#include "esphome/components/gpio_expander/cached_gpio_component.h"
#include "esphome/core/component.h"
#include "esphome/core/hal.h"
@@ -8,47 +8,15 @@ namespace esphome::mcp23xxx_base {
enum MCP23XXXInterruptMode : uint8_t { MCP23XXX_NO_INTERRUPT = 0, MCP23XXX_CHANGE, MCP23XXX_RISING, MCP23XXX_FALLING };
// Not a template member: GCC 10 (ESP8266) ignores section attributes on template instantiations, leaving the
// ISR in flash.
void mcp23xxx_gpio_intr(Component *arg);
template<uint8_t N> class MCP23XXXBase : public Component, public gpio_expander::CachedGpioExpander<uint8_t, N> {
template<uint8_t N> class MCP23XXXBase : public gpio_expander::CachedGpioExpanderComponent<uint8_t, N> {
public:
virtual void pin_mode(uint8_t pin, gpio::Flags flags);
virtual void pin_interrupt_mode(uint8_t pin, MCP23XXXInterruptMode interrupt_mode);
void set_open_drain_ints(const bool value) { this->open_drain_ints_ = value; }
void set_interrupt_pin(InternalGPIOPin *pin) { this->interrupt_pin_ = pin; }
InternalGPIOPin *get_interrupt_pin() const { return this->interrupt_pin_; }
float get_setup_priority() const override { return setup_priority::IO; }
void loop() override {
this->reset_pin_cache_();
// Only disable the loop once INT has actually gone HIGH. Input transitions that straddle the
// I2C read leave INT asserted without re-firing a falling edge, which would strand us with
// stale state forever; keep looping until the line is released so we self-heal.
if (this->interrupt_pin_ != nullptr && this->interrupt_pin_->digital_read()) {
this->disable_loop();
}
}
protected:
// No need to clear latched interrupts before attaching the ISR — if INT is
// already low the ISR fires immediately, loop runs, cache invalidates, and
// the GPIO read clears the latch. One harmless extra read at most.
void setup_interrupt_pin_() {
if (this->interrupt_pin_ != nullptr) {
this->interrupt_pin_->setup();
this->interrupt_pin_->attach_interrupt(&mcp23xxx_gpio_intr, static_cast<Component *>(this),
gpio::INTERRUPT_FALLING_EDGE);
this->set_invalidate_on_read_(false);
}
// Disable loop until an input pin is configured via pin_mode()
// For interrupt-driven mode, loop is re-enabled by the ISR
// For polling mode, loop is re-enabled when pin_mode() registers an input pin
this->disable_loop();
}
// read a given register
virtual bool read_reg(uint8_t reg, uint8_t *value) = 0;
// write a value to a given register
@@ -57,7 +25,6 @@ template<uint8_t N> class MCP23XXXBase : public Component, public gpio_expander:
virtual void update_reg(uint8_t pin, bool pin_value, uint8_t reg_a) = 0;
bool open_drain_ints_;
InternalGPIOPin *interrupt_pin_{nullptr};
};
template<uint8_t N> class MCP23XXXGPIOPin final : public GPIOPin {
-232
View File
@@ -1,232 +0,0 @@
import voluptuous as vol
from esphome import automation, pins
import esphome.codegen as cg
from esphome.components.const import CONF_RTS_PIN
from esphome.components.esp32 import add_idf_component, add_idf_sdkconfig_option
from esphome.components.network import add_use_address, require_ipv4
import esphome.config_validation as cv
from esphome.const import (
CONF_BAUD_RATE,
CONF_COMMAND,
CONF_ENABLE_ON_BOOT,
CONF_ID,
CONF_MODEL,
CONF_ON_CONNECT,
CONF_ON_DISCONNECT,
CONF_REBOOT_TIMEOUT,
CONF_RX_BUFFER_SIZE,
CONF_RX_PIN,
CONF_TX_BUFFER_SIZE,
CONF_TX_PIN,
CONF_USE_ADDRESS,
)
from esphome.core import CORE, ID, coroutine_with_priority
from esphome.coroutine import CoroPriority
from esphome.cpp_generator import MockObj
from esphome.types import ConfigType, TemplateArgsType
# Static so `api:` finds network before its dependency check; a config-dependent
# AUTO_LOAD resolves after that check and fails a cellular-only node
AUTO_LOAD = ["network"]
CODEOWNERS = ["@oarcher"]
CONFLICTS_WITH = ["captive_portal", "ethernet"]
DEPENDENCIES = ["esp32"]
DOMAIN = "modem"
CONF_PIN_CODE = "pin_code"
CONF_APN = "apn"
CONF_CTS_PIN = "cts_pin"
CONF_INIT_AT = "init_at"
CONF_ON_SYNC = "on_sync"
CONF_DTE_BUFFER_SIZE = "dte_buffer_size"
CONF_MODEM_ID = "modem_id"
esp_modem_ns = cg.global_ns.namespace("esp_modem")
# Model name to the esp_modem factory that drives it
MODEM_MODELS = {
"BG96": esp_modem_ns.create_BG96_dce,
"SIM800": esp_modem_ns.create_SIM800_dce,
"SIM7000": esp_modem_ns.create_SIM7000_dce,
"SIM7070": esp_modem_ns.create_SIM7070_dce,
"SIM7080": esp_modem_ns.create_SIM7000_dce,
"SIM7600": esp_modem_ns.create_SIM7600_dce,
"SIM7670": esp_modem_ns.create_SIM7600_dce,
"GENERIC": esp_modem_ns.create_generic_dce,
}
modem_ns = cg.esphome_ns.namespace("modem")
ModemComponent = modem_ns.class_("ModemComponent", cg.Component)
ModemState = modem_ns.enum("ModemState", is_class=True)
def _on_state(conf_key: str, state: str) -> automation.CallbackAutomation:
return automation.CallbackAutomation(
conf_key,
"add_on_state_callback",
params=[(ModemState, "state")],
forward=[],
when=f"state == modem::ModemState::{state}",
)
def _default_use_address(config: ConfigType) -> ConfigType:
# The host tools resolve the device through this, like the other network components
config.setdefault(CONF_USE_ADDRESS, f"{CORE.name}.local")
return config
_CALLBACK_AUTOMATIONS = (
_on_state(CONF_ON_CONNECT, "MODEM_STATE_CONNECTED"),
_on_state(CONF_ON_DISCONNECT, "MODEM_STATE_DISCONNECTING"),
_on_state(CONF_ON_SYNC, "MODEM_STATE_SYNCED"),
)
# AT_COMMAND_SIZE - 1 in modem_component.h; the buffer keeps one byte for the terminator
MAX_AT_COMMAND_LENGTH = 127
# A SIM PIN is 4 to 8 digits; anything else would spend one of the SIM's attempts. \Z, not $, so a
# trailing newline is refused too
_validate_pin_code = cv.All(
cv.string_strict,
vol.Match(r"^[0-9]{4,8}\Z", msg="pin_code must be 4 to 8 digits"),
)
# The IDF UART driver needs more than its 128 byte FIFO; the setters take uint16_t
_UART_BUFFER_SIZE = cv.int_range(min=129, max=65535)
CONFIG_SCHEMA = cv.All(
cv.Schema(
{
cv.GenerateID(): cv.declare_id(ModemComponent),
cv.Required(CONF_TX_PIN): pins.internal_gpio_output_pin_schema,
cv.Required(CONF_RX_PIN): pins.internal_gpio_input_pin_schema,
cv.Optional(CONF_RTS_PIN): pins.internal_gpio_output_pin_schema,
cv.Optional(CONF_CTS_PIN): pins.internal_gpio_input_pin_schema,
cv.Optional(CONF_BAUD_RATE): cv.positive_int,
cv.Required(CONF_MODEL): cv.one_of(*MODEM_MODELS, upper=True),
cv.Required(CONF_APN): cv.string,
cv.Optional(CONF_PIN_CODE): cv.sensitive(_validate_pin_code),
cv.Optional(CONF_USE_ADDRESS): cv.string_strict,
# The command count is stored in a byte
cv.Optional(CONF_INIT_AT): cv.All(
cv.ensure_list(
cv.All(cv.string, cv.ByteLength(max=MAX_AT_COMMAND_LENGTH))
),
cv.Length(max=255),
),
cv.Optional(CONF_ENABLE_ON_BOOT, default=True): cv.boolean,
cv.Optional(CONF_TX_BUFFER_SIZE, default=1024): _UART_BUFFER_SIZE,
cv.Optional(CONF_RX_BUFFER_SIZE, default=1024): _UART_BUFFER_SIZE,
# CMUX keeps 128 bytes of the DTE buffer back, so a smaller buffer could not be read
cv.Optional(CONF_DTE_BUFFER_SIZE, default=1024): cv.int_range(
min=256, max=65535
),
cv.Optional(
CONF_REBOOT_TIMEOUT, default="10min"
): cv.positive_time_period_milliseconds,
**{
cv.Optional(a.conf_key): automation.validate_automation({})
for a in _CALLBACK_AUTOMATIONS
},
}
).extend(cv.COMPONENT_SCHEMA),
cv.has_none_or_all_keys(CONF_RTS_PIN, CONF_CTS_PIN),
_default_use_address,
require_ipv4(DOMAIN),
)
MODEM_ID_SCHEMA = automation.maybe_simple_id(
{cv.GenerateID(): cv.use_id(ModemComponent)}
)
SEND_AT_SCHEMA = cv.maybe_simple_value(
{
cv.GenerateID(): cv.use_id(ModemComponent),
cv.Required(CONF_COMMAND): cv.templatable(
cv.All(cv.string, cv.ByteLength(max=MAX_AT_COMMAND_LENGTH))
),
},
key=CONF_COMMAND,
)
for _name in ("enable", "disable"):
automation.register_apply_action(
f"modem.{_name}", MODEM_ID_SCHEMA, automation.ApplyCall(f"{_name}()")
)
_send_at_builder = automation.apply_action_builder(
"modem.send_at",
SEND_AT_SCHEMA,
# A constant renders as a literal and takes the const char * overload; a lambda returns std::string
automation.ApplyField(CONF_COMMAND, "send_at", cg.std_string),
)
@automation.register_action(
"modem.send_at", automation.ApplyAction, SEND_AT_SCHEMA, synchronous=True
)
async def send_at_to_code(
config: ConfigType,
action_id: ID,
template_arg: cg.TemplateArguments,
args: TemplateArgsType,
) -> MockObj:
cg.add_define("USE_MODEM_SEND_AT")
return await _send_at_builder(config, action_id, template_arg, args)
automation.register_apply_condition(
"modem.connected", MODEM_ID_SCHEMA, "is_connected()"
)
automation.register_apply_condition("modem.enabled", MODEM_ID_SCHEMA, "is_enabled()")
@coroutine_with_priority(CoroPriority.COMMUNICATION)
async def to_code(config: ConfigType) -> None:
add_idf_component(name="espressif/esp_modem", ref="2.1.0")
add_idf_sdkconfig_option("CONFIG_LWIP_PPP_SUPPORT", True)
# LCP echo probes notice a modem that went away without a word, which ends the PPP session
add_idf_sdkconfig_option("CONFIG_LWIP_ENABLE_LCP_ECHO", True)
add_idf_sdkconfig_option("CONFIG_ESP_MODEM_CMUX_DELAY_AFTER_DLCI_SETUP", 500)
add_idf_sdkconfig_option(
"CONFIG_ESP_MODEM_CMUX_USE_SHORT_PAYLOADS_ONLY", True
) # Slower but more reliable
cg.add_define("USE_MODEM")
model = config[CONF_MODEL]
factory = MODEM_MODELS[model]
tx = await cg.gpio_pin_expression(config[CONF_TX_PIN])
rx = await cg.gpio_pin_expression(config[CONF_RX_PIN])
var = cg.new_Pvariable(config[CONF_ID], tx, rx, model, factory, config[CONF_APN])
cg.add(var.set_reboot_timeout(config[CONF_REBOOT_TIMEOUT]))
add_use_address(var, config[CONF_USE_ADDRESS])
if (pin_code := config.get(CONF_PIN_CODE)) is not None:
cg.add_define("USE_MODEM_PIN_CODE")
cg.add(var.set_pin_code(pin_code))
if config[CONF_ENABLE_ON_BOOT]:
cg.add(var.enable())
if init_at := config.get(CONF_INIT_AT):
# The commands are literals in flash; the component keeps a pointer to the array
arr = cg.shared_progmem_array(
"modem_init_at", cg.const_char_ptr, cg.ArrayInitializer(*init_at)
)
cg.add(var.set_init_at_commands(arr, len(init_at)))
await cg.add_pin(var.set_rts_pin, config.get(CONF_RTS_PIN))
await cg.add_pin(var.set_cts_pin, config.get(CONF_CTS_PIN))
cg.add_optional(var.set_baud_rate, config.get(CONF_BAUD_RATE))
cg.add(var.set_rx_buffer_size(config[CONF_RX_BUFFER_SIZE]))
cg.add(var.set_tx_buffer_size(config[CONF_TX_BUFFER_SIZE]))
cg.add(var.set_dte_buffer_size(config[CONF_DTE_BUFFER_SIZE]))
await automation.build_callback_automations(var, config, _CALLBACK_AUTOMATIONS)
await cg.register_component(var, config)
File diff suppressed because it is too large. Load diff
-282
View File
@@ -1,282 +0,0 @@
#pragma once
#ifdef USE_ESP32
#include "esphome/core/component.h"
#include "esphome/core/gpio.h"
#ifdef USE_MODEM_URC
#include "esphome/core/line_reader.h"
#endif
#include "esphome/core/preferences.h"
#include "esphome/core/static_task.h"
#include "esphome/components/network/ip_address.h"
#ifdef USE_MODEM_URC
#include "esphome/components/text_sensor/text_sensor.h"
#endif
#include <cxx_include/esp_modem_api.hpp>
#include <esp_modem_config.h>
#include <atomic>
#include <memory>
#include <string>
#include <utility>
namespace esphome::modem {
enum class ModemState : uint8_t {
MODEM_STATE_DISABLED,
MODEM_STATE_ENABLING,
MODEM_STATE_SYNCED,
MODEM_STATE_CONNECTING,
MODEM_STATE_WAIT_IP,
MODEM_STATE_CONNECTED,
MODEM_STATE_DISCONNECTING,
};
// CONNECTING runs its long commands once; the later passes only poll the attachment
enum class ConnectStep : uint8_t {
CONNECT_STEP_START,
CONNECT_STEP_RADIO_ON,
CONNECT_STEP_ATTACH_REQUESTED,
};
enum class SimResult : uint8_t {
SIM_RESULT_READY,
SIM_RESULT_RETRY, // status not readable yet, or a PIN step is in progress
SIM_RESULT_LOCKED, // nothing more to try: PUK, another lock, no pin_code or the PIN is not sent again
SIM_RESULT_HALT, // stop this attempt; a reboot may still open the SIM, so the reboot timer stays
};
// The step that failed most recently; CONNECTED clears it
enum class ModemFault : uint8_t {
MODEM_FAULT_NONE,
MODEM_FAULT_NO_RESPONSE,
MODEM_FAULT_BAUD,
MODEM_FAULT_INIT_AT,
MODEM_FAULT_SIM_NOT_READY,
MODEM_FAULT_SIM_LOCKED,
MODEM_FAULT_PIN_NOT_SENT,
MODEM_FAULT_PIN_RECORD,
MODEM_FAULT_SETUP,
MODEM_FAULT_NOT_ATTACHED,
MODEM_FAULT_PPP,
MODEM_FAULT_TEARDOWN,
};
#ifdef USE_MODEM_PIN_CODE
enum class PinStep : uint8_t {
PIN_STEP_IDLE,
PIN_STEP_SAVING, // the send is counted and waits for the record to reach flash
PIN_STEP_SENT,
};
// Written by loop() once it tried to save pin_record_; the task leaves the record alone while PENDING
enum class PinSave : uint8_t {
PIN_SAVE_DONE,
PIN_SAVE_PENDING,
PIN_SAVE_FAILED,
};
#endif
// Every esp_modem factory shares this signature, so codegen picks the one for the model
using DceFactory = std::unique_ptr<esp_modem::DCE> (*)(const esp_modem::dce_config *, std::shared_ptr<esp_modem::DTE>,
esp_netif_t *);
class ModemComponent : public Component {
public:
ModemComponent(InternalGPIOPin *tx_pin, InternalGPIOPin *rx_pin, const char *model, DceFactory factory,
const char *apn);
void setup() override;
void loop() override;
void dump_config() override;
float get_setup_priority() const override { return setup_priority::WIFI + 1; } // Just before Wi-Fi
void set_reboot_timeout(uint32_t timeout) { this->timeout_ = timeout; }
void set_use_address(const char *use_address) { this->use_address_ = use_address; }
void set_rts_pin(InternalGPIOPin *rts_pin) { this->rts_pin_ = rts_pin; }
void set_cts_pin(InternalGPIOPin *cts_pin) { this->cts_pin_ = cts_pin; }
void set_baud_rate(int baud_rate) { this->baud_rate_ = baud_rate; }
#ifdef USE_MODEM_PIN_CODE
void set_pin_code(const char *pin_code) { this->pin_code_ = pin_code; }
#endif
void set_tx_buffer_size(uint16_t tx_buffer_size) { this->tx_buffer_size_ = tx_buffer_size; }
void set_rx_buffer_size(uint16_t rx_buffer_size) { this->rx_buffer_size_ = rx_buffer_size; }
void set_dte_buffer_size(uint16_t dte_buffer_size) { this->dte_buffer_size_ = dte_buffer_size; }
void set_init_at_commands(const char *const *cmds, uint8_t count) {
this->init_at_commands_ = cmds;
this->init_at_count_ = count;
}
#ifdef USE_MODEM_URC
void set_urc_text_sensor(text_sensor::TextSensor *urc_text_sensor) { this->urc_text_sensor_ = urc_text_sensor; }
#endif
template<typename F> void add_on_state_callback(F &&callback) {
this->on_state_callback_.add(std::forward<F>(callback));
}
bool is_connected() const { return this->component_state_.load() == ModemState::MODEM_STATE_CONNECTED; }
bool is_disabled() const {
return this->component_state_.load() == ModemState::MODEM_STATE_DISABLED &&
this->target_state_.load() == ModemState::MODEM_STATE_DISABLED;
}
bool is_enabled() const { return !this->is_disabled(); }
void enable();
void disable();
#ifdef USE_MODEM_SEND_AT
/// Queue an AT command for the modem task, which sends it at its next pass and logs the result and response.
/// Returns false when a command is still queued or the command is too long.
bool send_at(const char *cmd, uint32_t timeout = COMMAND_TIMEOUT_MS);
bool send_at(const std::string &cmd, uint32_t timeout = COMMAND_TIMEOUT_MS);
#endif
network::IPAddresses get_ip_addresses();
const char *get_use_address() const { return this->use_address_; }
protected:
static constexpr uint32_t COMMAND_TIMEOUT_MS = 1000;
static constexpr uint32_t RETRY_DELAY_MS = 1000;
static constexpr size_t URC_LINE_SIZE = 128;
static constexpr size_t AT_COMMAND_SIZE = 128;
// esp_modem's calls block until the modem answers or their own timeouts run out, up to a minute for a
// mode change, so the state machine runs on its own task and leaves the main loop alone
static void task_entry(void *arg);
void run_task_();
void wake_task_();
ModemState compute_next_state_();
ModemState handle_state_enabling_();
ModemState handle_state_synced_();
ModemState handle_state_connecting_();
ModemState handle_state_wait_ip_();
ModemState handle_state_disconnecting_();
/// Pause the task before the next pass, then go to state
uint32_t next_probe_delay_();
ModemState retry_(ModemState state, uint32_t delay_ms = RETRY_DELAY_MS);
void transition_to_(ModemState next_state);
// What the task left for loop() to do
enum Pending : uint32_t {
PENDING_STATUS = 1 << 0,
PENDING_SAVE_BAUD = 1 << 1,
#ifdef USE_MODEM_PIN_CODE
PENDING_SAVE_PIN = 1 << 2,
#endif
PENDING_CONNECT_TIMEOUT = 1 << 3, // arm or cancel the reboot timer for the current state
PENDING_ON_SYNC = 1 << 4, // loop() wakes the task once on_sync ran
PENDING_ON_CONNECT = 1 << 5,
PENDING_ON_DISCONNECT = 1 << 6,
};
// Main loop only, so arming and cancelling cannot interleave
void arm_connect_timeout_();
void cancel_connect_timeout_();
void fail_setup_(const LogString *reason);
void set_fault_(ModemFault fault);
void set_teardown_fault_();
void clear_config_fault_(ModemFault fault);
void hand_over_(uint32_t pending);
/// Send an AT command from the task and log the result; blocks for up to the timeout
esp_modem::command_result at_(const char *cmd, uint32_t timeout = COMMAND_TIMEOUT_MS);
void publish_status_();
void not_responding_(int baud);
void dump_connect_params_();
SimResult prepare_sim_();
#ifdef USE_MODEM_PIN_CODE
SimResult send_pin_();
void save_pin_record_();
#endif
InternalGPIOPin *tx_pin_;
InternalGPIOPin *rx_pin_;
InternalGPIOPin *rts_pin_{nullptr};
InternalGPIOPin *cts_pin_{nullptr};
const char *model_;
DceFactory dce_factory_;
const char *apn_;
const char *const *init_at_commands_{nullptr}; // static array from code generation
#ifdef USE_MODEM_URC
text_sensor::TextSensor *urc_text_sensor_{nullptr};
const char *urc_line_{nullptr}; // the line urc_pending_ hands to loop(), inside urc_reader_
#endif
std::unique_ptr<esp_modem::DCE> dce_;
std::shared_ptr<esp_modem::DTE> dte_;
esp_netif_t *ppp_netif_{nullptr};
ESPPreferenceObject pref_;
LazyCallbackManager<void(ModemState)> on_state_callback_;
int baud_rate_{0}; // 0 = use the esp_modem default
int current_baud_rate_{0};
int restored_baud_{0};
StaticTask task_;
uint32_t timeout_{0};
uint32_t step_delay_ms_{0};
uint32_t state_started_ms_{0}; // when the IP wait or the PIN unlock wait began
std::atomic<uint32_t> pending_{0};
#ifdef USE_MODEM_PIN_CODE
const char *pin_code_{nullptr};
ESPPreferenceObject pin_pref_;
#endif
// Kept in flash so reboots cannot keep spending the SIM's attempts on a PIN it never confirmed
struct PinRecord {
uint32_t hash; // pin_code the record is about
uint8_t sends; // AT+CPIN= sent without the SIM reporting READY since
};
#ifdef USE_MODEM_PIN_CODE
PinRecord pin_record_{}; // the task writes it, loop() saves it while pin_save_state_ is PENDING
#endif
#ifdef USE_MODEM_SEND_AT
uint32_t at_timeout_{COMMAND_TIMEOUT_MS};
#endif
uint16_t tx_buffer_size_{1024};
uint16_t rx_buffer_size_{1024};
uint16_t dte_buffer_size_{1024};
// The task owns component_state_; the main loop sets target_state_ and reads both
std::atomic<ModemState> component_state_{ModemState::MODEM_STATE_DISABLED};
std::atomic<ModemState> target_state_{ModemState::MODEM_STATE_DISABLED};
// Written by the task only, read by loop()
std::atomic<ModemFault> fault_{ModemFault::MODEM_FAULT_NONE};
// A failed baud_rate or init_at step, shown once fault_ is clear until the step passes again
std::atomic<ModemFault> config_fault_{ModemFault::MODEM_FAULT_NONE};
// std::atomic<uint8_t> flags: GCC on Xtensa calls out of line for std::atomic<bool>
// Written by the IP event task, read by the modem task
std::atomic<uint8_t> got_ip_{0};
std::atomic<uint8_t> ppp_error_{0}; // last NETIF_PPP_STATUS error code, 0 = none
// Doublings of the silent-modem probe delay; enable() and an answer reset it
std::atomic<uint8_t> probe_backoff_{0};
// Set by loop() once on_sync ran; the task waits for it, not for any notification
std::atomic<uint8_t> sync_acked_{0};
#ifdef USE_MODEM_SEND_AT
// Set by send_at() on the main loop once at_command_ is written, cleared by the task after sending it
std::atomic<uint8_t> at_pending_{0};
#endif
#ifdef USE_MODEM_URC
// Set by the UART task with urc_line_; until loop() has published it, new lines are dropped
std::atomic<uint8_t> urc_pending_{0};
#endif
#ifdef USE_MODEM_PIN_CODE
std::atomic<PinSave> pin_save_state_{PinSave::PIN_SAVE_DONE};
PinStep pin_step_{PinStep::PIN_STEP_IDLE};
bool pin_record_unsaved_{false}; // loop() only; the last PIN record save failed
#endif
ConnectStep connect_step_{ConnectStep::CONNECT_STEP_START};
uint8_t init_at_count_{0};
uint8_t uart_port_{UART_NUM_MAX};
uint8_t attach_polls_{0};
bool connect_timeout_armed_{false};
bool modem_synced_{false}; // a modem that never answered has nothing to tear down
bool resync_{false}; // the attachment read failed, so CONNECTING syncs again first
#ifdef USE_MODEM_SEND_AT
char at_command_[AT_COMMAND_SIZE];
#endif
#ifdef USE_MODEM_URC
// Fed on the UART task
LineReader<URC_LINE_SIZE> urc_reader_;
#endif
private:
// Points at a string literal from code generation, never at dynamic storage
const char *use_address_{""};
};
extern ModemComponent *global_modem_component; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
} // namespace esphome::modem
#endif
-27
View File
@@ -1,27 +0,0 @@
import esphome.codegen as cg
from esphome.components import text_sensor
from esphome.components.esp32 import add_idf_sdkconfig_option
import esphome.config_validation as cv
from esphome.types import ConfigType
from . import CONF_MODEM_ID, ModemComponent
DEPENDENCIES = ["modem"]
CONF_URC = "urc"
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(CONF_MODEM_ID): cv.use_id(ModemComponent),
cv.Optional(CONF_URC): text_sensor.text_sensor_schema(),
}
)
async def to_code(config: ConfigType) -> None:
hub = await cg.get_variable(config[CONF_MODEM_ID])
texts = text_sensor.sub_text_sensors(config)
if await texts(CONF_URC, hub.set_urc_text_sensor) is not None:
cg.add_define("USE_MODEM_URC")
cg.add_define("USE_LINE_READER")
add_idf_sdkconfig_option("CONFIG_ESP_MODEM_URC_HANDLER", True)
+1 -2
View File
@@ -29,10 +29,9 @@ CONFIG_SCHEMA = (
cv.Schema(
{
cv.Required(CONF_ID): cv.declare_id(PCA6416AComponent),
cv.Optional(CONF_INTERRUPT_PIN): gpio_expander.validate_interrupt_pin,
}
)
.extend(cv.COMPONENT_SCHEMA)
.extend(gpio_expander.INTERRUPT_EXPANDER_SCHEMA)
.extend(i2c.i2c_device_schema(0x21))
)
+3 -26
View File
@@ -50,24 +50,7 @@ void PCA6416AComponent::setup() {
ESP_LOGD(TAG, "Initialization complete. Warning: %d, Error: %d", this->status_has_warning(),
this->status_has_error());
if (this->interrupt_pin_ != nullptr) {
this->interrupt_pin_->setup();
this->interrupt_pin_->attach_interrupt(&PCA6416AComponent::gpio_intr, this, gpio::INTERRUPT_FALLING_EDGE);
this->set_invalidate_on_read_(false);
}
this->disable_loop();
}
void IRAM_ATTR PCA6416AComponent::gpio_intr(PCA6416AComponent *arg) { arg->enable_loop_soon_any_context(); }
void PCA6416AComponent::loop() {
// Invalidate cache at the start of each loop
this->reset_pin_cache_();
// Only disable the loop once INT has actually gone HIGH. Input transitions that straddle the
// I2C read leave INT asserted without re-firing a falling edge, which would strand us with
// stale state forever; keep looping until the line is released so we self-heal.
if (this->interrupt_pin_ != nullptr && this->interrupt_pin_->digital_read()) {
this->disable_loop();
}
this->setup_interrupt_pin_();
}
void PCA6416AComponent::dump_config() {
@@ -113,9 +96,6 @@ void PCA6416AComponent::pin_mode(uint8_t pin, gpio::Flags flags) {
this->update_register_(pin, true, pull_dir);
this->update_register_(pin, false, pull_en);
}
if (this->interrupt_pin_ == nullptr) {
this->enable_loop();
}
} else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLUP)) {
this->update_register_(pin, true, io_dir);
if (has_pullup_) {
@@ -124,12 +104,11 @@ void PCA6416AComponent::pin_mode(uint8_t pin, gpio::Flags flags) {
} else {
ESP_LOGW(TAG, "Your PCA6416A does not support pull-up resistors");
}
if (this->interrupt_pin_ == nullptr) {
this->enable_loop();
}
} else if (flags == gpio::FLAG_OUTPUT) {
this->update_register_(pin, false, io_dir);
}
if (flags & gpio::FLAG_INPUT)
this->enable_polling_loop_();
}
bool PCA6416AComponent::read_register_(uint8_t reg, uint8_t *value) {
@@ -182,8 +161,6 @@ void PCA6416AComponent::update_register_(uint8_t pin, bool pin_value, uint8_t re
}
}
float PCA6416AComponent::get_setup_priority() const { return setup_priority::IO; }
} // namespace esphome::pca6416a
ESPHOME_EXPANDER_PIN_SUMMARY(esphome::pca6416a::PCA6416AComponent, "%u via PCA6416A")
+2 -11
View File
@@ -3,31 +3,23 @@
#include "esphome/core/component.h"
#include "esphome/core/hal.h"
#include "esphome/components/i2c/i2c.h"
#include "esphome/components/gpio_expander/cached_gpio.h"
#include "esphome/components/gpio_expander/cached_gpio_component.h"
#include "esphome/components/gpio_expander/expander_gpio_pin.h"
namespace esphome::pca6416a {
class PCA6416AComponent final : public Component,
public i2c::I2CDevice,
public gpio_expander::CachedGpioExpander<uint8_t, 16> {
class PCA6416AComponent final : public gpio_expander::CachedGpioExpanderComponent<uint8_t, 16>, public i2c::I2CDevice {
public:
PCA6416AComponent() = default;
/// Check i2c availability and setup masks
void setup() override;
void loop() override;
/// Helper function to set the pin mode of a pin.
void pin_mode(uint8_t pin, gpio::Flags flags);
float get_setup_priority() const override;
void dump_config() override;
void set_interrupt_pin(InternalGPIOPin *pin) { this->interrupt_pin_ = pin; }
protected:
static void gpio_intr(PCA6416AComponent *arg);
// Virtual methods from CachedGpioExpander
bool digital_read_hw(uint8_t pin) override;
bool digital_read_cache(uint8_t pin) override;
@@ -46,7 +38,6 @@ class PCA6416AComponent final : public Component,
esphome::i2c::ErrorCode last_error_;
/// Only the PCAL6416A has pull-up resistors
bool has_pullup_{false};
InternalGPIOPin *interrupt_pin_{nullptr};
};
using PCA6416AGPIOPin = gpio_expander::ExpanderGPIOPin<PCA6416AComponent>;
+1 -2
View File
@@ -24,10 +24,9 @@ CONFIG_SCHEMA = (
cv.Schema(
{
cv.Required(CONF_ID): cv.declare_id(PCA9505Component),
cv.Optional(CONF_INTERRUPT_PIN): gpio_expander.validate_interrupt_pin,
}
)
.extend(cv.COMPONENT_SCHEMA)
.extend(gpio_expander.INTERRUPT_EXPANDER_SCHEMA)
.extend(i2c.i2c_device_schema(0x20))
)
+3 -28
View File
@@ -38,28 +38,7 @@ void PCA9505Component::setup() {
}
this->write_all_banks_(MASK_REG, 0xFF);
if (this->interrupt_pin_ != nullptr) {
this->interrupt_pin_->setup();
this->interrupt_pin_->attach_interrupt(&PCA9505Component::gpio_intr, this, gpio::INTERRUPT_FALLING_EDGE);
// Don't invalidate cache on read - only invalidate when interrupt fires
this->set_invalidate_on_read_(false);
}
// Nothing to poll until pin_mode() registers an input pin.
this->disable_loop();
}
void IRAM_ATTR PCA9505Component::gpio_intr(PCA9505Component *arg) { arg->enable_loop_soon_any_context(); }
void PCA9505Component::loop() {
// Invalidate the cache so the next digital_read() triggers a fresh I2C read
this->reset_pin_cache_();
// Only disable the loop once INT has actually gone HIGH. Input transitions that straddle the
// I2C read leave INT asserted without re-firing a falling edge, which would strand us with
// stale state forever; keep looping until the line is released so we self-heal.
if (this->interrupt_pin_ != nullptr && this->interrupt_pin_->digital_read()) {
this->disable_loop();
}
this->setup_interrupt_pin_();
}
void PCA9505Component::dump_config() {
@@ -84,11 +63,9 @@ void PCA9505Component::pin_mode(uint8_t pin, gpio::Flags flags) {
const uint8_t mask = 1 << (pin % 8);
if (flags & gpio::FLAG_INPUT) {
this->config_[bank] &= ~mask;
// An input may raise INT; the polling loop is only needed when there is no interrupt pin.
// An input may raise INT.
this->mask_[bank] &= ~mask;
if (this->interrupt_pin_ == nullptr) {
this->enable_loop();
}
this->enable_polling_loop_();
} else if (flags & gpio::FLAG_OUTPUT) {
this->config_[bank] |= mask;
// An output must never raise INT.
@@ -130,8 +107,6 @@ bool PCA9505Component::write_all_banks_(uint8_t reg, uint8_t value) {
LOG_STR("Failed to write register"));
}
float PCA9505Component::get_setup_priority() const { return setup_priority::IO; }
} // namespace esphome::pca9505
ESPHOME_EXPANDER_PIN_SUMMARY(esphome::pca9505::PCA9505Component, "%u via PCA9505")
+3 -12
View File
@@ -3,7 +3,7 @@
#include "esphome/core/component.h"
#include "esphome/core/hal.h"
#include "esphome/core/helpers.h"
#include "esphome/components/gpio_expander/cached_gpio.h"
#include "esphome/components/gpio_expander/cached_gpio_component.h"
#include "esphome/components/gpio_expander/expander_gpio_pin.h"
#include "esphome/components/i2c/i2c.h"
@@ -14,23 +14,15 @@ static constexpr uint8_t PIN_COUNT = 40;
/// The pins are grouped into five ports of eight, each with its own set of registers.
static constexpr uint8_t BANK_COUNT = PIN_COUNT / 8;
class PCA9505Component final : public Component,
public i2c::I2CDevice,
public gpio_expander::CachedGpioExpander<uint8_t, PIN_COUNT> {
class PCA9505Component final : public gpio_expander::CachedGpioExpanderComponent<uint8_t, PIN_COUNT>,
public i2c::I2CDevice {
public:
void set_interrupt_pin(InternalGPIOPin *pin) { this->interrupt_pin_ = pin; }
void setup() override;
void loop() override;
void pin_mode(uint8_t pin, gpio::Flags flags);
float get_setup_priority() const override;
void dump_config() override;
protected:
static void gpio_intr(PCA9505Component *arg);
bool digital_read_hw(uint8_t pin) override;
bool digital_read_cache(uint8_t pin) override;
void digital_write_hw(uint8_t pin, bool value) override;
@@ -47,7 +39,6 @@ class PCA9505Component final : public Component,
uint8_t input_[BANK_COUNT]{};
/// Which pins may pull INT low - 1 means masked, matching the chip
uint8_t mask_[BANK_COUNT]{};
InternalGPIOPin *interrupt_pin_{nullptr};
};
using PCA9505GPIOPin = gpio_expander::ExpanderGPIOPin<PCA9505Component>;
+2 -4
View File
@@ -2,9 +2,8 @@ from typing import NamedTuple
from esphome import pins
import esphome.codegen as cg
from esphome.components import i2c
from esphome.components import gpio_expander, i2c
from esphome.components.const import CONF_DRIVE_STRENGTH
from esphome.components.gpio_expander import validate_interrupt_pin
import esphome.config_validation as cv
from esphome.const import (
CONF_ADDRESS,
@@ -90,9 +89,8 @@ CONFIG_SCHEMA = cv.All(
cv.Optional(CONF_ADDRESS): cv.i2c_address,
cv.Optional(CONF_DEVICE): cv.one_of(*DEVICES, upper=True),
cv.Optional(CONF_PIN_COUNT): cv.one_of(4, 8, 16),
cv.Optional(CONF_INTERRUPT_PIN): validate_interrupt_pin,
}
).extend(cv.COMPONENT_SCHEMA),
).extend(gpio_expander.INTERRUPT_EXPANDER_SCHEMA),
_validate_hub,
)
+2 -30
View File
@@ -52,29 +52,7 @@ void PCA9554Component::setup() {
ESP_LOGD(TAG, "Initialization complete. Warning: %d, Error: %d", this->status_has_warning(),
this->status_has_error());
if (this->interrupt_pin_ != nullptr) {
this->interrupt_pin_->setup();
this->interrupt_pin_->attach_interrupt(&PCA9554Component::gpio_intr, this, gpio::INTERRUPT_FALLING_EDGE);
// Don't invalidate cache on read — only invalidate when interrupt fires
this->set_invalidate_on_read_(false);
}
// Disable loop until an input pin is configured via pin_mode()
// For interrupt-driven mode, loop is re-enabled by the ISR
// For polling mode, loop is re-enabled when pin_mode() registers an input pin
this->disable_loop();
}
void IRAM_ATTR PCA9554Component::gpio_intr(PCA9554Component *arg) { arg->enable_loop_soon_any_context(); }
void PCA9554Component::loop() {
// Invalidate the cache so the next digital_read() triggers a fresh I2C read
this->reset_pin_cache_();
// Only disable the loop once INT has actually gone HIGH. Input transitions that straddle the
// I2C read leave INT asserted without re-firing a falling edge, which would strand us with
// stale state forever; keep looping until the line is released so we self-heal.
if (this->interrupt_pin_ != nullptr && this->interrupt_pin_->digital_read()) {
this->disable_loop();
}
this->setup_interrupt_pin_();
}
void PCA9554Component::dump_config() {
@@ -113,11 +91,7 @@ void PCA9554Component::pin_mode(uint8_t pin, gpio::Flags flags) {
if (flags & gpio::FLAG_INPUT) {
// Clear mode mask bit
this->config_mask_ &= ~(1 << pin);
// Enable polling loop for input pins (not needed for interrupt-driven mode
// where the ISR handles re-enabling loop)
if (this->interrupt_pin_ == nullptr) {
this->enable_loop();
}
this->enable_polling_loop_();
} else if (flags & gpio::FLAG_OUTPUT) {
// Set mode mask bit
this->config_mask_ |= 1 << pin;
@@ -204,8 +178,6 @@ void PCA9554Component::update_pcal_register_(uint8_t reg, uint16_t &shadow, uint
}
#endif
float PCA9554Component::get_setup_priority() const { return setup_priority::IO; }
} // namespace esphome::pca9554
ESPHOME_EXPANDER_PIN_SUMMARY(esphome::pca9554::PCA9554Component, "%u via PCA9554")
+2 -11
View File
@@ -3,28 +3,22 @@
#include "esphome/core/component.h"
#include "esphome/core/hal.h"
#include "esphome/components/i2c/i2c.h"
#include "esphome/components/gpio_expander/cached_gpio.h"
#include "esphome/components/gpio_expander/cached_gpio_component.h"
#include "esphome/components/gpio_expander/expander_gpio_pin.h"
namespace esphome::pca9554 {
class PCA9554Component final : public Component,
public i2c::I2CDevice,
public gpio_expander::CachedGpioExpander<uint16_t, 16> {
class PCA9554Component final : public gpio_expander::CachedGpioExpanderComponent<uint16_t, 16>, public i2c::I2CDevice {
public:
explicit PCA9554Component(uint8_t pin_count) : pin_count_(pin_count), reg_width_((pin_count + 7) / 8) {}
/// Check i2c availability and setup masks
void setup() override;
void loop() override;
/// Helper function to set the pin mode of a pin.
void pin_mode(uint8_t pin, gpio::Flags flags);
float get_setup_priority() const override;
void dump_config() override;
void set_interrupt_pin(InternalGPIOPin *pin) { this->interrupt_pin_ = pin; }
#ifdef USE_PCA9554_PCAL
/// The PCAL parts add pull resistors, input latches, an interrupt mask and drive strength.
void set_pcal() { this->pcal_ = true; }
@@ -38,8 +32,6 @@ class PCA9554Component final : public Component,
#endif
protected:
static void gpio_intr(PCA9554Component *arg);
bool read_inputs_();
bool write_register_(uint8_t reg, uint16_t value) { return this->write_address_(reg * this->reg_width_, value); }
bool write_address_(uint8_t address, uint16_t value);
@@ -61,7 +53,6 @@ class PCA9554Component final : public Component,
bool digital_read_cache(uint8_t pin) override;
void digital_write_hw(uint8_t pin, bool value) override;
InternalGPIOPin *interrupt_pin_{nullptr};
#ifdef USE_PCA9554_PCAL
/// Drive strength shadow, two bits per pin; the low reg_width_ bytes go to OUTPUT_DRIVE_0
uint32_t drive_{0xFFFFFFFF};
+1 -2
View File
@@ -36,10 +36,9 @@ CONFIG_SCHEMA = (
cv.Required(CONF_ID): cv.declare_id(PCF8574Component),
cv.Optional(CONF_PCF8575, default=False): cv.boolean,
cv.Optional(CONF_RESET, default=DEFAULT_RESET): cv.boolean,
cv.Optional(CONF_INTERRUPT_PIN): gpio_expander.validate_interrupt_pin,
}
)
.extend(cv.COMPONENT_SCHEMA)
.extend(gpio_expander.INTERRUPT_EXPANDER_SCHEMA)
.extend(i2c.i2c_device_schema(0x21))
)
+2 -27
View File
@@ -21,27 +21,7 @@ void PCF8574Component::setup() {
this->mode_mask_ = ~this->configured_input_pins_;
}
if (this->interrupt_pin_ != nullptr) {
this->interrupt_pin_->setup();
this->interrupt_pin_->attach_interrupt(&PCF8574Component::gpio_intr, this, gpio::INTERRUPT_FALLING_EDGE);
// Don't invalidate cache on read — only invalidate when interrupt fires
this->set_invalidate_on_read_(false);
}
// Disable loop until an input pin is configured via pin_mode()
// For interrupt-driven mode, loop is re-enabled by the ISR
// For polling mode, loop is re-enabled when pin_mode() registers an input pin
this->disable_loop();
}
void IRAM_ATTR PCF8574Component::gpio_intr(PCF8574Component *arg) { arg->enable_loop_soon_any_context(); }
void PCF8574Component::loop() {
// Invalidate the cache so the next digital_read() triggers a fresh I2C read
this->reset_pin_cache_();
// Only disable the loop once INT has actually gone HIGH. Input transitions that straddle the
// I2C read leave INT asserted without re-firing a falling edge, which would strand us with
// stale state forever; keep looping until the line is released so we self-heal.
if (this->interrupt_pin_ != nullptr && this->interrupt_pin_->digital_read()) {
this->disable_loop();
}
this->setup_interrupt_pin_();
}
void PCF8574Component::dump_config() {
ESP_LOGCONFIG(TAG,
@@ -69,11 +49,7 @@ void PCF8574Component::pin_mode(uint8_t pin, gpio::Flags flags) {
this->mode_mask_ &= ~(1 << pin);
// Write GPIO to enable input mode
this->write_gpio_();
// Enable polling loop for input pins (not needed for interrupt-driven mode
// where the ISR handles re-enabling loop)
if (this->interrupt_pin_ == nullptr) {
this->enable_loop();
}
this->enable_polling_loop_();
} else if (flags == gpio::FLAG_OUTPUT) {
// Set mode mask bit
this->mode_mask_ |= 1 << pin;
@@ -103,7 +79,6 @@ bool PCF8574Component::write_gpio_() {
data[1] = value >> 8;
return this->status_comm_result(this->write(data, this->pcf8575_ ? 2 : 1) == i2c::ERROR_OK);
}
float PCF8574Component::get_setup_priority() const { return setup_priority::IO; }
} // namespace esphome::pcf8574
+2 -11
View File
@@ -3,16 +3,14 @@
#include "esphome/core/component.h"
#include "esphome/core/hal.h"
#include "esphome/components/i2c/i2c.h"
#include "esphome/components/gpio_expander/cached_gpio.h"
#include "esphome/components/gpio_expander/cached_gpio_component.h"
#include "esphome/components/gpio_expander/expander_gpio_pin.h"
namespace esphome::pcf8574 {
// PCF8574(8 pins)/PCF8575(16 pins) always read/write all pins in a single I2C transaction
// so we use uint16_t as bank type to ensure all pins are in one bank and cached together
class PCF8574Component final : public Component,
public i2c::I2CDevice,
public gpio_expander::CachedGpioExpander<uint16_t, 16> {
class PCF8574Component final : public gpio_expander::CachedGpioExpanderComponent<uint16_t, 16>, public i2c::I2CDevice {
public:
PCF8574Component() = default;
@@ -20,21 +18,15 @@ class PCF8574Component final : public Component,
void set_reset(bool reset) { this->reset_ = reset; }
// Called by pin setters before any component's setup or GPIO writes.
void register_input_pin(uint8_t pin) { this->configured_input_pins_ |= 1 << pin; }
void set_interrupt_pin(InternalGPIOPin *pin) { this->interrupt_pin_ = pin; }
/// Check i2c availability and setup masks
void setup() override;
void loop() override;
/// Helper function to set the pin mode of a pin.
void pin_mode(uint8_t pin, gpio::Flags flags);
float get_setup_priority() const override;
void dump_config() override;
protected:
static void gpio_intr(PCF8574Component *arg);
bool digital_read_hw(uint8_t pin) override;
bool digital_read_cache(uint8_t pin) override;
void digital_write_hw(uint8_t pin, bool value) override;
@@ -54,7 +46,6 @@ class PCF8574Component final : public Component,
// Keep in sync with DEFAULT_RESET in __init__.py; codegen omits the setter for the default.
bool reset_{true};
bool pcf8575_; ///< TRUE->16-channel PCF8575, FALSE->8-channel PCF8574
InternalGPIOPin *interrupt_pin_{nullptr};
};
using PCF8574GPIOPin = gpio_expander::ExpanderGPIOPin<PCF8574Component>;
+1 -2
View File
@@ -34,10 +34,9 @@ CONFIG_SCHEMA = (
{
cv.Required(CONF_ID): cv.declare_id(PI4IOE5V6408Component),
cv.Optional(CONF_RESET, default=True): cv.boolean,
cv.Optional(CONF_INTERRUPT_PIN): gpio_expander.validate_interrupt_pin,
}
)
.extend(cv.COMPONENT_SCHEMA)
.extend(gpio_expander.INTERRUPT_EXPANDER_SCHEMA)
.extend(i2c.i2c_device_schema(0x43))
)
@@ -34,20 +34,8 @@ void PI4IOE5V6408Component::setup() {
}
}
// No need to clear latched interrupts before attaching the ISR — if INT is
// already low the ISR fires immediately, loop runs, cache invalidates, and
// the read clears the latch. One harmless extra read at most.
if (this->interrupt_pin_ != nullptr) {
this->interrupt_pin_->setup();
this->interrupt_pin_->attach_interrupt(&PI4IOE5V6408Component::gpio_intr, this, gpio::INTERRUPT_FALLING_EDGE);
this->set_invalidate_on_read_(false);
}
// Disable loop until an input pin is configured via pin_mode()
// For interrupt-driven mode, loop is re-enabled by the ISR
// For polling mode, loop is re-enabled when pin_mode() registers an input pin
this->disable_loop();
this->setup_interrupt_pin_();
}
void IRAM_ATTR PI4IOE5V6408Component::gpio_intr(PI4IOE5V6408Component *arg) { arg->enable_loop_soon_any_context(); }
void PI4IOE5V6408Component::dump_config() {
ESP_LOGCONFIG(TAG, "PI4IOE5V6408:");
LOG_PIN(" Interrupt Pin: ", this->interrupt_pin_);
@@ -67,26 +55,12 @@ void PI4IOE5V6408Component::pin_mode(uint8_t pin, gpio::Flags flags) {
this->pull_up_down_mask_ &= ~(1 << pin);
this->pull_enable_mask_ |= 1 << pin;
}
// Enable polling loop for input pins (not needed for interrupt-driven mode
// where the ISR handles re-enabling loop)
if (this->interrupt_pin_ == nullptr) {
this->enable_loop();
}
this->enable_polling_loop_();
}
// Write GPIO to enable input mode
this->write_gpio_modes_();
}
void PI4IOE5V6408Component::loop() {
this->reset_pin_cache_();
// Only disable the loop once INT has actually gone HIGH. Input transitions that straddle the
// I2C read leave INT asserted without re-firing a falling edge, which would strand us with
// stale state forever; keep looping until the line is released so we self-heal.
if (this->interrupt_pin_ != nullptr && this->interrupt_pin_->digital_read()) {
this->disable_loop();
}
}
bool PI4IOE5V6408Component::read_gpio_outputs_() {
if (this->is_failed())
return false;
@@ -177,8 +151,6 @@ bool PI4IOE5V6408Component::write_gpio_modes_() {
bool PI4IOE5V6408Component::digital_read_cache(uint8_t pin) { return (this->input_mask_ & (1 << pin)); }
float PI4IOE5V6408Component::get_setup_priority() const { return setup_priority::IO; }
} // namespace esphome::pi4ioe5v6408
ESPHOME_EXPANDER_PIN_SUMMARY(esphome::pi4ioe5v6408::PI4IOE5V6408Component, "%u via PI4IOE5V6408")
+3 -10
View File
@@ -1,32 +1,26 @@
#pragma once
#include "esphome/components/gpio_expander/cached_gpio.h"
#include "esphome/components/gpio_expander/cached_gpio_component.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"
namespace esphome::pi4ioe5v6408 {
class PI4IOE5V6408Component final : public Component,
public i2c::I2CDevice,
public gpio_expander::CachedGpioExpander<uint8_t, 8> {
class PI4IOE5V6408Component final : public gpio_expander::CachedGpioExpanderComponent<uint8_t, 8>,
public i2c::I2CDevice {
public:
PI4IOE5V6408Component() = default;
void setup() override;
void pin_mode(uint8_t pin, gpio::Flags flags);
float get_setup_priority() const override;
void dump_config() override;
void loop() override;
/// Indicate if the component should reset the state during setup
void set_reset(bool reset) { this->reset_ = reset; }
void set_interrupt_pin(InternalGPIOPin *pin) { this->interrupt_pin_ = pin; }
protected:
static void gpio_intr(PI4IOE5V6408Component *arg);
bool digital_read_hw(uint8_t pin) override;
bool digital_read_cache(uint8_t pin) override;
void digital_write_hw(uint8_t pin, bool value) override;
@@ -43,7 +37,6 @@ class PI4IOE5V6408Component final : public Component,
uint8_t pull_up_down_mask_{0x00};
bool reset_{true};
InternalGPIOPin *interrupt_pin_{nullptr};
bool read_gpio_modes_();
bool write_gpio_modes_();
+1 -2
View File
@@ -23,10 +23,9 @@ CONFIG_SCHEMA = (
cv.Schema(
{
cv.Required(CONF_ID): cv.declare_id(TCA9555Component),
cv.Optional(CONF_INTERRUPT_PIN): gpio_expander.validate_interrupt_pin,
}
)
.extend(cv.COMPONENT_SCHEMA)
.extend(gpio_expander.INTERRUPT_EXPANDER_SCHEMA)
.extend(i2c.i2c_device_schema(0x21))
)
+2 -21
View File
@@ -25,14 +25,8 @@ void TCA9555Component::setup() {
return;
}
if (this->interrupt_pin_ != nullptr) {
this->interrupt_pin_->setup();
this->interrupt_pin_->attach_interrupt(&TCA9555Component::gpio_intr, this, gpio::INTERRUPT_FALLING_EDGE);
this->set_invalidate_on_read_(false);
}
this->disable_loop();
this->setup_interrupt_pin_();
}
void IRAM_ATTR TCA9555Component::gpio_intr(TCA9555Component *arg) { arg->enable_loop_soon_any_context(); }
void TCA9555Component::dump_config() {
ESP_LOGCONFIG(TAG, "TCA9555:");
LOG_PIN(" Interrupt Pin: ", this->interrupt_pin_);
@@ -42,9 +36,7 @@ void TCA9555Component::pin_mode(uint8_t pin, gpio::Flags flags) {
if (flags == gpio::FLAG_INPUT) {
// Set mode mask bit
this->mode_mask_ |= 1 << pin;
if (this->interrupt_pin_ == nullptr) {
this->enable_loop();
}
this->enable_polling_loop_();
} else if (flags == gpio::FLAG_OUTPUT) {
// Clear mode mask bit
this->mode_mask_ &= ~(1 << pin);
@@ -52,15 +44,6 @@ void TCA9555Component::pin_mode(uint8_t pin, gpio::Flags flags) {
// Write GPIO to enable input mode
this->write_gpio_modes_();
}
void TCA9555Component::loop() {
this->reset_pin_cache_();
// Only disable the loop once INT has actually gone HIGH. Input transitions that straddle the
// I2C read leave INT asserted without re-firing a falling edge, which would strand us with
// stale state forever; keep looping until the line is released so we self-heal.
if (this->interrupt_pin_ != nullptr && this->interrupt_pin_->digital_read()) {
this->disable_loop();
}
}
bool TCA9555Component::read_gpio_outputs_() {
if (this->is_failed())
@@ -124,8 +107,6 @@ bool TCA9555Component::write_gpio_modes_() {
bool TCA9555Component::digital_read_cache(uint8_t pin) { return this->input_mask_ & (1 << pin); }
float TCA9555Component::get_setup_priority() const { return setup_priority::IO; }
} // namespace esphome::tca9555
ESPHOME_EXPANDER_PIN_SUMMARY(esphome::tca9555::TCA9555Component, "%u via TCA9555")
+2 -13
View File
@@ -1,6 +1,6 @@
#pragma once
#include "esphome/components/gpio_expander/cached_gpio.h"
#include "esphome/components/gpio_expander/cached_gpio_component.h"
#include "esphome/components/gpio_expander/expander_gpio_pin.h"
#include "esphome/components/i2c/i2c.h"
#include "esphome/core/component.h"
@@ -8,9 +8,7 @@
namespace esphome::tca9555 {
class TCA9555Component final : public Component,
public i2c::I2CDevice,
public gpio_expander::CachedGpioExpander<uint8_t, 16> {
class TCA9555Component final : public gpio_expander::CachedGpioExpanderComponent<uint8_t, 16>, public i2c::I2CDevice {
public:
TCA9555Component() = default;
@@ -18,16 +16,9 @@ class TCA9555Component final : public Component,
void setup() override;
void pin_mode(uint8_t pin, gpio::Flags flags);
float get_setup_priority() const override;
void dump_config() override;
void loop() override;
void set_interrupt_pin(InternalGPIOPin *pin) { this->interrupt_pin_ = pin; }
protected:
static void gpio_intr(TCA9555Component *arg);
// Virtual methods from GpioExpander base class — names come from base
bool digital_read_hw(uint8_t pin) override; // NOLINT(readability-identifier-naming)
bool digital_read_cache(uint8_t pin) override; // NOLINT(readability-identifier-naming)
@@ -43,8 +34,6 @@ class TCA9555Component final : public Component,
bool read_gpio_modes_();
bool write_gpio_modes_();
bool read_gpio_outputs_();
InternalGPIOPin *interrupt_pin_{nullptr};
};
using TCA9555GPIOPin = gpio_expander::ExpanderGPIOPin<TCA9555Component>;
+1 -8
View File
@@ -15,7 +15,6 @@ from esphome.const import (
CONF_ETHERNET,
CONF_OPENTHREAD,
CONF_PORT,
CONF_PRIORITY,
CONF_USE_ADDRESS,
CONF_WEB_SERVER,
CONF_WIFI,
@@ -768,13 +767,7 @@ class EsphomeCore:
if self.config is None:
raise ValueError("Config has not been loaded yet")
# Matches network::get_use_address_to(), with modem and ethernet never both present
# (CONFLICTS_WITH); a wifi-first priority list lifts wifi, the rule that emits
# USE_NETWORK_PRIMARY_INTERFACE_WIFI in network/__init__.py
priority = self.config.get("network", {}).get(CONF_PRIORITY, [])
wifi_first = bool(priority) and priority[0]["interface"] == CONF_WIFI
first = (CONF_WIFI, "modem") if wifi_first else ("modem", CONF_WIFI)
for network_type in (*first, CONF_ETHERNET, CONF_OPENTHREAD):
for network_type in (CONF_WIFI, CONF_ETHERNET, CONF_OPENTHREAD):
if network_type in self.config:
return self.config[network_type][CONF_USE_ADDRESS]
-7
View File
@@ -610,13 +610,6 @@
#define ESPHOME_ETHERNET_IP_STATE_LISTENERS 2
#define ESPHOME_ETHERNET_PHY_REGISTER_COUNT 2
#ifdef USE_ESP32
#define USE_MODEM
#define USE_MODEM_PIN_CODE
#define USE_MODEM_SEND_AT
#define USE_MODEM_URC
#endif
#ifdef USE_ESP32
#define MICRO_WAKE_WORD_TASK_CORE 0
#define USE_MICRO_WAKE_WORD
-2
View File
@@ -25,8 +25,6 @@ dependencies:
version: 1.1.2
espressif/esp32-camera:
version: 2.1.7
espressif/esp_modem:
version: 2.1.0
espressif/mdns:
version: 1.14.0
espressif/esp_wifi_remote:
-1
View File
@@ -98,7 +98,6 @@ ISOLATED_COMPONENTS = {
"microphone": "Defines PDM microphone requiring I2S port 0 - conflicts with micro_wake_word PDM mic when merged",
"mipi_rgb": "RGB display occupies many GPIOs (including ones used by the shared i2c bus) that conflict when merged with other bus components",
"modbus_controller": "Defines multiple modbus buses for testing client/server functionality - conflicts with package modbus bus",
"modem": "Cellular-only node with api - grouping with wifi-bearing components hides that case",
"neopixelbus": "RMT type conflict with ESP32 Arduino/ESP-IDF headers (enum vs struct rmt_channel_t)",
"packages": "cannot merge packages",
"tinyusb": "Conflicts with usb_host component - cannot be used together",
-4
View File
@@ -26,7 +26,3 @@ CONFIG_BT_ENABLED=y
# esp32_camera
CONFIG_SPIRAM=y
# modem
CONFIG_LWIP_PPP_SUPPORT=y
CONFIG_ESP_MODEM_URC_HANDLER=y
Whitespace-only changes.
@@ -1,14 +0,0 @@
"""libretiny_pwm rejects LN882H: LibreTiny has no analogWrite for that family."""
import pytest
from esphome import config_validation as cv
from esphome.components.libretiny.const import FAMILY_LN882H, KEY_FAMILY, KEY_LIBRETINY
from esphome.components.libretiny_pwm.output import CONFIG_SCHEMA
from esphome.core import CORE
def test_ln882h_is_rejected() -> None:
CORE.data[KEY_LIBRETINY] = {KEY_FAMILY: FAMILY_LN882H}
with pytest.raises(cv.Invalid, match="not available on LN882H"):
CONFIG_SCHEMA({"id": "pwm", "pin": "PA5"})
@@ -1,6 +1,6 @@
light:
- platform: esp32_rmt_led_strip
id: addressable_light_matrix
id: led_matrix_32x8
default_transition_length: 500ms
chipset: ws2812
channel_colors: GRB
@@ -9,8 +9,8 @@ light:
display:
- platform: addressable_light
id: addressable_light_display
addressable_light_id: addressable_light_matrix
id: led_matrix_32x8_display
addressable_light_id: led_matrix_32x8
width: 32
height: 8
pixel_mapper: |-
@@ -0,0 +1,32 @@
light:
- platform: fastled_clockless
id: led_matrix_32x8
name: led_matrix_32x8
chipset: WS2812B
pin: ${pin}
num_leds: 256
rgb_order: GRB
default_transition_length: 0s
color_correct: [50%, 50%, 50%]
display:
- platform: addressable_light
id: led_matrix_32x8_display
addressable_light_id: led_matrix_32x8
width: 32
height: 8
pixel_mapper: |-
if (x % 2 == 0) {
return (x * 8) + y;
}
return (x * 8) + (7 - y);
lambda: |-
Color red = Color(0xFF0000);
Color green = Color(0x00FF00);
Color blue = Color(0x0000FF);
it.rectangle(0, 0, it.get_width(), it.get_height(), red);
it.rectangle(1, 1, it.get_width()-2, it.get_height()-2, green);
it.rectangle(2, 2, it.get_width()-4, it.get_height()-4, blue);
it.rectangle(3, 3, it.get_width()-6, it.get_height()-6, red);
rotation: 0°
update_interval: 16ms
@@ -0,0 +1,30 @@
light:
- platform: esp32_rmt_led_strip
id: led_matrix_32x8
default_transition_length: 500ms
chipset: ws2812
channel_colors: GRB
num_leds: 256
pin: ${pin}
display:
- platform: addressable_light
id: led_matrix_32x8_display
addressable_light_id: led_matrix_32x8
width: 32
height: 8
pixel_mapper: |-
if (x % 2 == 0) {
return (x * 8) + y;
}
return (x * 8) + (7 - y);
lambda: |-
Color red = Color(0xFF0000);
Color green = Color(0x00FF00);
Color blue = Color(0x0000FF);
it.rectangle(0, 0, it.get_width(), it.get_height(), red);
it.rectangle(1, 1, it.get_width()-2, it.get_height()-2, green);
it.rectangle(2, 2, it.get_width()-4, it.get_height()-4, blue);
it.rectangle(3, 3, it.get_width()-6, it.get_height()-6, red);
rotation: 0°
update_interval: 16ms
@@ -0,0 +1,4 @@
substitutions:
pin: GPIO2
<<: !include common-idf-esp32_rmt_led_strip.yaml
@@ -0,0 +1,4 @@
substitutions:
pin: GPIO2
<<: !include common-idf-esp32_rmt_led_strip.yaml
@@ -1,5 +0,0 @@
substitutions:
pin: GPIO2
packages:
addressable_light: !include common.yaml
-4
View File
@@ -32,10 +32,6 @@ esphome:
action: notify.html5
data:
message: Button was pressed
- homeassistant.service:
service: notify.html5
data:
message: Button was pressed
- homeassistant.tag_scanned: pulse
- homeassistant.action:
action: weather.get_forecasts
@@ -1,6 +0,0 @@
packages:
api: !include common.yaml
wifi:
ssid: MySSID
password: password1
-4
View File
@@ -7,7 +7,3 @@ climate:
min_temperature: 18
max_temperature: 30
transmitter_id: xmitr
- platform: ballu
name: Ballu Climate
transmitter_id: xmitr
-17
View File
@@ -1,17 +0,0 @@
output:
- platform: gpio
id: binary_fan_output
pin: ${fan_pin}
- platform: gpio
id: binary_light_output
pin: ${light_pin}
fan:
- platform: binary
name: Binary Fan
output: binary_fan_output
light:
- platform: binary
name: Binary Light
output: binary_light_output
@@ -1,6 +0,0 @@
substitutions:
fan_pin: GPIO12
light_pin: GPIO13
packages:
binary: !include common.yaml
@@ -1,6 +0,0 @@
substitutions:
fan_pin: GPIO12
light_pin: GPIO13
packages:
binary: !include common.yaml
@@ -1,6 +0,0 @@
substitutions:
fan_pin: GPIO12
light_pin: GPIO13
packages:
binary: !include common.yaml
-34
View File
@@ -31,10 +31,6 @@ ble_client:
- ble_client.numeric_comparison_reply:
id: test_blec
accept: false
on_passkey_notification:
- logger.log:
format: "Passkey %u"
args: [passkey]
sensor:
- platform: ble_client
@@ -56,11 +52,6 @@ sensor:
name: "BLE Sensor without Lambda"
service_uuid: "abcd1234-abcd-1234-abcd-abcd12345678"
characteristic_uuid: "abcd1237-abcd-1234-abcd-abcd12345678"
notify: true
on_notify:
- logger.log:
format: "Notify %.2f"
args: [x]
number:
- platform: template
@@ -83,28 +74,3 @@ button:
value: !lambda |-
uint8_t val = (uint8_t)id(ble_client_test_number).state;
return std::vector<uint8_t>{0xAA, val, 0xBB};
- ble_client.disconnect: test_blec
- ble_client.connect: test_blec
- ble_client.remove_bond:
id: test_blec
switch:
- platform: ble_client
ble_client_id: test_blec
name: BLE Client Enabled
output:
- platform: ble_client
id: ble_client_output
ble_client_id: test_blec
service_uuid: "abcd1234-abcd-1234-abcd-abcd12345678"
characteristic_uuid: "abcd1238-abcd-1234-abcd-abcd12345678"
require_response: true
text_sensor:
- platform: ble_client
ble_client_id: test_blec
name: BLE Text Sensor
service_uuid: "abcd1234-abcd-1234-abcd-abcd12345678"
characteristic_uuid: "abcd1239-abcd-1234-abcd-abcd12345678"
notify: true
-14
View File
@@ -95,17 +95,3 @@ motion:
x: y
y: x
z: -z
button:
- platform: template
name: Calibrate Motion
on_press:
- motion.calibrate_level:
id: bmi270_motion
save: true
on_success:
- logger.log: Level calibrated
on_error:
- logger.log: Level calibration failed
- motion.calibrate_heading:
id: bmi270_motion
-6
View File
@@ -5,9 +5,3 @@ cap1188:
reset_pin: ${reset_pin}
touch_threshold: 0x20
allow_multiple_touches: true
binary_sensor:
- platform: cap1188
cap1188_id: cap1188_component
name: CAP1188 Touch Key 0
channel: 0
-61
View File
@@ -32,64 +32,3 @@ button:
- platform: copy
source_id: copy_source_button
name: Button Copy
binary_sensor:
- platform: template
id: copy_source_binary_sensor
lambda: return true;
- platform: copy
source_id: copy_source_binary_sensor
name: Binary Sensor Copy
cover:
- platform: template
id: copy_source_cover
optimistic: true
- platform: copy
source_id: copy_source_cover
name: Cover Copy
lock:
- platform: template
id: copy_source_lock
optimistic: true
- platform: copy
source_id: copy_source_lock
name: Lock Copy
number:
- platform: template
id: copy_source_number
optimistic: true
min_value: 0
max_value: 100
step: 1
- platform: copy
source_id: copy_source_number
name: Number Copy
switch:
- platform: template
id: copy_source_switch
optimistic: true
- platform: copy
source_id: copy_source_switch
name: Switch Copy
text:
- platform: template
id: copy_source_text
optimistic: true
mode: text
- platform: copy
source_id: copy_source_text
name: Text Copy
mode: text
text_sensor:
- platform: template
id: copy_source_text_sensor
lambda: return {"hello"};
- platform: copy
source_id: copy_source_text_sensor
name: Text Sensor Copy
@@ -24,11 +24,3 @@ display:
to: display_page_change_page2
then:
lambda: 'ESP_LOGD("display", "1 -> 2, %p %p", from, to);'
button:
- platform: template
name: Show Page
on_press:
- display.page.show: display_page_change_page1
- display.page.show:
id: !lambda return id(display_page_change_page2);
@@ -4,7 +4,6 @@ esp32:
type: esp-idf
advanced:
enable_ota_rollback: true
ignore_efuse_custom_mac: true
enable_lwip_mdns_queries: true
enable_lwip_bridge_interface: true
disable_libc_locks_in_iram: false # Test explicit opt-out of RAM optimization
-1
View File
@@ -1,6 +1,5 @@
esp32_ble:
io_capability: keyboard_only
advertising: true
button:
- platform: template
@@ -1,6 +0,0 @@
substitutions:
tx_pin: GPIO20
rx_pin: GPIO21
packages:
esp32_can: !include common.yaml
@@ -1,6 +0,0 @@
substitutions:
tx_pin: GPIO4
rx_pin: GPIO5
packages:
esp32_can: !include common.yaml
+1 -1
View File
@@ -1,4 +1,4 @@
output:
- platform: esp32_dac
id: dac_output
pin: ${dac_pin}
pin: 25
@@ -1,5 +1 @@
substitutions:
dac_pin: 25
packages:
esp32_dac: !include common.yaml
<<: !include common.yaml
@@ -1,5 +0,0 @@
substitutions:
dac_pin: 17
packages:
esp32_dac: !include common.yaml
-12
View File
@@ -4,15 +4,3 @@ sensor:
id: ph_ezo
address: 99
unit_of_measurement: pH
on_custom:
- logger.log: custom
on_calibration:
- logger.log: calibration
on_slope:
- logger.log: slope
on_t:
- logger.log: temperature compensation
on_device_information:
- logger.log: device information
on_led:
- logger.log: led
@@ -5,9 +5,3 @@ button:
factory_reset:
resets_required: 5
max_delay: 10s
on_increment:
- logger.log: Reset counter incremented
switch:
- platform: factory_reset
name: Factory Reset Switch
@@ -33,12 +33,3 @@ fan:
- logger.log:
format: "Preset %s"
args: ["x.c_str()"]
button:
- platform: template
name: Cycle Fan Speed
on_press:
- fan.cycle_speed: fan_triggers
- fan.cycle_speed:
id: fan_triggers
off_speed_cycle: false
@@ -0,0 +1,102 @@
#include <gtest/gtest.h>
#include "esphome/components/gpio_expander/cached_gpio_component.h"
namespace esphome::gpio_expander::testing {
class FakeComponent : public CachedGpioExpanderComponent<uint8_t, 8> {
public:
void setup() override { this->setup_interrupt_pin_(); }
void pin_mode(uint8_t pin, gpio::Flags flags) {
if (flags & gpio::FLAG_INPUT)
this->enable_polling_loop_();
}
unsigned reads{0};
protected:
bool digital_read_hw(uint8_t pin) override {
this->reads++;
return true;
}
bool digital_read_cache(uint8_t pin) override { return false; }
void digital_write_hw(uint8_t pin, bool value) override {}
};
class CachedGpioComponentTest : public ::testing::Test {
protected:
void SetUp() override { this->int_pin_.set_inverted(true); } // INT released
FakeComponent component_;
InternalGPIOPin int_pin_; // digital_read() returns the inverted flag on the host, so it doubles as the level
};
TEST_F(CachedGpioComponentTest, InputEnablesPollingOnlyWithoutAnInterruptPin) {
this->component_.setup();
ASSERT_TRUE(this->component_.is_idle());
this->component_.pin_mode(0, gpio::FLAG_OUTPUT);
EXPECT_TRUE(this->component_.is_idle());
this->component_.pin_mode(0, gpio::FLAG_INPUT);
EXPECT_TRUE(this->component_.is_in_loop_state());
FakeComponent with_interrupt;
with_interrupt.set_interrupt_pin(&this->int_pin_);
with_interrupt.setup();
ASSERT_TRUE(with_interrupt.is_idle());
with_interrupt.pin_mode(0, gpio::FLAG_INPUT);
EXPECT_TRUE(with_interrupt.is_idle());
}
TEST_F(CachedGpioComponentTest, WithoutAnInterruptPinEveryReadHitsTheChip) {
this->component_.setup();
this->component_.digital_read(0);
this->component_.digital_read(0);
EXPECT_EQ(this->component_.reads, 2u);
}
TEST_F(CachedGpioComponentTest, InterruptPinKeepsTheCacheUntilTheLoopRuns) {
this->component_.set_interrupt_pin(&this->int_pin_);
this->component_.setup();
this->component_.digital_read(0);
this->component_.digital_read(0);
EXPECT_EQ(this->component_.reads, 1u);
this->component_.loop();
this->component_.digital_read(0);
EXPECT_EQ(this->component_.reads, 2u);
}
TEST_F(CachedGpioComponentTest, LoopStaysOnUntilIntIsReleased) {
this->component_.set_interrupt_pin(&this->int_pin_);
this->component_.setup();
this->component_.enable_loop();
ASSERT_TRUE(this->component_.is_in_loop_state());
this->int_pin_.set_inverted(false); // INT still low
this->component_.loop();
EXPECT_TRUE(this->component_.is_in_loop_state());
this->int_pin_.set_inverted(true); // INT released
this->component_.loop();
EXPECT_TRUE(this->component_.is_idle());
}
TEST_F(CachedGpioComponentTest, IntAlreadyLowAtSetupRunsTheLoop) {
this->int_pin_.set_inverted(false); // INT asserted before the ISR is attached
this->component_.set_interrupt_pin(&this->int_pin_);
this->component_.setup();
EXPECT_TRUE(this->component_.is_in_loop_state());
this->int_pin_.set_inverted(true);
this->component_.loop();
EXPECT_TRUE(this->component_.is_idle());
}
TEST_F(CachedGpioComponentTest, PollingLoopNeverDisablesItself) {
this->component_.setup();
this->component_.pin_mode(0, gpio::FLAG_INPUT);
this->component_.loop();
EXPECT_TRUE(this->component_.is_in_loop_state());
}
} // namespace esphome::gpio_expander::testing
-4
View File
@@ -1,4 +0,0 @@
text_sensor:
- platform: libretiny
version:
name: LibreTiny Version
@@ -1,6 +1,3 @@
packages:
libretiny: !include common.yaml
logger:
level: VERBOSE
@@ -1,5 +1,2 @@
packages:
libretiny: !include common.yaml
logger:
level: VERBOSE
@@ -1,5 +1,2 @@
packages:
libretiny: !include common.yaml
logger:
level: VERBOSE
-4
View File
@@ -7,7 +7,3 @@ sensor:
integration_time: 100ms
ambient_light: "Ambient light"
ps_counts: "Proximity counts"
on_ps_high_threshold:
- logger.log: PS high
on_ps_low_threshold:
- logger.log: PS low
-4
View File
@@ -9,7 +9,3 @@ sensor:
full_spectrum_counts: "Full spectrum counts"
infrared_counts: "Infrared counts"
actual_gain: "Actual gain"
on_ps_high_threshold:
- logger.log: PS high
on_ps_low_threshold:
- logger.log: PS low
-27
View File
@@ -90,25 +90,6 @@ binary_sensor:
- lvgl.page.previous:
animation: move_right
time: 600ms
- lvgl.page.show: page1
- lvgl.page.show:
id: page1
animation: fade_in
time: 300ms
- lvgl.animimg.start: anim_img
- lvgl.tileview.select:
id: tileview_id
tile_id: tile_1
animated: true
- lvgl.tileview.select:
id: tileview_id
row: 0
column: 0
- if:
condition:
lvgl.is_paused:
then:
- logger.log: LVGL is paused
- platform: lvgl
id: common_button_checker
name: Common button
@@ -144,9 +125,6 @@ text:
mode: text
text_sensor:
- platform: lvgl
widget: hello_label
name: LVGL Label Text
- platform: template
id: test_text_sensor
name: "Test Text Sensor"
@@ -163,8 +141,3 @@ text_sensor:
return "WILDCARD";
}
return x.c_str();
switch:
- platform: lvgl
widget: checkbox_id
name: LVGL Checkbox Switch
-6
View File
@@ -13,9 +13,3 @@ binary_sensor:
input: true
pullup: true
inverted: false
output:
- platform: max6956
id: max6956_output
max6956: max6956_1
pin: 5
-6
View File
@@ -25,9 +25,3 @@ sensor:
max_ppm: 500.0
hydrogen:
name: MiCS-5524 H2
button:
- platform: template
name: MiCS-5524 Calibrate
on_press:
- mics_5524.request_calibration: mics_5524_module
@@ -6,9 +6,6 @@ modbus_controller:
on_online:
then:
logger.log: "Module Online"
on_command_sent:
then:
logger.log: "Command sent"
binary_sensor:
- platform: modbus_controller
modbus_controller_id: modbus_controller1
-19
View File
@@ -1,19 +0,0 @@
esphome:
on_boot:
priority: -100 # the modem is set up by then, so send_at reaches the DCE
then:
- modem.disable
- modem.enable
- if:
condition:
modem.enabled:
then:
modem.send_at: "AT+CGSN"
else:
modem.enable:
- if:
condition:
not:
modem.connected:
then:
logger.log: modem not connected
@@ -1,32 +0,0 @@
logger:
# No wifi: a cellular-only node must satisfy the api network dependency
api:
modem:
id: atmodem
rx_pin: GPIO26
tx_pin: GPIO27
rts_pin: GPIO25
cts_pin: GPIO33
baud_rate: 115200
model: SIM7600
apn: orange
pin_code: "0000"
init_at:
- ATI
reboot_timeout: 5min
on_connect:
logger.log: "modem connected"
on_disconnect:
logger.log: "modem disconnected"
on_sync:
logger.log: "modem synced - ready for AT commands"
text_sensor:
- platform: modem
urc:
name: Modem URC
packages:
modem: !include common.yaml
-1
View File
@@ -345,4 +345,3 @@ display:
touch_sleep_timeout: 3
update_interval: 5s
wake_up_page: 2
skip_connection_handshake: true
@@ -1,3 +0,0 @@
packages:
uart: !include ../../test_build_components/common/uart/bk72xx-ard.yaml
base: !include common.yaml
-3
View File
@@ -12,9 +12,6 @@ ota:
on_end:
then:
- logger.log: "OTA end"
on_abort:
then:
- logger.log: "OTA abort"
on_error:
then:
- logger.log:
-11
View File
@@ -20,14 +20,3 @@ output:
- platform: ${output_platform}
id: output_light_output_1
pin: ${pin}
switch:
- platform: output
name: Output Switch
output: output_light_output_1
button:
- platform: output
name: Output Button
output: output_light_output_1
duration: 500ms
@@ -69,6 +69,7 @@ class PCA9505Test : public ::testing::Test {
void SetUp() override {
this->component_.set_i2c_bus(&this->bus_);
this->component_.set_i2c_address(FakeBus::ADDRESS);
this->int_pin_.set_inverted(true); // INT released
}
PCA9505GPIOPin make_pin_(uint8_t number, gpio::Flags flags, bool inverted = false) {
-6
View File
@@ -15,12 +15,6 @@ esphome:
- tag.emulation_on: nfcc_pn7150
- tag.polling_off: nfcc_pn7150
- tag.polling_on: nfcc_pn7150
- if:
condition:
pn7150.is_writing:
id: nfcc_pn7150
then:
- logger.log: Writing
pn7150_i2c:
id: nfcc_pn7150
-6
View File
@@ -8,12 +8,6 @@ esphome:
message: https://www.home-assistant.io/tag/pulse
include_android_app_record: false
- tag.set_write_mode: nfcc_pn7160_i2c
- if:
condition:
pn7160.is_writing:
id: nfcc_pn7160_i2c
then:
- logger.log: Writing
- tag.set_emulation_message:
message: https://www.home-assistant.io/tag/pulse
include_android_app_record: false
-5
View File
@@ -7,8 +7,3 @@ binary_sensor:
rc522_id: rc522_nfcc_spi
name: RC522 NFC Tag
uid: 74-10-37-94
- platform: rc522_spi
rc522_id: rc522_nfcc_spi
name: RC522 SPI NFC Tag
uid: 74-10-37-95
@@ -1,29 +0,0 @@
zephyr_ble_server:
ota:
- platform: zephyr_mcumgr
transport:
ble: true
hardware_uart: CDC
safe_mode:
id: safe_mode_1
boot_is_good_after: 2min
num_attempts: 3
reboot_timeout: 2min
on_safe_mode:
- logger.log: Time for safe mode
button:
- platform: safe_mode
name: Safe Mode Button
switch:
- platform: safe_mode
name: Safe Mode Switch
esphome:
on_boot:
- safe_mode.mark_successful
- safe_mode.mark_successful:
id: safe_mode_1
-6
View File
@@ -73,9 +73,3 @@ interval:
- interval: 10s
then:
- snapshot.take: sdl_sdl_display
touchscreen:
- platform: sdl
id: sdl_touch
sdl_id: sdl_sdl_display
display: sdl_sdl_display
-14
View File
@@ -29,17 +29,3 @@ sim800l:
- sim800l.disconnect
on_ussd_received:
- logger.log: "ussd_received"
on_call_connected:
- logger.log: "call connected"
on_call_disconnected:
- logger.log: "call disconnected"
binary_sensor:
- platform: sim800l
registered:
name: SIM800L Registered
sensor:
- platform: sim800l
rssi:
name: SIM800L RSSI
-7
View File
@@ -57,10 +57,3 @@ button:
# Same bytes as above on purpose: equal payloads share one flash table
- sx126x.send_packet: [0xC5, 0x51, 0x78, 0x82, 0xB7, 0xF9, 0x9C, 0x5C]
- sx126x.send_packet: "hello"
packet_transport:
- platform: sx126x
id: sx126x_transport
update_interval: 5s
providers:
- name: other-device
-7
View File
@@ -57,10 +57,3 @@ button:
return {0x01, 0x02, (uint8_t)id(sx127x_my_number).state};
- sx127x.send_packet: [0xC5, 0x51, 0x78, 0x82, 0xB7, 0xF9, 0x9C, 0x5C]
- sx127x.send_packet: "hello"
packet_transport:
- platform: sx127x
id: sx127x_transport
update_interval: 5s
providers:
- name: other-device
Loaded 100 of 111 files, more files were not shown because too many files have changed in this diff. Show more