diff --git a/.ai/instructions.md b/.ai/instructions.md index 681829bae6b..9d48f467cb4 100644 --- a/.ai/instructions.md +++ b/.ai/instructions.md @@ -402,35 +402,45 @@ This document provides essential context for AI models interacting with this pro _use_feature = True ``` - **Good Pattern (CORE.data with Helpers):** + **Bad Pattern (Flat Keys):** ```python + # Don't do this - keys should be namespaced under component domain + MY_FEATURE_KEY = "my_component_feature" + CORE.data[MY_FEATURE_KEY] = True + ``` + + **Good Pattern (dataclass):** + ```python + from dataclasses import dataclass, field from esphome.core import CORE - # Keys for CORE.data storage - COMPONENT_STATE_KEY = "my_component_state" - USE_FEATURE_KEY = "my_component_use_feature" + DOMAIN = "my_component" - def _get_component_state() -> list: - """Get component state from CORE.data.""" - return CORE.data.setdefault(COMPONENT_STATE_KEY, []) + @dataclass + class MyComponentData: + feature_enabled: bool = False + item_count: int = 0 + items: list[str] = field(default_factory=list) - def _get_use_feature() -> bool | None: - """Get feature flag from CORE.data.""" - return CORE.data.get(USE_FEATURE_KEY) + def _get_data() -> MyComponentData: + if DOMAIN not in CORE.data: + CORE.data[DOMAIN] = MyComponentData() + return CORE.data[DOMAIN] - def _set_use_feature(value: bool) -> None: - """Set feature flag in CORE.data.""" - CORE.data[USE_FEATURE_KEY] = value + def request_feature() -> None: + _get_data().feature_enabled = True - def enable_feature(): - _set_use_feature(True) + def add_item(item: str) -> None: + _get_data().items.append(item) ``` + If you need a real-world example, search for components that use `@dataclass` with `CORE.data` in the codebase. Note: Some components may use `TypedDict` for dictionary-based storage; both patterns are acceptable depending on your needs. + **Why this matters:** - Module-level globals persist between compilation runs if the dashboard doesn't fork/exec - `CORE.data` automatically clears between runs - - Typed helper functions provide better IDE support and maintainability - - Encapsulation makes state management explicit and testable + - Namespacing under `DOMAIN` prevents key collisions between components + - `@dataclass` provides type safety and cleaner attribute access * **Security:** Be mindful of security when making changes to the API, web server, or any other network-related code. Do not hardcode secrets or keys. diff --git a/.clang-tidy.hash b/.clang-tidy.hash index 3ade00f0cd5..ab3217b5e52 100644 --- a/.clang-tidy.hash +++ b/.clang-tidy.hash @@ -1 +1 @@ -3d46b63015d761c85ca9cb77ab79a389509e5776701fb22aed16e7b79d432c0c +29270eecb86ffa07b2b1d2a4ca56dd7f84762ddc89c6248dbf3f012eca8780b6 diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 5843b3a5e0f..7e03e2a5f99 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -19,7 +19,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Stale - uses: actions/stale@5f858e3efba33a5ca4407a664cc011ad407f2008 # v10.1.0 + uses: actions/stale@997185467fa4f803885201cee163a9f38240193d # v10.1.1 with: debug-only: ${{ github.ref != 'refs/heads/dev' }} # Dry-run when not run on dev branch remove-stale-when-updated: true diff --git a/CODEOWNERS b/CODEOWNERS index 78618713235..65405f79d18 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -97,6 +97,7 @@ esphome/components/camera_encoder/* @DT-art1 esphome/components/canbus/* @danielschramm @mvturnho esphome/components/cap1188/* @mreditor97 esphome/components/captive_portal/* @esphome/core +esphome/components/cc1101/* @gabest11 @lygris esphome/components/ccs811/* @habbie esphome/components/cd74hc4067/* @asoehlke esphome/components/ch422g/* @clydebarrow @jesterret @@ -190,6 +191,7 @@ esphome/components/gps/* @coogle @ximex esphome/components/graph/* @synco esphome/components/graphical_display_menu/* @MrMDavidson esphome/components/gree/* @orestismers +esphome/components/gree/switch/* @nagyrobi esphome/components/grove_gas_mc_v2/* @YorkshireIoT esphome/components/grove_tb6612fng/* @max246 esphome/components/growatt_solar/* @leeuwte diff --git a/esphome/components/adc/__init__.py b/esphome/components/adc/__init__.py index 15dc447b6c6..8f751c496e0 100644 --- a/esphome/components/adc/__init__.py +++ b/esphome/components/adc/__init__.py @@ -107,6 +107,17 @@ ESP32_VARIANT_ADC1_PIN_TO_CHANNEL = { 4: adc_channel_t.ADC_CHANNEL_3, 5: adc_channel_t.ADC_CHANNEL_4, }, + # https://github.com/espressif/esp-idf/blob/master/components/soc/esp32p4/include/soc/adc_channel.h + VARIANT_ESP32P4: { + 16: adc_channel_t.ADC_CHANNEL_0, + 17: adc_channel_t.ADC_CHANNEL_1, + 18: adc_channel_t.ADC_CHANNEL_2, + 19: adc_channel_t.ADC_CHANNEL_3, + 20: adc_channel_t.ADC_CHANNEL_4, + 21: adc_channel_t.ADC_CHANNEL_5, + 22: adc_channel_t.ADC_CHANNEL_6, + 23: adc_channel_t.ADC_CHANNEL_7, + }, # https://github.com/espressif/esp-idf/blob/master/components/soc/esp32s2/include/soc/adc_channel.h VARIANT_ESP32S2: { 1: adc_channel_t.ADC_CHANNEL_0, @@ -133,16 +144,6 @@ ESP32_VARIANT_ADC1_PIN_TO_CHANNEL = { 9: adc_channel_t.ADC_CHANNEL_8, 10: adc_channel_t.ADC_CHANNEL_9, }, - VARIANT_ESP32P4: { - 16: adc_channel_t.ADC_CHANNEL_0, - 17: adc_channel_t.ADC_CHANNEL_1, - 18: adc_channel_t.ADC_CHANNEL_2, - 19: adc_channel_t.ADC_CHANNEL_3, - 20: adc_channel_t.ADC_CHANNEL_4, - 21: adc_channel_t.ADC_CHANNEL_5, - 22: adc_channel_t.ADC_CHANNEL_6, - 23: adc_channel_t.ADC_CHANNEL_7, - }, } # pin to adc2 channel mapping @@ -175,6 +176,15 @@ ESP32_VARIANT_ADC2_PIN_TO_CHANNEL = { VARIANT_ESP32C6: {}, # no ADC2 # https://github.com/espressif/esp-idf/blob/master/components/soc/esp32h2/include/soc/adc_channel.h VARIANT_ESP32H2: {}, # no ADC2 + # https://github.com/espressif/esp-idf/blob/master/components/soc/esp32p4/include/soc/adc_channel.h + VARIANT_ESP32P4: { + 49: adc_channel_t.ADC_CHANNEL_0, + 50: adc_channel_t.ADC_CHANNEL_1, + 51: adc_channel_t.ADC_CHANNEL_2, + 52: adc_channel_t.ADC_CHANNEL_3, + 53: adc_channel_t.ADC_CHANNEL_4, + 54: adc_channel_t.ADC_CHANNEL_5, + }, # https://github.com/espressif/esp-idf/blob/master/components/soc/esp32s2/include/soc/adc_channel.h VARIANT_ESP32S2: { 11: adc_channel_t.ADC_CHANNEL_0, @@ -201,14 +211,6 @@ ESP32_VARIANT_ADC2_PIN_TO_CHANNEL = { 19: adc_channel_t.ADC_CHANNEL_8, 20: adc_channel_t.ADC_CHANNEL_9, }, - VARIANT_ESP32P4: { - 49: adc_channel_t.ADC_CHANNEL_0, - 50: adc_channel_t.ADC_CHANNEL_1, - 51: adc_channel_t.ADC_CHANNEL_2, - 52: adc_channel_t.ADC_CHANNEL_3, - 53: adc_channel_t.ADC_CHANNEL_4, - 54: adc_channel_t.ADC_CHANNEL_5, - }, } diff --git a/esphome/components/adc/adc_sensor_esp32.cpp b/esphome/components/adc/adc_sensor_esp32.cpp index ab6a89fce0d..e25b275cd67 100644 --- a/esphome/components/adc/adc_sensor_esp32.cpp +++ b/esphome/components/adc/adc_sensor_esp32.cpp @@ -74,7 +74,7 @@ void ADCSensor::setup() { adc_cali_handle_t handle = nullptr; #if USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C5 || USE_ESP32_VARIANT_ESP32C6 || \ - USE_ESP32_VARIANT_ESP32S3 || USE_ESP32_VARIANT_ESP32H2 || USE_ESP32_VARIANT_ESP32P4 + USE_ESP32_VARIANT_ESP32H2 || USE_ESP32_VARIANT_ESP32P4 || USE_ESP32_VARIANT_ESP32S3 // RISC-V variants and S3 use curve fitting calibration adc_cali_curve_fitting_config_t cali_config = {}; // Zero initialize first #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0) @@ -111,7 +111,7 @@ void ADCSensor::setup() { ESP_LOGW(TAG, "Line fitting calibration failed with error %d, will use uncalibrated readings", err); this->setup_flags_.calibration_complete = false; } -#endif // USE_ESP32_VARIANT_ESP32C3 || ESP32C5 || ESP32C6 || ESP32S3 || ESP32H2 +#endif // USE_ESP32_VARIANT_ESP32C3 || ESP32C5 || ESP32C6 || ESP32H2 || ESP32P4 || ESP32S3 } this->setup_flags_.init_complete = true; @@ -186,11 +186,11 @@ float ADCSensor::sample_fixed_attenuation_() { ESP_LOGW(TAG, "ADC calibration conversion failed with error %d, disabling calibration", err); if (this->calibration_handle_ != nullptr) { #if USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C5 || USE_ESP32_VARIANT_ESP32C6 || \ - USE_ESP32_VARIANT_ESP32S3 || USE_ESP32_VARIANT_ESP32H2 || USE_ESP32_VARIANT_ESP32P4 + USE_ESP32_VARIANT_ESP32H2 || USE_ESP32_VARIANT_ESP32P4 || USE_ESP32_VARIANT_ESP32S3 adc_cali_delete_scheme_curve_fitting(this->calibration_handle_); #else // Other ESP32 variants use line fitting calibration adc_cali_delete_scheme_line_fitting(this->calibration_handle_); -#endif // USE_ESP32_VARIANT_ESP32C3 || ESP32C5 || ESP32C6 || ESP32S3 || ESP32H2 +#endif // USE_ESP32_VARIANT_ESP32C3 || ESP32C5 || ESP32C6 || ESP32H2 || ESP32P4 || ESP32S3 this->calibration_handle_ = nullptr; } } @@ -219,7 +219,7 @@ float ADCSensor::sample_autorange_() { if (this->calibration_handle_ != nullptr) { // Delete old calibration handle #if USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C5 || USE_ESP32_VARIANT_ESP32C6 || \ - USE_ESP32_VARIANT_ESP32S3 || USE_ESP32_VARIANT_ESP32H2 || USE_ESP32_VARIANT_ESP32P4 + USE_ESP32_VARIANT_ESP32H2 || USE_ESP32_VARIANT_ESP32P4 || USE_ESP32_VARIANT_ESP32S3 adc_cali_delete_scheme_curve_fitting(this->calibration_handle_); #else adc_cali_delete_scheme_line_fitting(this->calibration_handle_); @@ -231,7 +231,7 @@ float ADCSensor::sample_autorange_() { adc_cali_handle_t handle = nullptr; #if USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C5 || USE_ESP32_VARIANT_ESP32C6 || \ - USE_ESP32_VARIANT_ESP32S3 || USE_ESP32_VARIANT_ESP32H2 || USE_ESP32_VARIANT_ESP32P4 + USE_ESP32_VARIANT_ESP32H2 || USE_ESP32_VARIANT_ESP32P4 || USE_ESP32_VARIANT_ESP32S3 adc_cali_curve_fitting_config_t cali_config = {}; #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0) cali_config.chan = this->channel_; @@ -266,7 +266,7 @@ float ADCSensor::sample_autorange_() { ESP_LOGW(TAG, "ADC read failed in autorange with error %d", err); if (handle != nullptr) { #if USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C5 || USE_ESP32_VARIANT_ESP32C6 || \ - USE_ESP32_VARIANT_ESP32S3 || USE_ESP32_VARIANT_ESP32H2 || USE_ESP32_VARIANT_ESP32P4 + USE_ESP32_VARIANT_ESP32H2 || USE_ESP32_VARIANT_ESP32P4 || USE_ESP32_VARIANT_ESP32S3 adc_cali_delete_scheme_curve_fitting(handle); #else adc_cali_delete_scheme_line_fitting(handle); @@ -288,7 +288,7 @@ float ADCSensor::sample_autorange_() { } // Clean up calibration handle #if USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C5 || USE_ESP32_VARIANT_ESP32C6 || \ - USE_ESP32_VARIANT_ESP32S3 || USE_ESP32_VARIANT_ESP32H2 || USE_ESP32_VARIANT_ESP32P4 + USE_ESP32_VARIANT_ESP32H2 || USE_ESP32_VARIANT_ESP32P4 || USE_ESP32_VARIANT_ESP32S3 adc_cali_delete_scheme_curve_fitting(handle); #else adc_cali_delete_scheme_line_fitting(handle); diff --git a/esphome/components/cc1101/__init__.py b/esphome/components/cc1101/__init__.py new file mode 100644 index 00000000000..0f5743d0cdb --- /dev/null +++ b/esphome/components/cc1101/__init__.py @@ -0,0 +1,220 @@ +from esphome import automation +from esphome.automation import maybe_simple_id +import esphome.codegen as cg +from esphome.components import spi +import esphome.config_validation as cv +from esphome.const import CONF_CHANNEL, CONF_FREQUENCY, CONF_ID, CONF_WAIT_TIME + +CODEOWNERS = ["@lygris", "@gabest11"] +DEPENDENCIES = ["spi"] +MULTI_CONF = True + +ns = cg.esphome_ns.namespace("cc1101") +CC1101Component = ns.class_("CC1101Component", cg.Component, spi.SPIDevice) + +# Config keys +CONF_OUTPUT_POWER = "output_power" +CONF_RX_ATTENUATION = "rx_attenuation" +CONF_DC_BLOCKING_FILTER = "dc_blocking_filter" +CONF_IF_FREQUENCY = "if_frequency" +CONF_FILTER_BANDWIDTH = "filter_bandwidth" +CONF_CHANNEL_SPACING = "channel_spacing" +CONF_FSK_DEVIATION = "fsk_deviation" +CONF_MSK_DEVIATION = "msk_deviation" +CONF_SYMBOL_RATE = "symbol_rate" +CONF_SYNC_MODE = "sync_mode" +CONF_CARRIER_SENSE_ABOVE_THRESHOLD = "carrier_sense_above_threshold" +CONF_MODULATION_TYPE = "modulation_type" +CONF_MANCHESTER = "manchester" +CONF_NUM_PREAMBLE = "num_preamble" +CONF_SYNC1 = "sync1" +CONF_SYNC0 = "sync0" +CONF_PKTLEN = "pktlen" +CONF_MAGN_TARGET = "magn_target" +CONF_MAX_LNA_GAIN = "max_lna_gain" +CONF_MAX_DVGA_GAIN = "max_dvga_gain" +CONF_CARRIER_SENSE_ABS_THR = "carrier_sense_abs_thr" +CONF_CARRIER_SENSE_REL_THR = "carrier_sense_rel_thr" +CONF_LNA_PRIORITY = "lna_priority" +CONF_FILTER_LENGTH_FSK_MSK = "filter_length_fsk_msk" +CONF_FILTER_LENGTH_ASK_OOK = "filter_length_ask_ook" +CONF_FREEZE = "freeze" +CONF_HYST_LEVEL = "hyst_level" + +# Enums +SyncMode = ns.enum("SyncMode", True) +SYNC_MODE = { + "None": SyncMode.SYNC_MODE_NONE, + "15/16": SyncMode.SYNC_MODE_15_16, + "16/16": SyncMode.SYNC_MODE_16_16, + "30/32": SyncMode.SYNC_MODE_30_32, +} + +Modulation = ns.enum("Modulation", True) +MODULATION = { + "2-FSK": Modulation.MODULATION_2_FSK, + "GFSK": Modulation.MODULATION_GFSK, + "ASK/OOK": Modulation.MODULATION_ASK_OOK, + "4-FSK": Modulation.MODULATION_4_FSK, + "MSK": Modulation.MODULATION_MSK, +} + +RxAttenuation = ns.enum("RxAttenuation", True) +RX_ATTENUATION = { + "0dB": RxAttenuation.RX_ATTENUATION_0DB, + "6dB": RxAttenuation.RX_ATTENUATION_6DB, + "12dB": RxAttenuation.RX_ATTENUATION_12DB, + "18dB": RxAttenuation.RX_ATTENUATION_18DB, +} + +MagnTarget = ns.enum("MagnTarget", True) +MAGN_TARGET = { + "24dB": MagnTarget.MAGN_TARGET_24DB, + "27dB": MagnTarget.MAGN_TARGET_27DB, + "30dB": MagnTarget.MAGN_TARGET_30DB, + "33dB": MagnTarget.MAGN_TARGET_33DB, + "36dB": MagnTarget.MAGN_TARGET_36DB, + "38dB": MagnTarget.MAGN_TARGET_38DB, + "40dB": MagnTarget.MAGN_TARGET_40DB, + "42dB": MagnTarget.MAGN_TARGET_42DB, +} + +MaxLnaGain = ns.enum("MaxLnaGain", True) +MAX_LNA_GAIN = { + "Default": MaxLnaGain.MAX_LNA_GAIN_DEFAULT, + "2.6dB": MaxLnaGain.MAX_LNA_GAIN_MINUS_2P6DB, + "6.1dB": MaxLnaGain.MAX_LNA_GAIN_MINUS_6P1DB, + "7.4dB": MaxLnaGain.MAX_LNA_GAIN_MINUS_7P4DB, + "9.2dB": MaxLnaGain.MAX_LNA_GAIN_MINUS_9P2DB, + "11.5dB": MaxLnaGain.MAX_LNA_GAIN_MINUS_11P5DB, + "14.6dB": MaxLnaGain.MAX_LNA_GAIN_MINUS_14P6DB, + "17.1dB": MaxLnaGain.MAX_LNA_GAIN_MINUS_17P1DB, +} + +MaxDvgaGain = ns.enum("MaxDvgaGain", True) +MAX_DVGA_GAIN = { + "Default": MaxDvgaGain.MAX_DVGA_GAIN_DEFAULT, + "-1": MaxDvgaGain.MAX_DVGA_GAIN_MINUS_1, + "-2": MaxDvgaGain.MAX_DVGA_GAIN_MINUS_2, + "-3": MaxDvgaGain.MAX_DVGA_GAIN_MINUS_3, +} + +CarrierSenseRelThr = ns.enum("CarrierSenseRelThr", True) +CARRIER_SENSE_REL_THR = { + "Default": CarrierSenseRelThr.CARRIER_SENSE_REL_THR_DEFAULT, + "+6dB": CarrierSenseRelThr.CARRIER_SENSE_REL_THR_PLUS_6DB, + "+10dB": CarrierSenseRelThr.CARRIER_SENSE_REL_THR_PLUS_10DB, + "+14dB": CarrierSenseRelThr.CARRIER_SENSE_REL_THR_PLUS_14DB, +} + +FilterLengthFskMsk = ns.enum("FilterLengthFskMsk", True) +FILTER_LENGTH_FSK_MSK = { + "8": FilterLengthFskMsk.FILTER_LENGTH_8DB, + "16": FilterLengthFskMsk.FILTER_LENGTH_16DB, + "32": FilterLengthFskMsk.FILTER_LENGTH_32DB, + "64": FilterLengthFskMsk.FILTER_LENGTH_64DB, +} + +FilterLengthAskOok = ns.enum("FilterLengthAskOok", True) +FILTER_LENGTH_ASK_OOK = { + "4dB": FilterLengthAskOok.FILTER_LENGTH_4DB, + "8dB": FilterLengthAskOok.FILTER_LENGTH_8DB, + "12dB": FilterLengthAskOok.FILTER_LENGTH_12DB, + "16dB": FilterLengthAskOok.FILTER_LENGTH_16DB, +} + +Freeze = ns.enum("Freeze", True) +FREEZE = { + "Default": Freeze.FREEZE_DEFAULT, + "On Sync": Freeze.FREEZE_ON_SYNC, + "Analog Only": Freeze.FREEZE_ANALOG_ONLY, + "Analog And Digital": Freeze.FREEZE_ANALOG_AND_DIGITAL, +} + +WaitTime = ns.enum("WaitTime", True) +WAIT_TIME = { + "8": WaitTime.WAIT_TIME_8_SAMPLES, + "16": WaitTime.WAIT_TIME_16_SAMPLES, + "24": WaitTime.WAIT_TIME_24_SAMPLES, + "32": WaitTime.WAIT_TIME_32_SAMPLES, +} + +HystLevel = ns.enum("HystLevel", True) +HYST_LEVEL = { + "None": HystLevel.HYST_LEVEL_NONE, + "Low": HystLevel.HYST_LEVEL_LOW, + "Medium": HystLevel.HYST_LEVEL_MEDIUM, + "High": HystLevel.HYST_LEVEL_HIGH, +} + +# Config key -> Validator mapping +CONFIG_MAP = { + CONF_OUTPUT_POWER: cv.float_range(min=-30.0, max=11.0), + CONF_RX_ATTENUATION: cv.enum(RX_ATTENUATION, upper=False), + CONF_DC_BLOCKING_FILTER: cv.boolean, + CONF_FREQUENCY: cv.float_range(min=300000.0, max=928000.0), + CONF_IF_FREQUENCY: cv.float_range(min=25, max=788), + CONF_FILTER_BANDWIDTH: cv.float_range(min=58.0, max=812.0), + CONF_CHANNEL: cv.uint8_t, + CONF_CHANNEL_SPACING: cv.float_range(min=25, max=405), + CONF_FSK_DEVIATION: cv.float_range(min=1.5, max=381), + CONF_MSK_DEVIATION: cv.int_range(min=1, max=8), + CONF_SYMBOL_RATE: cv.float_range(min=600, max=500000), + CONF_SYNC_MODE: cv.enum(SYNC_MODE, upper=False), + CONF_CARRIER_SENSE_ABOVE_THRESHOLD: cv.boolean, + CONF_MODULATION_TYPE: cv.enum(MODULATION, upper=False), + CONF_MANCHESTER: cv.boolean, + CONF_NUM_PREAMBLE: cv.int_range(min=0, max=7), + CONF_SYNC1: cv.hex_uint8_t, + CONF_SYNC0: cv.hex_uint8_t, + CONF_PKTLEN: cv.uint8_t, + CONF_MAGN_TARGET: cv.enum(MAGN_TARGET, upper=False), + CONF_MAX_LNA_GAIN: cv.enum(MAX_LNA_GAIN, upper=False), + CONF_MAX_DVGA_GAIN: cv.enum(MAX_DVGA_GAIN, upper=False), + CONF_CARRIER_SENSE_ABS_THR: cv.int_range(min=-8, max=7), + CONF_CARRIER_SENSE_REL_THR: cv.enum(CARRIER_SENSE_REL_THR, upper=False), + CONF_LNA_PRIORITY: cv.boolean, + CONF_FILTER_LENGTH_FSK_MSK: cv.enum(FILTER_LENGTH_FSK_MSK, upper=False), + CONF_FILTER_LENGTH_ASK_OOK: cv.enum(FILTER_LENGTH_ASK_OOK, upper=False), + CONF_FREEZE: cv.enum(FREEZE, upper=False), + CONF_WAIT_TIME: cv.enum(WAIT_TIME, upper=False), + CONF_HYST_LEVEL: cv.enum(HYST_LEVEL, upper=False), +} + +CONFIG_SCHEMA = ( + cv.Schema({cv.GenerateID(): cv.declare_id(CC1101Component)}) + .extend({cv.Optional(key): validator for key, validator in CONFIG_MAP.items()}) + .extend(cv.COMPONENT_SCHEMA) + .extend(spi.spi_device_schema(cs_pin_required=True)) +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await spi.register_spi_device(var, config) + + for key in CONFIG_MAP: + if key in config: + cg.add(getattr(var, f"set_{key}")(config[key])) + + +# Actions +BeginTxAction = ns.class_("BeginTxAction", automation.Action) +BeginRxAction = ns.class_("BeginRxAction", automation.Action) +ResetAction = ns.class_("ResetAction", automation.Action) +SetIdleAction = ns.class_("SetIdleAction", automation.Action) + +CC1101_ACTION_SCHEMA = cv.Schema( + maybe_simple_id({cv.GenerateID(CONF_ID): cv.use_id(CC1101Component)}) +) + + +@automation.register_action("cc1101.begin_tx", BeginTxAction, CC1101_ACTION_SCHEMA) +@automation.register_action("cc1101.begin_rx", BeginRxAction, CC1101_ACTION_SCHEMA) +@automation.register_action("cc1101.reset", ResetAction, CC1101_ACTION_SCHEMA) +@automation.register_action("cc1101.set_idle", SetIdleAction, CC1101_ACTION_SCHEMA) +async def cc1101_action_to_code(config, action_id, template_arg, args): + var = cg.new_Pvariable(action_id, template_arg) + await cg.register_parented(var, config[CONF_ID]) + return var diff --git a/esphome/components/cc1101/cc1101.cpp b/esphome/components/cc1101/cc1101.cpp new file mode 100644 index 00000000000..1a758e415af --- /dev/null +++ b/esphome/components/cc1101/cc1101.cpp @@ -0,0 +1,550 @@ +#include "cc1101.h" +#include "cc1101pa.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" +#include + +namespace esphome::cc1101 { + +static const char *const TAG = "cc1101"; + +static void split_float(float value, int mbits, uint8_t &e, uint32_t &m) { + int e_tmp; + float m_tmp = std::frexp(value, &e_tmp); + if (e_tmp <= mbits) { + e = 0; + m = 0; + return; + } + e = static_cast(e_tmp - mbits - 1); + m = static_cast(((m_tmp * 2 - 1) * (1 << (mbits + 1))) + 1) >> 1; + if (m == (1UL << mbits)) { + e = e + 1; + m = 0; + } +} + +CC1101Component::CC1101Component() { + // Datasheet defaults + memset(&this->state_, 0, sizeof(this->state_)); + this->state_.GDO2_CFG = 0x0D; // Serial Data (for RX on GDO2) + this->state_.GDO1_CFG = 0x2E; + this->state_.GDO0_CFG = 0x0D; // Serial Data (for RX on GDO0 / TX Input) + this->state_.FIFO_THR = 7; + this->state_.SYNC1 = 0xD3; + this->state_.SYNC0 = 0x91; + this->state_.PKTLEN = 0xFF; + this->state_.APPEND_STATUS = 1; + this->state_.LENGTH_CONFIG = 1; + this->state_.CRC_EN = 1; + this->state_.WHITE_DATA = 1; + this->state_.FREQ_IF = 0x0F; + this->state_.FREQ2 = 0x1E; + this->state_.FREQ1 = 0xC4; + this->state_.FREQ0 = 0xEC; + this->state_.DRATE_E = 0x0C; + this->state_.CHANBW_E = 0x02; + this->state_.DRATE_M = 0x22; + this->state_.SYNC_MODE = 2; + this->state_.CHANSPC_E = 2; + this->state_.NUM_PREAMBLE = 2; + this->state_.CHANSPC_M = 0xF8; + this->state_.DEVIATION_M = 7; + this->state_.DEVIATION_E = 4; + this->state_.RX_TIME = 7; + this->state_.CCA_MODE = 3; + this->state_.PO_TIMEOUT = 1; + this->state_.FOC_LIMIT = 2; + this->state_.FOC_POST_K = 1; + this->state_.FOC_PRE_K = 2; + this->state_.FOC_BS_CS_GATE = 1; + this->state_.BS_POST_KP = 1; + this->state_.BS_POST_KI = 1; + this->state_.BS_PRE_KP = 2; + this->state_.BS_PRE_KI = 1; + this->state_.MAGN_TARGET = 3; + this->state_.AGC_LNA_PRIORITY = 1; + this->state_.FILTER_LENGTH = 1; + this->state_.WAIT_TIME = 1; + this->state_.HYST_LEVEL = 2; + this->state_.WOREVT1 = 0x87; + this->state_.WOREVT0 = 0x6B; + this->state_.RC_CAL = 1; + this->state_.EVENT1 = 7; + this->state_.RC_PD = 1; + this->state_.MIX_CURRENT = 2; + this->state_.LODIV_BUF_CURRENT_RX = 1; + this->state_.LNA2MIX_CURRENT = 1; + this->state_.LNA_CURRENT = 1; + this->state_.LODIV_BUF_CURRENT_TX = 1; + this->state_.FSCAL3_LO = 9; + this->state_.CHP_CURR_CAL_EN = 2; + this->state_.FSCAL3_HI = 2; + this->state_.FSCAL2 = 0x0A; + this->state_.FSCAL1 = 0x20; + this->state_.FSCAL0 = 0x0D; + this->state_.RCCTRL1 = 0x41; + this->state_.FSTEST = 0x59; + this->state_.PTEST = 0x7F; + this->state_.AGCTEST = 0x3F; + this->state_.TEST2 = 0x88; + this->state_.TEST1 = 0x31; + this->state_.TEST0_LO = 1; + this->state_.VCO_SEL_CAL_EN = 1; + this->state_.TEST0_HI = 2; + + // PKTCTRL0 + this->state_.PKT_FORMAT = 3; + this->state_.LENGTH_CONFIG = 2; + this->state_.FS_AUTOCAL = 1; + + // Default Settings + this->set_frequency(433920); + this->set_if_frequency(153); + this->set_filter_bandwidth(203); + this->set_channel(0); + this->set_channel_spacing(200); + this->set_symbol_rate(5000); + this->set_sync_mode(SyncMode::SYNC_MODE_NONE); + this->set_carrier_sense_above_threshold(true); + this->set_modulation_type(Modulation::MODULATION_ASK_OOK); + this->set_magn_target(MagnTarget::MAGN_TARGET_42DB); + this->set_max_lna_gain(MaxLnaGain::MAX_LNA_GAIN_DEFAULT); + this->set_max_dvga_gain(MaxDvgaGain::MAX_DVGA_GAIN_MINUS_3); + this->set_lna_priority(false); + this->set_wait_time(WaitTime::WAIT_TIME_32_SAMPLES); + + // CRITICAL: Initialize PA Table to avoid transmitting 0 power (Silence) + memset(this->pa_table_, 0, sizeof(this->pa_table_)); + this->set_output_power(10.0f); +} + +void CC1101Component::setup() { + this->spi_setup(); + this->cs_->digital_write(true); + delayMicroseconds(1); + this->cs_->digital_write(false); + delayMicroseconds(1); + this->cs_->digital_write(true); + delayMicroseconds(41); + this->cs_->digital_write(false); + delay(5); + + this->strobe_(Command::RES); + delay(5); + + this->read_(Register::PARTNUM); + this->read_(Register::VERSION); + this->chip_id_ = encode_uint16(this->state_.PARTNUM, this->state_.VERSION); + ESP_LOGD(TAG, "CC1101 found! Chip ID: 0x%04X", this->chip_id_); + if (this->state_.VERSION == 0 || this->state_.PARTNUM == 0xFF) { + ESP_LOGE(TAG, "Failed to verify CC1101."); + this->mark_failed(); + return; + } + + this->initialized_ = true; + + for (uint8_t i = 0; i <= static_cast(Register::TEST0); i++) { + if (i == static_cast(Register::FSTEST) || i == static_cast(Register::AGCTEST)) { + continue; + } + this->write_(static_cast(i)); + } + this->write_(Register::PATABLE, this->pa_table_, sizeof(this->pa_table_)); + this->strobe_(Command::RX); +} + +void CC1101Component::dump_config() { + static const char *const MODULATION_NAMES[] = {"2-FSK", "GFSK", "UNUSED", "ASK/OOK", + "4-FSK", "UNUSED", "UNUSED", "MSK"}; + int32_t freq = static_cast(this->state_.FREQ2 << 16 | this->state_.FREQ1 << 8 | this->state_.FREQ0) * + XTAL_FREQUENCY / (1 << 16); + float symbol_rate = + (((256.0f + this->state_.DRATE_M) * (1 << this->state_.DRATE_E)) / (1 << 28)) * XTAL_FREQUENCY * 1000.0f; + float bw = XTAL_FREQUENCY / (8.0f * (4 + this->state_.CHANBW_M) * (1 << this->state_.CHANBW_E)); + ESP_LOGCONFIG(TAG, "CC1101:"); + LOG_PIN(" CS Pin: ", this->cs_); + ESP_LOGCONFIG(TAG, + " Chip ID: 0x%04X\n" + " Frequency: %" PRId32 " kHz\n" + " Channel: %u\n" + " Modulation: %s\n" + " Symbol Rate: %.0f baud\n" + " Filter Bandwidth: %.1f kHz\n" + " Output Power: %.1f dBm", + this->chip_id_, freq, this->state_.CHANNR, MODULATION_NAMES[this->state_.MOD_FORMAT & 0x07], + symbol_rate, bw, this->output_power_effective_); +} + +void CC1101Component::begin_tx() { + // Ensure Packet Format is 3 (Async Serial), use GDO0 as input during TX + this->write_(Register::PKTCTRL0, 0x32); + ESP_LOGV(TAG, "Beginning TX sequence"); + this->strobe_(Command::TX); + if (!this->wait_for_state_(State::TX, 50)) { + ESP_LOGW(TAG, "Timed out waiting for TX state!"); + } +} + +void CC1101Component::begin_rx() { + ESP_LOGV(TAG, "Beginning RX sequence"); + this->strobe_(Command::RX); +} + +void CC1101Component::reset() { + this->strobe_(Command::RES); + this->setup(); +} + +void CC1101Component::set_idle() { + ESP_LOGV(TAG, "Setting IDLE state"); + this->enter_idle_(); +} + +void CC1101Component::set_gdo0_config(uint8_t value) { + this->state_.GDO0_CFG = value; + if (this->initialized_) { + this->write_(Register::IOCFG0); + } +} + +void CC1101Component::set_gdo2_config(uint8_t value) { + this->state_.GDO2_CFG = value; + if (this->initialized_) { + this->write_(Register::IOCFG2); + } +} + +bool CC1101Component::wait_for_state_(State target_state, uint32_t timeout_ms) { + uint32_t start = millis(); + while (millis() - start < timeout_ms) { + this->read_(Register::MARCSTATE); + State s = static_cast(this->state_.MARC_STATE); + if (s == target_state) { + return true; + } + delayMicroseconds(100); + } + return false; +} + +void CC1101Component::enter_idle_() { + this->strobe_(Command::IDLE); + this->wait_for_state_(State::IDLE); +} + +uint8_t CC1101Component::strobe_(Command cmd) { + uint8_t index = static_cast(cmd); + if (cmd < Command::RES || cmd > Command::NOP) { + return 0xFF; + } + this->enable(); + uint8_t status_byte = this->transfer_byte(index); + this->disable(); + return status_byte; +} + +void CC1101Component::write_(Register reg) { + uint8_t index = static_cast(reg); + this->enable(); + this->write_byte(index); + this->write_array(&this->state_.regs()[index], 1); + this->disable(); +} + +void CC1101Component::write_(Register reg, uint8_t value) { + uint8_t index = static_cast(reg); + this->state_.regs()[index] = value; + this->write_(reg); +} + +void CC1101Component::write_(Register reg, const uint8_t *buffer, size_t length) { + uint8_t index = static_cast(reg); + this->enable(); + this->write_byte(index | BUS_WRITE | BUS_BURST); + this->write_array(buffer, length); + this->disable(); +} + +void CC1101Component::read_(Register reg) { + uint8_t index = static_cast(reg); + this->enable(); + this->write_byte(index | BUS_READ | BUS_BURST); + this->state_.regs()[index] = this->transfer_byte(0); + this->disable(); +} + +void CC1101Component::read_(Register reg, uint8_t *buffer, size_t length) { + uint8_t index = static_cast(reg); + this->enable(); + this->write_byte(index | BUS_READ | BUS_BURST); + this->read_array(buffer, length); + this->disable(); +} + +// Setters +void CC1101Component::set_output_power(float value) { + this->output_power_requested_ = value; + int32_t freq = static_cast(this->state_.FREQ2 << 16 | this->state_.FREQ1 << 8 | this->state_.FREQ0) * + XTAL_FREQUENCY / (1 << 16); + uint8_t a = 0xC0; + if (freq >= 300000 && freq <= 348000) { + a = PowerTableItem::find(PA_TABLE_315, sizeof(PA_TABLE_315) / sizeof(PA_TABLE_315[0]), value); + } else if (freq >= 378000 && freq <= 464000) { + a = PowerTableItem::find(PA_TABLE_433, sizeof(PA_TABLE_433) / sizeof(PA_TABLE_433[0]), value); + } else if (freq >= 779000 && freq < 900000) { + a = PowerTableItem::find(PA_TABLE_868, sizeof(PA_TABLE_868) / sizeof(PA_TABLE_868[0]), value); + } else if (freq >= 900000 && freq <= 928000) { + a = PowerTableItem::find(PA_TABLE_915, sizeof(PA_TABLE_915) / sizeof(PA_TABLE_915[0]), value); + } + + if (static_cast(this->state_.MOD_FORMAT) == Modulation::MODULATION_ASK_OOK) { + this->pa_table_[0] = 0; + this->pa_table_[1] = a; + } else { + this->pa_table_[0] = a; + this->pa_table_[1] = 0; + } + this->output_power_effective_ = value; + if (this->initialized_) { + this->write_(Register::PATABLE, this->pa_table_, sizeof(this->pa_table_)); + } +} + +void CC1101Component::set_rx_attenuation(RxAttenuation value) { + this->state_.CLOSE_IN_RX = static_cast(value); + if (this->initialized_) { + this->write_(Register::FIFOTHR); + } +} + +void CC1101Component::set_dc_blocking_filter(bool value) { + this->state_.DEM_DCFILT_OFF = value ? 0 : 1; + if (this->initialized_) { + this->write_(Register::MDMCFG2); + } +} + +void CC1101Component::set_frequency(float value) { + int32_t freq = static_cast(value * (1 << 16) / XTAL_FREQUENCY); + this->state_.FREQ2 = static_cast(freq >> 16); + this->state_.FREQ1 = static_cast(freq >> 8); + this->state_.FREQ0 = static_cast(freq); + if (this->initialized_) { + this->enter_idle_(); + this->write_(Register::FREQ2); + this->write_(Register::FREQ1); + this->write_(Register::FREQ0); + this->strobe_(Command::RX); + } +} + +void CC1101Component::set_if_frequency(float value) { + this->state_.FREQ_IF = value * (1 << 10) / XTAL_FREQUENCY; + if (this->initialized_) { + this->write_(Register::FSCTRL1); + } +} + +void CC1101Component::set_filter_bandwidth(float value) { + uint8_t e; + uint32_t m; + split_float(XTAL_FREQUENCY / (value * 8), 2, e, m); + this->state_.CHANBW_E = e; + this->state_.CHANBW_M = static_cast(m); + if (this->initialized_) { + this->write_(Register::MDMCFG4); + } +} + +void CC1101Component::set_channel(uint8_t value) { + this->state_.CHANNR = value; + if (this->initialized_) { + this->enter_idle_(); + this->write_(Register::CHANNR); + this->strobe_(Command::RX); + } +} + +void CC1101Component::set_channel_spacing(float value) { + uint8_t e; + uint32_t m; + split_float(value * (1 << 18) / XTAL_FREQUENCY, 8, e, m); + this->state_.CHANSPC_E = e; + this->state_.CHANSPC_M = static_cast(m); + if (this->initialized_) { + this->write_(Register::MDMCFG1); + this->write_(Register::MDMCFG0); + } +} + +void CC1101Component::set_fsk_deviation(float value) { + uint8_t e; + uint32_t m; + split_float(value * (1 << 17) / XTAL_FREQUENCY, 3, e, m); + this->state_.DEVIATION_E = e; + this->state_.DEVIATION_M = static_cast(m); + if (this->initialized_) { + this->write_(Register::DEVIATN); + } +} + +void CC1101Component::set_msk_deviation(uint8_t value) { + this->state_.DEVIATION_E = 0; + this->state_.DEVIATION_M = value - 1; + if (this->initialized_) { + this->write_(Register::DEVIATN); + } +} + +void CC1101Component::set_symbol_rate(float value) { + uint8_t e; + uint32_t m; + split_float(value * (1 << 28) / (XTAL_FREQUENCY * 1000), 8, e, m); + this->state_.DRATE_E = e; + this->state_.DRATE_M = static_cast(m); + if (this->initialized_) { + this->write_(Register::MDMCFG4); + this->write_(Register::MDMCFG3); + } +} + +void CC1101Component::set_sync_mode(SyncMode value) { + this->state_.SYNC_MODE = static_cast(value); + if (this->initialized_) { + this->write_(Register::MDMCFG2); + } +} + +void CC1101Component::set_carrier_sense_above_threshold(bool value) { + this->state_.CARRIER_SENSE_ABOVE_THRESHOLD = value ? 1 : 0; + if (this->initialized_) { + this->write_(Register::MDMCFG2); + } +} + +void CC1101Component::set_modulation_type(Modulation value) { + this->state_.MOD_FORMAT = static_cast(value); + this->state_.PA_POWER = value == Modulation::MODULATION_ASK_OOK ? 1 : 0; + if (this->initialized_) { + this->enter_idle_(); + this->write_(Register::MDMCFG2); + this->write_(Register::FREND0); + this->strobe_(Command::RX); + } +} + +void CC1101Component::set_manchester(bool value) { + this->state_.MANCHESTER_EN = value ? 1 : 0; + if (this->initialized_) { + this->write_(Register::MDMCFG2); + } +} + +void CC1101Component::set_num_preamble(uint8_t value) { + this->state_.NUM_PREAMBLE = value; + if (this->initialized_) { + this->write_(Register::MDMCFG1); + } +} + +void CC1101Component::set_sync1(uint8_t value) { + this->state_.SYNC1 = value; + if (this->initialized_) { + this->write_(Register::SYNC1); + } +} + +void CC1101Component::set_sync0(uint8_t value) { + this->state_.SYNC0 = value; + if (this->initialized_) { + this->write_(Register::SYNC0); + } +} + +void CC1101Component::set_pktlen(uint8_t value) { + this->state_.PKTLEN = value; + if (this->initialized_) { + this->write_(Register::PKTLEN); + } +} + +void CC1101Component::set_magn_target(MagnTarget value) { + this->state_.MAGN_TARGET = static_cast(value); + if (this->initialized_) { + this->write_(Register::AGCCTRL2); + } +} + +void CC1101Component::set_max_lna_gain(MaxLnaGain value) { + this->state_.MAX_LNA_GAIN = static_cast(value); + if (this->initialized_) { + this->write_(Register::AGCCTRL2); + } +} + +void CC1101Component::set_max_dvga_gain(MaxDvgaGain value) { + this->state_.MAX_DVGA_GAIN = static_cast(value); + if (this->initialized_) { + this->write_(Register::AGCCTRL2); + } +} + +void CC1101Component::set_carrier_sense_abs_thr(int8_t value) { + this->state_.CARRIER_SENSE_ABS_THR = static_cast(value & 0b1111); + if (this->initialized_) { + this->write_(Register::AGCCTRL1); + } +} + +void CC1101Component::set_carrier_sense_rel_thr(CarrierSenseRelThr value) { + this->state_.CARRIER_SENSE_REL_THR = static_cast(value); + if (this->initialized_) { + this->write_(Register::AGCCTRL1); + } +} + +void CC1101Component::set_lna_priority(bool value) { + this->state_.AGC_LNA_PRIORITY = value ? 1 : 0; + if (this->initialized_) { + this->write_(Register::AGCCTRL1); + } +} + +void CC1101Component::set_filter_length_fsk_msk(FilterLengthFskMsk value) { + this->state_.FILTER_LENGTH = static_cast(value); + if (this->initialized_) { + this->write_(Register::AGCCTRL0); + } +} + +void CC1101Component::set_filter_length_ask_ook(FilterLengthAskOok value) { + this->state_.FILTER_LENGTH = static_cast(value); + if (this->initialized_) { + this->write_(Register::AGCCTRL0); + } +} + +void CC1101Component::set_freeze(Freeze value) { + this->state_.AGC_FREEZE = static_cast(value); + if (this->initialized_) { + this->write_(Register::AGCCTRL0); + } +} + +void CC1101Component::set_wait_time(WaitTime value) { + this->state_.WAIT_TIME = static_cast(value); + if (this->initialized_) { + this->write_(Register::AGCCTRL0); + } +} + +void CC1101Component::set_hyst_level(HystLevel value) { + this->state_.HYST_LEVEL = static_cast(value); + if (this->initialized_) { + this->write_(Register::AGCCTRL0); + } +} + +} // namespace esphome::cc1101 diff --git a/esphome/components/cc1101/cc1101.h b/esphome/components/cc1101/cc1101.h new file mode 100644 index 00000000000..65aeb2ea82c --- /dev/null +++ b/esphome/components/cc1101/cc1101.h @@ -0,0 +1,110 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/hal.h" +#include "esphome/components/spi/spi.h" +#include "esphome/core/automation.h" +#include "cc1101defs.h" + +namespace esphome::cc1101 { + +class CC1101Component : public Component, + public spi::SPIDevice { + public: + CC1101Component(); + + void setup() override; + void dump_config() override; + + // Actions + void begin_tx(); + void begin_rx(); + void reset(); + void set_idle(); + + // GDO Pin Configuration + void set_gdo0_config(uint8_t value); + void set_gdo2_config(uint8_t value); + + // Configuration Setters + void set_output_power(float value); + void set_rx_attenuation(RxAttenuation value); + void set_dc_blocking_filter(bool value); + + // Tuner settings + void set_frequency(float value); + void set_if_frequency(float value); + void set_filter_bandwidth(float value); + void set_channel(uint8_t value); + void set_channel_spacing(float value); + void set_fsk_deviation(float value); + void set_msk_deviation(uint8_t value); + void set_symbol_rate(float value); + void set_sync_mode(SyncMode value); + void set_carrier_sense_above_threshold(bool value); + void set_modulation_type(Modulation value); + void set_manchester(bool value); + void set_num_preamble(uint8_t value); + void set_sync1(uint8_t value); + void set_sync0(uint8_t value); + void set_pktlen(uint8_t value); + + // AGC settings + void set_magn_target(MagnTarget value); + void set_max_lna_gain(MaxLnaGain value); + void set_max_dvga_gain(MaxDvgaGain value); + void set_carrier_sense_abs_thr(int8_t value); + void set_carrier_sense_rel_thr(CarrierSenseRelThr value); + void set_lna_priority(bool value); + void set_filter_length_fsk_msk(FilterLengthFskMsk value); + void set_filter_length_ask_ook(FilterLengthAskOok value); + void set_freeze(Freeze value); + void set_wait_time(WaitTime value); + void set_hyst_level(HystLevel value); + + protected: + uint16_t chip_id_{0}; + bool initialized_{false}; + + float output_power_requested_{10.0f}; + float output_power_effective_{10.0f}; + uint8_t pa_table_[PA_TABLE_SIZE]{}; + + CC1101State state_; + + // Low-level Helpers + uint8_t strobe_(Command cmd); + void write_(Register reg); + void write_(Register reg, uint8_t value); + void write_(Register reg, const uint8_t *buffer, size_t length); + void read_(Register reg); + void read_(Register reg, uint8_t *buffer, size_t length); + + // State Management + bool wait_for_state_(State target_state, uint32_t timeout_ms = 100); + void enter_idle_(); +}; + +// Action Wrappers +template class BeginTxAction : public Action, public Parented { + public: + void play(const Ts &...x) override { this->parent_->begin_tx(); } +}; + +template class BeginRxAction : public Action, public Parented { + public: + void play(const Ts &...x) override { this->parent_->begin_rx(); } +}; + +template class ResetAction : public Action, public Parented { + public: + void play(const Ts &...x) override { this->parent_->reset(); } +}; + +template class SetIdleAction : public Action, public Parented { + public: + void play(const Ts &...x) override { this->parent_->set_idle(); } +}; + +} // namespace esphome::cc1101 diff --git a/esphome/components/cc1101/cc1101defs.h b/esphome/components/cc1101/cc1101defs.h new file mode 100644 index 00000000000..52f15cb85a7 --- /dev/null +++ b/esphome/components/cc1101/cc1101defs.h @@ -0,0 +1,644 @@ +#pragma once + +#include + +namespace esphome::cc1101 { + +static constexpr float XTAL_FREQUENCY = 26000; + +static constexpr uint8_t BUS_BURST = 0x40; +static constexpr uint8_t BUS_READ = 0x80; +static constexpr uint8_t BUS_WRITE = 0x00; +static constexpr uint8_t BYTES_IN_RXFIFO = 0x7F; // byte number in RXfifo +static constexpr size_t PA_TABLE_SIZE = 8; + +enum class Register : uint8_t { + IOCFG2 = 0x00, // GDO2 output pin configuration + IOCFG1 = 0x01, // GDO1 output pin configuration + IOCFG0 = 0x02, // GDO0 output pin configuration + FIFOTHR = 0x03, // RX FIFO and TX FIFO thresholds + SYNC1 = 0x04, // Sync word, high INT8U + SYNC0 = 0x05, // Sync word, low INT8U + PKTLEN = 0x06, // Packet length + PKTCTRL1 = 0x07, // Packet automation control + PKTCTRL0 = 0x08, // Packet automation control + ADDR = 0x09, // Device address + CHANNR = 0x0A, // Channel number + FSCTRL1 = 0x0B, // Frequency synthesizer control + FSCTRL0 = 0x0C, // Frequency synthesizer control + FREQ2 = 0x0D, // Frequency control word, high INT8U + FREQ1 = 0x0E, // Frequency control word, middle INT8U + FREQ0 = 0x0F, // Frequency control word, low INT8U + MDMCFG4 = 0x10, // Modem configuration + MDMCFG3 = 0x11, // Modem configuration + MDMCFG2 = 0x12, // Modem configuration + MDMCFG1 = 0x13, // Modem configuration + MDMCFG0 = 0x14, // Modem configuration + DEVIATN = 0x15, // Modem deviation setting + MCSM2 = 0x16, // Main Radio Control State Machine configuration + MCSM1 = 0x17, // Main Radio Control State Machine configuration + MCSM0 = 0x18, // Main Radio Control State Machine configuration + FOCCFG = 0x19, // Frequency Offset Compensation configuration + BSCFG = 0x1A, // Bit Synchronization configuration + AGCCTRL2 = 0x1B, // AGC control + AGCCTRL1 = 0x1C, // AGC control + AGCCTRL0 = 0x1D, // AGC control + WOREVT1 = 0x1E, // High INT8U Event 0 timeout + WOREVT0 = 0x1F, // Low INT8U Event 0 timeout + WORCTRL = 0x20, // Wake On Radio control + FREND1 = 0x21, // Front end RX configuration + FREND0 = 0x22, // Front end TX configuration + FSCAL3 = 0x23, // Frequency synthesizer calibration + FSCAL2 = 0x24, // Frequency synthesizer calibration + FSCAL1 = 0x25, // Frequency synthesizer calibration + FSCAL0 = 0x26, // Frequency synthesizer calibration + RCCTRL1 = 0x27, // RC oscillator configuration + RCCTRL0 = 0x28, // RC oscillator configuration + FSTEST = 0x29, // Frequency synthesizer calibration control + PTEST = 0x2A, // Production test + AGCTEST = 0x2B, // AGC test + TEST2 = 0x2C, // Various test settings + TEST1 = 0x2D, // Various test settings + TEST0 = 0x2E, // Various test settings + UNUSED = 0x2F, + PARTNUM = 0x30, + VERSION = 0x31, + FREQEST = 0x32, + LQI = 0x33, + RSSI = 0x34, + MARCSTATE = 0x35, + WORTIME1 = 0x36, + WORTIME0 = 0x37, + PKTSTATUS = 0x38, + VCO_VC_DAC = 0x39, + TXBYTES = 0x3A, + RXBYTES = 0x3B, + RCCTRL1_STATUS = 0x3C, + RCCTRL0_STATUS = 0x3D, + PATABLE = 0x3E, + FIFO = 0x3F, +}; + +enum class Command : uint8_t { + RES = 0x30, // Reset chip. + FSTXON = 0x31, // Enable and calibrate frequency synthesizer + XOFF = 0x32, // Turn off crystal oscillator. + CAL = 0x33, // Calibrate frequency synthesizer and turn it off + RX = 0x34, // Enable RX. + TX = 0x35, // Enable TX. + IDLE = 0x36, // Exit RX / TX + // 0x37 is RESERVED / UNDEFINED in CC1101 Datasheet + WOR = 0x38, // Start automatic RX polling sequence (Wake-on-Radio) + PWD = 0x39, // Enter power down mode when CSn goes high. + FRX = 0x3A, // Flush the RX FIFO buffer. + FTX = 0x3B, // Flush the TX FIFO buffer. + WORRST = 0x3C, // Reset real time clock. + NOP = 0x3D, // No operation. +}; + +enum class State : uint8_t { + SLEEP, + IDLE, + XOFF, + VCOON_MC, + REGON_MC, + MANCAL, + VCOON, + REGON, + STARTCAL, + BWBOOST, + FS_LOCK, + IFADCON, + ENDCAL, + RX, + RX_END, + RX_RST, + TXRX_SWITCH, + RXFIFO_OVERFLOW, + FSTXON, + TX, + TX_END, + RXTX_SWITCH, + TXFIFO_UNDERFLOW, +}; + +enum class RxAttenuation : uint8_t { + RX_ATTENUATION_0DB, + RX_ATTENUATION_6DB, + RX_ATTENUATION_12DB, + RX_ATTENUATION_18DB, +}; + +enum class SyncMode : uint8_t { + SYNC_MODE_NONE, + SYNC_MODE_15_16, + SYNC_MODE_16_16, + SYNC_MODE_30_32, +}; + +enum class Modulation : uint8_t { + MODULATION_2_FSK, + MODULATION_GFSK, + MODULATION_UNUSED_2, + MODULATION_ASK_OOK, + MODULATION_4_FSK, + MODULATION_UNUSED_5, + MODULATION_UNUSED_6, + MODULATION_MSK, +}; + +enum class MagnTarget : uint8_t { + MAGN_TARGET_24DB, + MAGN_TARGET_27DB, + MAGN_TARGET_30DB, + MAGN_TARGET_33DB, + MAGN_TARGET_36DB, + MAGN_TARGET_38DB, + MAGN_TARGET_40DB, + MAGN_TARGET_42DB, +}; + +enum class MaxLnaGain : uint8_t { + MAX_LNA_GAIN_DEFAULT, + MAX_LNA_GAIN_MINUS_2P6DB, + MAX_LNA_GAIN_MINUS_6P1DB, + MAX_LNA_GAIN_MINUS_7P4DB, + MAX_LNA_GAIN_MINUS_9P2DB, + MAX_LNA_GAIN_MINUS_11P5DB, + MAX_LNA_GAIN_MINUS_14P6DB, + MAX_LNA_GAIN_MINUS_17P1DB, +}; + +enum class MaxDvgaGain : uint8_t { + MAX_DVGA_GAIN_DEFAULT, + MAX_DVGA_GAIN_MINUS_1, + MAX_DVGA_GAIN_MINUS_2, + MAX_DVGA_GAIN_MINUS_3, +}; + +enum class CarrierSenseRelThr : uint8_t { + CARRIER_SENSE_REL_THR_DEFAULT, + CARRIER_SENSE_REL_THR_PLUS_6DB, + CARRIER_SENSE_REL_THR_PLUS_10DB, + CARRIER_SENSE_REL_THR_PLUS_14DB, +}; + +enum class FilterLengthFskMsk : uint8_t { + FILTER_LENGTH_8DB, + FILTER_LENGTH_16DB, + FILTER_LENGTH_32DB, + FILTER_LENGTH_64DB, +}; + +enum class FilterLengthAskOok : uint8_t { + FILTER_LENGTH_4DB, + FILTER_LENGTH_8DB, + FILTER_LENGTH_12DB, + FILTER_LENGTH_16DB, +}; + +enum class Freeze : uint8_t { + FREEZE_DEFAULT, + FREEZE_ON_SYNC, + FREEZE_ANALOG_ONLY, + FREEZE_ANALOG_AND_DIGITAL, +}; + +enum class WaitTime : uint8_t { + WAIT_TIME_8_SAMPLES, + WAIT_TIME_16_SAMPLES, + WAIT_TIME_24_SAMPLES, + WAIT_TIME_32_SAMPLES, +}; + +enum class HystLevel : uint8_t { + HYST_LEVEL_NONE, + HYST_LEVEL_LOW, + HYST_LEVEL_MEDIUM, + HYST_LEVEL_HIGH, +}; + +struct __attribute__((packed)) CC1101State { + // Byte array accessors for bulk SPI transfers + uint8_t *regs() { return reinterpret_cast(this); } + const uint8_t *regs() const { return reinterpret_cast(this); } + + // 0x00 + union { + uint8_t IOCFG2; + struct { + uint8_t GDO2_CFG : 6; + uint8_t GDO2_INV : 1; + uint8_t : 1; + }; + }; + // 0x01 + union { + uint8_t IOCFG1; + struct { + uint8_t GDO1_CFG : 6; + uint8_t GDO1_INV : 1; + uint8_t GDO_DS : 1; // GDO, not GD0 + }; + }; + // 0x02 + union { + uint8_t IOCFG0; + struct { + uint8_t GDO0_CFG : 6; + uint8_t GDO0_INV : 1; + uint8_t TEMP_SENSOR_ENABLE : 1; + }; + }; + // 0x03 + union { + uint8_t FIFOTHR; + struct { + uint8_t FIFO_THR : 4; + uint8_t CLOSE_IN_RX : 2; // RxAttenuation + uint8_t ADC_RETENTION : 1; + uint8_t : 1; + }; + }; + // 0x04 + uint8_t SYNC1; + // 0x05 + uint8_t SYNC0; + // 0x06 + uint8_t PKTLEN; + // 0x07 + union { + uint8_t PKTCTRL1; + struct { + uint8_t ADR_CHK : 2; + uint8_t APPEND_STATUS : 1; + uint8_t CRC_AUTOFLUSH : 1; + uint8_t : 1; + uint8_t PQT : 3; + }; + }; + // 0x08 + union { + uint8_t PKTCTRL0; + struct { + uint8_t LENGTH_CONFIG : 2; + uint8_t CRC_EN : 1; + uint8_t : 1; + uint8_t PKT_FORMAT : 2; + uint8_t WHITE_DATA : 1; + uint8_t : 1; + }; + }; + // 0x09 + uint8_t ADDR; + // 0x0A + uint8_t CHANNR; + // 0x0B + union { + uint8_t FSCTRL1; + struct { + uint8_t FREQ_IF : 5; + uint8_t RESERVED : 1; // hm? + uint8_t : 2; + }; + }; + // 0x0C + uint8_t FSCTRL0; + // 0x0D + uint8_t FREQ2; // [7:6] always zero + // 0x0E + uint8_t FREQ1; + // 0x0F + uint8_t FREQ0; + // 0x10 + union { + uint8_t MDMCFG4; + struct { + uint8_t DRATE_E : 4; + uint8_t CHANBW_M : 2; + uint8_t CHANBW_E : 2; + }; + }; + // 0x11 + union { + uint8_t MDMCFG3; + struct { + uint8_t DRATE_M : 8; + }; + }; + // 0x12 + union { + uint8_t MDMCFG2; + struct { + uint8_t SYNC_MODE : 2; + uint8_t CARRIER_SENSE_ABOVE_THRESHOLD : 1; + uint8_t MANCHESTER_EN : 1; + uint8_t MOD_FORMAT : 3; // Modulation + uint8_t DEM_DCFILT_OFF : 1; + }; + }; + // 0x13 + union { + uint8_t MDMCFG1; + struct { + uint8_t CHANSPC_E : 2; + uint8_t : 2; + uint8_t NUM_PREAMBLE : 3; + uint8_t FEC_EN : 1; + }; + }; + // 0x14 + union { + uint8_t MDMCFG0; + struct { + uint8_t CHANSPC_M : 8; + }; + }; + // 0x15 + union { + uint8_t DEVIATN; + struct { + uint8_t DEVIATION_M : 3; + uint8_t : 1; + uint8_t DEVIATION_E : 3; + uint8_t : 1; + }; + }; + // 0x16 + union { + uint8_t MCSM2; + struct { + uint8_t RX_TIME : 3; + uint8_t RX_TIME_QUAL : 1; + uint8_t RX_TIME_RSSI : 1; + uint8_t : 3; + }; + }; + // 0x17 + union { + uint8_t MCSM1; + struct { + uint8_t TXOFF_MODE : 2; + uint8_t RXOFF_MODE : 2; + uint8_t CCA_MODE : 2; + uint8_t : 2; + }; + }; + // 0x18 + union { + uint8_t MCSM0; + struct { + uint8_t XOSC_FORCE_ON : 1; + uint8_t PIN_CTRL_EN : 1; + uint8_t PO_TIMEOUT : 2; + uint8_t FS_AUTOCAL : 2; + uint8_t : 2; + }; + }; + // 0x19 + union { + uint8_t FOCCFG; + struct { + uint8_t FOC_LIMIT : 2; + uint8_t FOC_POST_K : 1; + uint8_t FOC_PRE_K : 2; + uint8_t FOC_BS_CS_GATE : 1; + uint8_t : 2; + }; + }; + // 0x1A + union { + uint8_t BSCFG; + struct { + uint8_t BS_LIMIT : 2; + uint8_t BS_POST_KP : 1; + uint8_t BS_POST_KI : 1; + uint8_t BS_PRE_KP : 2; + uint8_t BS_PRE_KI : 2; + }; + }; + // 0x1B + union { + uint8_t AGCCTRL2; + struct { + uint8_t MAGN_TARGET : 3; // MagnTarget + uint8_t MAX_LNA_GAIN : 3; // MaxLnaGain + uint8_t MAX_DVGA_GAIN : 2; // MaxDvgaGain + }; + }; + // 0x1C + union { + uint8_t AGCCTRL1; + struct { + uint8_t CARRIER_SENSE_ABS_THR : 4; + uint8_t CARRIER_SENSE_REL_THR : 2; // CarrierSenseRelThr + uint8_t AGC_LNA_PRIORITY : 1; + uint8_t : 1; + }; + }; + // 0x1D + union { + uint8_t AGCCTRL0; + struct { + uint8_t FILTER_LENGTH : 2; // FilterLengthFskMsk or FilterLengthAskOok + uint8_t AGC_FREEZE : 2; // Freeze + uint8_t WAIT_TIME : 2; // WaitTime + uint8_t HYST_LEVEL : 2; // HystLevel + }; + }; + // 0x1E + uint8_t WOREVT1; + // 0x1F + uint8_t WOREVT0; + // 0x20 + union { + uint8_t WORCTRL; + struct { + uint8_t WOR_RES : 2; + uint8_t : 1; + uint8_t RC_CAL : 1; + uint8_t EVENT1 : 3; + uint8_t RC_PD : 1; + }; + }; + // 0x21 + union { + uint8_t FREND1; + struct { + uint8_t MIX_CURRENT : 2; + uint8_t LODIV_BUF_CURRENT_RX : 2; + uint8_t LNA2MIX_CURRENT : 2; + uint8_t LNA_CURRENT : 2; + }; + }; + // 0x22 + union { + uint8_t FREND0; + struct { + uint8_t PA_POWER : 3; + uint8_t : 1; + uint8_t LODIV_BUF_CURRENT_TX : 2; + uint8_t : 2; + }; + }; + // 0x23 + union { + uint8_t FSCAL3; + struct { + uint8_t FSCAL3_LO : 4; + uint8_t CHP_CURR_CAL_EN : 2; // Disable charge pump calibration stage when 0. + uint8_t FSCAL3_HI : 2; + }; + }; + // 0x24 + union { + // uint8_t FSCAL2; + struct { + uint8_t FSCAL2 : 5; + uint8_t VCO_CORE_H_EN : 1; + uint8_t : 2; + }; + }; + // 0x25 + union { + // uint8_t FSCAL1; + struct { + uint8_t FSCAL1 : 6; + uint8_t : 2; + }; + }; + // 0x26 + union { + // uint8_t FSCAL0; + struct { + uint8_t FSCAL0 : 7; + uint8_t : 1; + }; + }; + // 0x27 + union { + // uint8_t RCCTRL1; + struct { + uint8_t RCCTRL1 : 7; + uint8_t : 1; + }; + }; + // 0x28 + union { + // uint8_t RCCTRL0; + struct { + uint8_t RCCTRL0 : 7; + uint8_t : 1; + }; + }; + // 0x29 + uint8_t FSTEST; + // 0x2A + uint8_t PTEST; + // 0x2B + uint8_t AGCTEST; + // 0x2C + uint8_t TEST2; + // 0x2D + uint8_t TEST1; + // 0x2E + union { + uint8_t TEST0; + struct { + uint8_t TEST0_LO : 1; + uint8_t VCO_SEL_CAL_EN : 1; // Enable VCO selection calibration stage when 1 + uint8_t TEST0_HI : 6; + }; + }; + // 0x2F + uint8_t REG_2F; + // 0x30 + uint8_t PARTNUM; + // 0x31 + uint8_t VERSION; + // 0x32 + union { + uint8_t FREQEST; + struct { + int8_t FREQOFF_EST : 8; + }; + }; + // 0x33 + union { + uint8_t LQI; + struct { + uint8_t LQI_EST : 7; + uint8_t LQI_CRC_OK : 1; + }; + }; + // 0x34 + int8_t RSSI; + // 0x35 + union { + // uint8_t MARCSTATE; + struct { + uint8_t MARC_STATE : 5; // State + uint8_t : 3; + }; + }; + // 0x36 + uint8_t WORTIME1; + // 0x37 + uint8_t WORTIME0; + // 0x38 + union { + uint8_t PKTSTATUS; + struct { + uint8_t GDO0 : 1; + uint8_t : 1; + uint8_t GDO2 : 1; + uint8_t SFD : 1; + uint8_t CCA : 1; + uint8_t PQT_REACHED : 1; + uint8_t CS : 1; + uint8_t CRC_OK : 1; // same as LQI_CRC_OK? + }; + }; + // 0x39 + uint8_t VCO_VC_DAC; + // 0x3A + union { + uint8_t TXBYTES; + struct { + uint8_t NUM_TXBYTES : 7; + uint8_t TXFIFO_UNDERFLOW : 1; + }; + }; + // 0x3B + union { + uint8_t RXBYTES; + struct { + uint8_t NUM_RXBYTES : 7; + uint8_t RXFIFO_OVERFLOW : 1; + }; + }; + // 0x3C + union { + // uint8_t RCCTRL1_STATUS; + struct { + uint8_t RCCTRL1_STATUS : 7; + uint8_t : 1; + }; + }; + // 0x3D + union { + // uint8_t RCCTRL0_STATUS; + struct { + uint8_t RCCTRL0_STATUS : 7; + uint8_t : 1; + }; + }; + // 0x3E + uint8_t REG_3E; + // 0x3F + uint8_t REG_3F; +}; + +static_assert(sizeof(CC1101State) == 0x40, "CC1101State size mismatch"); + +} // namespace esphome::cc1101 diff --git a/esphome/components/cc1101/cc1101pa.h b/esphome/components/cc1101/cc1101pa.h new file mode 100644 index 00000000000..e5e7a47c51a --- /dev/null +++ b/esphome/components/cc1101/cc1101pa.h @@ -0,0 +1,174 @@ +#pragma once + +#include +#include +#include + +namespace esphome::cc1101 { + +// CC1101 Design Note DN013 + +struct PowerTableItem { + uint8_t value; + uint8_t dbm_diff; // starts from 12.0, diff to previous entry, scaled by 10 + + static uint8_t find(const PowerTableItem *items, size_t count, float &dbm_target) { + int32_t dbmi = 120; + int32_t dbmi_target = static_cast(std::lround(dbm_target * 10)); + for (size_t i = 0; i < count; i++) { + dbmi -= items[i].dbm_diff; + if (dbmi_target >= dbmi) { + // Skip invalid PA settings (magic numbers derived from TI DN013/SmartRC logic) + if (items[i].value >= 0x61 && items[i].value <= 0x6F) { + continue; + } + dbm_target = static_cast(dbmi) / 10.0f; + return items[i].value; + } + } + dbm_target = -30.0f; + return 0x03; + } +}; + +static const PowerTableItem PA_TABLE_315[] = { + {0xC0, 14}, // C0 10.6 -35.3 -44.4 -57.8 -53.8 -58.3 -57.2 -57.8 -56.7 28.5 + {0xC3, 10}, // C3 9.6 -39.2 -45.3 -59.0 -54.2 -59.0 -57.5 -58.3 -57.2 26.2 + {0xC6, 11}, // C6 8.5 -43.2 -46.3 -59.2 -54.7 -59.1 -57.7 -58.3 -57.4 24.4 + {0xC9, 10}, // C9 7.5 -47.0 -47.3 -58.9 -55.0 -59.0 -57.9 -58.4 -57.5 23.0 + {0x81, 12}, // 81 6.3 -49.2 -45.7 -57.3 -53.6 -59.0 -56.0 -56.5 -57.5 19.5 + {0x85, 13}, // 85 5.0 -51.0 -47.2 -59.8 -54.2 -59.0 -56.9 -57.9 -58.0 18.3 + {0x88, 11}, // 88 3.9 -46.6 -48.1 -60.0 -55.0 -58.9 -57.5 -58.2 -58.2 17.4 + {0xCF, 11}, // CF 2.8 -49.8 -51.3 -57.6 -56.8 -59.1 -58.4 -58.1 -58.3 18.0 + {0x8D, 11}, // 8D 1.7 -43.8 -49.5 -58.9 -56.3 -58.8 -58.2 -58.4 -58.5 15.8 + {0x50, 10}, // 50 0.7 -59.2 -51.2 -59.0 -56.5 -59.0 -58.3 -58.3 -58.2 15.3 + {0x40, 10}, // 40 -0.3 -58.2 -52.1 -59.4 -56.9 -59.0 -58.4 -58.4 -58.3 14.7 + {0x3D, 10}, // 3D -1.3 -54.4 -48.4 -59.8 -57.5 -58.9 -58.3 -58.5 -58.5 19.3 + {0x55, 10}, // 55 -2.3 -56.7 -53.6 -59.7 -57.5 -59.1 -58.7 -58.4 -58.4 13.7 + {0x39, 11}, // 39 -3.4 -50.9 -49.5 -59.8 -58.0 -59.0 -58.5 -58.4 -58.4 16.8 + {0x2B, 15}, // 2B -4.9 -51.2 -50.4 -59.9 -58.0 -58.9 -58.7 -58.3 -58.4 15.6 + {0x29, 16}, // 29 -6.5 -51.8 -51.6 -59.9 -58.4 -59.0 -58.8 -58.3 -58.3 14.7 + {0x28, 10}, // 28 -7.5 -52.2 -52.5 -60.0 -58.6 -59.0 -58.8 -58.2 -58.4 14.3 + {0x27, 11}, // 27 -8.6 -52.9 -53.1 -60.0 -58.8 -59.1 -58.8 -58.3 -58.5 13.9 + {0x26, 12}, // 26 -9.8 -53.6 -54.3 -60.1 -58.7 -59.0 -58.7 -58.4 -58.4 13.4 + {0x25, 13}, // 25 -11.1 -54.3 -55.5 -60.1 -58.8 -59.1 -58.8 -58.4 -58.4 13.0 + {0x33, 11}, // 33 -12.2 -55.0 -56.3 -60.0 -58.7 -59.0 -58.9 -58.4 -58.4 12.8 + {0x1F, 11}, // 1F -13.3 -55.6 -57.2 -60.0 -58.8 -58.9 -58.9 -58.3 -58.4 12.4 + {0x1D, 12}, // 1D -14.5 -56.0 -58.0 -60.0 -58.8 -59.1 -58.7 -58.4 -58.5 12.1 + {0x32, 11}, // 32 -15.6 -56.9 -58.8 -59.9 -58.8 -59.0 -58.8 -58.3 -58.5 12.2 + {0x1A, 10}, // 1A -16.6 -57.3 -59.5 -59.9 -58.8 -59.1 -58.8 -58.4 -58.4 11.8 + {0x18, 19}, // 18 -18.5 -57.8 -60.3 -60.0 -58.8 -59.0 -58.9 -58.2 -58.5 11.6 + {0x17, 11}, // 17 -19.6 -58.7 -60.9 -60.0 -58.7 -58.9 -58.9 -58.5 -58.4 11.4 + {0x0C, 11}, // C -20.7 -59.4 -61.1 -60.0 -58.8 -59.1 -58.9 -58.4 -58.3 11.3 + {0x0A, 15}, // A -22.2 -59.9 -61.9 -60.0 -58.9 -59.0 -58.9 -58.4 -58.5 11.2 + {0x08, 18}, // 8 -24.0 -60.5 -62.5 -60.0 -58.7 -59.1 -58.8 -58.3 -58.5 11.1 + {0x07, 11}, // 7 -25.1 -61.3 -62.9 -60.1 -58.8 -59.1 -58.8 -58.4 -58.4 11.0 + {0x06, 13}, // 6 -26.4 -61.6 -63.2 -60.1 -58.7 -59.0 -58.9 -58.5 -58.5 11.0 + {0x05, 13}, // 5 -27.7 -62.3 -63.4 -60.1 -58.7 -59.2 -58.8 -58.4 -58.5 10.9 + {0x04, 19}, // 4 -29.6 -62.7 -63.6 -59.9 -58.7 -59.0 -58.9 -58.4 -58.4 10.8 +}; + +static const PowerTableItem PA_TABLE_433[] = { + {0xC0, 21}, // C0 9.9 -43.4 -45.0 -53.9 -55.2 -55.8 -52.3 -55.6 29.1 + {0xC3, 11}, // C3 8.8 -49.3 -45.9 -55.9 -55.4 -57.2 -52.6 -57.5 26.9 + {0xC6, 10}, // C6 7.8 -56.2 -46.9 -56.9 -55.6 -58.2 -53.2 -57.9 25.2 + {0xC9, 10}, // C9 6.8 -56.1 -47.9 -57.3 -55.9 -58.5 -54.0 -56.9 23.8 + {0xCC, 10}, // CC 5.8 -52.8 -48.9 -57.0 -56.1 -58.4 -54.6 -56.2 22.6 + {0x85, 10}, // 85 4.8 -54.2 -53.0 -58.3 -55.0 -57.8 -56.8 -58.0 19.1 + {0x88, 12}, // 88 3.6 -56.2 -53.8 -58.3 -55.7 -58.1 -57.2 -58.2 18.2 + {0x8B, 13}, // 8B 2.3 -57.7 -54.5 -58.0 -56.3 -58.1 -57.5 -58.2 17.3 + {0x8E, 19}, // 8E 0.4 -58.0 -55.5 -57.8 -57.4 -58.2 -58.1 -58.4 16.2 + {0x40, 12}, // 40 -0.8 -59.7 -56.1 -58.2 -57.7 -58.4 -58.3 -58.2 15.4 + {0x3C, 13}, // 3C -2.1 -60.6 -57.3 -58.2 -58.0 -58.5 -58.4 -58.5 19.3 + {0x3A, 10}, // 3A -3.1 -59.5 -57.5 -58.3 -58.3 -58.6 -58.1 -58.6 18.1 + {0x8F, 15}, // 8F -4.6 -52.2 -57.7 -58.1 -58.8 -58.4 -58.7 -58.3 14.4 + {0x37, 10}, // 37 -5.6 -56.8 -58.3 -58.3 -58.8 -58.4 -58.5 -58.4 16.2 + {0x36, 12}, // 36 -6.8 -56.8 -58.9 -58.3 -58.8 -58.3 -58.5 -58.5 15.6 + {0x28, 10}, // 28 -7.8 -56.6 -59.0 -58.2 -59.0 -58.4 -58.5 -58.4 15.1 + {0x26, 21}, // 26 -9.9 -57.0 -59.4 -58.3 -59.0 -58.4 -58.7 -58.4 14.3 + {0x25, 15}, // 25 -11.4 -57.3 -59.7 -58.4 -59.0 -58.3 -58.7 -58.5 13.9 + {0x24, 19}, // 24 -13.3 -57.9 -59.9 -58.2 -59.0 -58.6 -58.7 -58.5 13.5 + {0x1E, 10}, // 1E -14.3 -58.4 -59.8 -58.2 -59.0 -58.4 -58.6 -58.6 13.2 + {0x1C, 12}, // 1C -15.5 -58.6 -59.9 -58.4 -58.8 -58.6 -58.8 -58.5 12.9 + {0x1A, 15}, // 1A -17.0 -59.4 -59.9 -58.3 -59.1 -58.5 -58.7 -58.4 12.7 + {0x18, 18}, // 18 -18.8 -60.2 -59.9 -58.2 -59.0 -58.5 -58.7 -58.6 12.5 + {0x17, 10}, // 17 -19.8 -60.6 -59.9 -58.2 -58.9 -58.4 -58.7 -58.4 12.4 + {0x0C, 12}, // C -21.0 -61.1 -59.9 -58.4 -59.0 -58.5 -58.7 -58.6 12.3 + {0x15, 15}, // 15 -22.5 -61.7 -60.0 -58.2 -59.1 -58.3 -58.6 -58.7 12.2 + {0x08, 18}, // 8 -24.3 -62.3 -59.9 -58.3 -59.0 -58.4 -58.8 -58.5 12.1 + {0x07, 10}, // 7 -25.3 -62.6 -59.9 -58.2 -59.0 -58.6 -58.7 -58.5 12.0 + {0x06, 12}, // 6 -26.5 -63.2 -59.9 -58.3 -58.9 -58.5 -58.6 -58.6 12.0 + {0x05, 14}, // 5 -27.9 -63.5 -59.8 -58.3 -59.1 -58.5 -58.7 -58.4 11.9 + {0x04, 16}, // 4 -29.5 -63.7 -59.9 -58.3 -58.9 -58.5 -58.5 -58.5 11.9 +}; + +static const PowerTableItem PA_TABLE_868[] = { + {0xC0, 13}, // C0 10.7 -35.1 -58.6 -58.6 -57.5 -50.0 34.2 + {0xC3, 11}, // C3 9.6 -41.5 -58.5 -58.3 -57.4 -54.4 31.6 + {0xC6, 11}, // C6 8.5 -47.7 -58.5 -58.3 -57.6 -55.0 29.5 + {0xC9, 10}, // C9 7.5 -44.4 -58.5 -58.5 -57.7 -53.6 27.8 + {0xCC, 10}, // CC 6.5 -40.6 -58.6 -58.4 -57.6 -52.5 26.3 + {0xCE, 10}, // CE 5.5 -38.5 -58.5 -58.4 -57.8 -52.2 25.0 + {0x84, 11}, // 84 4.4 -35.3 -58.7 -58.5 -57.8 -55.8 20.3 + {0x87, 10}, // 87 3.4 -39.4 -58.6 -58.6 -57.8 -55.7 19.5 + {0xCF, 10}, // CF 2.4 -36.6 -58.6 -58.4 -57.7 -53.6 22.0 + {0x8C, 13}, // 8C 1.1 -50.2 -58.6 -58.5 -57.7 -55.9 17.9 + {0x50, 14}, // 50 -0.3 -42.1 -58.5 -58.5 -57.6 -57.1 16.9 + {0x40, 12}, // 40 -1.5 -43.2 -58.5 -58.7 -57.7 -57.2 16.1 + {0x3F, 11}, // 3F -2.6 -53.7 -58.6 -58.5 -57.8 -57.5 21.4 + {0x55, 10}, // 55 -3.6 -44.9 -58.6 -58.4 -57.8 -57.5 15.0 + {0x57, 12}, // 57 -4.8 -46.0 -58.6 -58.5 -57.6 -57.4 14.5 + {0x8F, 12}, // 8F -6.0 -51.6 -58.5 -58.6 -57.7 -57.1 15.0 + {0x2A, 14}, // 2A -7.4 -49.3 -58.5 -58.6 -57.7 -57.4 16.2 + {0x28, 16}, // 28 -9.0 -49.0 -58.5 -58.6 -57.7 -57.4 15.4 + {0x26, 20}, // 26 -11.0 -49.2 -58.5 -58.5 -57.7 -57.4 14.6 + {0x25, 15}, // 25 -12.5 -49.5 -58.6 -58.6 -57.8 -57.3 14.1 + {0x24, 18}, // 24 -14.3 -50.2 -58.5 -58.4 -57.8 -57.4 13.7 + {0x1D, 14}, // 1D -15.7 -50.7 -58.6 -58.6 -57.8 -57.5 13.3 + {0x1B, 13}, // 1B -17.0 -51.3 -58.5 -58.4 -57.7 -57.5 13.1 + {0x19, 16}, // 19 -18.6 -52.0 -58.6 -58.5 -57.8 -57.5 12.9 + {0x22, 10}, // 22 -19.6 -52.5 -58.5 -58.6 -57.7 -57.4 12.9 + {0x0D, 15}, // D -21.1 -53.3 -58.6 -58.6 -57.8 -57.4 12.6 + {0x0B, 12}, // B -22.3 -53.9 -58.6 -58.5 -57.8 -57.4 12.5 + {0x09, 15}, // 9 -23.8 -54.7 -58.5 -58.5 -57.8 -57.5 12.4 + {0x21, 10}, // 21 -24.8 -55.1 -58.5 -58.5 -57.7 -57.5 12.5 + {0x13, 17}, // 13 -26.5 -55.9 -58.6 -58.5 -57.6 -57.6 12.3 + {0x05, 12}, // 5 -27.7 -56.4 -58.4 -58.4 -57.7 -57.5 12.2 + {0x12, 12}, // 12 -28.9 -57.1 -58.4 -58.5 -57.7 -57.3 12.2 +}; + +static const PowerTableItem PA_TABLE_915[] = { + {0xC0, 26}, // C0 9.4 -33.5 -58.5 -58.4 -55.8 -32.6 31.8 + {0xC3, 11}, // C3 8.3 -41.5 -58.6 -58.4 -56.3 -38.0 29.3 + {0xC6, 11}, // C6 7.2 -42.5 -58.5 -58.4 -56.7 -40.5 27.4 + {0xC9, 10}, // C9 6.2 -37.6 -58.6 -58.4 -57.2 -38.8 25.9 + {0xCD, 12}, // CD 5.0 -34.2 -58.6 -58.5 -57.5 -37.3 24.3 + {0x84, 11}, // 84 3.9 -32.0 -58.6 -58.4 -57.7 -40.1 19.7 + {0x87, 10}, // 87 2.9 -36.5 -58.4 -58.5 -57.7 -39.6 18.9 + {0x8A, 11}, // 8A 1.8 -42.2 -58.5 -58.4 -57.7 -39.6 18.1 + {0x8D, 13}, // 8D 0.5 -46.8 -58.5 -58.5 -57.7 -40.4 17.3 + {0x8E, 11}, // 8E -0.6 -46.6 -58.5 -58.5 -57.8 -41.1 16.7 + {0x51, 10}, // 51 -1.6 -38.7 -58.4 -58.5 -57.7 -46.9 16.0 + {0x3E, 11}, // 3E -2.7 -50.0 -58.5 -58.4 -57.6 -55.3 20.7 + {0x3B, 11}, // 3B -3.8 -50.7 -58.6 -58.4 -57.6 -55.2 18.9 + {0x39, 13}, // 39 -5.1 -50.0 -58.5 -58.5 -57.6 -54.0 17.7 + {0x2B, 13}, // 2B -6.4 -47.6 -58.4 -58.4 -57.8 -52.1 16.5 + {0x36, 15}, // 36 -7.9 -46.9 -58.5 -58.4 -57.7 -51.2 15.8 + {0x35, 14}, // 35 -9.3 -46.7 -58.6 -58.4 -57.7 -50.7 15.2 + {0x26, 16}, // 26 -10.9 -47.0 -58.6 -58.4 -57.8 -50.9 14.5 + {0x25, 14}, // 25 -12.3 -47.2 -58.6 -58.3 -57.7 -51.0 14.1 + {0x24, 18}, // 24 -14.1 -48.1 -58.4 -58.4 -57.8 -51.4 13.7 + {0x1D, 14}, // 1D -15.5 -48.7 -58.4 -58.5 -57.7 -51.9 13.2 + {0x1B, 13}, // 1B -16.8 -49.3 -58.6 -58.4 -57.8 -52.3 13.0 + {0x19, 15}, // 19 -18.3 -50.2 -58.5 -58.5 -57.6 -52.8 12.8 + {0x18, 10}, // 18 -19.3 -50.6 -58.5 -58.5 -57.7 -53.1 12.7 + {0x17, 10}, // 17 -20.3 -51.2 -58.6 -58.5 -57.8 -53.1 12.6 + {0x0C, 11}, // C -21.4 -51.8 -58.4 -58.5 -57.7 -53.4 12.5 + {0x0A, 13}, // A -22.7 -52.6 -58.5 -58.4 -57.7 -53.6 12.4 + {0x08, 16}, // 8 -24.3 -53.6 -58.4 -58.4 -57.6 -54.1 12.3 + {0x13, 19}, // 13 -26.2 -54.6 -58.4 -58.5 -57.7 -54.3 12.2 + {0x05, 11}, // 5 -27.3 -55.3 -58.4 -58.4 -57.8 -54.5 12.1 + {0x12, 13}, // 12 -28.6 -55.9 -58.6 -58.5 -57.7 -54.7 12.1 + {0x03, 12}, // 3 -29.8 -56.9 -58.5 -58.4 -57.7 -54.7 12.0 +}; +} // namespace esphome::cc1101 diff --git a/esphome/components/cst816/touchscreen/cst816_touchscreen.cpp b/esphome/components/cst816/touchscreen/cst816_touchscreen.cpp index f6280a75a1e..5be93692c0b 100644 --- a/esphome/components/cst816/touchscreen/cst816_touchscreen.cpp +++ b/esphome/components/cst816/touchscreen/cst816_touchscreen.cpp @@ -9,27 +9,40 @@ void CST816Touchscreen::continue_setup_() { this->interrupt_pin_->setup(); this->attach_interrupt_(this->interrupt_pin_, gpio::INTERRUPT_FALLING_EDGE); } - if (this->read_byte(REG_CHIP_ID, &this->chip_id_)) { - switch (this->chip_id_) { - case CST820_CHIP_ID: - case CST826_CHIP_ID: - case CST716_CHIP_ID: - case CST816S_CHIP_ID: - case CST816D_CHIP_ID: - case CST816T_CHIP_ID: - break; - default: + + if (!this->read_byte(REG_CHIP_ID, &this->chip_id_) && !this->skip_probe_) { + this->status_set_error(LOG_STR("Failed to read chip ID")); + this->mark_failed(); + return; + } + + // CST826/CST836 return 0 for chip ID, need to read from factory ID register + if (this->chip_id_ == 0) { + if (!this->read_byte(REG_FACTORY_ID, &this->chip_id_) && !this->skip_probe_) { + this->status_set_error(LOG_STR("Failed to read chip ID")); + this->mark_failed(); + return; + } + } + + switch (this->chip_id_) { + case CST716_CHIP_ID: + case CST816S_CHIP_ID: + case CST816D_CHIP_ID: + case CST816T_CHIP_ID: + case CST820_CHIP_ID: + case CST826_CHIP_ID: + case CST836_CHIP_ID: + break; + default: + if (!this->skip_probe_) { ESP_LOGE(TAG, "Unknown chip ID: 0x%02X", this->chip_id_); this->status_set_error(LOG_STR("Unknown chip ID")); this->mark_failed(); return; - } - this->write_byte(REG_IRQ_CTL, IRQ_EN_MOTION); - } else if (!this->skip_probe_) { - this->status_set_error(LOG_STR("Failed to read chip id")); - this->mark_failed(); - return; + } } + this->write_byte(REG_IRQ_CTL, IRQ_EN_MOTION); if (this->x_raw_max_ == this->x_raw_min_) { this->x_raw_max_ = this->display_->get_native_width(); } @@ -80,11 +93,8 @@ void CST816Touchscreen::dump_config() { this->x_raw_min_, this->x_raw_max_, this->y_raw_min_, this->y_raw_max_); const char *name; switch (this->chip_id_) { - case CST820_CHIP_ID: - name = "CST820"; - break; - case CST826_CHIP_ID: - name = "CST826"; + case CST716_CHIP_ID: + name = "CST716"; break; case CST816S_CHIP_ID: name = "CST816S"; @@ -92,12 +102,18 @@ void CST816Touchscreen::dump_config() { case CST816D_CHIP_ID: name = "CST816D"; break; - case CST716_CHIP_ID: - name = "CST716"; - break; case CST816T_CHIP_ID: name = "CST816T"; break; + case CST820_CHIP_ID: + name = "CST820"; + break; + case CST826_CHIP_ID: + name = "CST826"; + break; + case CST836_CHIP_ID: + name = "CST836"; + break; default: name = "Unknown"; break; diff --git a/esphome/components/cst816/touchscreen/cst816_touchscreen.h b/esphome/components/cst816/touchscreen/cst816_touchscreen.h index 99ea085e371..99b93d8342f 100644 --- a/esphome/components/cst816/touchscreen/cst816_touchscreen.h +++ b/esphome/components/cst816/touchscreen/cst816_touchscreen.h @@ -19,12 +19,14 @@ static const uint8_t REG_YPOS_HIGH = 0x05; static const uint8_t REG_YPOS_LOW = 0x06; static const uint8_t REG_DIS_AUTOSLEEP = 0xFE; static const uint8_t REG_CHIP_ID = 0xA7; +static const uint8_t REG_FACTORY_ID = 0xAA; static const uint8_t REG_FW_VERSION = 0xA9; static const uint8_t REG_SLEEP = 0xE5; static const uint8_t REG_IRQ_CTL = 0xFA; static const uint8_t IRQ_EN_MOTION = 0x70; static const uint8_t CST826_CHIP_ID = 0x11; +static const uint8_t CST836_CHIP_ID = 0x13; static const uint8_t CST820_CHIP_ID = 0xB7; static const uint8_t CST816S_CHIP_ID = 0xB4; static const uint8_t CST816D_CHIP_ID = 0xB6; diff --git a/esphome/components/deep_sleep/__init__.py b/esphome/components/deep_sleep/__init__.py index 19fb726016f..18ba167952b 100644 --- a/esphome/components/deep_sleep/__init__.py +++ b/esphome/components/deep_sleep/__init__.py @@ -52,7 +52,10 @@ WAKEUP_PINS = { 38, 39, ], + VARIANT_ESP32C2: [0, 1, 2, 3, 4, 5], VARIANT_ESP32C3: [0, 1, 2, 3, 4, 5], + VARIANT_ESP32C6: [0, 1, 2, 3, 4, 5, 6, 7], + VARIANT_ESP32H2: [7, 8, 9, 10, 11, 12, 13, 14], VARIANT_ESP32S2: [ 0, 1, @@ -101,9 +104,6 @@ WAKEUP_PINS = { 20, 21, ], - VARIANT_ESP32C2: [0, 1, 2, 3, 4, 5], - VARIANT_ESP32C6: [0, 1, 2, 3, 4, 5, 6, 7], - VARIANT_ESP32H2: [7, 8, 9, 10, 11, 12, 13, 14], } @@ -122,10 +122,10 @@ def _validate_ex1_wakeup_mode(value): if value == "ANY_LOW": esp32.only_on_variant( supported=[ - VARIANT_ESP32S2, - VARIANT_ESP32S3, VARIANT_ESP32C6, VARIANT_ESP32H2, + VARIANT_ESP32S2, + VARIANT_ESP32S3, ], msg_prefix="ANY_LOW", )(value) diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index 6c5aaca869f..c6d01006738 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -122,14 +122,14 @@ def get_cpu_frequencies(*frequencies): CPU_FREQUENCIES = { VARIANT_ESP32: get_cpu_frequencies(80, 160, 240), - VARIANT_ESP32S2: get_cpu_frequencies(80, 160, 240), - VARIANT_ESP32S3: get_cpu_frequencies(80, 160, 240), VARIANT_ESP32C2: get_cpu_frequencies(80, 120), VARIANT_ESP32C3: get_cpu_frequencies(80, 160), VARIANT_ESP32C5: get_cpu_frequencies(80, 160, 240), VARIANT_ESP32C6: get_cpu_frequencies(80, 120, 160), VARIANT_ESP32H2: get_cpu_frequencies(16, 32, 48, 64, 96), VARIANT_ESP32P4: get_cpu_frequencies(40, 360, 400), + VARIANT_ESP32S2: get_cpu_frequencies(80, 160, 240), + VARIANT_ESP32S3: get_cpu_frequencies(80, 160, 240), } # Make sure not missed here if a new variant added. @@ -921,6 +921,7 @@ async def to_code(config): ) cg.set_cpp_standard("gnu++20") cg.add_build_flag("-DUSE_ESP32") + cg.add_build_flag("-Wl,-z,noexecstack") cg.add_define("ESPHOME_BOARD", config[CONF_BOARD]) variant = config[CONF_VARIANT] cg.add_build_flag(f"-DUSE_ESP32_VARIANT_{variant}") diff --git a/esphome/components/esp32/const.py b/esphome/components/esp32/const.py index 9bef18847fc..4358a4b712d 100644 --- a/esphome/components/esp32/const.py +++ b/esphome/components/esp32/const.py @@ -13,36 +13,36 @@ KEY_SUBMODULES = "submodules" KEY_EXTRA_BUILD_FILES = "extra_build_files" VARIANT_ESP32 = "ESP32" -VARIANT_ESP32S2 = "ESP32S2" -VARIANT_ESP32S3 = "ESP32S3" VARIANT_ESP32C2 = "ESP32C2" VARIANT_ESP32C3 = "ESP32C3" VARIANT_ESP32C5 = "ESP32C5" VARIANT_ESP32C6 = "ESP32C6" VARIANT_ESP32H2 = "ESP32H2" VARIANT_ESP32P4 = "ESP32P4" +VARIANT_ESP32S2 = "ESP32S2" +VARIANT_ESP32S3 = "ESP32S3" VARIANTS = [ VARIANT_ESP32, - VARIANT_ESP32S2, - VARIANT_ESP32S3, VARIANT_ESP32C2, VARIANT_ESP32C3, VARIANT_ESP32C5, VARIANT_ESP32C6, VARIANT_ESP32H2, VARIANT_ESP32P4, + VARIANT_ESP32S2, + VARIANT_ESP32S3, ] VARIANT_FRIENDLY = { VARIANT_ESP32: "ESP32", - VARIANT_ESP32S2: "ESP32-S2", - VARIANT_ESP32S3: "ESP32-S3", VARIANT_ESP32C2: "ESP32-C2", VARIANT_ESP32C3: "ESP32-C3", VARIANT_ESP32C5: "ESP32-C5", VARIANT_ESP32C6: "ESP32-C6", VARIANT_ESP32H2: "ESP32-H2", VARIANT_ESP32P4: "ESP32-P4", + VARIANT_ESP32S2: "ESP32-S2", + VARIANT_ESP32S3: "ESP32-S3", } esp32_ns = cg.esphome_ns.namespace("esp32") diff --git a/esphome/components/esp32_can/canbus.py b/esphome/components/esp32_can/canbus.py index acc3785f22b..5cee27506a6 100644 --- a/esphome/components/esp32_can/canbus.py +++ b/esphome/components/esp32_can/canbus.py @@ -64,12 +64,12 @@ CAN_SPEEDS_ESP32_P4 = {**CAN_SPEEDS_ESP32_S2} CAN_SPEEDS = { VARIANT_ESP32: CAN_SPEEDS_ESP32, - VARIANT_ESP32S2: CAN_SPEEDS_ESP32_S2, - VARIANT_ESP32S3: CAN_SPEEDS_ESP32_S3, VARIANT_ESP32C3: CAN_SPEEDS_ESP32_C3, VARIANT_ESP32C6: CAN_SPEEDS_ESP32_C6, VARIANT_ESP32H2: CAN_SPEEDS_ESP32_H2, VARIANT_ESP32P4: CAN_SPEEDS_ESP32_P4, + VARIANT_ESP32S2: CAN_SPEEDS_ESP32_S2, + VARIANT_ESP32S3: CAN_SPEEDS_ESP32_S3, } diff --git a/esphome/components/esp32_can/esp32_can.cpp b/esphome/components/esp32_can/esp32_can.cpp index f9b63b8ebc2..c10ad01450c 100644 --- a/esphome/components/esp32_can/esp32_can.cpp +++ b/esphome/components/esp32_can/esp32_can.cpp @@ -16,8 +16,8 @@ static const char *const TAG = "esp32_can"; static bool get_bitrate(canbus::CanSpeed bitrate, twai_timing_config_t *t_config) { switch (bitrate) { -#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32C3) || \ - defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32H2) || defined(USE_ESP32_VARIANT_ESP32P4) +#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32H2) || \ + defined(USE_ESP32_VARIANT_ESP32P4) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) case canbus::CAN_1KBPS: *t_config = (twai_timing_config_t) TWAI_TIMING_CONFIG_1KBITS(); return true; diff --git a/esphome/components/esp32_rmt_led_strip/light.py b/esphome/components/esp32_rmt_led_strip/light.py index ac4f0b2e92a..2ec0750ae68 100644 --- a/esphome/components/esp32_rmt_led_strip/light.py +++ b/esphome/components/esp32_rmt_led_strip/light.py @@ -77,13 +77,13 @@ CONFIG_SCHEMA = cv.All( cv.SplitDefault( CONF_RMT_SYMBOLS, esp32=192, - esp32_s2=192, - esp32_s3=192, - esp32_p4=192, esp32_c3=96, esp32_c5=96, esp32_c6=96, esp32_h2=96, + esp32_p4=192, + esp32_s2=192, + esp32_s3=192, ): cv.int_range(min=2), cv.Optional(CONF_MAX_REFRESH_RATE): cv.positive_time_period_microseconds, cv.Optional(CONF_CHIPSET): cv.one_of(*CHIPSETS, upper=True), @@ -91,7 +91,7 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_IS_WRGB, default=False): cv.boolean, cv.Optional(CONF_USE_DMA): cv.All( esp32.only_on_variant( - supported=[esp32.const.VARIANT_ESP32S3, esp32.const.VARIANT_ESP32P4] + supported=[esp32.const.VARIANT_ESP32P4, esp32.const.VARIANT_ESP32S3] ), cv.boolean, ), diff --git a/esphome/components/ethernet/ethernet_component.cpp b/esphome/components/ethernet/ethernet_component.cpp index 9a46aa2687c..757e358db39 100644 --- a/esphome/components/ethernet/ethernet_component.cpp +++ b/esphome/components/ethernet/ethernet_component.cpp @@ -87,8 +87,8 @@ void EthernetComponent::setup() { .intr_flags = 0, }; -#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) || \ - defined(USE_ESP32_VARIANT_ESP32C6) +#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32S2) || \ + defined(USE_ESP32_VARIANT_ESP32S3) auto host = SPI2_HOST; #else auto host = SPI3_HOST; diff --git a/esphome/components/gree/__init__.py b/esphome/components/gree/__init__.py index e69de29bb2d..2dd9ac0f1c7 100644 --- a/esphome/components/gree/__init__.py +++ b/esphome/components/gree/__init__.py @@ -0,0 +1,3 @@ +import esphome.codegen as cg + +gree_ns = cg.esphome_ns.namespace("gree") diff --git a/esphome/components/gree/climate.py b/esphome/components/gree/climate.py index 057ba67b942..0892155fd22 100644 --- a/esphome/components/gree/climate.py +++ b/esphome/components/gree/climate.py @@ -3,11 +3,11 @@ from esphome.components import climate_ir import esphome.config_validation as cv from esphome.const import CONF_MODEL +from . import gree_ns + CODEOWNERS = ["@orestismers"] AUTO_LOAD = ["climate_ir"] - -gree_ns = cg.esphome_ns.namespace("gree") GreeClimate = gree_ns.class_("GreeClimate", climate_ir.ClimateIR) Model = gree_ns.enum("Model") @@ -23,7 +23,7 @@ MODELS = { CONFIG_SCHEMA = climate_ir.climate_ir_with_receiver_schema(GreeClimate).extend( { - cv.Required(CONF_MODEL): cv.enum(MODELS), + cv.Required(CONF_MODEL): cv.enum(MODELS, lower=True), } ) diff --git a/esphome/components/gree/gree.cpp b/esphome/components/gree/gree.cpp index e0cacb4f1ea..b8cf8a39a85 100644 --- a/esphome/components/gree/gree.cpp +++ b/esphome/components/gree/gree.cpp @@ -16,13 +16,28 @@ void GreeClimate::set_model(Model model) { this->model_ = model; } +void GreeClimate::set_mode_bit(uint8_t bit_mask, bool enabled) { + if (enabled) { + this->mode_bits_ |= bit_mask; + } else { + this->mode_bits_ &= ~bit_mask; + } + this->transmit_state(); +} + void GreeClimate::transmit_state() { uint8_t remote_state[8] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00}; remote_state[0] = this->fan_speed_() | this->operation_mode_(); remote_state[1] = this->temperature_(); - if (this->model_ == GREE_YAN || this->model_ == GREE_YX1FF || this->model_ == GREE_YAG) { + if (this->model_ == GREE_YAN) { + remote_state[2] = 0x20; // bits 0..3 always 0000, bits 4..7 TURBO, LIGHT, HEALTH, X-FAN + remote_state[3] = 0x50; // bits 4..7 always 0101 + remote_state[4] = this->vertical_swing_(); + } + + if (this->model_ == GREE_YX1FF || this->model_ == GREE_YAG) { remote_state[2] = 0x60; remote_state[3] = 0x50; remote_state[4] = this->vertical_swing_(); @@ -41,7 +56,7 @@ void GreeClimate::transmit_state() { } if (this->model_ == GREE_YAA || this->model_ == GREE_YAC || this->model_ == GREE_YAC1FB9) { - remote_state[2] = 0x20; // bits 0..3 always 0000, bits 4..7 TURBO,LIGHT,HEALTH,X-FAN + remote_state[2] = 0x20; // bits 0..3 always 0000, bits 4..7 TURBO, LIGHT, HEALTH, X-FAN remote_state[3] = 0x50; // bits 4..7 always 0101 remote_state[6] = 0x20; // YAA1FB, FAA1FB1, YB1F2 bits 4..7 always 0010 @@ -52,6 +67,13 @@ void GreeClimate::transmit_state() { } } + if (this->model_ == GREE_YAN || this->model_ == GREE_YAA || this->model_ == GREE_YAC || + this->model_ == GREE_YAC1FB9) { + // Merge the mode bits into remote_state[2] + // Clear the mode bits (bits 4-7) and OR in the current mode_bits_ + remote_state[2] = (remote_state[2] & 0x0F) | this->mode_bits_; + } + if (this->model_ == GREE_YX1FF) { if (this->fan_speed_() == GREE_FAN_TURBO) { remote_state[2] |= GREE_FAN_TURBO_BIT; diff --git a/esphome/components/gree/gree.h b/esphome/components/gree/gree.h index f91d78cabd6..24453750ae3 100644 --- a/esphome/components/gree/gree.h +++ b/esphome/components/gree/gree.h @@ -2,80 +2,79 @@ #include "esphome/components/climate_ir/climate_ir.h" -namespace esphome { -namespace gree { +namespace esphome::gree { // Values for GREE IR Controllers // Temperature -const uint8_t GREE_TEMP_MIN = 16; // Celsius -const uint8_t GREE_TEMP_MAX = 30; // Celsius +static constexpr uint8_t GREE_TEMP_MIN = 16; // Celsius +static constexpr uint8_t GREE_TEMP_MAX = 30; // Celsius // Modes -const uint8_t GREE_MODE_AUTO = 0x00; -const uint8_t GREE_MODE_COOL = 0x01; -const uint8_t GREE_MODE_HEAT = 0x04; -const uint8_t GREE_MODE_DRY = 0x02; -const uint8_t GREE_MODE_FAN = 0x03; +static constexpr uint8_t GREE_MODE_AUTO = 0x00; +static constexpr uint8_t GREE_MODE_COOL = 0x01; +static constexpr uint8_t GREE_MODE_HEAT = 0x04; +static constexpr uint8_t GREE_MODE_DRY = 0x02; +static constexpr uint8_t GREE_MODE_FAN = 0x03; -const uint8_t GREE_MODE_OFF = 0x00; -const uint8_t GREE_MODE_ON = 0x08; +static constexpr uint8_t GREE_MODE_OFF = 0x00; +static constexpr uint8_t GREE_MODE_ON = 0x08; // Fan Speed -const uint8_t GREE_FAN_AUTO = 0x00; -const uint8_t GREE_FAN_1 = 0x10; -const uint8_t GREE_FAN_2 = 0x20; -const uint8_t GREE_FAN_3 = 0x30; +static constexpr uint8_t GREE_FAN_AUTO = 0x00; +static constexpr uint8_t GREE_FAN_1 = 0x10; +static constexpr uint8_t GREE_FAN_2 = 0x20; +static constexpr uint8_t GREE_FAN_3 = 0x30; // IR Transmission -const uint32_t GREE_IR_FREQUENCY = 38000; -const uint32_t GREE_HEADER_MARK = 9000; -const uint32_t GREE_HEADER_SPACE = 4000; -const uint32_t GREE_BIT_MARK = 620; -const uint32_t GREE_ONE_SPACE = 1600; -const uint32_t GREE_ZERO_SPACE = 540; -const uint32_t GREE_MESSAGE_SPACE = 19000; +static constexpr uint32_t GREE_IR_FREQUENCY = 38000; +static constexpr uint32_t GREE_HEADER_MARK = 9000; +static constexpr uint32_t GREE_HEADER_SPACE = 4000; +static constexpr uint32_t GREE_BIT_MARK = 620; +static constexpr uint32_t GREE_ONE_SPACE = 1600; +static constexpr uint32_t GREE_ZERO_SPACE = 540; +static constexpr uint32_t GREE_MESSAGE_SPACE = 19000; // Timing specific for YAC features (I-Feel mode) -const uint32_t GREE_YAC_HEADER_MARK = 6000; -const uint32_t GREE_YAC_HEADER_SPACE = 3000; -const uint32_t GREE_YAC_BIT_MARK = 650; +static constexpr uint32_t GREE_YAC_HEADER_MARK = 6000; +static constexpr uint32_t GREE_YAC_HEADER_SPACE = 3000; +static constexpr uint32_t GREE_YAC_BIT_MARK = 650; // Timing specific to YAC1FB9 -const uint32_t GREE_YAC1FB9_HEADER_SPACE = 4500; -const uint32_t GREE_YAC1FB9_MESSAGE_SPACE = 19980; +static constexpr uint32_t GREE_YAC1FB9_HEADER_SPACE = 4500; +static constexpr uint32_t GREE_YAC1FB9_MESSAGE_SPACE = 19980; // State Frame size -const uint8_t GREE_STATE_FRAME_SIZE = 8; +static constexpr uint8_t GREE_STATE_FRAME_SIZE = 8; // Only available on YAN // Vertical air directions. Note that these cannot be set on all heat pumps -const uint8_t GREE_VDIR_AUTO = 0x00; -const uint8_t GREE_VDIR_MANUAL = 0x00; -const uint8_t GREE_VDIR_SWING = 0x01; -const uint8_t GREE_VDIR_UP = 0x02; -const uint8_t GREE_VDIR_MUP = 0x03; -const uint8_t GREE_VDIR_MIDDLE = 0x04; -const uint8_t GREE_VDIR_MDOWN = 0x05; -const uint8_t GREE_VDIR_DOWN = 0x06; +static constexpr uint8_t GREE_VDIR_AUTO = 0x00; +static constexpr uint8_t GREE_VDIR_MANUAL = 0x00; +static constexpr uint8_t GREE_VDIR_SWING = 0x01; +static constexpr uint8_t GREE_VDIR_UP = 0x02; +static constexpr uint8_t GREE_VDIR_MUP = 0x03; +static constexpr uint8_t GREE_VDIR_MIDDLE = 0x04; +static constexpr uint8_t GREE_VDIR_MDOWN = 0x05; +static constexpr uint8_t GREE_VDIR_DOWN = 0x06; // Only available on YAC/YAG // Horizontal air directions. Note that these cannot be set on all heat pumps -const uint8_t GREE_HDIR_AUTO = 0x00; -const uint8_t GREE_HDIR_MANUAL = 0x00; -const uint8_t GREE_HDIR_SWING = 0x01; -const uint8_t GREE_HDIR_LEFT = 0x02; -const uint8_t GREE_HDIR_MLEFT = 0x03; -const uint8_t GREE_HDIR_MIDDLE = 0x04; -const uint8_t GREE_HDIR_MRIGHT = 0x05; -const uint8_t GREE_HDIR_RIGHT = 0x06; +static constexpr uint8_t GREE_HDIR_AUTO = 0x00; +static constexpr uint8_t GREE_HDIR_MANUAL = 0x00; +static constexpr uint8_t GREE_HDIR_SWING = 0x01; +static constexpr uint8_t GREE_HDIR_LEFT = 0x02; +static constexpr uint8_t GREE_HDIR_MLEFT = 0x03; +static constexpr uint8_t GREE_HDIR_MIDDLE = 0x04; +static constexpr uint8_t GREE_HDIR_MRIGHT = 0x05; +static constexpr uint8_t GREE_HDIR_RIGHT = 0x06; // Only available on YX1FF // Turbo (high) fan mode + sleep preset mode -const uint8_t GREE_FAN_TURBO = 0x80; -const uint8_t GREE_FAN_TURBO_BIT = 0x10; -const uint8_t GREE_PRESET_NONE = 0x00; -const uint8_t GREE_PRESET_SLEEP = 0x01; -const uint8_t GREE_PRESET_SLEEP_BIT = 0x80; +static constexpr uint8_t GREE_FAN_TURBO = 0x80; +static constexpr uint8_t GREE_FAN_TURBO_BIT = 0x10; +static constexpr uint8_t GREE_PRESET_NONE = 0x00; +static constexpr uint8_t GREE_PRESET_SLEEP = 0x01; +static constexpr uint8_t GREE_PRESET_SLEEP_BIT = 0x80; // Model codes enum Model { GREE_GENERIC, GREE_YAN, GREE_YAA, GREE_YAC, GREE_YAC1FB9, GREE_YX1FF, GREE_YAG }; @@ -90,6 +89,7 @@ class GreeClimate : public climate_ir::ClimateIR { climate::CLIMATE_SWING_HORIZONTAL, climate::CLIMATE_SWING_BOTH}) {} void set_model(Model model); + void set_mode_bit(uint8_t bit_mask, bool enabled); protected: // Transmit via IR the state of this climate controller. @@ -103,7 +103,7 @@ class GreeClimate : public climate_ir::ClimateIR { uint8_t preset_(); Model model_{}; + uint8_t mode_bits_{0}; // Combined mode bits for remote_state[2] }; -} // namespace gree -} // namespace esphome +} // namespace esphome::gree diff --git a/esphome/components/gree/switch/__init__.py b/esphome/components/gree/switch/__init__.py new file mode 100644 index 00000000000..111fea65d27 --- /dev/null +++ b/esphome/components/gree/switch/__init__.py @@ -0,0 +1,74 @@ +import esphome.codegen as cg +from esphome.components import switch +import esphome.config_validation as cv +from esphome.const import CONF_LIGHT, DEVICE_CLASS_SWITCH, ENTITY_CATEGORY_CONFIG +import esphome.final_validate as fv + +from .. import gree_ns +from ..climate import CONF_MODEL, GreeClimate + +CODEOWNERS = ["@nagyrobi"] + +GreeModeBitSwitch = gree_ns.class_("GreeModeBitSwitch", switch.Switch, cg.Component) + +CONF_TURBO = "turbo" +CONF_HEALTH = "health" +CONF_XFAN = "xfan" +CONF_GREE_ID = "gree_id" + +# Switch configurations: (config_key, display_name, bit_mask, icon) +SWITCH_CONFIGS = ( + (CONF_TURBO, "Gree Turbo Switch", 0x10, "mdi:car-turbocharger"), + (CONF_LIGHT, "Gree Light Switch", 0x20, "mdi:led-outline"), + (CONF_HEALTH, "Gree Health Switch", 0x40, "mdi:pine-tree"), + (CONF_XFAN, "Gree X-FAN Switch", 0x80, "mdi:wall-sconce-flat"), +) + +SUPPORTED_MODELS = { + "yan", + "yaa", + "yac", + "yac1fb9", +} + +CONFIG_SCHEMA = cv.Schema( + { + cv.Required(CONF_GREE_ID): cv.use_id(GreeClimate), + **{ + cv.Optional(key): switch.switch_schema( + GreeModeBitSwitch, + icon=icon, + default_restore_mode="RESTORE_DEFAULT_OFF", + device_class=DEVICE_CLASS_SWITCH, + entity_category=ENTITY_CATEGORY_CONFIG, + ) + for key, _, _, icon in SWITCH_CONFIGS + }, + } +) + + +def _validate_model(config): + full_config = fv.full_config.get() + climate_path = full_config.get_path_for_id(config[CONF_GREE_ID])[:-1] + climate_conf = full_config.get_config_for_path(climate_path) + if climate_conf[CONF_MODEL] not in SUPPORTED_MODELS: + raise cv.Invalid( + "Gree switches are only supported for the " + + ", ".join(SUPPORTED_MODELS) + + " models" + ) + + +FINAL_VALIDATE_SCHEMA = _validate_model + + +async def to_code(config): + parent = await cg.get_variable(config[CONF_GREE_ID]) + + for conf_key, name, bit_mask, _ in SWITCH_CONFIGS: + if switch_conf := config.get(conf_key): + sw = cg.new_Pvariable(switch_conf[cv.CONF_ID], name, bit_mask) + await switch.register_switch(sw, switch_conf) + await cg.register_component(sw, switch_conf) + await cg.register_parented(sw, parent) diff --git a/esphome/components/gree/switch/gree_switch.cpp b/esphome/components/gree/switch/gree_switch.cpp new file mode 100644 index 00000000000..13f14e54530 --- /dev/null +++ b/esphome/components/gree/switch/gree_switch.cpp @@ -0,0 +1,24 @@ +#include "gree_switch.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace gree { + +static const char *const TAG = "gree.switch"; + +void GreeModeBitSwitch::setup() { + auto initial = this->get_initial_state_with_restore_mode(); + if (initial.has_value()) { + this->write_state(*initial); + } +} + +void GreeModeBitSwitch::dump_config() { log_switch(TAG, " ", this->name_, this); } + +void GreeModeBitSwitch::write_state(bool state) { + this->parent_->set_mode_bit(this->bit_mask_, state); + this->publish_state(state); +} + +} // namespace gree +} // namespace esphome diff --git a/esphome/components/gree/switch/gree_switch.h b/esphome/components/gree/switch/gree_switch.h new file mode 100644 index 00000000000..239ac4bf171 --- /dev/null +++ b/esphome/components/gree/switch/gree_switch.h @@ -0,0 +1,24 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/switch/switch.h" +#include "esphome/components/gree/gree.h" + +namespace esphome { +namespace gree { + +class GreeModeBitSwitch : public switch_::Switch, public Component, public Parented { + public: + GreeModeBitSwitch(const char *name, uint8_t bit_mask) : name_(name), bit_mask_(bit_mask) {} + + void setup() override; + void dump_config() override; + void write_state(bool state) override; + + protected: + const char *name_; + uint8_t bit_mask_; +}; + +} // namespace gree +} // namespace esphome diff --git a/esphome/components/i2s_audio/__init__.py b/esphome/components/i2s_audio/__init__.py index 0e7c3a59375..5707c1348d1 100644 --- a/esphome/components/i2s_audio/__init__.py +++ b/esphome/components/i2s_audio/__init__.py @@ -72,13 +72,13 @@ I2S_ROLE_OPTIONS = { # https://github.com/espressif/esp-idf/blob/master/components/soc/{variant}/include/soc/soc_caps.h (SOC_I2S_NUM) I2S_PORTS = { VARIANT_ESP32: 2, - VARIANT_ESP32S2: 1, - VARIANT_ESP32S3: 2, VARIANT_ESP32C3: 1, VARIANT_ESP32C5: 1, VARIANT_ESP32C6: 1, VARIANT_ESP32H2: 1, VARIANT_ESP32P4: 3, + VARIANT_ESP32S2: 1, + VARIANT_ESP32S3: 2, } i2s_channel_fmt_t = cg.global_ns.enum("i2s_channel_fmt_t") diff --git a/esphome/components/improv_serial/improv_serial_component.h b/esphome/components/improv_serial/improv_serial_component.h index 057247f3762..abe50b87f28 100644 --- a/esphome/components/improv_serial/improv_serial_component.h +++ b/esphome/components/improv_serial/improv_serial_component.h @@ -11,8 +11,8 @@ #ifdef USE_ESP32 #include -#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32S3) || \ - defined(USE_ESP32_VARIANT_ESP32H2) +#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32H2) || \ + defined(USE_ESP32_VARIANT_ESP32S3) #include #include #endif diff --git a/esphome/components/internal_temperature/internal_temperature.cpp b/esphome/components/internal_temperature/internal_temperature.cpp index 28ac55d6deb..6365392ce94 100644 --- a/esphome/components/internal_temperature/internal_temperature.cpp +++ b/esphome/components/internal_temperature/internal_temperature.cpp @@ -7,9 +7,9 @@ extern "C" { uint8_t temprature_sens_read(); } -#elif defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || \ - defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32H2) || \ - defined(USE_ESP32_VARIANT_ESP32C2) || defined(USE_ESP32_VARIANT_ESP32P4) +#elif defined(USE_ESP32_VARIANT_ESP32C2) || defined(USE_ESP32_VARIANT_ESP32C3) || \ + defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32H2) || defined(USE_ESP32_VARIANT_ESP32P4) || \ + defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) #include "driver/temperature_sensor.h" #endif // USE_ESP32_VARIANT #endif // USE_ESP32 @@ -27,9 +27,9 @@ namespace internal_temperature { static const char *const TAG = "internal_temperature"; #ifdef USE_ESP32 -#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32S2) || \ - defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32H2) || defined(USE_ESP32_VARIANT_ESP32C2) || \ - defined(USE_ESP32_VARIANT_ESP32P4) +#if defined(USE_ESP32_VARIANT_ESP32C2) || defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || \ + defined(USE_ESP32_VARIANT_ESP32H2) || defined(USE_ESP32_VARIANT_ESP32P4) || defined(USE_ESP32_VARIANT_ESP32S2) || \ + defined(USE_ESP32_VARIANT_ESP32S3) static temperature_sensor_handle_t tsensNew = NULL; #endif // USE_ESP32_VARIANT #endif // USE_ESP32 @@ -43,9 +43,9 @@ void InternalTemperatureSensor::update() { ESP_LOGV(TAG, "Raw temperature value: %d", raw); temperature = (raw - 32) / 1.8f; success = (raw != 128); -#elif defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || \ - defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32H2) || \ - defined(USE_ESP32_VARIANT_ESP32C2) || defined(USE_ESP32_VARIANT_ESP32P4) +#elif defined(USE_ESP32_VARIANT_ESP32C2) || defined(USE_ESP32_VARIANT_ESP32C3) || \ + defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32H2) || defined(USE_ESP32_VARIANT_ESP32P4) || \ + defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) esp_err_t result = temperature_sensor_get_celsius(tsensNew, &temperature); success = (result == ESP_OK); if (!success) { @@ -81,9 +81,9 @@ void InternalTemperatureSensor::update() { void InternalTemperatureSensor::setup() { #ifdef USE_ESP32 -#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32S2) || \ - defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32H2) || defined(USE_ESP32_VARIANT_ESP32C2) || \ - defined(USE_ESP32_VARIANT_ESP32P4) +#if defined(USE_ESP32_VARIANT_ESP32C2) || defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || \ + defined(USE_ESP32_VARIANT_ESP32H2) || defined(USE_ESP32_VARIANT_ESP32P4) || defined(USE_ESP32_VARIANT_ESP32S2) || \ + defined(USE_ESP32_VARIANT_ESP32S3) temperature_sensor_config_t tsens_config = TEMPERATURE_SENSOR_CONFIG_DEFAULT(-10, 80); esp_err_t result = temperature_sensor_install(&tsens_config, &tsensNew); diff --git a/esphome/components/ld2420/ld2420.cpp b/esphome/components/ld2420/ld2420.cpp index 4fca9494aa7..10c623bce06 100644 --- a/esphome/components/ld2420/ld2420.cpp +++ b/esphome/components/ld2420/ld2420.cpp @@ -204,8 +204,10 @@ void LD2420Component::dump_config() { LOG_BUTTON(" ", "Factory Reset:", this->factory_reset_button_); LOG_BUTTON(" ", "Restart Module:", this->restart_module_button_); #endif +#ifdef USE_SELECT ESP_LOGCONFIG(TAG, "Select:"); LOG_SELECT(" ", "Operating Mode", this->operating_selector_); +#endif if (ld2420::get_firmware_int(this->firmware_ver_) < CALIBRATE_VERSION_MIN) { ESP_LOGW(TAG, "Firmware version %s and older supports Simple Mode only", this->firmware_ver_); } @@ -237,12 +239,20 @@ void LD2420Component::setup() { memcpy(&this->new_config, &this->current_config, sizeof(this->current_config)); if (ld2420::get_firmware_int(this->firmware_ver_) < CALIBRATE_VERSION_MIN) { this->set_operating_mode(OP_SIMPLE_MODE_STRING); - this->operating_selector_->publish_state(OP_SIMPLE_MODE_STRING); +#ifdef USE_SELECT + if (this->operating_selector_ != nullptr) { + this->operating_selector_->publish_state(OP_SIMPLE_MODE_STRING); + } +#endif this->set_mode_(CMD_SYSTEM_MODE_SIMPLE); ESP_LOGW(TAG, "Firmware version %s and older supports Simple Mode only", this->firmware_ver_); } else { this->set_mode_(CMD_SYSTEM_MODE_ENERGY); - this->operating_selector_->publish_state(OP_NORMAL_MODE_STRING); +#ifdef USE_SELECT + if (this->operating_selector_ != nullptr) { + this->operating_selector_->publish_state(OP_NORMAL_MODE_STRING); + } +#endif } #ifdef USE_NUMBER this->init_gate_config_numbers(); @@ -382,8 +392,12 @@ void LD2420Component::set_operating_mode(const char *state) { // If unsupported firmware ignore mode select if (ld2420::get_firmware_int(firmware_ver_) >= CALIBRATE_VERSION_MIN) { this->current_operating_mode = find_uint8(OP_MODE_BY_STR, state); - // Entering Auto Calibrate we need to clear the privoiuos data collection - this->operating_selector_->publish_state(state); + // Entering Auto Calibrate we need to clear the previous data collection +#ifdef USE_SELECT + if (this->operating_selector_ != nullptr) { + this->operating_selector_->publish_state(state); + } +#endif if (current_operating_mode == OP_CALIBRATE_MODE) { this->set_calibration_(true); for (uint8_t gate = 0; gate < TOTAL_GATES; gate++) { @@ -403,7 +417,11 @@ void LD2420Component::set_operating_mode(const char *state) { } } else { this->current_operating_mode = OP_SIMPLE_MODE; - this->operating_selector_->publish_state(OP_SIMPLE_MODE_STRING); +#ifdef USE_SELECT + if (this->operating_selector_ != nullptr) { + this->operating_selector_->publish_state(OP_SIMPLE_MODE_STRING); + } +#endif } } diff --git a/esphome/components/logger/__init__.py b/esphome/components/logger/__init__.py index d9ca44d3c9e..c81ade8fc32 100644 --- a/esphome/components/logger/__init__.py +++ b/esphome/components/logger/__init__.py @@ -100,14 +100,14 @@ CONF_TASK_LOG_BUFFER_SIZE = "task_log_buffer_size" UART_SELECTION_ESP32 = { VARIANT_ESP32: [UART0, UART1, UART2], - VARIANT_ESP32S2: [UART0, UART1, USB_CDC], - VARIANT_ESP32S3: [UART0, UART1, USB_CDC, USB_SERIAL_JTAG], - VARIANT_ESP32C3: [UART0, UART1, USB_CDC, USB_SERIAL_JTAG], VARIANT_ESP32C2: [UART0, UART1], + VARIANT_ESP32C3: [UART0, UART1, USB_CDC, USB_SERIAL_JTAG], VARIANT_ESP32C5: [UART0, UART1, USB_CDC, USB_SERIAL_JTAG], VARIANT_ESP32C6: [UART0, UART1, USB_CDC, USB_SERIAL_JTAG], VARIANT_ESP32H2: [UART0, UART1, USB_CDC, USB_SERIAL_JTAG], VARIANT_ESP32P4: [UART0, UART1, USB_CDC, USB_SERIAL_JTAG], + VARIANT_ESP32S2: [UART0, UART1, USB_CDC], + VARIANT_ESP32S3: [UART0, UART1, USB_CDC, USB_SERIAL_JTAG], } UART_SELECTION_ESP8266 = [UART0, UART0_SWAP, UART1] @@ -238,12 +238,12 @@ CONFIG_SCHEMA = cv.All( CONF_HARDWARE_UART, esp8266=UART0, esp32=UART0, - esp32_s2=USB_CDC, - esp32_s3=USB_SERIAL_JTAG, esp32_c3=USB_SERIAL_JTAG, esp32_c5=USB_SERIAL_JTAG, esp32_c6=USB_SERIAL_JTAG, esp32_p4=USB_SERIAL_JTAG, + esp32_s2=USB_CDC, + esp32_s3=USB_SERIAL_JTAG, rp2040=USB_CDC, bk72xx=DEFAULT, ln882x=DEFAULT, diff --git a/esphome/components/lvgl/__init__.py b/esphome/components/lvgl/__init__.py index 040661495c5..19c258fcd53 100644 --- a/esphome/components/lvgl/__init__.py +++ b/esphome/components/lvgl/__init__.py @@ -1,5 +1,6 @@ import importlib import logging +from pathlib import Path import pkgutil from esphome.automation import build_automation, validate_automation @@ -26,6 +27,7 @@ from esphome.core import CORE, ID, Lambda from esphome.cpp_generator import MockObj from esphome.final_validate import full_config from esphome.helpers import write_file_if_changed +from esphome.yaml_util import load_yaml from . import defines as df, helpers, lv_validation as lvalid, widgets from .automation import disp_update, focused_widgets, refreshed_widgets @@ -37,7 +39,6 @@ from .encoders import ( initial_focus_to_code, ) from .gradient import GRADIENT_SCHEMA, gradients_to_code -from .hello_world import get_hello_world from .keypads import KEYPADS_CONFIG, keypads_to_code from .lv_validation import lv_bool, lv_images_used from .lvcode import LvContext, LvglComponent, lvgl_static @@ -84,6 +85,7 @@ DEPENDENCIES = ["display"] AUTO_LOAD = ["key_provider"] CODEOWNERS = ["@clydebarrow"] LOGGER = logging.getLogger(__name__) +HELLO_WORLD_FILE = "hello_world.yaml" SIMPLE_TRIGGERS = ( @@ -354,7 +356,8 @@ def display_schema(config): def add_hello_world(config): if df.CONF_WIDGETS not in config and CONF_PAGES not in config: LOGGER.info("No pages or widgets configured, creating default hello_world page") - config[df.CONF_WIDGETS] = any_widget_schema()(get_hello_world()) + hello_world_path = Path(__file__).parent / HELLO_WORLD_FILE + config[df.CONF_WIDGETS] = any_widget_schema()(load_yaml(hello_world_path)) return config diff --git a/esphome/components/lvgl/hello_world.py b/esphome/components/lvgl/hello_world.py deleted file mode 100644 index f85da9d8e41..00000000000 --- a/esphome/components/lvgl/hello_world.py +++ /dev/null @@ -1,127 +0,0 @@ -from io import StringIO - -from esphome.yaml_util import parse_yaml - -CONFIG = """ -- obj: - id: hello_world_card_ - pad_all: 12 - bg_color: white - height: 100% - width: 100% - scrollable: false - widgets: - - obj: - align: top_mid - outline_width: 0 - border_width: 0 - pad_all: 4 - scrollable: false - height: size_content - width: 100% - layout: - type: flex - flex_flow: row - flex_align_cross: center - flex_align_track: start - flex_align_main: space_between - widgets: - - button: - checkable: true - radius: 4 - text_font: montserrat_20 - on_click: - lvgl.label.update: - id: hello_world_label_ - text: "Clicked!" - widgets: - - label: - text: "Button" - - label: - id: hello_world_title_ - text: ESPHome - text_font: montserrat_20 - width: 100% - text_align: center - on_boot: - lvgl.widget.refresh: hello_world_title_ - hidden: !lambda |- - return lv_obj_get_width(lv_scr_act()) < 400; - - checkbox: - text: Checkbox - id: hello_world_checkbox_ - on_boot: - lvgl.widget.refresh: hello_world_checkbox_ - hidden: !lambda |- - return lv_obj_get_width(lv_scr_act()) < 240; - on_click: - lvgl.label.update: - id: hello_world_label_ - text: "Checked!" - - obj: - id: hello_world_container_ - align: center - y: 14 - pad_all: 0 - outline_width: 0 - border_width: 0 - width: 100% - height: size_content - scrollable: false - on_click: - lvgl.spinner.update: - id: hello_world_spinner_ - arc_color: springgreen - layout: - type: flex - flex_flow: row_wrap - flex_align_cross: center - flex_align_track: center - flex_align_main: space_evenly - widgets: - - spinner: - id: hello_world_spinner_ - indicator: - arc_color: tomato - height: 100 - width: 100 - spin_time: 2s - arc_length: 60deg - widgets: - - label: - id: hello_world_label_ - text: "Hello World!" - align: center - - obj: - id: hello_world_qrcode_ - outline_width: 0 - border_width: 0 - hidden: !lambda |- - return lv_obj_get_width(lv_scr_act()) < 300 && lv_obj_get_height(lv_scr_act()) < 400; - widgets: - - label: - text_font: montserrat_14 - text: esphome.io - align: top_mid - - qrcode: - text: "https://esphome.io" - size: 80 - align: bottom_mid - on_boot: - lvgl.widget.refresh: hello_world_qrcode_ - - - slider: - width: 80% - align: bottom_mid - on_value: - lvgl.label.update: - id: hello_world_label_ - text: - format: "%.0f%%" - args: [x] -""" - - -def get_hello_world(): - with StringIO(CONFIG) as fp: - return parse_yaml("hello_world", fp) diff --git a/esphome/components/lvgl/hello_world.yaml b/esphome/components/lvgl/hello_world.yaml new file mode 100644 index 00000000000..359e73cd525 --- /dev/null +++ b/esphome/components/lvgl/hello_world.yaml @@ -0,0 +1,118 @@ +# This file defines a placeholder LVGL "Hello World" that is shown when no +# widgets are configured. +- obj: + id: hello_world_card_ + pad_all: 12 + bg_color: white + height: 100% + width: 100% + scrollable: false + widgets: + - obj: + align: top_mid + outline_width: 0 + border_width: 0 + pad_all: 4 + scrollable: false + height: size_content + width: 100% + layout: + type: flex + flex_flow: row + flex_align_cross: center + flex_align_track: start + flex_align_main: space_between + widgets: + - button: + checkable: true + radius: 4 + text_font: montserrat_20 + on_click: + lvgl.label.update: + id: hello_world_label_ + text: "Clicked!" + widgets: + - label: + text: "Button" + - label: + id: hello_world_title_ + text: ESPHome + text_font: montserrat_20 + width: 100% + text_align: center + on_boot: + lvgl.widget.refresh: hello_world_title_ + hidden: !lambda |- + return lv_obj_get_width(lv_scr_act()) < 400; + - checkbox: + text: Checkbox + id: hello_world_checkbox_ + on_boot: + lvgl.widget.refresh: hello_world_checkbox_ + hidden: !lambda |- + return lv_obj_get_width(lv_scr_act()) < 240; + on_click: + lvgl.label.update: + id: hello_world_label_ + text: "Checked!" + - obj: + id: hello_world_container_ + align: center + y: 14 + pad_all: 0 + outline_width: 0 + border_width: 0 + width: 100% + height: size_content + scrollable: false + on_click: + lvgl.spinner.update: + id: hello_world_spinner_ + arc_color: springgreen + layout: + type: flex + flex_flow: row_wrap + flex_align_cross: center + flex_align_track: center + flex_align_main: space_evenly + widgets: + - spinner: + id: hello_world_spinner_ + indicator: + arc_color: tomato + height: 100 + width: 100 + spin_time: 2s + arc_length: 60deg + widgets: + - label: + id: hello_world_label_ + text: "Hello World!" + align: center + - obj: + id: hello_world_qrcode_ + outline_width: 0 + border_width: 0 + hidden: !lambda |- + return lv_obj_get_width(lv_scr_act()) < 300 && lv_obj_get_height(lv_scr_act()) < 400; + widgets: + - label: + text_font: montserrat_14 + text: esphome.io + align: top_mid + - qrcode: + text: "https://esphome.io" + size: 80 + align: bottom_mid + on_boot: + lvgl.widget.refresh: hello_world_qrcode_ + + - slider: + width: 80% + align: bottom_mid + on_value: + lvgl.label.update: + id: hello_world_label_ + text: + format: "%.0f%%" + args: [x] diff --git a/esphome/components/psram/__init__.py b/esphome/components/psram/__init__.py index 4ee4e97696c..529097889d9 100644 --- a/esphome/components/psram/__init__.py +++ b/esphome/components/psram/__init__.py @@ -54,18 +54,18 @@ CONF_ENABLE_ECC = "enable_ecc" SPIRAM_MODES = { VARIANT_ESP32: (TYPE_QUAD,), + VARIANT_ESP32C5: (TYPE_QUAD,), VARIANT_ESP32S2: (TYPE_QUAD,), VARIANT_ESP32S3: (TYPE_QUAD, TYPE_OCTAL), - VARIANT_ESP32C5: (TYPE_QUAD,), VARIANT_ESP32P4: (TYPE_HEX,), } SPIRAM_SPEEDS = { VARIANT_ESP32: (40, 80, 120), + VARIANT_ESP32C5: (40, 80, 120), VARIANT_ESP32S2: (40, 80, 120), VARIANT_ESP32S3: (40, 80, 120), - VARIANT_ESP32C5: (40, 80, 120), VARIANT_ESP32P4: (20, 100, 200), } diff --git a/esphome/components/remote_receiver/__init__.py b/esphome/components/remote_receiver/__init__.py index e79b3f91edc..7f70e2c2a25 100644 --- a/esphome/components/remote_receiver/__init__.py +++ b/esphome/components/remote_receiver/__init__.py @@ -131,13 +131,13 @@ CONFIG_SCHEMA = remote_base.validate_triggers( cv.SplitDefault( CONF_RMT_SYMBOLS, esp32=192, - esp32_s2=192, - esp32_s3=192, - esp32_p4=192, esp32_c3=96, esp32_c5=96, esp32_c6=96, esp32_h2=96, + esp32_p4=192, + esp32_s2=192, + esp32_s3=192, ): cv.All(cv.only_on_esp32, cv.int_range(min=2)), cv.Optional(CONF_FILTER_SYMBOLS): cv.All( cv.only_on_esp32, cv.int_range(min=0) @@ -148,7 +148,7 @@ CONFIG_SCHEMA = remote_base.validate_triggers( ): cv.All(cv.only_on_esp32, cv.int_range(min=2)), cv.Optional(CONF_USE_DMA): cv.All( esp32.only_on_variant( - supported=[esp32.const.VARIANT_ESP32S3, esp32.const.VARIANT_ESP32P4] + supported=[esp32.const.VARIANT_ESP32P4, esp32.const.VARIANT_ESP32S3] ), cv.boolean, ), diff --git a/esphome/components/remote_transmitter/__init__.py b/esphome/components/remote_transmitter/__init__.py index ff055b959bf..ec4f62666dd 100644 --- a/esphome/components/remote_transmitter/__init__.py +++ b/esphome/components/remote_transmitter/__init__.py @@ -55,20 +55,20 @@ CONFIG_SCHEMA = cv.Schema( cv.Optional(CONF_EOT_LEVEL): cv.All(cv.only_on_esp32, cv.boolean), cv.Optional(CONF_USE_DMA): cv.All( esp32.only_on_variant( - supported=[esp32.const.VARIANT_ESP32S3, esp32.const.VARIANT_ESP32P4] + supported=[esp32.const.VARIANT_ESP32P4, esp32.const.VARIANT_ESP32S3] ), cv.boolean, ), cv.SplitDefault( CONF_RMT_SYMBOLS, esp32=64, - esp32_s2=64, - esp32_s3=48, - esp32_p4=48, esp32_c3=48, esp32_c5=48, esp32_c6=48, esp32_h2=48, + esp32_p4=48, + esp32_s2=64, + esp32_s3=48, ): cv.All(cv.only_on_esp32, cv.int_range(min=2)), cv.Optional(CONF_NON_BLOCKING): cv.All(cv.only_on_esp32, cv.boolean), cv.Optional(CONF_ON_TRANSMIT): automation.validate_automation(single=True), diff --git a/esphome/components/rtl87xx/__init__.py b/esphome/components/rtl87xx/__init__.py index d24ffcea3d3..8f275441088 100644 --- a/esphome/components/rtl87xx/__init__.py +++ b/esphome/components/rtl87xx/__init__.py @@ -10,7 +10,9 @@ import esphome.codegen as cg from esphome.components import libretiny from esphome.components.libretiny.const import ( COMPONENT_RTL87XX, + FAMILY_RTL8710B, KEY_COMPONENT_DATA, + KEY_FAMILY, KEY_LIBRETINY, LibreTinyComponent, ) @@ -48,7 +50,9 @@ CONFIG_SCHEMA.prepend_extra(_set_core_data) async def to_code(config): # Use FreeRTOS 8.2.3+ for xTaskNotifyGive/ulTaskNotifyTake required by AsyncTCP 3.4.3+ # https://github.com/esphome/esphome/issues/10220 - cg.add_platformio_option("custom_versions.freertos", "8.2.3") + # Only for RTL8710B (ambz) - RTL8720C (ambz2) requires FreeRTOS 10.x + if CORE.data[KEY_LIBRETINY][KEY_FAMILY] == FAMILY_RTL8710B: + cg.add_platformio_option("custom_versions.freertos", "8.2.3") return await libretiny.component_to_code(config) diff --git a/esphome/components/spi/__init__.py b/esphome/components/spi/__init__.py index d803ee66dc2..8f23735fff2 100644 --- a/esphome/components/spi/__init__.py +++ b/esphome/components/spi/__init__.py @@ -310,7 +310,7 @@ def spi_mode_schema(mode): if pin_count == 8: onlys.append( only_on_variant( - supported=[VARIANT_ESP32S3, VARIANT_ESP32S2, VARIANT_ESP32P4] + supported=[VARIANT_ESP32P4, VARIANT_ESP32S2, VARIANT_ESP32S3] ) ) return cv.All( diff --git a/esphome/components/text_sensor/text_sensor.h b/esphome/components/text_sensor/text_sensor.h index fbd02c6688d..6410cbd9610 100644 --- a/esphome/components/text_sensor/text_sensor.h +++ b/esphome/components/text_sensor/text_sensor.h @@ -24,7 +24,17 @@ void log_text_sensor(const char *tag, const char *prefix, const char *type, Text class TextSensor : public EntityBase, public EntityBase_DeviceClass { public: + std::string state; + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" + /// @deprecated Use get_raw_state() instead. This member will be removed in ESPHome 2026.6.0. + ESPDEPRECATED("Use get_raw_state() instead of .raw_state. Will be removed in 2026.6.0", "2025.12.0") + std::string raw_state; + TextSensor() = default; + ~TextSensor() = default; +#pragma GCC diagnostic pop /// Getter-syntax for .state. std::string get_state() const; @@ -49,15 +59,6 @@ class TextSensor : public EntityBase, public EntityBase_DeviceClass { /// Add a callback that will be called every time the sensor sends a raw value. void add_on_raw_state_callback(std::function callback); - std::string state; - -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wdeprecated-declarations" - /// @deprecated Use get_raw_state() instead. This member will be removed in ESPHome 2026.6.0. - ESPDEPRECATED("Use get_raw_state() instead of .raw_state. Will be removed in 2026.6.0", "2025.12.0") - std::string raw_state; -#pragma GCC diagnostic pop - // ========== INTERNAL METHODS ========== // (In most use cases you won't need these) diff --git a/esphome/components/tinyusb/tinyusb_component.cpp b/esphome/components/tinyusb/tinyusb_component.cpp index a2057c90cea..19bb545c4b5 100644 --- a/esphome/components/tinyusb/tinyusb_component.cpp +++ b/esphome/components/tinyusb/tinyusb_component.cpp @@ -41,4 +41,4 @@ void TinyUSB::dump_config() { } } // namespace esphome::tinyusb -#endif +#endif // USE_ESP32_VARIANT_ESP32P4 || USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3 diff --git a/esphome/components/tinyusb/tinyusb_component.h b/esphome/components/tinyusb/tinyusb_component.h index 56c286f4559..7d8caade740 100644 --- a/esphome/components/tinyusb/tinyusb_component.h +++ b/esphome/components/tinyusb/tinyusb_component.h @@ -69,4 +69,4 @@ class TinyUSB : public Component { }; } // namespace esphome::tinyusb -#endif +#endif // USE_ESP32_VARIANT_ESP32P4 || USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3 diff --git a/esphome/components/usb_host/usb_host.h b/esphome/components/usb_host/usb_host.h index 31bdde2df8c..d11a148a0fb 100644 --- a/esphome/components/usb_host/usb_host.h +++ b/esphome/components/usb_host/usb_host.h @@ -1,7 +1,7 @@ #pragma once // Should not be needed, but it's required to pass CI clang-tidy checks -#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32P4) +#if defined(USE_ESP32_VARIANT_ESP32P4) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) #include "esphome/core/defines.h" #include "esphome/core/component.h" #include @@ -188,4 +188,4 @@ class USBHost : public Component { } // namespace usb_host } // namespace esphome -#endif // USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3 +#endif // USE_ESP32_VARIANT_ESP32P4 || USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3 diff --git a/esphome/components/usb_host/usb_host_client.cpp b/esphome/components/usb_host/usb_host_client.cpp index fe61353b5db..664f49d1375 100644 --- a/esphome/components/usb_host/usb_host_client.cpp +++ b/esphome/components/usb_host/usb_host_client.cpp @@ -1,5 +1,5 @@ // Should not be needed, but it's required to pass CI clang-tidy checks -#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32P4) +#if defined(USE_ESP32_VARIANT_ESP32P4) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) #include "usb_host.h" #include "esphome/core/log.h" #include "esphome/core/hal.h" @@ -531,4 +531,4 @@ void USBClient::release_trq(TransferRequest *trq) { } // namespace usb_host } // namespace esphome -#endif // USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3 +#endif // USE_ESP32_VARIANT_ESP32P4 || USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3 diff --git a/esphome/components/usb_host/usb_host_component.cpp b/esphome/components/usb_host/usb_host_component.cpp index 1e70c289df5..790fe6713b2 100644 --- a/esphome/components/usb_host/usb_host_component.cpp +++ b/esphome/components/usb_host/usb_host_component.cpp @@ -1,5 +1,5 @@ // Should not be needed, but it's required to pass CI clang-tidy checks -#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32P4) +#if defined(USE_ESP32_VARIANT_ESP32P4) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) #include "usb_host.h" #include #include "esphome/core/log.h" @@ -31,4 +31,4 @@ void USBHost::loop() { } // namespace usb_host } // namespace esphome -#endif // USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3 +#endif // USE_ESP32_VARIANT_ESP32P4 || USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3 diff --git a/esphome/components/usb_uart/ch34x.cpp b/esphome/components/usb_uart/ch34x.cpp index 889366b5797..caa4b656575 100644 --- a/esphome/components/usb_uart/ch34x.cpp +++ b/esphome/components/usb_uart/ch34x.cpp @@ -1,4 +1,4 @@ -#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32P4) +#if defined(USE_ESP32_VARIANT_ESP32P4) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) #include "usb_uart.h" #include "usb/usb_host.h" #include "esphome/core/log.h" @@ -78,4 +78,4 @@ void USBUartTypeCH34X::enable_channels() { } } // namespace usb_uart } // namespace esphome -#endif // USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3 +#endif // USE_ESP32_VARIANT_ESP32P4 || USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3 diff --git a/esphome/components/usb_uart/cp210x.cpp b/esphome/components/usb_uart/cp210x.cpp index 5fec0bed029..be024d1ba2a 100644 --- a/esphome/components/usb_uart/cp210x.cpp +++ b/esphome/components/usb_uart/cp210x.cpp @@ -1,4 +1,4 @@ -#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32P4) +#if defined(USE_ESP32_VARIANT_ESP32P4) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) #include "usb_uart.h" #include "usb/usb_host.h" #include "esphome/core/log.h" @@ -123,4 +123,4 @@ void USBUartTypeCP210X::enable_channels() { } } // namespace usb_uart } // namespace esphome -#endif // USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3 +#endif // USE_ESP32_VARIANT_ESP32P4 || USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3 diff --git a/esphome/components/usb_uart/usb_uart.cpp b/esphome/components/usb_uart/usb_uart.cpp index fefccd36451..edd01c26c67 100644 --- a/esphome/components/usb_uart/usb_uart.cpp +++ b/esphome/components/usb_uart/usb_uart.cpp @@ -1,5 +1,5 @@ // Should not be needed, but it's required to pass CI clang-tidy checks -#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32P4) +#if defined(USE_ESP32_VARIANT_ESP32P4) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) #include "usb_uart.h" #include "esphome/core/log.h" #include "esphome/core/application.h" @@ -392,4 +392,4 @@ void USBUartTypeCdcAcm::enable_channels() { } // namespace usb_uart } // namespace esphome -#endif // USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3 +#endif // USE_ESP32_VARIANT_ESP32P4 || USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3 diff --git a/esphome/components/usb_uart/usb_uart.h b/esphome/components/usb_uart/usb_uart.h index a5e7905ac5c..96c17bd1559 100644 --- a/esphome/components/usb_uart/usb_uart.h +++ b/esphome/components/usb_uart/usb_uart.h @@ -1,6 +1,6 @@ #pragma once -#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32P4) +#if defined(USE_ESP32_VARIANT_ESP32P4) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) #include "esphome/core/component.h" #include "esphome/core/helpers.h" #include "esphome/components/uart/uart_component.h" @@ -173,4 +173,4 @@ class USBUartTypeCH34X : public USBUartTypeCdcAcm { } // namespace usb_uart } // namespace esphome -#endif // USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3 +#endif // USE_ESP32_VARIANT_ESP32P4 || USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3 diff --git a/esphome/config_validation.py b/esphome/config_validation.py index a3fd271a86c..ee926b1b6de 100644 --- a/esphome/config_validation.py +++ b/esphome/config_validation.py @@ -740,9 +740,10 @@ def has_at_most_one_key(*keys): if not isinstance(obj, dict): raise Invalid("expected dictionary") - number = sum(k in keys for k in obj) - if number > 1: - raise Invalid(f"Cannot specify more than one of {', '.join(keys)}.") + used = set(obj) & set(keys) + if len(used) > 1: + msg = "Cannot specify more than one of '" + "', '".join(used) + "'." + raise MultipleInvalid([Invalid(msg, path=[k]) for k in used]) return obj return validate diff --git a/esphome/core/__init__.py b/esphome/core/__init__.py index e0aa5bd4d53..9eb3351f335 100644 --- a/esphome/core/__init__.py +++ b/esphome/core/__init__.py @@ -541,8 +541,22 @@ class EsphomeCore: self.friendly_name: str | None = None # The area / zone of the node self.area: str | None = None - # Additional data components can store temporary data in - # The first key to this dict should always be the integration name + # Additional data components can store temporary data in. + # This dict is cleared between compilation runs. + # + # Usage pattern (use @dataclass for type safety): + # DOMAIN = "my_component" + # + # @dataclass + # class MyComponentData: + # feature_enabled: bool = False + # + # def _get_data() -> MyComponentData: + # if DOMAIN not in CORE.data: + # CORE.data[DOMAIN] = MyComponentData() + # return CORE.data[DOMAIN] + # + # The first key should always be the component domain name (DOMAIN constant). self.data = {} # The relative path to the configuration YAML self.config_path: Path | None = None diff --git a/esphome/core/defines.h b/esphome/core/defines.h index d54c0fec047..4a0a7a7a217 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -234,9 +234,9 @@ #if defined(USE_ESP32_VARIANT_ESP32S2) #define USE_LOGGER_USB_CDC -#elif defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32C3) || \ - defined(USE_ESP32_VARIANT_ESP32C5) || defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32H2) || \ - defined(USE_ESP32_VARIANT_ESP32P4) +#elif defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C5) || \ + defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32H2) || defined(USE_ESP32_VARIANT_ESP32P4) || \ + defined(USE_ESP32_VARIANT_ESP32S3) #define USE_LOGGER_USB_CDC #define USE_LOGGER_USB_SERIAL_JTAG #endif diff --git a/platformio.ini b/platformio.ini index 94f58f84ab6..81f8b3295bb 100644 --- a/platformio.ini +++ b/platformio.ini @@ -378,6 +378,18 @@ build_flags = build_unflags = ${common.build_unflags} +;;;;;;;; ESP32-P4 ;;;;;;;; + +[env:esp32p4-idf] +extends = common:esp32-idf +board = esp32-p4-evboard + +board_build.esp-idf.sdkconfig_path = .temp/sdkconfig-esp32p4-idf +build_flags = + ${common:esp32-idf.build_flags} + ${flags:runtime.build_flags} + -DUSE_ESP32_VARIANT_ESP32P4 + ;;;;;;;; ESP32-S2 ;;;;;;;; [env:esp32s2-arduino] @@ -466,18 +478,6 @@ build_flags = build_unflags = ${common.build_unflags} -;;;;;;;; ESP32-P4 ;;;;;;;; - -[env:esp32p4-idf] -extends = common:esp32-idf -board = esp32-p4-evboard - -board_build.esp-idf.sdkconfig_path = .temp/sdkconfig-esp32p4-idf -build_flags = - ${common:esp32-idf.build_flags} - ${flags:runtime.build_flags} - -DUSE_ESP32_VARIANT_ESP32P4 - ;;;;;;;; RP2040 ;;;;;;;; [env:rp2040-pico-arduino] diff --git a/tests/component_tests/mipi_spi/test_init.py b/tests/component_tests/mipi_spi/test_init.py index 56a52df2abf..0c7dea22865 100644 --- a/tests/component_tests/mipi_spi/test_init.py +++ b/tests/component_tests/mipi_spi/test_init.py @@ -304,14 +304,14 @@ def test_all_predefined_models( config = {"model": name} # Get the pins required by this model and find a compatible variant - pins = [ - pin - for pin in [ - model.get_default(pin, None) - for pin in ("dc_pin", "reset_pin", "cs_pin") - ] - if pin is not None - ] + pins = [] + for pin_name in ("dc_pin", "reset_pin", "cs_pin", "enable_pin"): + pin_value = model.get_default(pin_name, None) + if pin_value is not None: + if isinstance(pin_value, list): + pins.extend(pin_value) + else: + pins.append(pin_value) choose_variant_with_pins(pins) # Add required fields that don't have defaults diff --git a/tests/components/cc1101/common.yaml b/tests/components/cc1101/common.yaml new file mode 100644 index 00000000000..7fd265ca4a1 --- /dev/null +++ b/tests/components/cc1101/common.yaml @@ -0,0 +1,20 @@ +cc1101: + id: transceiver + cs_pin: ${cs_pin} + frequency: 433920 + if_frequency: 153 + filter_bandwidth: 203 + channel: 0 + channel_spacing: 200 + symbol_rate: 5000 + modulation_type: ASK/OOK + +button: + - platform: template + name: "CC1101 Button" + on_press: + then: + - cc1101.begin_tx: transceiver + - cc1101.begin_rx: transceiver + - cc1101.set_idle: transceiver + - cc1101.reset: transceiver diff --git a/tests/components/cc1101/test.esp32-idf.yaml b/tests/components/cc1101/test.esp32-idf.yaml new file mode 100644 index 00000000000..e075629679e --- /dev/null +++ b/tests/components/cc1101/test.esp32-idf.yaml @@ -0,0 +1,8 @@ +substitutions: + cs_pin: GPIO5 + +packages: + spi: !include ../../test_build_components/common/spi/esp32-idf.yaml + remote_receiver: !include ../../test_build_components/common/remote_receiver/esp32-idf.yaml + +<<: !include common.yaml diff --git a/tests/components/cc1101/test.esp8266.yaml b/tests/components/cc1101/test.esp8266.yaml new file mode 100644 index 00000000000..7900658bc10 --- /dev/null +++ b/tests/components/cc1101/test.esp8266.yaml @@ -0,0 +1,8 @@ +substitutions: + cs_pin: GPIO5 + +packages: + spi: !include ../../test_build_components/common/spi/esp8266-ard.yaml + remote_receiver: !include ../../test_build_components/common/remote_receiver/esp8266-ard.yaml + +<<: !include common.yaml diff --git a/tests/components/gree/common.yaml b/tests/components/gree/common.yaml index e7060760343..1ddce781bb8 100644 --- a/tests/components/gree/common.yaml +++ b/tests/components/gree/common.yaml @@ -1,5 +1,18 @@ climate: - platform: gree name: GREE - model: generic + id: my_gree_ac + model: YAN transmitter_id: xmitr + +switch: + - platform: gree + gree_id: my_gree_ac + light: + name: "AC Lights" + turbo: + name: "AC Turbo" + health: + name: "AC Health" + xfan: + name: "AC X-Fan" diff --git a/tests/unit_tests/fixtures/substitutions/00-simple_var.approved.yaml b/tests/unit_tests/fixtures/substitutions/00-simple_var.approved.yaml index 6f3bae1ac4b..9ed9b99c49a 100644 --- a/tests/unit_tests/fixtures/substitutions/00-simple_var.approved.yaml +++ b/tests/unit_tests/fixtures/substitutions/00-simple_var.approved.yaml @@ -8,6 +8,8 @@ substitutions: position: x: 79 y: 82 + a: 15 + b: 20 esphome: name: test @@ -34,3 +36,5 @@ test_list: - '{{{"AA"}}}' - '"HELLO"' - '{ 79, 82 }' + - a: 15 should be 15, overridden from command line + b: 20 should stay as 20, not overridden diff --git a/tests/unit_tests/fixtures/substitutions/00-simple_var.input.yaml b/tests/unit_tests/fixtures/substitutions/00-simple_var.input.yaml index 306119b753d..64701c03ddc 100644 --- a/tests/unit_tests/fixtures/substitutions/00-simple_var.input.yaml +++ b/tests/unit_tests/fixtures/substitutions/00-simple_var.input.yaml @@ -11,6 +11,13 @@ substitutions: position: x: 79 y: 82 + a: 10 + b: 20 + +# The following key is only used by the test framework +# to simulate command line substitutions +command_line_substitutions: + a: 15 test_list: - "$var1" @@ -35,3 +42,5 @@ test_list: - ${ '{{{"AA"}}}' } - ${ '"HELLO"' } - '{ ${position.x}, ${position.y} }' + - a: ${a} should be 15, overridden from command line + b: ${b} should stay as 20, not overridden diff --git a/tests/unit_tests/test_config_validation.py b/tests/unit_tests/test_config_validation.py index 104cdc2b7a4..73b15aaadfe 100644 --- a/tests/unit_tests/test_config_validation.py +++ b/tests/unit_tests/test_config_validation.py @@ -251,15 +251,6 @@ def test_split_default(framework, platform, variant, full, idf, arduino, simple) "host": "24", } - idf_mappings = { - "esp32_idf": "4", - "esp32_s2_idf": "7", - "esp32_s3_idf": "10", - "esp32_c3_idf": "13", - "esp32_c6_idf": "16", - "esp32_h2_idf": "19", - } - arduino_mappings = { "esp32_arduino": "3", "esp32_s2_arduino": "6", @@ -269,6 +260,15 @@ def test_split_default(framework, platform, variant, full, idf, arduino, simple) "esp32_h2_arduino": "18", } + idf_mappings = { + "esp32_idf": "4", + "esp32_s2_idf": "7", + "esp32_s3_idf": "10", + "esp32_c3_idf": "13", + "esp32_c6_idf": "16", + "esp32_h2_idf": "19", + } + schema = config_validation.Schema( { config_validation.SplitDefault( @@ -293,8 +293,8 @@ def test_split_default(framework, platform, variant, full, idf, arduino, simple) @pytest.mark.parametrize( "framework, platform, message", [ - ("esp-idf", PLATFORM_ESP32, "ESP32 using esp-idf framework"), ("arduino", PLATFORM_ESP32, "ESP32 using arduino framework"), + ("esp-idf", PLATFORM_ESP32, "ESP32 using esp-idf framework"), ("arduino", PLATFORM_ESP8266, "ESP8266 using arduino framework"), ("arduino", PLATFORM_RP2040, "RP2040 using arduino framework"), ("arduino", PLATFORM_BK72XX, "BK72XX using arduino framework"), diff --git a/tests/unit_tests/test_substitutions.py b/tests/unit_tests/test_substitutions.py index c5e6618ea6f..cba1e398c3b 100644 --- a/tests/unit_tests/test_substitutions.py +++ b/tests/unit_tests/test_substitutions.py @@ -2,13 +2,16 @@ import glob import logging from pathlib import Path from typing import Any -from unittest.mock import patch +from unittest.mock import MagicMock, patch + +import pytest from esphome import config as config_module, yaml_util from esphome.components import substitutions +from esphome.components.packages import do_packages_pass from esphome.config import resolve_extend_remove from esphome.config_helpers import merge_config -from esphome.const import CONF_PACKAGES, CONF_SUBSTITUTIONS +from esphome.const import CONF_SUBSTITUTIONS from esphome.core import CORE from esphome.util import OrderedDict @@ -91,13 +94,22 @@ REMOTES = { ("https://github.com/esphome/repo2", "main"): "remotes/repo2/main", } +# Collect all input YAML files for test_substitutions_fixtures parametrized tests: +HERE = Path(__file__).parent +BASE_DIR = HERE / "fixtures" / "substitutions" +SOURCES = sorted(glob.glob(str(BASE_DIR / "*.input.yaml"))) +assert SOURCES, f"test_substitutions_fixtures: No input YAML files found in {BASE_DIR}" + +@pytest.mark.parametrize( + "source_path", + [Path(p) for p in SOURCES], + ids=lambda p: p.name, +) @patch("esphome.git.clone_or_update") -def test_substitutions_fixtures(mock_clone_or_update, fixture_path): - base_dir = fixture_path / "substitutions" - sources = sorted(glob.glob(str(base_dir / "*.input.yaml"))) - assert sources, f"No input YAML files found in {base_dir}" - +def test_substitutions_fixtures( + mock_clone_or_update: MagicMock, source_path: Path +) -> None: def fake_clone_or_update( *, url: str, @@ -116,72 +128,61 @@ def test_substitutions_fixtures(mock_clone_or_update, fixture_path): raise RuntimeError( f"Cannot find test repository for {url} @ {ref}. Check the REMOTES mapping in test_substitutions.py" ) - return base_dir / path, None + return BASE_DIR / path, None mock_clone_or_update.side_effect = fake_clone_or_update - failures = [] - for source_path in sources: - source_path = Path(source_path) - try: - expected_path = source_path.with_suffix("").with_suffix(".approved.yaml") - test_case = source_path.with_suffix("").stem + expected_path = source_path.with_suffix("").with_suffix(".approved.yaml") + test_case = source_path.with_suffix("").stem - # Load using ESPHome's YAML loader - config = yaml_util.load_yaml(source_path) + # Load using ESPHome's YAML loader + config = yaml_util.load_yaml(source_path) - if CONF_PACKAGES in config: - from esphome.components.packages import do_packages_pass + command_line_substitutions = config.pop("command_line_substitutions", None) - config = do_packages_pass(config) + config = do_packages_pass(config) - substitutions.do_substitution_pass(config, None) + substitutions.do_substitution_pass(config, command_line_substitutions) - resolve_extend_remove(config) - verify_database_result = verify_database(config) - if verify_database_result is not None: - raise AssertionError(verify_database_result) + resolve_extend_remove(config) + verify_database_result = verify_database(config) + if verify_database_result is not None: + raise AssertionError(verify_database_result) - # Also load expected using ESPHome's loader, or use {} if missing and DEV_MODE - if expected_path.is_file(): - expected = yaml_util.load_yaml(expected_path) - elif DEV_MODE: - expected = {} - else: - assert expected_path.is_file(), ( - f"Expected file missing: {expected_path}" - ) + # Also load expected using ESPHome's loader, or use {} if missing and DEV_MODE + if expected_path.is_file(): + expected = yaml_util.load_yaml(expected_path) + elif DEV_MODE: + expected = {} + else: + assert expected_path.is_file(), f"Expected file missing: {expected_path}" - # Sort dicts only (not lists) for comparison - got_sorted = sort_dicts(config) - expected_sorted = sort_dicts(expected) + # Sort dicts only (not lists) for comparison + got_sorted = sort_dicts(config) + expected_sorted = sort_dicts(expected) - if got_sorted != expected_sorted: - diff = "\n".join(dict_diff(got_sorted, expected_sorted)) - msg = ( - f"Substitution result mismatch for {source_path.name}\n" - f"Diff:\n{diff}\n\n" - f"Got: {got_sorted}\n" - f"Expected: {expected_sorted}" - ) - # Write out the received file when test fails - if DEV_MODE: - received_path = source_path.with_name(f"{test_case}.received.yaml") - write_yaml(received_path, config) - print(msg) - failures.append(msg) - else: - raise AssertionError(msg) - except Exception as err: - _LOGGER.error("Error in test file %s", source_path) - raise err - - if DEV_MODE and failures: - print(f"\n{len(failures)} substitution test case(s) failed.") + if got_sorted != expected_sorted: + diff = "\n".join(dict_diff(got_sorted, expected_sorted)) + msg = ( + f"Substitution result mismatch for {source_path.name}\n" + f"Diff:\n{diff}\n\n" + f"Got: {got_sorted}\n" + f"Expected: {expected_sorted}" + ) + # Write out the received file when test fails + if DEV_MODE: + received_path = source_path.with_name(f"{test_case}.received.yaml") + write_yaml(received_path, config) + msg += f"\nWrote received file to {received_path}." + raise AssertionError(msg) if DEV_MODE: _LOGGER.error("Tests passed, but Dev mode is enabled.") - assert not DEV_MODE # make sure DEV_MODE is disabled after you are finished. + assert ( + not DEV_MODE # make sure DEV_MODE is disabled after you are finished. + ), ( + "Test passed but DEV_MODE must be disabled when running tests. Please set DEV_MODE=False." + ) def test_substitutions_with_command_line_maintains_ordered_dict() -> None: