mirror of
https://github.com/esphome/esphome.git
synced 2026-09-04 03:56:04 +00:00
[rp2040] Add variant config option for RP2040/RP2350 (#16602)
This commit is contained in:
@@ -1,8 +1,10 @@
|
||||
from collections.abc import Callable
|
||||
import logging
|
||||
from pathlib import Path
|
||||
import re
|
||||
from string import ascii_letters, digits
|
||||
import subprocess
|
||||
from typing import Any
|
||||
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
@@ -12,6 +14,7 @@ from esphome.const import (
|
||||
CONF_FRAMEWORK,
|
||||
CONF_PLATFORM_VERSION,
|
||||
CONF_SOURCE,
|
||||
CONF_VARIANT,
|
||||
CONF_VERSION,
|
||||
CONF_WATCHDOG_TIMEOUT,
|
||||
KEY_CORE,
|
||||
@@ -21,12 +24,30 @@ from esphome.const import (
|
||||
PLATFORM_RP2040,
|
||||
ThreadModel,
|
||||
)
|
||||
from esphome.core import CORE, CoroPriority, EsphomeError, coroutine_with_priority
|
||||
from esphome.core import (
|
||||
CORE,
|
||||
CoroPriority,
|
||||
EsphomeCore,
|
||||
EsphomeError,
|
||||
coroutine_with_priority,
|
||||
)
|
||||
from esphome.core.config import BOARD_MAX_LENGTH
|
||||
from esphome.helpers import copy_file_if_changed, read_file, write_file_if_changed
|
||||
from esphome.types import ConfigType
|
||||
|
||||
from . import boards
|
||||
from .const import KEY_BOARD, KEY_LWIP_OPTS, KEY_PIO_FILES, KEY_RP2040, rp2040_ns
|
||||
from .const import (
|
||||
KEY_BOARD,
|
||||
KEY_LWIP_OPTS,
|
||||
KEY_PIO_FILES,
|
||||
KEY_RP2040,
|
||||
KEY_VARIANT,
|
||||
MCU_TO_VARIANT,
|
||||
STANDARD_BOARDS,
|
||||
VARIANT_FRIENDLY,
|
||||
VARIANTS,
|
||||
rp2040_ns,
|
||||
)
|
||||
|
||||
# force import gpio to register pin schema
|
||||
from .gpio import rp2040_pin_to_code # noqa
|
||||
@@ -68,7 +89,7 @@ def board_id_has_wifi(board_id: str) -> bool:
|
||||
return board_info.get("wifi", False)
|
||||
|
||||
|
||||
def set_core_data(config):
|
||||
def set_core_data(config: ConfigType) -> ConfigType:
|
||||
CORE.data[KEY_RP2040] = {}
|
||||
CORE.data[KEY_CORE][KEY_TARGET_PLATFORM] = PLATFORM_RP2040
|
||||
CORE.data[KEY_CORE][KEY_TARGET_FRAMEWORK] = "arduino"
|
||||
@@ -76,12 +97,46 @@ def set_core_data(config):
|
||||
config[CONF_FRAMEWORK][CONF_VERSION]
|
||||
)
|
||||
CORE.data[KEY_RP2040][KEY_BOARD] = config[CONF_BOARD]
|
||||
CORE.data[KEY_RP2040][KEY_VARIANT] = config[CONF_VARIANT]
|
||||
|
||||
CORE.data[KEY_RP2040][KEY_PIO_FILES] = {}
|
||||
|
||||
return config
|
||||
|
||||
|
||||
def get_rp2040_variant(core_obj: EsphomeCore | None = None) -> str:
|
||||
return (core_obj or CORE).data[KEY_RP2040][KEY_VARIANT]
|
||||
|
||||
|
||||
def only_on_variant(
|
||||
*,
|
||||
supported: str | list[str] | None = None,
|
||||
unsupported: str | list[str] | None = None,
|
||||
msg_prefix: str = "This feature",
|
||||
) -> Callable[[Any], Any]:
|
||||
"""Config validator for features only available on some RP2040 variants."""
|
||||
if supported is not None and not isinstance(supported, list):
|
||||
supported = [supported]
|
||||
if unsupported is not None and not isinstance(unsupported, list):
|
||||
unsupported = [unsupported]
|
||||
|
||||
def validator_(obj: Any) -> Any:
|
||||
if not CORE.is_rp2040:
|
||||
raise cv.Invalid(f"{msg_prefix} is only available on RP2040")
|
||||
variant = get_rp2040_variant()
|
||||
if supported is not None and variant not in supported:
|
||||
raise cv.Invalid(
|
||||
f"{msg_prefix} is only available on {', '.join(supported)}"
|
||||
)
|
||||
if unsupported is not None and variant in unsupported:
|
||||
raise cv.Invalid(
|
||||
f"{msg_prefix} is not available on {', '.join(unsupported)}"
|
||||
)
|
||||
return obj
|
||||
|
||||
return validator_
|
||||
|
||||
|
||||
def get_download_types(storage_json):
|
||||
"""Binary-download entries for a built RP2040 firmware.
|
||||
|
||||
@@ -192,12 +247,52 @@ ARDUINO_FRAMEWORK_SCHEMA = cv.All(
|
||||
_arduino_check_versions,
|
||||
)
|
||||
|
||||
|
||||
def _detect_variant(value: ConfigType) -> ConfigType:
|
||||
value = value.copy()
|
||||
board: str | None = value.get(CONF_BOARD)
|
||||
variant: str | None = value.get(CONF_VARIANT)
|
||||
|
||||
if board is None:
|
||||
# `cv.has_at_least_one_key` guarantees variant is set here.
|
||||
board = STANDARD_BOARDS[variant]
|
||||
value[CONF_BOARD] = board
|
||||
|
||||
board_info = boards.BOARDS.get(board)
|
||||
if board_info is None:
|
||||
if variant is None:
|
||||
raise cv.Invalid(
|
||||
"This board is unknown; please specify the chip variant using "
|
||||
f"the '{CONF_VARIANT}' option.",
|
||||
path=[CONF_BOARD],
|
||||
)
|
||||
_LOGGER.warning(
|
||||
"This board is unknown; the specified variant '%s' will be used "
|
||||
"but this may not work as expected.",
|
||||
variant,
|
||||
)
|
||||
else:
|
||||
board_variant = MCU_TO_VARIANT[board_info["mcu"]]
|
||||
if variant is None:
|
||||
variant = board_variant
|
||||
elif variant != board_variant:
|
||||
raise cv.Invalid(
|
||||
f"Option '{CONF_VARIANT}' ({variant}) does not match the "
|
||||
f"selected board '{board}' ({board_variant}).",
|
||||
path=[CONF_VARIANT],
|
||||
)
|
||||
|
||||
value[CONF_VARIANT] = variant
|
||||
return value
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_BOARD): cv.All(
|
||||
cv.Optional(CONF_BOARD): cv.All(
|
||||
cv.string_strict, cv.ByteLength(max=BOARD_MAX_LENGTH)
|
||||
),
|
||||
cv.Optional(CONF_VARIANT): cv.one_of(*VARIANTS, upper=True),
|
||||
cv.Optional(CONF_FRAMEWORK, default={}): ARDUINO_FRAMEWORK_SCHEMA,
|
||||
cv.Optional(CONF_WATCHDOG_TIMEOUT, default="8388ms"): cv.All(
|
||||
cv.positive_time_period_milliseconds,
|
||||
@@ -206,6 +301,8 @@ CONFIG_SCHEMA = cv.All(
|
||||
cv.Optional(CONF_ENABLE_FULL_PRINTF, default=False): cv.boolean,
|
||||
}
|
||||
),
|
||||
cv.has_at_least_one_key(CONF_BOARD, CONF_VARIANT),
|
||||
_detect_variant,
|
||||
set_core_data,
|
||||
)
|
||||
|
||||
@@ -223,7 +320,9 @@ async def to_code(config):
|
||||
cg.add_define("USE_NATIVE_64BIT_TIME")
|
||||
cg.set_cpp_standard("gnu++20")
|
||||
cg.add_define("ESPHOME_BOARD", config[CONF_BOARD])
|
||||
cg.add_define("ESPHOME_VARIANT", "RP2040")
|
||||
variant = config[CONF_VARIANT]
|
||||
cg.add_build_flag(f"-DUSE_RP2040_VARIANT_{variant}")
|
||||
cg.add_define("ESPHOME_VARIANT", VARIANT_FRIENDLY[variant])
|
||||
cg.add_define(ThreadModel.SINGLE)
|
||||
|
||||
cg.add_platformio_option("extra_scripts", ["post:post_build.py"])
|
||||
|
||||
@@ -4,5 +4,31 @@ KEY_BOARD = "board"
|
||||
KEY_LWIP_OPTS = "lwip_opts"
|
||||
KEY_RP2040 = "rp2040"
|
||||
KEY_PIO_FILES = "pio_files"
|
||||
KEY_VARIANT = "variant"
|
||||
|
||||
VARIANT_RP2040 = "RP2040"
|
||||
VARIANT_RP2350 = "RP2350"
|
||||
VARIANTS = [
|
||||
VARIANT_RP2040,
|
||||
VARIANT_RP2350,
|
||||
]
|
||||
|
||||
VARIANT_FRIENDLY = {
|
||||
VARIANT_RP2040: "RP2040",
|
||||
VARIANT_RP2350: "RP2350",
|
||||
}
|
||||
|
||||
# Map BOARDS[board]["mcu"] (lowercase) to canonical variant constant
|
||||
MCU_TO_VARIANT = {
|
||||
"rp2040": VARIANT_RP2040,
|
||||
"rp2350": VARIANT_RP2350,
|
||||
}
|
||||
|
||||
# Default board chosen when only `variant` is specified — the Raspberry Pi
|
||||
# Foundation reference boards (Pico W / Pico 2 W).
|
||||
STANDARD_BOARDS = {
|
||||
VARIANT_RP2040: "rpipicow",
|
||||
VARIANT_RP2350: "rpipico2w",
|
||||
}
|
||||
|
||||
rp2040_ns = cg.esphome_ns.namespace("rp2040")
|
||||
|
||||
@@ -401,6 +401,7 @@
|
||||
#define USE_LOGGER_USB_CDC
|
||||
#define USE_SOCKET_IMPL_LWIP_TCP
|
||||
#define USE_RP2040_BLE
|
||||
#define USE_RP2040_VARIANT_RP2040
|
||||
#define USE_SPI
|
||||
#ifndef USE_ETHERNET
|
||||
#define USE_ETHERNET
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
rp2040:
|
||||
variant: rp2040
|
||||
enable_full_printf: false
|
||||
|
||||
logger:
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
rp2040:
|
||||
variant: rp2350
|
||||
enable_full_printf: false
|
||||
|
||||
logger:
|
||||
level: VERBOSE
|
||||
@@ -1,6 +1,11 @@
|
||||
"""Tests for RP2040 component public helpers."""
|
||||
"""Tests for RP2040 component public helpers and variant detection."""
|
||||
|
||||
from esphome.components.rp2040 import board_id_has_wifi
|
||||
import pytest
|
||||
|
||||
from esphome.components.rp2040 import _detect_variant, board_id_has_wifi
|
||||
from esphome.components.rp2040.const import VARIANT_RP2040, VARIANT_RP2350
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_BOARD, CONF_VARIANT
|
||||
|
||||
|
||||
def test_board_id_has_wifi_for_known_wifi_board() -> None:
|
||||
@@ -27,3 +32,61 @@ def test_board_id_has_wifi_for_unknown_board_returns_true() -> None:
|
||||
"no CYW43" guard at compile time.
|
||||
"""
|
||||
assert board_id_has_wifi("not-a-real-board-id") is True
|
||||
|
||||
|
||||
def test_detect_variant_derives_variant_from_board() -> None:
|
||||
"""Board alone resolves to the matching variant."""
|
||||
result = _detect_variant({CONF_BOARD: "rpipicow"})
|
||||
assert result[CONF_BOARD] == "rpipicow"
|
||||
assert result[CONF_VARIANT] == VARIANT_RP2040
|
||||
|
||||
|
||||
def test_detect_variant_derives_variant_from_rp2350_board() -> None:
|
||||
"""An RP2350 board resolves to ``RP2350``."""
|
||||
result = _detect_variant({CONF_BOARD: "rpipico2"})
|
||||
assert result[CONF_BOARD] == "rpipico2"
|
||||
assert result[CONF_VARIANT] == VARIANT_RP2350
|
||||
|
||||
|
||||
def test_detect_variant_only_picks_default_board_rp2040() -> None:
|
||||
"""Variant alone picks Pico W as the canonical RP2040 board."""
|
||||
result = _detect_variant({CONF_VARIANT: VARIANT_RP2040})
|
||||
assert result[CONF_BOARD] == "rpipicow"
|
||||
assert result[CONF_VARIANT] == VARIANT_RP2040
|
||||
|
||||
|
||||
def test_detect_variant_only_picks_default_board_rp2350() -> None:
|
||||
"""Variant alone picks Pico 2 W as the canonical RP2350 board."""
|
||||
result = _detect_variant({CONF_VARIANT: VARIANT_RP2350})
|
||||
assert result[CONF_BOARD] == "rpipico2w"
|
||||
assert result[CONF_VARIANT] == VARIANT_RP2350
|
||||
|
||||
|
||||
def test_detect_variant_matching_explicit_variant_passes() -> None:
|
||||
"""Specifying both a board and the matching variant is allowed."""
|
||||
result = _detect_variant({CONF_BOARD: "rpipico2", CONF_VARIANT: VARIANT_RP2350})
|
||||
assert result[CONF_BOARD] == "rpipico2"
|
||||
assert result[CONF_VARIANT] == VARIANT_RP2350
|
||||
|
||||
|
||||
def test_detect_variant_mismatched_variant_raises() -> None:
|
||||
"""Board/variant mismatch must be rejected and name the offending board."""
|
||||
with pytest.raises(
|
||||
cv.Invalid, match=r"does not match the selected board 'rpipicow'"
|
||||
):
|
||||
_detect_variant({CONF_BOARD: "rpipicow", CONF_VARIANT: VARIANT_RP2350})
|
||||
|
||||
|
||||
def test_detect_variant_unknown_board_without_variant_raises() -> None:
|
||||
"""Unknown board with no variant tells the user how to recover."""
|
||||
with pytest.raises(cv.Invalid, match="please specify the chip variant"):
|
||||
_detect_variant({CONF_BOARD: "not-a-real-board"})
|
||||
|
||||
|
||||
def test_detect_variant_unknown_board_with_variant_passes() -> None:
|
||||
"""Unknown board + explicit variant is accepted (with a warning)."""
|
||||
result = _detect_variant(
|
||||
{CONF_BOARD: "not-a-real-board", CONF_VARIANT: VARIANT_RP2040}
|
||||
)
|
||||
assert result[CONF_BOARD] == "not-a-real-board"
|
||||
assert result[CONF_VARIANT] == VARIANT_RP2040
|
||||
|
||||
Reference in New Issue
Block a user