Merge branch 'dev' into esp32_touch_isr

This commit is contained in:
J. Nick Koston 2025-06-30 00:59:07 -05:00 committed by GitHub
commit dcbdc0ac51
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
45 changed files with 787 additions and 161 deletions

View File

@ -248,6 +248,7 @@ esphome/components/libretiny_pwm/* @kuba2k2
esphome/components/light/* @esphome/core esphome/components/light/* @esphome/core
esphome/components/lightwaverf/* @max246 esphome/components/lightwaverf/* @max246
esphome/components/lilygo_t5_47/touchscreen/* @jesserockz esphome/components/lilygo_t5_47/touchscreen/* @jesserockz
esphome/components/ln882x/* @lamauny
esphome/components/lock/* @esphome/core esphome/components/lock/* @esphome/core
esphome/components/logger/* @esphome/core esphome/components/logger/* @esphome/core
esphome/components/logger/select/* @clydebarrow esphome/components/logger/select/* @clydebarrow

View File

@ -34,11 +34,9 @@ from esphome.const import (
CONF_PORT, CONF_PORT,
CONF_SUBSTITUTIONS, CONF_SUBSTITUTIONS,
CONF_TOPIC, CONF_TOPIC,
PLATFORM_BK72XX,
PLATFORM_ESP32, PLATFORM_ESP32,
PLATFORM_ESP8266, PLATFORM_ESP8266,
PLATFORM_RP2040, PLATFORM_RP2040,
PLATFORM_RTL87XX,
SECRETS_FILES, SECRETS_FILES,
) )
from esphome.core import CORE, EsphomeError, coroutine from esphome.core import CORE, EsphomeError, coroutine
@ -354,7 +352,7 @@ def upload_program(config, args, host):
if CORE.target_platform in (PLATFORM_RP2040): if CORE.target_platform in (PLATFORM_RP2040):
return upload_using_platformio(config, args.device) return upload_using_platformio(config, args.device)
if CORE.target_platform in (PLATFORM_BK72XX, PLATFORM_RTL87XX): if CORE.is_libretiny:
return upload_using_platformio(config, host) return upload_using_platformio(config, host)
return 1 # Unknown target platform return 1 # Unknown target platform

View File

@ -1537,6 +1537,8 @@ DeviceInfoResponse APIConnection::device_info(const DeviceInfoRequest &msg) {
resp.manufacturer = "Raspberry Pi"; resp.manufacturer = "Raspberry Pi";
#elif defined(USE_BK72XX) #elif defined(USE_BK72XX)
resp.manufacturer = "Beken"; resp.manufacturer = "Beken";
#elif defined(USE_LN882X)
resp.manufacturer = "Lightning";
#elif defined(USE_RTL87XX) #elif defined(USE_RTL87XX)
resp.manufacturer = "Realtek"; resp.manufacturer = "Realtek";
#elif defined(USE_HOST) #elif defined(USE_HOST)

View File

@ -5,6 +5,7 @@ from esphome.const import (
PLATFORM_BK72XX, PLATFORM_BK72XX,
PLATFORM_ESP32, PLATFORM_ESP32,
PLATFORM_ESP8266, PLATFORM_ESP8266,
PLATFORM_LN882X,
PLATFORM_RTL87XX, PLATFORM_RTL87XX,
) )
from esphome.core import CORE, coroutine_with_priority from esphome.core import CORE, coroutine_with_priority
@ -14,7 +15,15 @@ CODEOWNERS = ["@OttoWinter"]
CONFIG_SCHEMA = cv.All( CONFIG_SCHEMA = cv.All(
cv.Schema({}), cv.Schema({}),
cv.only_with_arduino, cv.only_with_arduino,
cv.only_on([PLATFORM_ESP32, PLATFORM_ESP8266, PLATFORM_BK72XX, PLATFORM_RTL87XX]), cv.only_on(
[
PLATFORM_ESP32,
PLATFORM_ESP8266,
PLATFORM_BK72XX,
PLATFORM_LN882X,
PLATFORM_RTL87XX,
]
),
) )

View File

@ -7,6 +7,7 @@ from esphome.const import (
PLATFORM_BK72XX, PLATFORM_BK72XX,
PLATFORM_ESP32, PLATFORM_ESP32,
PLATFORM_ESP8266, PLATFORM_ESP8266,
PLATFORM_LN882X,
PLATFORM_RTL87XX, PLATFORM_RTL87XX,
) )
from esphome.core import CORE, coroutine_with_priority from esphome.core import CORE, coroutine_with_priority
@ -27,7 +28,15 @@ CONFIG_SCHEMA = cv.All(
), ),
} }
).extend(cv.COMPONENT_SCHEMA), ).extend(cv.COMPONENT_SCHEMA),
cv.only_on([PLATFORM_ESP32, PLATFORM_ESP8266, PLATFORM_BK72XX, PLATFORM_RTL87XX]), cv.only_on(
[
PLATFORM_ESP32,
PLATFORM_ESP8266,
PLATFORM_BK72XX,
PLATFORM_LN882X,
PLATFORM_RTL87XX,
]
),
) )

View File

@ -100,6 +100,7 @@ CONFIG_SCHEMA = (
esp32=3232, esp32=3232,
rp2040=2040, rp2040=2040,
bk72xx=8892, bk72xx=8892,
ln882x=8820,
rtl87xx=8892, rtl87xx=8892,
): cv.port, ): cv.port,
cv.Optional(CONF_PASSWORD): cv.string, cv.Optional(CONF_PASSWORD): cv.string,

View File

@ -66,9 +66,10 @@ ETHERNET_TYPES = {
"KSZ8081RNA": EthernetType.ETHERNET_TYPE_KSZ8081RNA, "KSZ8081RNA": EthernetType.ETHERNET_TYPE_KSZ8081RNA,
"W5500": EthernetType.ETHERNET_TYPE_W5500, "W5500": EthernetType.ETHERNET_TYPE_W5500,
"OPENETH": EthernetType.ETHERNET_TYPE_OPENETH, "OPENETH": EthernetType.ETHERNET_TYPE_OPENETH,
"DM9051": EthernetType.ETHERNET_TYPE_DM9051,
} }
SPI_ETHERNET_TYPES = ["W5500"] SPI_ETHERNET_TYPES = ["W5500", "DM9051"]
SPI_ETHERNET_DEFAULT_POLLING_INTERVAL = TimePeriodMilliseconds(milliseconds=10) SPI_ETHERNET_DEFAULT_POLLING_INTERVAL = TimePeriodMilliseconds(milliseconds=10)
emac_rmii_clock_mode_t = cg.global_ns.enum("emac_rmii_clock_mode_t") emac_rmii_clock_mode_t = cg.global_ns.enum("emac_rmii_clock_mode_t")
@ -224,6 +225,7 @@ CONFIG_SCHEMA = cv.All(
"KSZ8081RNA": RMII_SCHEMA, "KSZ8081RNA": RMII_SCHEMA,
"W5500": SPI_SCHEMA, "W5500": SPI_SCHEMA,
"OPENETH": BASE_SCHEMA, "OPENETH": BASE_SCHEMA,
"DM9051": SPI_SCHEMA,
}, },
upper=True, upper=True,
), ),
@ -278,7 +280,7 @@ async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID]) var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config) await cg.register_component(var, config)
if config[CONF_TYPE] == "W5500": if config[CONF_TYPE] in SPI_ETHERNET_TYPES:
cg.add(var.set_clk_pin(config[CONF_CLK_PIN])) cg.add(var.set_clk_pin(config[CONF_CLK_PIN]))
cg.add(var.set_miso_pin(config[CONF_MISO_PIN])) cg.add(var.set_miso_pin(config[CONF_MISO_PIN]))
cg.add(var.set_mosi_pin(config[CONF_MOSI_PIN])) cg.add(var.set_mosi_pin(config[CONF_MOSI_PIN]))
@ -296,7 +298,9 @@ async def to_code(config):
cg.add_define("USE_ETHERNET_SPI") cg.add_define("USE_ETHERNET_SPI")
if CORE.using_esp_idf: if CORE.using_esp_idf:
add_idf_sdkconfig_option("CONFIG_ETH_USE_SPI_ETHERNET", True) add_idf_sdkconfig_option("CONFIG_ETH_USE_SPI_ETHERNET", True)
add_idf_sdkconfig_option("CONFIG_ETH_SPI_ETHERNET_W5500", True) add_idf_sdkconfig_option(
f"CONFIG_ETH_SPI_ETHERNET_{config[CONF_TYPE]}", True
)
elif config[CONF_TYPE] == "OPENETH": elif config[CONF_TYPE] == "OPENETH":
cg.add_define("USE_ETHERNET_OPENETH") cg.add_define("USE_ETHERNET_OPENETH")
add_idf_sdkconfig_option("CONFIG_ETH_USE_OPENETH", True) add_idf_sdkconfig_option("CONFIG_ETH_USE_OPENETH", True)

View File

@ -90,8 +90,8 @@ void EthernetComponent::setup() {
#ifdef USE_ETHERNET_SPI // Configure SPI interface and Ethernet driver for specific SPI module #ifdef USE_ETHERNET_SPI // Configure SPI interface and Ethernet driver for specific SPI module
spi_device_interface_config_t devcfg = { spi_device_interface_config_t devcfg = {
.command_bits = 16, // Actually it's the address phase in W5500 SPI frame .command_bits = 0,
.address_bits = 8, // Actually it's the control phase in W5500 SPI frame .address_bits = 0,
.dummy_bits = 0, .dummy_bits = 0,
.mode = 0, .mode = 0,
.duty_cycle_pos = 0, .duty_cycle_pos = 0,
@ -107,22 +107,43 @@ void EthernetComponent::setup() {
}; };
#if ESP_IDF_VERSION_MAJOR >= 5 #if ESP_IDF_VERSION_MAJOR >= 5
#if CONFIG_ETH_SPI_ETHERNET_W5500
eth_w5500_config_t w5500_config = ETH_W5500_DEFAULT_CONFIG(host, &devcfg); eth_w5500_config_t w5500_config = ETH_W5500_DEFAULT_CONFIG(host, &devcfg);
#endif
#if CONFIG_ETH_SPI_ETHERNET_DM9051
eth_dm9051_config_t dm9051_config = ETH_DM9051_DEFAULT_CONFIG(host, &devcfg);
#endif
#else #else
spi_device_handle_t spi_handle = nullptr; spi_device_handle_t spi_handle = nullptr;
err = spi_bus_add_device(host, &devcfg, &spi_handle); err = spi_bus_add_device(host, &devcfg, &spi_handle);
ESPHL_ERROR_CHECK(err, "SPI bus add device error"); ESPHL_ERROR_CHECK(err, "SPI bus add device error");
#if CONFIG_ETH_SPI_ETHERNET_W5500
eth_w5500_config_t w5500_config = ETH_W5500_DEFAULT_CONFIG(spi_handle); eth_w5500_config_t w5500_config = ETH_W5500_DEFAULT_CONFIG(spi_handle);
#endif #endif
#if CONFIG_ETH_SPI_ETHERNET_DM9051
eth_dm9051_config_t dm9051_config = ETH_DM9051_DEFAULT_CONFIG(spi_handle);
#endif
#endif // ESP_IDF_VERSION_MAJOR >= 5
#if CONFIG_ETH_SPI_ETHERNET_W5500
w5500_config.int_gpio_num = this->interrupt_pin_; w5500_config.int_gpio_num = this->interrupt_pin_;
#ifdef USE_ETHERNET_SPI_POLLING_SUPPORT #ifdef USE_ETHERNET_SPI_POLLING_SUPPORT
w5500_config.poll_period_ms = this->polling_interval_; w5500_config.poll_period_ms = this->polling_interval_;
#endif #endif
#endif
#if CONFIG_ETH_SPI_ETHERNET_DM9051
dm9051_config.int_gpio_num = this->interrupt_pin_;
#ifdef USE_ETHERNET_SPI_POLLING_SUPPORT
dm9051_config.poll_period_ms = this->polling_interval_;
#endif
#endif
phy_config.phy_addr = this->phy_addr_spi_; phy_config.phy_addr = this->phy_addr_spi_;
phy_config.reset_gpio_num = this->reset_pin_; phy_config.reset_gpio_num = this->reset_pin_;
esp_eth_mac_t *mac = esp_eth_mac_new_w5500(&w5500_config, &mac_config); esp_eth_mac_t *mac = nullptr;
#elif defined(USE_ETHERNET_OPENETH) #elif defined(USE_ETHERNET_OPENETH)
esp_eth_mac_t *mac = esp_eth_mac_new_openeth(&mac_config); esp_eth_mac_t *mac = esp_eth_mac_new_openeth(&mac_config);
#else #else
@ -187,10 +208,20 @@ void EthernetComponent::setup() {
} }
#endif #endif
#ifdef USE_ETHERNET_SPI #ifdef USE_ETHERNET_SPI
#if CONFIG_ETH_SPI_ETHERNET_W5500
case ETHERNET_TYPE_W5500: { case ETHERNET_TYPE_W5500: {
mac = esp_eth_mac_new_w5500(&w5500_config, &mac_config);
this->phy_ = esp_eth_phy_new_w5500(&phy_config); this->phy_ = esp_eth_phy_new_w5500(&phy_config);
break; break;
} }
#endif
#if CONFIG_ETH_SPI_ETHERNET_DM9051
case ETHERNET_TYPE_DM9051: {
mac = esp_eth_mac_new_dm9051(&dm9051_config, &mac_config);
this->phy_ = esp_eth_phy_new_dm9051(&phy_config);
break;
}
#endif
#endif #endif
default: { default: {
this->mark_failed(); this->mark_failed();
@ -321,6 +352,10 @@ void EthernetComponent::dump_config() {
eth_type = "OPENETH"; eth_type = "OPENETH";
break; break;
case ETHERNET_TYPE_DM9051:
eth_type = "DM9051";
break;
default: default:
eth_type = "Unknown"; eth_type = "Unknown";
break; break;

View File

@ -26,6 +26,7 @@ enum EthernetType : uint8_t {
ETHERNET_TYPE_KSZ8081RNA, ETHERNET_TYPE_KSZ8081RNA,
ETHERNET_TYPE_W5500, ETHERNET_TYPE_W5500,
ETHERNET_TYPE_OPENETH, ETHERNET_TYPE_OPENETH,
ETHERNET_TYPE_DM9051,
}; };
struct ManualIP { struct ManualIP {

View File

@ -50,6 +50,7 @@ KEY_FAMILY = "family"
# COMPONENTS - auto-generated! Do not modify this block. # COMPONENTS - auto-generated! Do not modify this block.
COMPONENT_BK72XX = "bk72xx" COMPONENT_BK72XX = "bk72xx"
COMPONENT_LN882X = "ln882x"
COMPONENT_RTL87XX = "rtl87xx" COMPONENT_RTL87XX = "rtl87xx"
# COMPONENTS - end # COMPONENTS - end
@ -58,6 +59,7 @@ FAMILY_BK7231N = "BK7231N"
FAMILY_BK7231Q = "BK7231Q" FAMILY_BK7231Q = "BK7231Q"
FAMILY_BK7231T = "BK7231T" FAMILY_BK7231T = "BK7231T"
FAMILY_BK7251 = "BK7251" FAMILY_BK7251 = "BK7251"
FAMILY_LN882H = "LN882H"
FAMILY_RTL8710B = "RTL8710B" FAMILY_RTL8710B = "RTL8710B"
FAMILY_RTL8720C = "RTL8720C" FAMILY_RTL8720C = "RTL8720C"
FAMILIES = [ FAMILIES = [
@ -65,6 +67,7 @@ FAMILIES = [
FAMILY_BK7231Q, FAMILY_BK7231Q,
FAMILY_BK7231T, FAMILY_BK7231T,
FAMILY_BK7251, FAMILY_BK7251,
FAMILY_LN882H,
FAMILY_RTL8710B, FAMILY_RTL8710B,
FAMILY_RTL8720C, FAMILY_RTL8720C,
] ]
@ -73,6 +76,7 @@ FAMILY_FRIENDLY = {
FAMILY_BK7231Q: "BK7231Q", FAMILY_BK7231Q: "BK7231Q",
FAMILY_BK7231T: "BK7231T", FAMILY_BK7231T: "BK7231T",
FAMILY_BK7251: "BK7251", FAMILY_BK7251: "BK7251",
FAMILY_LN882H: "LN882H",
FAMILY_RTL8710B: "RTL8710B", FAMILY_RTL8710B: "RTL8710B",
FAMILY_RTL8720C: "RTL8720C", FAMILY_RTL8720C: "RTL8720C",
} }
@ -81,6 +85,7 @@ FAMILY_COMPONENT = {
FAMILY_BK7231Q: COMPONENT_BK72XX, FAMILY_BK7231Q: COMPONENT_BK72XX,
FAMILY_BK7231T: COMPONENT_BK72XX, FAMILY_BK7231T: COMPONENT_BK72XX,
FAMILY_BK7251: COMPONENT_BK72XX, FAMILY_BK7251: COMPONENT_BK72XX,
FAMILY_LN882H: COMPONENT_LN882X,
FAMILY_RTL8710B: COMPONENT_RTL87XX, FAMILY_RTL8710B: COMPONENT_RTL87XX,
FAMILY_RTL8720C: COMPONENT_RTL87XX, FAMILY_RTL8720C: COMPONENT_RTL87XX,
} }

View File

@ -94,6 +94,7 @@ PIN_SCHEMA_EXTRA = f"libretiny.BASE_PIN_SCHEMA.extend({VAR_PIN_SCHEMA})"
COMPONENT_MAP = { COMPONENT_MAP = {
"rtl87xx": "realtek-amb", "rtl87xx": "realtek-amb",
"bk72xx": "beken-72xx", "bk72xx": "beken-72xx",
"ln882x": "lightning-ln882x",
} }

View File

@ -86,16 +86,16 @@ class LightColorValues {
static LightColorValues lerp(const LightColorValues &start, const LightColorValues &end, float completion) { static LightColorValues lerp(const LightColorValues &start, const LightColorValues &end, float completion) {
LightColorValues v; LightColorValues v;
v.set_color_mode(end.color_mode_); v.set_color_mode(end.color_mode_);
v.set_state(esphome::lerp(completion, start.get_state(), end.get_state())); v.set_state(std::lerp(start.get_state(), end.get_state(), completion));
v.set_brightness(esphome::lerp(completion, start.get_brightness(), end.get_brightness())); v.set_brightness(std::lerp(start.get_brightness(), end.get_brightness(), completion));
v.set_color_brightness(esphome::lerp(completion, start.get_color_brightness(), end.get_color_brightness())); v.set_color_brightness(std::lerp(start.get_color_brightness(), end.get_color_brightness(), completion));
v.set_red(esphome::lerp(completion, start.get_red(), end.get_red())); v.set_red(std::lerp(start.get_red(), end.get_red(), completion));
v.set_green(esphome::lerp(completion, start.get_green(), end.get_green())); v.set_green(std::lerp(start.get_green(), end.get_green(), completion));
v.set_blue(esphome::lerp(completion, start.get_blue(), end.get_blue())); v.set_blue(std::lerp(start.get_blue(), end.get_blue(), completion));
v.set_white(esphome::lerp(completion, start.get_white(), end.get_white())); v.set_white(std::lerp(start.get_white(), end.get_white(), completion));
v.set_color_temperature(esphome::lerp(completion, start.get_color_temperature(), end.get_color_temperature())); v.set_color_temperature(std::lerp(start.get_color_temperature(), end.get_color_temperature(), completion));
v.set_cold_white(esphome::lerp(completion, start.get_cold_white(), end.get_cold_white())); v.set_cold_white(std::lerp(start.get_cold_white(), end.get_cold_white(), completion));
v.set_warm_white(esphome::lerp(completion, start.get_warm_white(), end.get_warm_white())); v.set_warm_white(std::lerp(start.get_warm_white(), end.get_warm_white(), completion));
return v; return v;
} }

View File

@ -0,0 +1,52 @@
# This file was auto-generated by libretiny/generate_components.py
# Do not modify its contents.
# For custom pin validators, put validate_pin() or validate_usage()
# in gpio.py file in this directory.
# For changing schema/pin schema, put COMPONENT_SCHEMA or COMPONENT_PIN_SCHEMA
# in schema.py file in this directory.
from esphome import pins
from esphome.components import libretiny
from esphome.components.libretiny.const import (
COMPONENT_LN882X,
KEY_COMPONENT_DATA,
KEY_LIBRETINY,
LibreTinyComponent,
)
from esphome.core import CORE
from .boards import LN882X_BOARD_PINS, LN882X_BOARDS
CODEOWNERS = ["@lamauny"]
AUTO_LOAD = ["libretiny"]
IS_TARGET_PLATFORM = True
COMPONENT_DATA = LibreTinyComponent(
name=COMPONENT_LN882X,
boards=LN882X_BOARDS,
board_pins=LN882X_BOARD_PINS,
pin_validation=None,
usage_validation=None,
)
def _set_core_data(config):
CORE.data[KEY_LIBRETINY] = {}
CORE.data[KEY_LIBRETINY][KEY_COMPONENT_DATA] = COMPONENT_DATA
return config
CONFIG_SCHEMA = libretiny.BASE_SCHEMA
PIN_SCHEMA = libretiny.gpio.BASE_PIN_SCHEMA
CONFIG_SCHEMA.prepend_extra(_set_core_data)
async def to_code(config):
return await libretiny.component_to_code(config)
@pins.PIN_SCHEMA_REGISTRY.register("ln882x", PIN_SCHEMA)
async def pin_to_code(config):
return await libretiny.gpio.component_pin_to_code(config)

View File

@ -0,0 +1,285 @@
# This file was auto-generated by libretiny/generate_components.py
# Do not modify its contents.
from esphome.components.libretiny.const import FAMILY_LN882H
LN882X_BOARDS = {
"wl2s": {
"name": "WL2S Wi-Fi/BLE Module",
"family": FAMILY_LN882H,
},
"ln-02": {
"name": "LN-02 Wi-Fi/BLE Module",
"family": FAMILY_LN882H,
},
"generic-ln882hki": {
"name": "Generic - LN882HKI",
"family": FAMILY_LN882H,
},
}
LN882X_BOARD_PINS = {
"wl2s": {
"WIRE0_SCL_0": 7,
"WIRE0_SCL_1": 12,
"WIRE0_SCL_2": 3,
"WIRE0_SCL_3": 10,
"WIRE0_SCL_4": 2,
"WIRE0_SCL_5": 0,
"WIRE0_SCL_6": 19,
"WIRE0_SCL_7": 11,
"WIRE0_SCL_8": 9,
"WIRE0_SCL_9": 24,
"WIRE0_SCL_10": 25,
"WIRE0_SCL_11": 5,
"WIRE0_SCL_12": 1,
"WIRE0_SDA_0": 7,
"WIRE0_SDA_1": 12,
"WIRE0_SDA_2": 3,
"WIRE0_SDA_3": 10,
"WIRE0_SDA_4": 2,
"WIRE0_SDA_5": 0,
"WIRE0_SDA_6": 19,
"WIRE0_SDA_7": 11,
"WIRE0_SDA_8": 9,
"WIRE0_SDA_9": 24,
"WIRE0_SDA_10": 25,
"WIRE0_SDA_11": 5,
"WIRE0_SDA_12": 1,
"SERIAL0_RX": 3,
"SERIAL0_TX": 2,
"SERIAL1_RX": 24,
"SERIAL1_TX": 25,
"ADC2": 0,
"ADC3": 1,
"ADC5": 19,
"PA00": 0,
"PA0": 0,
"PA01": 1,
"PA1": 1,
"PA02": 2,
"PA2": 2,
"PA03": 3,
"PA3": 3,
"PA05": 5,
"PA5": 5,
"PA07": 7,
"PA7": 7,
"PA09": 9,
"PA9": 9,
"PA10": 10,
"PA11": 11,
"PA12": 12,
"PB03": 19,
"PB3": 19,
"PB08": 24,
"PB8": 24,
"PB09": 25,
"PB9": 25,
"RX0": 3,
"RX1": 24,
"SCL0": 1,
"SDA0": 1,
"TX0": 2,
"TX1": 25,
"D0": 7,
"D1": 12,
"D2": 3,
"D3": 10,
"D4": 2,
"D5": 0,
"D6": 19,
"D7": 11,
"D8": 9,
"D9": 24,
"D10": 25,
"D11": 5,
"D12": 1,
"A0": 0,
"A1": 19,
"A2": 1,
},
"ln-02": {
"WIRE0_SCL_0": 11,
"WIRE0_SCL_1": 19,
"WIRE0_SCL_2": 3,
"WIRE0_SCL_3": 24,
"WIRE0_SCL_4": 2,
"WIRE0_SCL_5": 25,
"WIRE0_SCL_6": 1,
"WIRE0_SCL_7": 0,
"WIRE0_SCL_8": 9,
"WIRE0_SDA_0": 11,
"WIRE0_SDA_1": 19,
"WIRE0_SDA_2": 3,
"WIRE0_SDA_3": 24,
"WIRE0_SDA_4": 2,
"WIRE0_SDA_5": 25,
"WIRE0_SDA_6": 1,
"WIRE0_SDA_7": 0,
"WIRE0_SDA_8": 9,
"SERIAL0_RX": 3,
"SERIAL0_TX": 2,
"SERIAL1_RX": 24,
"SERIAL1_TX": 25,
"ADC2": 0,
"ADC3": 1,
"ADC5": 19,
"PA00": 0,
"PA0": 0,
"PA01": 1,
"PA1": 1,
"PA02": 2,
"PA2": 2,
"PA03": 3,
"PA3": 3,
"PA09": 9,
"PA9": 9,
"PA11": 11,
"PB03": 19,
"PB3": 19,
"PB08": 24,
"PB8": 24,
"PB09": 25,
"PB9": 25,
"RX0": 3,
"RX1": 24,
"SCL0": 9,
"SDA0": 9,
"TX0": 2,
"TX1": 25,
"D0": 11,
"D1": 19,
"D2": 3,
"D3": 24,
"D4": 2,
"D5": 25,
"D6": 1,
"D7": 0,
"D8": 9,
"A0": 19,
"A1": 1,
"A2": 0,
},
"generic-ln882hki": {
"WIRE0_SCL_0": 0,
"WIRE0_SCL_1": 1,
"WIRE0_SCL_2": 2,
"WIRE0_SCL_3": 3,
"WIRE0_SCL_4": 4,
"WIRE0_SCL_5": 5,
"WIRE0_SCL_6": 6,
"WIRE0_SCL_7": 7,
"WIRE0_SCL_8": 8,
"WIRE0_SCL_9": 9,
"WIRE0_SCL_10": 10,
"WIRE0_SCL_11": 11,
"WIRE0_SCL_12": 12,
"WIRE0_SCL_13": 19,
"WIRE0_SCL_14": 20,
"WIRE0_SCL_15": 21,
"WIRE0_SCL_16": 22,
"WIRE0_SCL_17": 23,
"WIRE0_SCL_18": 24,
"WIRE0_SCL_19": 25,
"WIRE0_SDA_0": 0,
"WIRE0_SDA_1": 1,
"WIRE0_SDA_2": 2,
"WIRE0_SDA_3": 3,
"WIRE0_SDA_4": 4,
"WIRE0_SDA_5": 5,
"WIRE0_SDA_6": 6,
"WIRE0_SDA_7": 7,
"WIRE0_SDA_8": 8,
"WIRE0_SDA_9": 9,
"WIRE0_SDA_10": 10,
"WIRE0_SDA_11": 11,
"WIRE0_SDA_12": 12,
"WIRE0_SDA_13": 19,
"WIRE0_SDA_14": 20,
"WIRE0_SDA_15": 21,
"WIRE0_SDA_16": 22,
"WIRE0_SDA_17": 23,
"WIRE0_SDA_18": 24,
"WIRE0_SDA_19": 25,
"SERIAL0_RX": 3,
"SERIAL0_TX": 2,
"SERIAL1_RX": 24,
"SERIAL1_TX": 25,
"ADC2": 0,
"ADC3": 1,
"ADC4": 4,
"ADC5": 19,
"ADC6": 20,
"ADC7": 21,
"PA00": 0,
"PA0": 0,
"PA01": 1,
"PA1": 1,
"PA02": 2,
"PA2": 2,
"PA03": 3,
"PA3": 3,
"PA04": 4,
"PA4": 4,
"PA05": 5,
"PA5": 5,
"PA06": 6,
"PA6": 6,
"PA07": 7,
"PA7": 7,
"PA08": 8,
"PA8": 8,
"PA09": 9,
"PA9": 9,
"PA10": 10,
"PA11": 11,
"PA12": 12,
"PB03": 19,
"PB3": 19,
"PB04": 20,
"PB4": 20,
"PB05": 21,
"PB5": 21,
"PB06": 22,
"PB6": 22,
"PB07": 23,
"PB7": 23,
"PB08": 24,
"PB8": 24,
"PB09": 25,
"PB9": 25,
"RX0": 3,
"RX1": 24,
"TX0": 2,
"TX1": 25,
"D0": 0,
"D1": 1,
"D2": 2,
"D3": 3,
"D4": 4,
"D5": 5,
"D6": 6,
"D7": 7,
"D8": 8,
"D9": 9,
"D10": 10,
"D11": 11,
"D12": 12,
"D13": 19,
"D14": 20,
"D15": 21,
"D16": 22,
"D17": 23,
"D18": 24,
"D19": 25,
"A2": 0,
"A3": 1,
"A4": 4,
"A5": 19,
"A6": 20,
"A7": 21,
},
}
BOARDS = LN882X_BOARDS

View File

@ -16,7 +16,11 @@ from esphome.components.esp32.const import (
VARIANT_ESP32S3, VARIANT_ESP32S3,
) )
from esphome.components.libretiny import get_libretiny_component, get_libretiny_family from esphome.components.libretiny import get_libretiny_component, get_libretiny_family
from esphome.components.libretiny.const import COMPONENT_BK72XX, COMPONENT_RTL87XX from esphome.components.libretiny.const import (
COMPONENT_BK72XX,
COMPONENT_LN882X,
COMPONENT_RTL87XX,
)
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.const import ( from esphome.const import (
CONF_ARGS, CONF_ARGS,
@ -35,6 +39,7 @@ from esphome.const import (
PLATFORM_BK72XX, PLATFORM_BK72XX,
PLATFORM_ESP32, PLATFORM_ESP32,
PLATFORM_ESP8266, PLATFORM_ESP8266,
PLATFORM_LN882X,
PLATFORM_RP2040, PLATFORM_RP2040,
PLATFORM_RTL87XX, PLATFORM_RTL87XX,
) )
@ -100,6 +105,7 @@ UART_SELECTION_ESP8266 = [UART0, UART0_SWAP, UART1]
UART_SELECTION_LIBRETINY = { UART_SELECTION_LIBRETINY = {
COMPONENT_BK72XX: [DEFAULT, UART1, UART2], COMPONENT_BK72XX: [DEFAULT, UART1, UART2],
COMPONENT_LN882X: [DEFAULT, UART0, UART1, UART2],
COMPONENT_RTL87XX: [DEFAULT, UART0, UART1, UART2], COMPONENT_RTL87XX: [DEFAULT, UART0, UART1, UART2],
} }
@ -217,6 +223,7 @@ CONFIG_SCHEMA = cv.All(
esp32_p4_idf=USB_SERIAL_JTAG, esp32_p4_idf=USB_SERIAL_JTAG,
rp2040=USB_CDC, rp2040=USB_CDC,
bk72xx=DEFAULT, bk72xx=DEFAULT,
ln882x=DEFAULT,
rtl87xx=DEFAULT, rtl87xx=DEFAULT,
): cv.All( ): cv.All(
cv.only_on( cv.only_on(
@ -225,6 +232,7 @@ CONFIG_SCHEMA = cv.All(
PLATFORM_ESP32, PLATFORM_ESP32,
PLATFORM_RP2040, PLATFORM_RP2040,
PLATFORM_BK72XX, PLATFORM_BK72XX,
PLATFORM_LN882X,
PLATFORM_RTL87XX, PLATFORM_RTL87XX,
] ]
), ),

View File

@ -57,7 +57,7 @@ RemoteReceiverBinarySensorBase = ns.class_(
RemoteReceiverTrigger = ns.class_( RemoteReceiverTrigger = ns.class_(
"RemoteReceiverTrigger", automation.Trigger, RemoteReceiverListener "RemoteReceiverTrigger", automation.Trigger, RemoteReceiverListener
) )
RemoteTransmitterDumper = ns.class_("RemoteTransmitterDumper") RemoteReceiverDumperBase = ns.class_("RemoteReceiverDumperBase")
RemoteTransmittable = ns.class_("RemoteTransmittable") RemoteTransmittable = ns.class_("RemoteTransmittable")
RemoteTransmitterActionBase = ns.class_( RemoteTransmitterActionBase = ns.class_(
"RemoteTransmitterActionBase", RemoteTransmittable, automation.Action "RemoteTransmitterActionBase", RemoteTransmittable, automation.Action
@ -126,8 +126,10 @@ def register_trigger(name, type, data_type):
return decorator return decorator
def register_dumper(name, type): def register_dumper(name, type, schema=None):
registerer = DUMPER_REGISTRY.register(name, type, {}) if schema is None:
schema = {}
registerer = DUMPER_REGISTRY.register(name, type, schema)
def decorator(func): def decorator(func):
async def new_func(config, dumper_id): async def new_func(config, dumper_id):
@ -189,7 +191,7 @@ def declare_protocol(name):
binary_sensor_ = ns.class_(f"{name}BinarySensor", RemoteReceiverBinarySensorBase) binary_sensor_ = ns.class_(f"{name}BinarySensor", RemoteReceiverBinarySensorBase)
trigger = ns.class_(f"{name}Trigger", RemoteReceiverTrigger) trigger = ns.class_(f"{name}Trigger", RemoteReceiverTrigger)
action = ns.class_(f"{name}Action", RemoteTransmitterActionBase) action = ns.class_(f"{name}Action", RemoteTransmitterActionBase)
dumper = ns.class_(f"{name}Dumper", RemoteTransmitterDumper) dumper = ns.class_(f"{name}Dumper", RemoteReceiverDumperBase)
return data, binary_sensor_, trigger, action, dumper return data, binary_sensor_, trigger, action, dumper
@ -1405,7 +1407,7 @@ rc_switch_protocols = ns.RC_SWITCH_PROTOCOLS
RCSwitchData = ns.struct("RCSwitchData") RCSwitchData = ns.struct("RCSwitchData")
RCSwitchBase = ns.class_("RCSwitchBase") RCSwitchBase = ns.class_("RCSwitchBase")
RCSwitchTrigger = ns.class_("RCSwitchTrigger", RemoteReceiverTrigger) RCSwitchTrigger = ns.class_("RCSwitchTrigger", RemoteReceiverTrigger)
RCSwitchDumper = ns.class_("RCSwitchDumper", RemoteTransmitterDumper) RCSwitchDumper = ns.class_("RCSwitchDumper", RemoteReceiverDumperBase)
RCSwitchRawAction = ns.class_("RCSwitchRawAction", RemoteTransmitterActionBase) RCSwitchRawAction = ns.class_("RCSwitchRawAction", RemoteTransmitterActionBase)
RCSwitchTypeAAction = ns.class_("RCSwitchTypeAAction", RemoteTransmitterActionBase) RCSwitchTypeAAction = ns.class_("RCSwitchTypeAAction", RemoteTransmitterActionBase)
RCSwitchTypeBAction = ns.class_("RCSwitchTypeBAction", RemoteTransmitterActionBase) RCSwitchTypeBAction = ns.class_("RCSwitchTypeBAction", RemoteTransmitterActionBase)

View File

@ -19,6 +19,22 @@ bool RemoteReceiveData::peek_mark(uint32_t length, uint32_t offset) const {
return value >= 0 && lo <= value && value <= hi; return value >= 0 && lo <= value && value <= hi;
} }
bool RemoteReceiveData::peek_mark_at_least(uint32_t length, uint32_t offset) const {
if (!this->is_valid(offset))
return false;
const int32_t value = this->peek(offset);
const int32_t lo = this->lower_bound_(length);
return value >= 0 && lo <= value;
}
bool RemoteReceiveData::peek_mark_at_most(uint32_t length, uint32_t offset) const {
if (!this->is_valid(offset))
return false;
const int32_t value = this->peek(offset);
const int32_t hi = this->upper_bound_(length);
return value >= 0 && value <= hi;
}
bool RemoteReceiveData::peek_space(uint32_t length, uint32_t offset) const { bool RemoteReceiveData::peek_space(uint32_t length, uint32_t offset) const {
if (!this->is_valid(offset)) if (!this->is_valid(offset))
return false; return false;
@ -36,6 +52,14 @@ bool RemoteReceiveData::peek_space_at_least(uint32_t length, uint32_t offset) co
return value <= 0 && lo <= -value; return value <= 0 && lo <= -value;
} }
bool RemoteReceiveData::peek_space_at_most(uint32_t length, uint32_t offset) const {
if (!this->is_valid(offset))
return false;
const int32_t value = this->peek(offset);
const int32_t hi = this->upper_bound_(length);
return value <= 0 && -value <= hi;
}
bool RemoteReceiveData::expect_mark(uint32_t length) { bool RemoteReceiveData::expect_mark(uint32_t length) {
if (!this->peek_mark(length)) if (!this->peek_mark(length))
return false; return false;

View File

@ -53,8 +53,11 @@ class RemoteReceiveData {
bool is_valid(uint32_t offset = 0) const { return this->index_ + offset < this->data_.size(); } bool is_valid(uint32_t offset = 0) const { return this->index_ + offset < this->data_.size(); }
int32_t peek(uint32_t offset = 0) const { return this->data_[this->index_ + offset]; } int32_t peek(uint32_t offset = 0) const { return this->data_[this->index_ + offset]; }
bool peek_mark(uint32_t length, uint32_t offset = 0) const; bool peek_mark(uint32_t length, uint32_t offset = 0) const;
bool peek_mark_at_least(uint32_t length, uint32_t offset = 0) const;
bool peek_mark_at_most(uint32_t length, uint32_t offset = 0) const;
bool peek_space(uint32_t length, uint32_t offset = 0) const; bool peek_space(uint32_t length, uint32_t offset = 0) const;
bool peek_space_at_least(uint32_t length, uint32_t offset = 0) const; bool peek_space_at_least(uint32_t length, uint32_t offset = 0) const;
bool peek_space_at_most(uint32_t length, uint32_t offset = 0) const;
bool peek_item(uint32_t mark, uint32_t space, uint32_t offset = 0) const { bool peek_item(uint32_t mark, uint32_t space, uint32_t offset = 0) const {
return this->peek_space(space, offset + 1) && this->peek_mark(mark, offset); return this->peek_space(space, offset + 1) && this->peek_mark(mark, offset);
} }

View File

@ -94,6 +94,7 @@ CONFIG_SCHEMA = remote_base.validate_triggers(
esp32="10000b", esp32="10000b",
esp8266="1000b", esp8266="1000b",
bk72xx="1000b", bk72xx="1000b",
ln882x="1000b",
rtl87xx="1000b", rtl87xx="1000b",
): cv.validate_bytes, ): cv.validate_bytes,
cv.Optional(CONF_FILTER, default="50us"): cv.All( cv.Optional(CONF_FILTER, default="50us"): cv.All(

View File

@ -7,6 +7,7 @@ from esphome.const import (
PLATFORM_BK72XX, PLATFORM_BK72XX,
PLATFORM_ESP32, PLATFORM_ESP32,
PLATFORM_ESP8266, PLATFORM_ESP8266,
PLATFORM_LN882X,
PLATFORM_RP2040, PLATFORM_RP2040,
PLATFORM_RTL87XX, PLATFORM_RTL87XX,
) )
@ -33,6 +34,7 @@ CONFIG_SCHEMA = cv.All(
PLATFORM_ESP8266, PLATFORM_ESP8266,
PLATFORM_RP2040, PLATFORM_RP2040,
PLATFORM_BK72XX, PLATFORM_BK72XX,
PLATFORM_LN882X,
PLATFORM_RTL87XX, PLATFORM_RTL87XX,
] ]
), ),

View File

@ -16,6 +16,7 @@ CONFIG_SCHEMA = cv.Schema(
esp32=IMPLEMENTATION_BSD_SOCKETS, esp32=IMPLEMENTATION_BSD_SOCKETS,
rp2040=IMPLEMENTATION_LWIP_TCP, rp2040=IMPLEMENTATION_LWIP_TCP,
bk72xx=IMPLEMENTATION_LWIP_SOCKETS, bk72xx=IMPLEMENTATION_LWIP_SOCKETS,
ln882x=IMPLEMENTATION_LWIP_SOCKETS,
rtl87xx=IMPLEMENTATION_LWIP_SOCKETS, rtl87xx=IMPLEMENTATION_LWIP_SOCKETS,
host=IMPLEMENTATION_BSD_SOCKETS, host=IMPLEMENTATION_BSD_SOCKETS,
): cv.one_of( ): cv.one_of(

View File

@ -28,6 +28,7 @@ from esphome.const import (
PLATFORM_BK72XX, PLATFORM_BK72XX,
PLATFORM_ESP32, PLATFORM_ESP32,
PLATFORM_ESP8266, PLATFORM_ESP8266,
PLATFORM_LN882X,
PLATFORM_RTL87XX, PLATFORM_RTL87XX,
) )
from esphome.core import CORE, coroutine_with_priority from esphome.core import CORE, coroutine_with_priority
@ -180,6 +181,7 @@ CONFIG_SCHEMA = cv.All(
esp32_arduino=True, esp32_arduino=True,
esp32_idf=False, esp32_idf=False,
bk72xx=True, bk72xx=True,
ln882x=True,
rtl87xx=True, rtl87xx=True,
): cv.boolean, ): cv.boolean,
cv.Optional(CONF_LOG, default=True): cv.boolean, cv.Optional(CONF_LOG, default=True): cv.boolean,
@ -187,7 +189,15 @@ CONFIG_SCHEMA = cv.All(
cv.Optional(CONF_SORTING_GROUPS): cv.ensure_list(sorting_group), cv.Optional(CONF_SORTING_GROUPS): cv.ensure_list(sorting_group),
} }
).extend(cv.COMPONENT_SCHEMA), ).extend(cv.COMPONENT_SCHEMA),
cv.only_on([PLATFORM_ESP32, PLATFORM_ESP8266, PLATFORM_BK72XX, PLATFORM_RTL87XX]), cv.only_on(
[
PLATFORM_ESP32,
PLATFORM_ESP8266,
PLATFORM_BK72XX,
PLATFORM_LN882X,
PLATFORM_RTL87XX,
]
),
default_url, default_url,
validate_local, validate_local,
validate_ota, validate_ota,

View File

@ -49,26 +49,69 @@ static const char *const HEADER_CORS_ALLOW_PNA = "Access-Control-Allow-Private-N
UrlMatch match_url(const std::string &url, bool only_domain = false) { UrlMatch match_url(const std::string &url, bool only_domain = false) {
UrlMatch match; UrlMatch match;
match.valid = false; match.valid = false;
size_t domain_end = url.find('/', 1); match.domain = nullptr;
if (domain_end == std::string::npos) match.id = nullptr;
match.method = nullptr;
match.domain_len = 0;
match.id_len = 0;
match.method_len = 0;
const char *url_ptr = url.c_str();
size_t url_len = url.length();
// URL must start with '/'
if (url_len < 2 || url_ptr[0] != '/')
return match; return match;
match.domain = url.substr(1, domain_end - 1);
// Find domain
size_t domain_start = 1;
size_t domain_end = url.find('/', domain_start);
if (domain_end == std::string::npos) {
// URL is just "/domain"
match.domain = url_ptr + domain_start;
match.domain_len = url_len - domain_start;
match.valid = true;
return match;
}
// Set domain
match.domain = url_ptr + domain_start;
match.domain_len = domain_end - domain_start;
if (only_domain) { if (only_domain) {
match.valid = true; match.valid = true;
return match; return match;
} }
if (url.length() == domain_end - 1)
// Check if there's anything after domain
if (url_len == domain_end + 1)
return match; return match;
// Find ID
size_t id_begin = domain_end + 1; size_t id_begin = domain_end + 1;
size_t id_end = url.find('/', id_begin); size_t id_end = url.find('/', id_begin);
match.valid = true; match.valid = true;
if (id_end == std::string::npos) { if (id_end == std::string::npos) {
match.id = url.substr(id_begin, url.length() - id_begin); // URL is "/domain/id" with no method
match.id = url_ptr + id_begin;
match.id_len = url_len - id_begin;
return match; return match;
} }
match.id = url.substr(id_begin, id_end - id_begin);
// Set ID
match.id = url_ptr + id_begin;
match.id_len = id_end - id_begin;
// Set method if present
size_t method_begin = id_end + 1; size_t method_begin = id_end + 1;
match.method = url.substr(method_begin, url.length() - method_begin); if (method_begin < url_len) {
match.method = url_ptr + method_begin;
match.method_len = url_len - method_begin;
}
return match; return match;
} }
@ -386,9 +429,9 @@ void WebServer::on_sensor_update(sensor::Sensor *obj, float state) {
} }
void WebServer::handle_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match) { void WebServer::handle_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match) {
for (sensor::Sensor *obj : App.get_sensors()) { for (sensor::Sensor *obj : App.get_sensors()) {
if (obj->get_object_id() != match.id) if (!match.id_equals(obj->get_object_id()))
continue; continue;
if (request->method() == HTTP_GET && match.method.empty()) { if (request->method() == HTTP_GET && match.method_empty()) {
auto detail = get_request_detail(request); auto detail = get_request_detail(request);
std::string data = this->sensor_json(obj, obj->state, detail); std::string data = this->sensor_json(obj, obj->state, detail);
request->send(200, "application/json", data.c_str()); request->send(200, "application/json", data.c_str());
@ -431,9 +474,9 @@ void WebServer::on_text_sensor_update(text_sensor::TextSensor *obj, const std::s
} }
void WebServer::handle_text_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match) { void WebServer::handle_text_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match) {
for (text_sensor::TextSensor *obj : App.get_text_sensors()) { for (text_sensor::TextSensor *obj : App.get_text_sensors()) {
if (obj->get_object_id() != match.id) if (!match.id_equals(obj->get_object_id()))
continue; continue;
if (request->method() == HTTP_GET && match.method.empty()) { if (request->method() == HTTP_GET && match.method_empty()) {
auto detail = get_request_detail(request); auto detail = get_request_detail(request);
std::string data = this->text_sensor_json(obj, obj->state, detail); std::string data = this->text_sensor_json(obj, obj->state, detail);
request->send(200, "application/json", data.c_str()); request->send(200, "application/json", data.c_str());
@ -469,20 +512,20 @@ void WebServer::on_switch_update(switch_::Switch *obj, bool state) {
} }
void WebServer::handle_switch_request(AsyncWebServerRequest *request, const UrlMatch &match) { void WebServer::handle_switch_request(AsyncWebServerRequest *request, const UrlMatch &match) {
for (switch_::Switch *obj : App.get_switches()) { for (switch_::Switch *obj : App.get_switches()) {
if (obj->get_object_id() != match.id) if (!match.id_equals(obj->get_object_id()))
continue; continue;
if (request->method() == HTTP_GET && match.method.empty()) { if (request->method() == HTTP_GET && match.method_empty()) {
auto detail = get_request_detail(request); auto detail = get_request_detail(request);
std::string data = this->switch_json(obj, obj->state, detail); std::string data = this->switch_json(obj, obj->state, detail);
request->send(200, "application/json", data.c_str()); request->send(200, "application/json", data.c_str());
} else if (match.method == "toggle") { } else if (match.method_equals("toggle")) {
this->schedule_([obj]() { obj->toggle(); }); this->schedule_([obj]() { obj->toggle(); });
request->send(200); request->send(200);
} else if (match.method == "turn_on") { } else if (match.method_equals("turn_on")) {
this->schedule_([obj]() { obj->turn_on(); }); this->schedule_([obj]() { obj->turn_on(); });
request->send(200); request->send(200);
} else if (match.method == "turn_off") { } else if (match.method_equals("turn_off")) {
this->schedule_([obj]() { obj->turn_off(); }); this->schedule_([obj]() { obj->turn_off(); });
request->send(200); request->send(200);
} else { } else {
@ -512,13 +555,13 @@ std::string WebServer::switch_json(switch_::Switch *obj, bool value, JsonDetail
#ifdef USE_BUTTON #ifdef USE_BUTTON
void WebServer::handle_button_request(AsyncWebServerRequest *request, const UrlMatch &match) { void WebServer::handle_button_request(AsyncWebServerRequest *request, const UrlMatch &match) {
for (button::Button *obj : App.get_buttons()) { for (button::Button *obj : App.get_buttons()) {
if (obj->get_object_id() != match.id) if (!match.id_equals(obj->get_object_id()))
continue; continue;
if (request->method() == HTTP_GET && match.method.empty()) { if (request->method() == HTTP_GET && match.method_empty()) {
auto detail = get_request_detail(request); auto detail = get_request_detail(request);
std::string data = this->button_json(obj, detail); std::string data = this->button_json(obj, detail);
request->send(200, "application/json", data.c_str()); request->send(200, "application/json", data.c_str());
} else if (match.method == "press") { } else if (match.method_equals("press")) {
this->schedule_([obj]() { obj->press(); }); this->schedule_([obj]() { obj->press(); });
request->send(200); request->send(200);
return; return;
@ -553,9 +596,9 @@ void WebServer::on_binary_sensor_update(binary_sensor::BinarySensor *obj) {
} }
void WebServer::handle_binary_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match) { void WebServer::handle_binary_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match) {
for (binary_sensor::BinarySensor *obj : App.get_binary_sensors()) { for (binary_sensor::BinarySensor *obj : App.get_binary_sensors()) {
if (obj->get_object_id() != match.id) if (!match.id_equals(obj->get_object_id()))
continue; continue;
if (request->method() == HTTP_GET && match.method.empty()) { if (request->method() == HTTP_GET && match.method_empty()) {
auto detail = get_request_detail(request); auto detail = get_request_detail(request);
std::string data = this->binary_sensor_json(obj, obj->state, detail); std::string data = this->binary_sensor_json(obj, obj->state, detail);
request->send(200, "application/json", data.c_str()); request->send(200, "application/json", data.c_str());
@ -591,18 +634,18 @@ void WebServer::on_fan_update(fan::Fan *obj) {
} }
void WebServer::handle_fan_request(AsyncWebServerRequest *request, const UrlMatch &match) { void WebServer::handle_fan_request(AsyncWebServerRequest *request, const UrlMatch &match) {
for (fan::Fan *obj : App.get_fans()) { for (fan::Fan *obj : App.get_fans()) {
if (obj->get_object_id() != match.id) if (!match.id_equals(obj->get_object_id()))
continue; continue;
if (request->method() == HTTP_GET && match.method.empty()) { if (request->method() == HTTP_GET && match.method_empty()) {
auto detail = get_request_detail(request); auto detail = get_request_detail(request);
std::string data = this->fan_json(obj, detail); std::string data = this->fan_json(obj, detail);
request->send(200, "application/json", data.c_str()); request->send(200, "application/json", data.c_str());
} else if (match.method == "toggle") { } else if (match.method_equals("toggle")) {
this->schedule_([obj]() { obj->toggle().perform(); }); this->schedule_([obj]() { obj->toggle().perform(); });
request->send(200); request->send(200);
} else if (match.method == "turn_on" || match.method == "turn_off") { } else if (match.method_equals("turn_on") || match.method_equals("turn_off")) {
auto call = match.method == "turn_on" ? obj->turn_on() : obj->turn_off(); auto call = match.method_equals("turn_on") ? obj->turn_on() : obj->turn_off();
if (request->hasParam("speed_level")) { if (request->hasParam("speed_level")) {
auto speed_level = request->getParam("speed_level")->value(); auto speed_level = request->getParam("speed_level")->value();
@ -672,17 +715,17 @@ void WebServer::on_light_update(light::LightState *obj) {
} }
void WebServer::handle_light_request(AsyncWebServerRequest *request, const UrlMatch &match) { void WebServer::handle_light_request(AsyncWebServerRequest *request, const UrlMatch &match) {
for (light::LightState *obj : App.get_lights()) { for (light::LightState *obj : App.get_lights()) {
if (obj->get_object_id() != match.id) if (!match.id_equals(obj->get_object_id()))
continue; continue;
if (request->method() == HTTP_GET && match.method.empty()) { if (request->method() == HTTP_GET && match.method_empty()) {
auto detail = get_request_detail(request); auto detail = get_request_detail(request);
std::string data = this->light_json(obj, detail); std::string data = this->light_json(obj, detail);
request->send(200, "application/json", data.c_str()); request->send(200, "application/json", data.c_str());
} else if (match.method == "toggle") { } else if (match.method_equals("toggle")) {
this->schedule_([obj]() { obj->toggle().perform(); }); this->schedule_([obj]() { obj->toggle().perform(); });
request->send(200); request->send(200);
} else if (match.method == "turn_on") { } else if (match.method_equals("turn_on")) {
auto call = obj->turn_on(); auto call = obj->turn_on();
if (request->hasParam("brightness")) { if (request->hasParam("brightness")) {
auto brightness = parse_number<float>(request->getParam("brightness")->value().c_str()); auto brightness = parse_number<float>(request->getParam("brightness")->value().c_str());
@ -739,7 +782,7 @@ void WebServer::handle_light_request(AsyncWebServerRequest *request, const UrlMa
this->schedule_([call]() mutable { call.perform(); }); this->schedule_([call]() mutable { call.perform(); });
request->send(200); request->send(200);
} else if (match.method == "turn_off") { } else if (match.method_equals("turn_off")) {
auto call = obj->turn_off(); auto call = obj->turn_off();
if (request->hasParam("transition")) { if (request->hasParam("transition")) {
auto transition = parse_number<uint32_t>(request->getParam("transition")->value().c_str()); auto transition = parse_number<uint32_t>(request->getParam("transition")->value().c_str());
@ -788,10 +831,10 @@ void WebServer::on_cover_update(cover::Cover *obj) {
} }
void WebServer::handle_cover_request(AsyncWebServerRequest *request, const UrlMatch &match) { void WebServer::handle_cover_request(AsyncWebServerRequest *request, const UrlMatch &match) {
for (cover::Cover *obj : App.get_covers()) { for (cover::Cover *obj : App.get_covers()) {
if (obj->get_object_id() != match.id) if (!match.id_equals(obj->get_object_id()))
continue; continue;
if (request->method() == HTTP_GET && match.method.empty()) { if (request->method() == HTTP_GET && match.method_empty()) {
auto detail = get_request_detail(request); auto detail = get_request_detail(request);
std::string data = this->cover_json(obj, detail); std::string data = this->cover_json(obj, detail);
request->send(200, "application/json", data.c_str()); request->send(200, "application/json", data.c_str());
@ -799,15 +842,15 @@ void WebServer::handle_cover_request(AsyncWebServerRequest *request, const UrlMa
} }
auto call = obj->make_call(); auto call = obj->make_call();
if (match.method == "open") { if (match.method_equals("open")) {
call.set_command_open(); call.set_command_open();
} else if (match.method == "close") { } else if (match.method_equals("close")) {
call.set_command_close(); call.set_command_close();
} else if (match.method == "stop") { } else if (match.method_equals("stop")) {
call.set_command_stop(); call.set_command_stop();
} else if (match.method == "toggle") { } else if (match.method_equals("toggle")) {
call.set_command_toggle(); call.set_command_toggle();
} else if (match.method != "set") { } else if (!match.method_equals("set")) {
request->send(404); request->send(404);
return; return;
} }
@ -869,16 +912,16 @@ void WebServer::on_number_update(number::Number *obj, float state) {
} }
void WebServer::handle_number_request(AsyncWebServerRequest *request, const UrlMatch &match) { void WebServer::handle_number_request(AsyncWebServerRequest *request, const UrlMatch &match) {
for (auto *obj : App.get_numbers()) { for (auto *obj : App.get_numbers()) {
if (obj->get_object_id() != match.id) if (!match.id_equals(obj->get_object_id()))
continue; continue;
if (request->method() == HTTP_GET && match.method.empty()) { if (request->method() == HTTP_GET && match.method_empty()) {
auto detail = get_request_detail(request); auto detail = get_request_detail(request);
std::string data = this->number_json(obj, obj->state, detail); std::string data = this->number_json(obj, obj->state, detail);
request->send(200, "application/json", data.c_str()); request->send(200, "application/json", data.c_str());
return; return;
} }
if (match.method != "set") { if (!match.method_equals("set")) {
request->send(404); request->send(404);
return; return;
} }
@ -940,15 +983,15 @@ void WebServer::on_date_update(datetime::DateEntity *obj) {
} }
void WebServer::handle_date_request(AsyncWebServerRequest *request, const UrlMatch &match) { void WebServer::handle_date_request(AsyncWebServerRequest *request, const UrlMatch &match) {
for (auto *obj : App.get_dates()) { for (auto *obj : App.get_dates()) {
if (obj->get_object_id() != match.id) if (!match.id_equals(obj->get_object_id()))
continue; continue;
if (request->method() == HTTP_GET && match.method.empty()) { if (request->method() == HTTP_GET && match.method_empty()) {
auto detail = get_request_detail(request); auto detail = get_request_detail(request);
std::string data = this->date_json(obj, detail); std::string data = this->date_json(obj, detail);
request->send(200, "application/json", data.c_str()); request->send(200, "application/json", data.c_str());
return; return;
} }
if (match.method != "set") { if (!match.method_equals("set")) {
request->send(404); request->send(404);
return; return;
} }
@ -999,15 +1042,15 @@ void WebServer::on_time_update(datetime::TimeEntity *obj) {
} }
void WebServer::handle_time_request(AsyncWebServerRequest *request, const UrlMatch &match) { void WebServer::handle_time_request(AsyncWebServerRequest *request, const UrlMatch &match) {
for (auto *obj : App.get_times()) { for (auto *obj : App.get_times()) {
if (obj->get_object_id() != match.id) if (!match.id_equals(obj->get_object_id()))
continue; continue;
if (request->method() == HTTP_GET && match.method.empty()) { if (request->method() == HTTP_GET && match.method_empty()) {
auto detail = get_request_detail(request); auto detail = get_request_detail(request);
std::string data = this->time_json(obj, detail); std::string data = this->time_json(obj, detail);
request->send(200, "application/json", data.c_str()); request->send(200, "application/json", data.c_str());
return; return;
} }
if (match.method != "set") { if (!match.method_equals("set")) {
request->send(404); request->send(404);
return; return;
} }
@ -1057,15 +1100,15 @@ void WebServer::on_datetime_update(datetime::DateTimeEntity *obj) {
} }
void WebServer::handle_datetime_request(AsyncWebServerRequest *request, const UrlMatch &match) { void WebServer::handle_datetime_request(AsyncWebServerRequest *request, const UrlMatch &match) {
for (auto *obj : App.get_datetimes()) { for (auto *obj : App.get_datetimes()) {
if (obj->get_object_id() != match.id) if (!match.id_equals(obj->get_object_id()))
continue; continue;
if (request->method() == HTTP_GET && match.method.empty()) { if (request->method() == HTTP_GET && match.method_empty()) {
auto detail = get_request_detail(request); auto detail = get_request_detail(request);
std::string data = this->datetime_json(obj, detail); std::string data = this->datetime_json(obj, detail);
request->send(200, "application/json", data.c_str()); request->send(200, "application/json", data.c_str());
return; return;
} }
if (match.method != "set") { if (!match.method_equals("set")) {
request->send(404); request->send(404);
return; return;
} }
@ -1116,16 +1159,16 @@ void WebServer::on_text_update(text::Text *obj, const std::string &state) {
} }
void WebServer::handle_text_request(AsyncWebServerRequest *request, const UrlMatch &match) { void WebServer::handle_text_request(AsyncWebServerRequest *request, const UrlMatch &match) {
for (auto *obj : App.get_texts()) { for (auto *obj : App.get_texts()) {
if (obj->get_object_id() != match.id) if (!match.id_equals(obj->get_object_id()))
continue; continue;
if (request->method() == HTTP_GET && match.method.empty()) { if (request->method() == HTTP_GET && match.method_empty()) {
auto detail = get_request_detail(request); auto detail = get_request_detail(request);
std::string data = this->text_json(obj, obj->state, detail); std::string data = this->text_json(obj, obj->state, detail);
request->send(200, "application/json", data.c_str()); request->send(200, "application/json", data.c_str());
return; return;
} }
if (match.method != "set") { if (!match.method_equals("set")) {
request->send(404); request->send(404);
return; return;
} }
@ -1177,17 +1220,17 @@ void WebServer::on_select_update(select::Select *obj, const std::string &state,
} }
void WebServer::handle_select_request(AsyncWebServerRequest *request, const UrlMatch &match) { void WebServer::handle_select_request(AsyncWebServerRequest *request, const UrlMatch &match) {
for (auto *obj : App.get_selects()) { for (auto *obj : App.get_selects()) {
if (obj->get_object_id() != match.id) if (!match.id_equals(obj->get_object_id()))
continue; continue;
if (request->method() == HTTP_GET && match.method.empty()) { if (request->method() == HTTP_GET && match.method_empty()) {
auto detail = get_request_detail(request); auto detail = get_request_detail(request);
std::string data = this->select_json(obj, obj->state, detail); std::string data = this->select_json(obj, obj->state, detail);
request->send(200, "application/json", data.c_str()); request->send(200, "application/json", data.c_str());
return; return;
} }
if (match.method != "set") { if (!match.method_equals("set")) {
request->send(404); request->send(404);
return; return;
} }
@ -1236,17 +1279,17 @@ void WebServer::on_climate_update(climate::Climate *obj) {
} }
void WebServer::handle_climate_request(AsyncWebServerRequest *request, const UrlMatch &match) { void WebServer::handle_climate_request(AsyncWebServerRequest *request, const UrlMatch &match) {
for (auto *obj : App.get_climates()) { for (auto *obj : App.get_climates()) {
if (obj->get_object_id() != match.id) if (!match.id_equals(obj->get_object_id()))
continue; continue;
if (request->method() == HTTP_GET && match.method.empty()) { if (request->method() == HTTP_GET && match.method_empty()) {
auto detail = get_request_detail(request); auto detail = get_request_detail(request);
std::string data = this->climate_json(obj, detail); std::string data = this->climate_json(obj, detail);
request->send(200, "application/json", data.c_str()); request->send(200, "application/json", data.c_str());
return; return;
} }
if (match.method != "set") { if (!match.method_equals("set")) {
request->send(404); request->send(404);
return; return;
} }
@ -1395,20 +1438,20 @@ void WebServer::on_lock_update(lock::Lock *obj) {
} }
void WebServer::handle_lock_request(AsyncWebServerRequest *request, const UrlMatch &match) { void WebServer::handle_lock_request(AsyncWebServerRequest *request, const UrlMatch &match) {
for (lock::Lock *obj : App.get_locks()) { for (lock::Lock *obj : App.get_locks()) {
if (obj->get_object_id() != match.id) if (!match.id_equals(obj->get_object_id()))
continue; continue;
if (request->method() == HTTP_GET && match.method.empty()) { if (request->method() == HTTP_GET && match.method_empty()) {
auto detail = get_request_detail(request); auto detail = get_request_detail(request);
std::string data = this->lock_json(obj, obj->state, detail); std::string data = this->lock_json(obj, obj->state, detail);
request->send(200, "application/json", data.c_str()); request->send(200, "application/json", data.c_str());
} else if (match.method == "lock") { } else if (match.method_equals("lock")) {
this->schedule_([obj]() { obj->lock(); }); this->schedule_([obj]() { obj->lock(); });
request->send(200); request->send(200);
} else if (match.method == "unlock") { } else if (match.method_equals("unlock")) {
this->schedule_([obj]() { obj->unlock(); }); this->schedule_([obj]() { obj->unlock(); });
request->send(200); request->send(200);
} else if (match.method == "open") { } else if (match.method_equals("open")) {
this->schedule_([obj]() { obj->open(); }); this->schedule_([obj]() { obj->open(); });
request->send(200); request->send(200);
} else { } else {
@ -1443,10 +1486,10 @@ void WebServer::on_valve_update(valve::Valve *obj) {
} }
void WebServer::handle_valve_request(AsyncWebServerRequest *request, const UrlMatch &match) { void WebServer::handle_valve_request(AsyncWebServerRequest *request, const UrlMatch &match) {
for (valve::Valve *obj : App.get_valves()) { for (valve::Valve *obj : App.get_valves()) {
if (obj->get_object_id() != match.id) if (!match.id_equals(obj->get_object_id()))
continue; continue;
if (request->method() == HTTP_GET && match.method.empty()) { if (request->method() == HTTP_GET && match.method_empty()) {
auto detail = get_request_detail(request); auto detail = get_request_detail(request);
std::string data = this->valve_json(obj, detail); std::string data = this->valve_json(obj, detail);
request->send(200, "application/json", data.c_str()); request->send(200, "application/json", data.c_str());
@ -1454,15 +1497,15 @@ void WebServer::handle_valve_request(AsyncWebServerRequest *request, const UrlMa
} }
auto call = obj->make_call(); auto call = obj->make_call();
if (match.method == "open") { if (match.method_equals("open")) {
call.set_command_open(); call.set_command_open();
} else if (match.method == "close") { } else if (match.method_equals("close")) {
call.set_command_close(); call.set_command_close();
} else if (match.method == "stop") { } else if (match.method_equals("stop")) {
call.set_command_stop(); call.set_command_stop();
} else if (match.method == "toggle") { } else if (match.method_equals("toggle")) {
call.set_command_toggle(); call.set_command_toggle();
} else if (match.method != "set") { } else if (!match.method_equals("set")) {
request->send(404); request->send(404);
return; return;
} }
@ -1515,10 +1558,10 @@ void WebServer::on_alarm_control_panel_update(alarm_control_panel::AlarmControlP
} }
void WebServer::handle_alarm_control_panel_request(AsyncWebServerRequest *request, const UrlMatch &match) { void WebServer::handle_alarm_control_panel_request(AsyncWebServerRequest *request, const UrlMatch &match) {
for (alarm_control_panel::AlarmControlPanel *obj : App.get_alarm_control_panels()) { for (alarm_control_panel::AlarmControlPanel *obj : App.get_alarm_control_panels()) {
if (obj->get_object_id() != match.id) if (!match.id_equals(obj->get_object_id()))
continue; continue;
if (request->method() == HTTP_GET && match.method.empty()) { if (request->method() == HTTP_GET && match.method_empty()) {
auto detail = get_request_detail(request); auto detail = get_request_detail(request);
std::string data = this->alarm_control_panel_json(obj, obj->get_state(), detail); std::string data = this->alarm_control_panel_json(obj, obj->get_state(), detail);
request->send(200, "application/json", data.c_str()); request->send(200, "application/json", data.c_str());
@ -1530,15 +1573,15 @@ void WebServer::handle_alarm_control_panel_request(AsyncWebServerRequest *reques
call.set_code(request->getParam("code")->value().c_str()); // NOLINT call.set_code(request->getParam("code")->value().c_str()); // NOLINT
} }
if (match.method == "disarm") { if (match.method_equals("disarm")) {
call.disarm(); call.disarm();
} else if (match.method == "arm_away") { } else if (match.method_equals("arm_away")) {
call.arm_away(); call.arm_away();
} else if (match.method == "arm_home") { } else if (match.method_equals("arm_home")) {
call.arm_home(); call.arm_home();
} else if (match.method == "arm_night") { } else if (match.method_equals("arm_night")) {
call.arm_night(); call.arm_night();
} else if (match.method == "arm_vacation") { } else if (match.method_equals("arm_vacation")) {
call.arm_vacation(); call.arm_vacation();
} else { } else {
request->send(404); request->send(404);
@ -1582,10 +1625,10 @@ void WebServer::on_event(event::Event *obj, const std::string &event_type) {
void WebServer::handle_event_request(AsyncWebServerRequest *request, const UrlMatch &match) { void WebServer::handle_event_request(AsyncWebServerRequest *request, const UrlMatch &match) {
for (event::Event *obj : App.get_events()) { for (event::Event *obj : App.get_events()) {
if (obj->get_object_id() != match.id) if (!match.id_equals(obj->get_object_id()))
continue; continue;
if (request->method() == HTTP_GET && match.method.empty()) { if (request->method() == HTTP_GET && match.method_empty()) {
auto detail = get_request_detail(request); auto detail = get_request_detail(request);
std::string data = this->event_json(obj, "", detail); std::string data = this->event_json(obj, "", detail);
request->send(200, "application/json", data.c_str()); request->send(200, "application/json", data.c_str());
@ -1631,17 +1674,17 @@ void WebServer::on_update(update::UpdateEntity *obj) {
} }
void WebServer::handle_update_request(AsyncWebServerRequest *request, const UrlMatch &match) { void WebServer::handle_update_request(AsyncWebServerRequest *request, const UrlMatch &match) {
for (update::UpdateEntity *obj : App.get_updates()) { for (update::UpdateEntity *obj : App.get_updates()) {
if (obj->get_object_id() != match.id) if (!match.id_equals(obj->get_object_id()))
continue; continue;
if (request->method() == HTTP_GET && match.method.empty()) { if (request->method() == HTTP_GET && match.method_empty()) {
auto detail = get_request_detail(request); auto detail = get_request_detail(request);
std::string data = this->update_json(obj, detail); std::string data = this->update_json(obj, detail);
request->send(200, "application/json", data.c_str()); request->send(200, "application/json", data.c_str());
return; return;
} }
if (match.method != "install") { if (!match.method_equals("install")) {
request->send(404); request->send(404);
return; return;
} }
@ -1717,102 +1760,102 @@ bool WebServer::canHandle(AsyncWebServerRequest *request) const {
if (!match.valid) if (!match.valid)
return false; return false;
#ifdef USE_SENSOR #ifdef USE_SENSOR
if (request->method() == HTTP_GET && match.domain == "sensor") if (request->method() == HTTP_GET && match.domain_equals("sensor"))
return true; return true;
#endif #endif
#ifdef USE_SWITCH #ifdef USE_SWITCH
if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain == "switch") if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain_equals("switch"))
return true; return true;
#endif #endif
#ifdef USE_BUTTON #ifdef USE_BUTTON
if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain == "button") if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain_equals("button"))
return true; return true;
#endif #endif
#ifdef USE_BINARY_SENSOR #ifdef USE_BINARY_SENSOR
if (request->method() == HTTP_GET && match.domain == "binary_sensor") if (request->method() == HTTP_GET && match.domain_equals("binary_sensor"))
return true; return true;
#endif #endif
#ifdef USE_FAN #ifdef USE_FAN
if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain == "fan") if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain_equals("fan"))
return true; return true;
#endif #endif
#ifdef USE_LIGHT #ifdef USE_LIGHT
if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain == "light") if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain_equals("light"))
return true; return true;
#endif #endif
#ifdef USE_TEXT_SENSOR #ifdef USE_TEXT_SENSOR
if (request->method() == HTTP_GET && match.domain == "text_sensor") if (request->method() == HTTP_GET && match.domain_equals("text_sensor"))
return true; return true;
#endif #endif
#ifdef USE_COVER #ifdef USE_COVER
if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain == "cover") if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain_equals("cover"))
return true; return true;
#endif #endif
#ifdef USE_NUMBER #ifdef USE_NUMBER
if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain == "number") if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain_equals("number"))
return true; return true;
#endif #endif
#ifdef USE_DATETIME_DATE #ifdef USE_DATETIME_DATE
if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain == "date") if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain_equals("date"))
return true; return true;
#endif #endif
#ifdef USE_DATETIME_TIME #ifdef USE_DATETIME_TIME
if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain == "time") if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain_equals("time"))
return true; return true;
#endif #endif
#ifdef USE_DATETIME_DATETIME #ifdef USE_DATETIME_DATETIME
if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain == "datetime") if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain_equals("datetime"))
return true; return true;
#endif #endif
#ifdef USE_TEXT #ifdef USE_TEXT
if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain == "text") if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain_equals("text"))
return true; return true;
#endif #endif
#ifdef USE_SELECT #ifdef USE_SELECT
if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain == "select") if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain_equals("select"))
return true; return true;
#endif #endif
#ifdef USE_CLIMATE #ifdef USE_CLIMATE
if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain == "climate") if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain_equals("climate"))
return true; return true;
#endif #endif
#ifdef USE_LOCK #ifdef USE_LOCK
if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain == "lock") if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain_equals("lock"))
return true; return true;
#endif #endif
#ifdef USE_VALVE #ifdef USE_VALVE
if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain == "valve") if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain_equals("valve"))
return true; return true;
#endif #endif
#ifdef USE_ALARM_CONTROL_PANEL #ifdef USE_ALARM_CONTROL_PANEL
if ((request->method() == HTTP_GET || request->method() == HTTP_POST) && match.domain == "alarm_control_panel") if ((request->method() == HTTP_GET || request->method() == HTTP_POST) && match.domain_equals("alarm_control_panel"))
return true; return true;
#endif #endif
#ifdef USE_EVENT #ifdef USE_EVENT
if (request->method() == HTTP_GET && match.domain == "event") if (request->method() == HTTP_GET && match.domain_equals("event"))
return true; return true;
#endif #endif
#ifdef USE_UPDATE #ifdef USE_UPDATE
if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain == "update") if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain_equals("update"))
return true; return true;
#endif #endif
@ -1854,112 +1897,112 @@ void WebServer::handleRequest(AsyncWebServerRequest *request) {
UrlMatch match = match_url(request->url().c_str()); // NOLINT UrlMatch match = match_url(request->url().c_str()); // NOLINT
#ifdef USE_SENSOR #ifdef USE_SENSOR
if (match.domain == "sensor") { if (match.domain_equals("sensor")) {
this->handle_sensor_request(request, match); this->handle_sensor_request(request, match);
return; return;
} }
#endif #endif
#ifdef USE_SWITCH #ifdef USE_SWITCH
if (match.domain == "switch") { if (match.domain_equals("switch")) {
this->handle_switch_request(request, match); this->handle_switch_request(request, match);
return; return;
} }
#endif #endif
#ifdef USE_BUTTON #ifdef USE_BUTTON
if (match.domain == "button") { if (match.domain_equals("button")) {
this->handle_button_request(request, match); this->handle_button_request(request, match);
return; return;
} }
#endif #endif
#ifdef USE_BINARY_SENSOR #ifdef USE_BINARY_SENSOR
if (match.domain == "binary_sensor") { if (match.domain_equals("binary_sensor")) {
this->handle_binary_sensor_request(request, match); this->handle_binary_sensor_request(request, match);
return; return;
} }
#endif #endif
#ifdef USE_FAN #ifdef USE_FAN
if (match.domain == "fan") { if (match.domain_equals("fan")) {
this->handle_fan_request(request, match); this->handle_fan_request(request, match);
return; return;
} }
#endif #endif
#ifdef USE_LIGHT #ifdef USE_LIGHT
if (match.domain == "light") { if (match.domain_equals("light")) {
this->handle_light_request(request, match); this->handle_light_request(request, match);
return; return;
} }
#endif #endif
#ifdef USE_TEXT_SENSOR #ifdef USE_TEXT_SENSOR
if (match.domain == "text_sensor") { if (match.domain_equals("text_sensor")) {
this->handle_text_sensor_request(request, match); this->handle_text_sensor_request(request, match);
return; return;
} }
#endif #endif
#ifdef USE_COVER #ifdef USE_COVER
if (match.domain == "cover") { if (match.domain_equals("cover")) {
this->handle_cover_request(request, match); this->handle_cover_request(request, match);
return; return;
} }
#endif #endif
#ifdef USE_NUMBER #ifdef USE_NUMBER
if (match.domain == "number") { if (match.domain_equals("number")) {
this->handle_number_request(request, match); this->handle_number_request(request, match);
return; return;
} }
#endif #endif
#ifdef USE_DATETIME_DATE #ifdef USE_DATETIME_DATE
if (match.domain == "date") { if (match.domain_equals("date")) {
this->handle_date_request(request, match); this->handle_date_request(request, match);
return; return;
} }
#endif #endif
#ifdef USE_DATETIME_TIME #ifdef USE_DATETIME_TIME
if (match.domain == "time") { if (match.domain_equals("time")) {
this->handle_time_request(request, match); this->handle_time_request(request, match);
return; return;
} }
#endif #endif
#ifdef USE_DATETIME_DATETIME #ifdef USE_DATETIME_DATETIME
if (match.domain == "datetime") { if (match.domain_equals("datetime")) {
this->handle_datetime_request(request, match); this->handle_datetime_request(request, match);
return; return;
} }
#endif #endif
#ifdef USE_TEXT #ifdef USE_TEXT
if (match.domain == "text") { if (match.domain_equals("text")) {
this->handle_text_request(request, match); this->handle_text_request(request, match);
return; return;
} }
#endif #endif
#ifdef USE_SELECT #ifdef USE_SELECT
if (match.domain == "select") { if (match.domain_equals("select")) {
this->handle_select_request(request, match); this->handle_select_request(request, match);
return; return;
} }
#endif #endif
#ifdef USE_CLIMATE #ifdef USE_CLIMATE
if (match.domain == "climate") { if (match.domain_equals("climate")) {
this->handle_climate_request(request, match); this->handle_climate_request(request, match);
return; return;
} }
#endif #endif
#ifdef USE_LOCK #ifdef USE_LOCK
if (match.domain == "lock") { if (match.domain_equals("lock")) {
this->handle_lock_request(request, match); this->handle_lock_request(request, match);
return; return;
@ -1967,14 +2010,14 @@ void WebServer::handleRequest(AsyncWebServerRequest *request) {
#endif #endif
#ifdef USE_VALVE #ifdef USE_VALVE
if (match.domain == "valve") { if (match.domain_equals("valve")) {
this->handle_valve_request(request, match); this->handle_valve_request(request, match);
return; return;
} }
#endif #endif
#ifdef USE_ALARM_CONTROL_PANEL #ifdef USE_ALARM_CONTROL_PANEL
if (match.domain == "alarm_control_panel") { if (match.domain_equals("alarm_control_panel")) {
this->handle_alarm_control_panel_request(request, match); this->handle_alarm_control_panel_request(request, match);
return; return;
@ -1982,7 +2025,7 @@ void WebServer::handleRequest(AsyncWebServerRequest *request) {
#endif #endif
#ifdef USE_UPDATE #ifdef USE_UPDATE
if (match.domain == "update") { if (match.domain_equals("update")) {
this->handle_update_request(request, match); this->handle_update_request(request, match);
return; return;
} }

View File

@ -40,10 +40,28 @@ namespace web_server {
/// Internal helper struct that is used to parse incoming URLs /// Internal helper struct that is used to parse incoming URLs
struct UrlMatch { struct UrlMatch {
std::string domain; ///< The domain of the component, for example "sensor" const char *domain; ///< Pointer to domain within URL, for example "sensor"
std::string id; ///< The id of the device that's being accessed, for example "living_room_fan" const char *id; ///< Pointer to id within URL, for example "living_room_fan"
std::string method; ///< The method that's being called, for example "turn_on" const char *method; ///< Pointer to method within URL, for example "turn_on"
uint8_t domain_len; ///< Length of domain string
uint8_t id_len; ///< Length of id string
uint8_t method_len; ///< Length of method string
bool valid; ///< Whether this match is valid bool valid; ///< Whether this match is valid
// Helper methods for string comparisons
bool domain_equals(const char *str) const {
return domain && domain_len == strlen(str) && memcmp(domain, str, domain_len) == 0;
}
bool id_equals(const std::string &str) const {
return id && id_len == str.length() && memcmp(id, str.c_str(), id_len) == 0;
}
bool method_equals(const char *str) const {
return method && method_len == strlen(str) && memcmp(method, str, method_len) == 0;
}
bool method_empty() const { return method_len == 0; }
}; };
#ifdef USE_WEBSERVER_SORTING #ifdef USE_WEBSERVER_SORTING

View File

@ -309,6 +309,7 @@ CONFIG_SCHEMA = cv.All(
rp2040="light", rp2040="light",
bk72xx="none", bk72xx="none",
rtl87xx="none", rtl87xx="none",
ln882x="light",
): cv.enum(WIFI_POWER_SAVE_MODES, upper=True), ): cv.enum(WIFI_POWER_SAVE_MODES, upper=True),
cv.Optional(CONF_FAST_CONNECT, default=False): cv.boolean, cv.Optional(CONF_FAST_CONNECT, default=False): cv.boolean,
cv.Optional(CONF_USE_ADDRESS): cv.string_strict, cv.Optional(CONF_USE_ADDRESS): cv.string_strict,

View File

@ -12,6 +12,7 @@ PLATFORM_ESP32 = "esp32"
PLATFORM_ESP8266 = "esp8266" PLATFORM_ESP8266 = "esp8266"
PLATFORM_HOST = "host" PLATFORM_HOST = "host"
PLATFORM_LIBRETINY_OLDSTYLE = "libretiny" PLATFORM_LIBRETINY_OLDSTYLE = "libretiny"
PLATFORM_LN882X = "ln882x"
PLATFORM_RP2040 = "rp2040" PLATFORM_RP2040 = "rp2040"
PLATFORM_RTL87XX = "rtl87xx" PLATFORM_RTL87XX = "rtl87xx"

View File

@ -20,6 +20,7 @@ from esphome.const import (
PLATFORM_ESP32, PLATFORM_ESP32,
PLATFORM_ESP8266, PLATFORM_ESP8266,
PLATFORM_HOST, PLATFORM_HOST,
PLATFORM_LN882X,
PLATFORM_RP2040, PLATFORM_RP2040,
PLATFORM_RTL87XX, PLATFORM_RTL87XX,
) )
@ -661,9 +662,13 @@ class EsphomeCore:
def is_rtl87xx(self): def is_rtl87xx(self):
return self.target_platform == PLATFORM_RTL87XX return self.target_platform == PLATFORM_RTL87XX
@property
def is_ln882x(self):
return self.target_platform == PLATFORM_LN882X
@property @property
def is_libretiny(self): def is_libretiny(self):
return self.is_bk72xx or self.is_rtl87xx return self.is_bk72xx or self.is_rtl87xx or self.is_ln882x
@property @property
def is_host(self): def is_host(self):

View File

@ -639,7 +639,11 @@ class DownloadListRequestHandler(BaseHandler):
if platform.upper() in ESP32_VARIANTS: if platform.upper() in ESP32_VARIANTS:
platform = "esp32" platform = "esp32"
elif platform in (const.PLATFORM_RTL87XX, const.PLATFORM_BK72XX): elif platform in (
const.PLATFORM_RTL87XX,
const.PLATFORM_BK72XX,
const.PLATFORM_LN882X,
):
platform = "libretiny" platform = "libretiny"
try: try:
@ -837,6 +841,10 @@ class BoardsRequestHandler(BaseHandler):
from esphome.components.bk72xx.boards import BOARDS as BK72XX_BOARDS from esphome.components.bk72xx.boards import BOARDS as BK72XX_BOARDS
boards = BK72XX_BOARDS boards = BK72XX_BOARDS
elif platform == const.PLATFORM_LN882X:
from esphome.components.ln882x.boards import BOARDS as LN882X_BOARDS
boards = LN882X_BOARDS
elif platform == const.PLATFORM_RTL87XX: elif platform == const.PLATFORM_RTL87XX:
from esphome.components.rtl87xx.boards import BOARDS as RTL87XX_BOARDS from esphome.components.rtl87xx.boards import BOARDS as RTL87XX_BOARDS

View File

@ -83,6 +83,11 @@ bk72xx:
board: {board} board: {board}
""" """
LN882X_CONFIG = """
ln882x:
board: {board}
"""
RTL87XX_CONFIG = """ RTL87XX_CONFIG = """
rtl87xx: rtl87xx:
board: {board} board: {board}
@ -93,6 +98,7 @@ HARDWARE_BASE_CONFIGS = {
"ESP32": ESP32_CONFIG, "ESP32": ESP32_CONFIG,
"RP2040": RP2040_CONFIG, "RP2040": RP2040_CONFIG,
"BK72XX": BK72XX_CONFIG, "BK72XX": BK72XX_CONFIG,
"LN882X": LN882X_CONFIG,
"RTL87XX": RTL87XX_CONFIG, "RTL87XX": RTL87XX_CONFIG,
} }
@ -157,7 +163,7 @@ def wizard_file(**kwargs):
""" """
# pylint: disable=consider-using-f-string # pylint: disable=consider-using-f-string
if kwargs["platform"] in ["ESP8266", "ESP32", "BK72XX", "RTL87XX"]: if kwargs["platform"] in ["ESP8266", "ESP32", "BK72XX", "LN882X", "RTL87XX"]:
config += """ config += """
# Enable fallback hotspot (captive portal) in case wifi connection fails # Enable fallback hotspot (captive portal) in case wifi connection fails
ap: ap:
@ -181,6 +187,7 @@ def wizard_write(path, **kwargs):
from esphome.components.bk72xx import boards as bk72xx_boards from esphome.components.bk72xx import boards as bk72xx_boards
from esphome.components.esp32 import boards as esp32_boards from esphome.components.esp32 import boards as esp32_boards
from esphome.components.esp8266 import boards as esp8266_boards from esphome.components.esp8266 import boards as esp8266_boards
from esphome.components.ln882x import boards as ln882x_boards
from esphome.components.rp2040 import boards as rp2040_boards from esphome.components.rp2040 import boards as rp2040_boards
from esphome.components.rtl87xx import boards as rtl87xx_boards from esphome.components.rtl87xx import boards as rtl87xx_boards
@ -200,6 +207,8 @@ def wizard_write(path, **kwargs):
platform = "RP2040" platform = "RP2040"
elif board in bk72xx_boards.BOARDS: elif board in bk72xx_boards.BOARDS:
platform = "BK72XX" platform = "BK72XX"
elif board in ln882x_boards.BOARDS:
platform = "LN882X"
elif board in rtl87xx_boards.BOARDS: elif board in rtl87xx_boards.BOARDS:
platform = "RTL87XX" platform = "RTL87XX"
else: else:
@ -253,6 +262,7 @@ def wizard(path):
from esphome.components.bk72xx import boards as bk72xx_boards from esphome.components.bk72xx import boards as bk72xx_boards
from esphome.components.esp32 import boards as esp32_boards from esphome.components.esp32 import boards as esp32_boards
from esphome.components.esp8266 import boards as esp8266_boards from esphome.components.esp8266 import boards as esp8266_boards
from esphome.components.ln882x import boards as ln882x_boards
from esphome.components.rp2040 import boards as rp2040_boards from esphome.components.rp2040 import boards as rp2040_boards
from esphome.components.rtl87xx import boards as rtl87xx_boards from esphome.components.rtl87xx import boards as rtl87xx_boards
@ -325,7 +335,7 @@ def wizard(path):
"firmwares for it." "firmwares for it."
) )
wizard_platforms = ["ESP32", "ESP8266", "BK72XX", "RTL87XX", "RP2040"] wizard_platforms = ["ESP32", "ESP8266", "BK72XX", "LN882X", "RTL87XX", "RP2040"]
safe_print( safe_print(
"Please choose one of the supported microcontrollers " "Please choose one of the supported microcontrollers "
"(Use ESP8266 for Sonoff devices)." "(Use ESP8266 for Sonoff devices)."
@ -361,7 +371,7 @@ def wizard(path):
board_link = ( board_link = (
"https://www.raspberrypi.com/documentation/microcontrollers/rp2040.html" "https://www.raspberrypi.com/documentation/microcontrollers/rp2040.html"
) )
elif platform in ["BK72XX", "RTL87XX"]: elif platform in ["BK72XX", "LN882X", "RTL87XX"]:
board_link = "https://docs.libretiny.eu/docs/status/supported/" board_link = "https://docs.libretiny.eu/docs/status/supported/"
else: else:
raise NotImplementedError("Unknown platform!") raise NotImplementedError("Unknown platform!")
@ -384,6 +394,9 @@ def wizard(path):
elif platform == "BK72XX": elif platform == "BK72XX":
safe_print(f'For example "{color(AnsiFore.BOLD_WHITE, "cb2s")}".') safe_print(f'For example "{color(AnsiFore.BOLD_WHITE, "cb2s")}".')
boards_list = bk72xx_boards.BOARDS.items() boards_list = bk72xx_boards.BOARDS.items()
elif platform == "LN882X":
safe_print(f'For example "{color(AnsiFore.BOLD_WHITE, "wl2s")}".')
boards_list = ln882x_boards.BOARDS.items()
elif platform == "RTL87XX": elif platform == "RTL87XX":
safe_print(f'For example "{color(AnsiFore.BOLD_WHITE, "wr3")}".') safe_print(f'For example "{color(AnsiFore.BOLD_WHITE, "wr3")}".')
boards_list = rtl87xx_boards.BOARDS.items() boards_list = rtl87xx_boards.BOARDS.items()

View File

@ -4,7 +4,7 @@
; It's *not* used during runtime. ; It's *not* used during runtime.
[platformio] [platformio]
default_envs = esp8266-arduino, esp32-arduino, esp32-idf, bk72xx-arduino default_envs = esp8266-arduino, esp32-arduino, esp32-idf, bk72xx-arduino, ln882h-arduino
; Ideally, we want src_dir to be the root directory of the repository, to mimic the runtime build ; Ideally, we want src_dir to be the root directory of the repository, to mimic the runtime build
; environment as best as possible. Unfortunately, the ESP-IDF toolchain really doesn't like this ; environment as best as possible. Unfortunately, the ESP-IDF toolchain really doesn't like this
; being the root directory. Instead, set esphome/ as the source directory, all our sources are in ; being the root directory. Instead, set esphome/ as the source directory, all our sources are in
@ -530,6 +530,17 @@ build_flags =
build_unflags = build_unflags =
${common.build_unflags} ${common.build_unflags}
[env:ln882h-arduino]
extends = common:libretiny-arduino
board = generic-ln882hki
build_flags =
${common:libretiny-arduino.build_flags}
${flags:runtime.build_flags}
-DUSE_LN882X
-DUSE_LIBRETINY_VARIANT_LN882H
build_unflags =
${common.build_unflags}
[env:rtl87xxb-arduino] [env:rtl87xxb-arduino]
extends = common:libretiny-arduino extends = common:libretiny-arduino
board = generic-rtl8710bn-2mb-788k board = generic-rtl8710bn-2mb-788k

View File

@ -0,0 +1,4 @@
sensor:
- platform: adc
pin: PA0
name: Basic ADC Test

View File

@ -0,0 +1,2 @@
packages:
common: !include common.yaml

View File

@ -0,0 +1 @@
<<: !include common.yaml

View File

@ -0,0 +1,14 @@
ethernet:
type: DM9051
clk_pin: 19
mosi_pin: 21
miso_pin: 23
cs_pin: 18
interrupt_pin: 36
reset_pin: 22
clock_speed: 10Mhz
manual_ip:
static_ip: 192.168.178.56
gateway: 192.168.178.1
subnet: 255.255.255.0
domain: .local

View File

@ -0,0 +1 @@
<<: !include common-dm9051.yaml

View File

@ -0,0 +1 @@
<<: !include common-dm9051.yaml

View File

@ -0,0 +1,2 @@
packages:
common: !include common.yaml

View File

@ -0,0 +1 @@
<<: !include common.yaml

View File

@ -0,0 +1 @@
<<: !include common.yaml

View File

@ -0,0 +1,2 @@
packages:
common: !include common.yaml

View File

@ -0,0 +1 @@
<<: !include common.yaml

View File

@ -0,0 +1,2 @@
packages:
common: !include common.yaml

View File

@ -0,0 +1,15 @@
esphome:
name: componenttestespln882x
friendly_name: $component_name
ln882x:
board: generic-ln882hki
logger:
level: VERY_VERBOSE
packages:
component_under_test: !include
file: $component_test_file
vars:
component_test_file: $component_test_file

View File

@ -20,6 +20,7 @@ from esphome.const import (
PLATFORM_ESP32, PLATFORM_ESP32,
PLATFORM_ESP8266, PLATFORM_ESP8266,
PLATFORM_HOST, PLATFORM_HOST,
PLATFORM_LN882X,
PLATFORM_RP2040, PLATFORM_RP2040,
PLATFORM_RTL87XX, PLATFORM_RTL87XX,
) )
@ -214,7 +215,8 @@ def hex_int__valid(value):
("arduino", PLATFORM_RP2040, None, "20", "20", "20", "20"), ("arduino", PLATFORM_RP2040, None, "20", "20", "20", "20"),
("arduino", PLATFORM_BK72XX, None, "21", "21", "21", "21"), ("arduino", PLATFORM_BK72XX, None, "21", "21", "21", "21"),
("arduino", PLATFORM_RTL87XX, None, "22", "22", "22", "22"), ("arduino", PLATFORM_RTL87XX, None, "22", "22", "22", "22"),
("host", PLATFORM_HOST, None, "23", "23", "23", "23"), ("arduino", PLATFORM_LN882X, None, "23", "23", "23", "23"),
("host", PLATFORM_HOST, None, "24", "24", "24", "24"),
], ],
) )
def test_split_default(framework, platform, variant, full, idf, arduino, simple): def test_split_default(framework, platform, variant, full, idf, arduino, simple):
@ -244,7 +246,8 @@ def test_split_default(framework, platform, variant, full, idf, arduino, simple)
"rp2040": "20", "rp2040": "20",
"bk72xx": "21", "bk72xx": "21",
"rtl87xx": "22", "rtl87xx": "22",
"host": "23", "ln882x": "23",
"host": "24",
} }
idf_mappings = { idf_mappings = {

View File

@ -8,6 +8,7 @@ import pytest
from esphome.components.bk72xx.boards import BK72XX_BOARD_PINS from esphome.components.bk72xx.boards import BK72XX_BOARD_PINS
from esphome.components.esp32.boards import ESP32_BOARD_PINS from esphome.components.esp32.boards import ESP32_BOARD_PINS
from esphome.components.esp8266.boards import ESP8266_BOARD_PINS from esphome.components.esp8266.boards import ESP8266_BOARD_PINS
from esphome.components.ln882x.boards import LN882X_BOARD_PINS
from esphome.components.rtl87xx.boards import RTL87XX_BOARD_PINS from esphome.components.rtl87xx.boards import RTL87XX_BOARD_PINS
from esphome.core import CORE from esphome.core import CORE
import esphome.wizard as wz import esphome.wizard as wz
@ -187,6 +188,27 @@ def test_wizard_write_defaults_platform_from_board_bk72xx(
assert "bk72xx:" in generated_config assert "bk72xx:" in generated_config
def test_wizard_write_defaults_platform_from_board_ln882x(
default_config, tmp_path, monkeypatch
):
"""
If the platform is not explicitly set, use "LN882X" if the board is one of LN882X boards
"""
# Given
del default_config["platform"]
default_config["board"] = [*LN882X_BOARD_PINS][0]
monkeypatch.setattr(wz, "write_file", MagicMock())
monkeypatch.setattr(CORE, "config_path", os.path.dirname(tmp_path))
# When
wz.wizard_write(tmp_path, **default_config)
# Then
generated_config = wz.write_file.call_args.args[1]
assert "ln882x:" in generated_config
def test_wizard_write_defaults_platform_from_board_rtl87xx( def test_wizard_write_defaults_platform_from_board_rtl87xx(
default_config, tmp_path, monkeypatch default_config, tmp_path, monkeypatch
): ):