Merge remote-tracking branch 'upstream/dev' into integration

# Conflicts:
#	esphome/components/ethernet/__init__.py
#	esphome/components/ethernet/ethernet_component_esp32.cpp
This commit is contained in:
J. Nick Koston
2026-03-23 12:45:25 -10:00
18 changed files with 262 additions and 11 deletions
+1
View File
@@ -459,6 +459,7 @@ esphome/components/sonoff_d1/* @anatoly-savchenkov
esphome/components/sound_level/* @kahrendt
esphome/components/spa06_base/* @danielkent-net
esphome/components/spa06_i2c/* @danielkent-net
esphome/components/spa06_spi/* @danielkent-net
esphome/components/speaker/* @jesserockz @kahrendt
esphome/components/speaker/media_player/* @kahrendt @synesthesiam
esphome/components/speaker_source/* @kahrendt
+20 -7
View File
@@ -157,8 +157,13 @@ _IDF6_ETHERNET_COMPONENTS: dict[str, IDFRegistryComponent] = {
"KSZ8081RNA": IDFRegistryComponent("espressif/ksz80xx", "1.0.0"),
"W5500": IDFRegistryComponent("espressif/w5500", "1.0.1"),
"DM9051": IDFRegistryComponent("espressif/dm9051", "1.0.0"),
"ENC28J60": IDFRegistryComponent("espressif/enc28j60", "1.0.1"),
"LAN8670": IDFRegistryComponent("espressif/lan867x", "2.0.0"),
}
# These types are always external IDF components (never built-in to ESP-IDF)
_ALWAYS_EXTERNAL_IDF_COMPONENTS = {"LAN8670", "ENC28J60"}
SPI_ETHERNET_TYPES = ["W5500", "DM9051", "ENC28J60"]
# RP2040-supported SPI ethernet types
RP2040_SPI_ETHERNET_TYPES = ["W5500", "ENC28J60"]
@@ -222,7 +227,18 @@ def _validate(config):
if CORE.is_esp32:
if config[CONF_TYPE] in SPI_ETHERNET_TYPES:
if _is_framework_spi_polling_mode_supported():
# ENC28J60 driver does not support polling mode - interrupt is required
if config[CONF_TYPE] == "ENC28J60":
if CONF_POLLING_INTERVAL in config:
raise cv.Invalid(
f"'{CONF_POLLING_INTERVAL}' is not supported for ENC28J60. "
f"'{CONF_INTERRUPT_PIN}' is required."
)
if CONF_INTERRUPT_PIN not in config:
raise cv.Invalid(
f"'{CONF_INTERRUPT_PIN}' is a required option for ENC28J60."
)
elif _is_framework_spi_polling_mode_supported():
if CONF_POLLING_INTERVAL in config and CONF_INTERRUPT_PIN in config:
raise cv.Invalid(
f"Cannot specify more than one of {CONF_INTERRUPT_PIN}, {CONF_POLLING_INTERVAL}"
@@ -537,12 +553,9 @@ async def _to_code_esp32(var: cg.Pvariable, config: ConfigType) -> None:
# Re-enable ESP-IDF's Ethernet driver (excluded by default to save compile time)
include_builtin_idf_component("esp_eth")
if config[CONF_TYPE] == "LAN8670":
# Add LAN867x 10BASE-T1S PHY support component
add_idf_component(name="espressif/lan867x", ref="2.0.0")
elif config[CONF_TYPE] == "ENC28J60":
# ENC28J60 is always an external component (never built-in to ESP-IDF)
add_idf_component(name="espressif/enc28j60", ref="1.0.1")
if config[CONF_TYPE] in _ALWAYS_EXTERNAL_IDF_COMPONENTS:
component = _IDF6_ETHERNET_COMPONENTS[config[CONF_TYPE]]
add_idf_component(name=component.name, ref=component.version)
elif idf_version() >= cv.Version(6, 0, 0) and (
# IDF 6.0 moved per-chip PHY/MAC drivers to the Espressif Component Registry
component := _IDF6_ETHERNET_COMPONENTS.get(config[CONF_TYPE])
@@ -219,9 +219,7 @@ void EthernetComponent::setup() {
#endif
#elif defined(USE_ETHERNET_ENC28J60)
enc28j60_config.int_gpio_num = this->interrupt_pin_;
#ifdef USE_ETHERNET_SPI_POLLING_SUPPORT
enc28j60_config.poll_period_ms = this->polling_interval_;
#endif
// ENC28J60 does not support poll_period_ms
#endif
phy_config.phy_addr = this->phy_addr_spi_;
+41
View File
@@ -0,0 +1,41 @@
import logging
import esphome.codegen as cg
from esphome.components import spi
from esphome.components.spi import CONF_SPI_MODE
import esphome.config_validation as cv
from ..spa06_base import CONFIG_SCHEMA_BASE, to_code_base
AUTO_LOAD = ["spa06_base"]
CODEOWNERS = ["@danielkent-net"]
DEPENDENCIES = ["spi"]
spa06_ns = cg.esphome_ns.namespace("spa06_spi")
SPA06SPIComponent = spa06_ns.class_(
"SPA06SPIComponent", cg.PollingComponent, spi.SPIDevice
)
_LOGGER = logging.getLogger(__name__)
VALID_SPI_MODES = {3: "MODE3", "3": "MODE3", "MODE3": "MODE3"}
def check_spi_mode(config):
spi_mode = config.get(CONF_SPI_MODE)
if spi_mode not in VALID_SPI_MODES:
raise cv.Invalid("SPA06 only supports SPI mode 3")
return config
CONFIG_SCHEMA = cv.All(
CONFIG_SCHEMA_BASE.extend(spi.spi_device_schema(default_mode="mode3")).extend(
{cv.GenerateID(): cv.declare_id(SPA06SPIComponent)}
),
check_spi_mode,
)
async def to_code(config):
var = await to_code_base(config)
await spi.register_spi_device(var, config)
@@ -0,0 +1,72 @@
#include <cstdint>
#include <cstddef>
#include "spa06_spi.h"
#include "esphome/components/spa06_base/spa06_base.h"
#include "esphome/components/spi/spi.h"
// OR (|) register with SPA06_SPI_READ for read.
inline constexpr uint8_t SPA06_SPI_READ = 0x80;
// AND (&) register with SPA06_SPI_WRITE for write.
inline constexpr uint8_t SPA06_SPI_WRITE = 0x7F;
namespace esphome::spa06_spi {
static const char *const TAG = "spa06_spi";
void SPA06SPIComponent::dump_config() {
SPA06Component::dump_config();
LOG_SPI_DEVICE(this)
}
void SPA06SPIComponent::setup() {
this->spi_setup();
SPA06Component::setup();
}
void SPA06SPIComponent::protocol_reset() {
// Forces the device into SPI mode using a dummy read
uint8_t dummy_read = 0;
this->spa_read_byte(spa06_base::SPA06_ID, &dummy_read);
}
// In SPI mode, only 7 bits of the register addresses are used; the MSB of register address
// is not used and replaced by a read/write bit (RW = 0 for write and RW = 1 for read).
// Example: address 0xF7 is accessed by using SPI register address 0x77. For write access,
// the byte 0x77 is transferred, for read access, the byte 0xF7 is transferred.
// The expressions SPA06_SPI_READ (| with register) and SPA06_SPI_WRITE (& with register)
// are defined for readability.
bool SPA06SPIComponent::spa_read_byte(uint8_t a_register, uint8_t *data) {
this->enable();
this->transfer_byte(a_register | SPA06_SPI_READ);
*data = this->transfer_byte(0);
this->disable();
return true;
}
bool SPA06SPIComponent::spa_write_byte(uint8_t a_register, uint8_t data) {
this->enable();
this->transfer_byte(a_register & SPA06_SPI_WRITE);
this->transfer_byte(data);
this->disable();
return true;
}
bool SPA06SPIComponent::spa_read_bytes(uint8_t a_register, uint8_t *data, size_t len) {
this->enable();
this->transfer_byte(a_register | SPA06_SPI_READ);
this->read_array(data, len);
this->disable();
return true;
}
bool SPA06SPIComponent::spa_write_bytes(uint8_t a_register, uint8_t *data, size_t len) {
this->enable();
this->transfer_byte(a_register & SPA06_SPI_WRITE);
this->write_array(data, len);
this->disable();
return true;
}
} // namespace esphome::spa06_spi
+22
View File
@@ -0,0 +1,22 @@
#pragma once
#include "esphome/components/spa06_base/spa06_base.h"
#include "esphome/components/spi/spi.h"
namespace esphome::spa06_spi {
class SPA06SPIComponent : public spa06_base::SPA06Component,
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_HIGH,
spi::CLOCK_PHASE_TRAILING, spi::DATA_RATE_200KHZ> {
void setup() override;
bool spa_read_byte(uint8_t a_register, uint8_t *data) override;
bool spa_write_byte(uint8_t a_register, uint8_t data) override;
bool spa_read_bytes(uint8_t a_register, uint8_t *data, size_t len) override;
bool spa_write_bytes(uint8_t a_register, uint8_t *data, size_t len) override;
void dump_config() override;
protected:
void protocol_reset() override;
};
} // namespace esphome::spa06_spi
+2
View File
@@ -34,6 +34,8 @@ using SPIInterface = void *; // Stub for platforms without SPI (e.g., Zephyr)
*/
namespace esphome::spi {
#define LOG_SPI_DEVICE(this) ESP_LOGCONFIG(TAG, " CS Pin: %d", esphome::spi::Utility::get_pin_no(this->cs_));
/// The bit-order for SPI devices. This defines how the data read from and written to the device is interpreted.
enum SPIBitOrder {
/// The least significant bit is transmitted/received first.
@@ -0,0 +1,25 @@
import esphome.codegen as cg
from esphome.components import text_sensor
import esphome.config_validation as cv
from esphome.const import CONF_SOURCE_ID
from .. import Text, text_ns
TextTextSensor = text_ns.class_("TextTextSensor", text_sensor.TextSensor, cg.Component)
CONFIG_SCHEMA = (
text_sensor.text_sensor_schema(TextTextSensor)
.extend(
{
cv.Required(CONF_SOURCE_ID): cv.use_id(Text),
}
)
.extend(cv.COMPONENT_SCHEMA)
)
async def to_code(config):
source = await cg.get_variable(config[CONF_SOURCE_ID])
var = await text_sensor.new_text_sensor(config, source)
await cg.register_component(var, config)
@@ -0,0 +1,16 @@
#include "text_text_sensor.h"
#include "esphome/core/log.h"
namespace esphome::text {
static const char *const TAG = "text.text_sensor";
void TextTextSensor::setup() {
this->source_->add_on_state_callback([this](const std::string &value) { this->publish_state(value); });
if (this->source_->has_state())
this->publish_state(this->source_->state);
}
void TextTextSensor::dump_config() { LOG_TEXT_SENSOR("", "Text Text Sensor", this); }
} // namespace esphome::text
@@ -0,0 +1,19 @@
#pragma once
#include "../text.h"
#include "esphome/core/component.h"
#include "esphome/components/text_sensor/text_sensor.h"
namespace esphome::text {
class TextTextSensor : public text_sensor::TextSensor, public Component {
public:
explicit TextTextSensor(Text *source) : source_(source) {}
void setup() override;
void dump_config() override;
protected:
Text *source_;
};
} // namespace esphome::text
+1 -1
View File
@@ -6,7 +6,7 @@ pre-commit
# Unit tests
pytest==9.0.2
pytest-cov==7.0.0
pytest-cov==7.1.0
pytest-mock==3.15.1
pytest-asyncio==1.3.0
pytest-xdist==3.8.0
+1
View File
@@ -963,6 +963,7 @@ def lint_log_multiline_continuation(fname, content):
"esphome/components/nextion/nextion_base.h",
"esphome/components/select/select.h",
"esphome/components/sensor/sensor.h",
"esphome/components/spi/spi.h",
"esphome/components/stepper/stepper.h",
"esphome/components/switch/switch.h",
"esphome/components/text/text.h",
+15
View File
@@ -0,0 +1,15 @@
sensor:
- platform: spa06_spi
spi_id: spi_bus
cs_pin: ${cs_pin}
temperature:
id: spa06_spi_temperature
name: Outside Temperature
sample_rate: 1
oversampling: NONE
pressure:
name: Outside Pressure
id: spa06_spi_pressure
sample_rate: 25p4
oversampling: 16X
update_interval: 15s
@@ -0,0 +1,7 @@
substitutions:
cs_pin: GPIO5
packages:
spi: !include ../../test_build_components/common/spi/esp32-idf.yaml
<<: !include common.yaml
@@ -0,0 +1,7 @@
substitutions:
cs_pin: GPIO15
packages:
spi: !include ../../test_build_components/common/spi/esp8266-ard.yaml
<<: !include common.yaml
@@ -0,0 +1,7 @@
substitutions:
cs_pin: GPIO17
packages:
spi: !include ../../test_build_components/common/spi/rp2040-ard.yaml
<<: !include common.yaml
+5
View File
@@ -23,3 +23,8 @@ text:
min_length: 8
max_length: 32
mode: password
text_sensor:
- platform: text
name: "Test Text State"
source_id: test_text