From 6153bcc6ad818c8ece3fc149614d7aa391bd483f Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 20 Oct 2022 16:50:39 +1300 Subject: [PATCH] Initial Support for RP2040 platform (#3284) Co-authored-by: Paulus Schoutsen --- .github/workflows/ci.yml | 4 + CODEOWNERS | 2 + esphome/__main__.py | 72 ++++--- esphome/components/logger/__init__.py | 17 +- esphome/components/logger/logger.cpp | 6 +- esphome/components/logger/logger.h | 12 +- esphome/components/md5/md5.cpp | 15 +- esphome/components/md5/md5.h | 5 + esphome/components/mdns/mdns_component.cpp | 3 + esphome/components/mdns/mdns_rp2040.cpp | 40 ++++ esphome/components/ota/__init__.py | 5 +- .../ota/ota_backend_arduino_rp2040.cpp | 55 +++++ .../ota/ota_backend_arduino_rp2040.h | 27 +++ esphome/components/ota/ota_component.cpp | 4 + esphome/components/ota/ota_component.h | 1 + esphome/components/rp2040/__init__.py | 157 ++++++++++++++ esphome/components/rp2040/boards.py | 7 + esphome/components/rp2040/const.py | 6 + esphome/components/rp2040/core.cpp | 32 +++ esphome/components/rp2040/core.h | 14 ++ esphome/components/rp2040/gpio.cpp | 103 +++++++++ esphome/components/rp2040/gpio.h | 38 ++++ esphome/components/rp2040/gpio.py | 91 ++++++++ esphome/components/rp2040/preferences.cpp | 49 +++++ esphome/components/rp2040/preferences.h | 13 ++ esphome/components/rp2040_pwm/__init__.py | 0 esphome/components/rp2040_pwm/output.py | 55 +++++ esphome/components/rp2040_pwm/rp2040_pwm.cpp | 45 ++++ esphome/components/rp2040_pwm/rp2040_pwm.h | 57 +++++ esphome/components/socket/__init__.py | 1 + esphome/components/socket/headers.h | 2 +- esphome/components/spi/__init__.py | 4 +- esphome/components/spi/spi.h | 20 ++ esphome/components/wifi/__init__.py | 4 +- esphome/components/wifi/wifi_component.cpp | 2 + esphome/components/wifi/wifi_component.h | 17 ++ .../components/wifi/wifi_component_pico_w.cpp | 204 ++++++++++++++++++ esphome/config_validation.py | 4 + esphome/const.py | 3 +- esphome/core/__init__.py | 4 + esphome/core/helpers.cpp | 8 + esphome/dashboard/dashboard.py | 28 ++- esphome/storage_json.py | 6 +- esphome/wizard.py | 20 +- platformio.ini | 25 +++ script/ci-custom.py | 1 + tests/README.md | 1 + tests/dummy_main.cpp | 3 +- tests/test6.yaml | 39 ++++ 49 files changed, 1270 insertions(+), 61 deletions(-) create mode 100644 esphome/components/mdns/mdns_rp2040.cpp create mode 100644 esphome/components/ota/ota_backend_arduino_rp2040.cpp create mode 100644 esphome/components/ota/ota_backend_arduino_rp2040.h create mode 100644 esphome/components/rp2040/__init__.py create mode 100644 esphome/components/rp2040/boards.py create mode 100644 esphome/components/rp2040/const.py create mode 100644 esphome/components/rp2040/core.cpp create mode 100644 esphome/components/rp2040/core.h create mode 100644 esphome/components/rp2040/gpio.cpp create mode 100644 esphome/components/rp2040/gpio.h create mode 100644 esphome/components/rp2040/gpio.py create mode 100644 esphome/components/rp2040/preferences.cpp create mode 100644 esphome/components/rp2040/preferences.h create mode 100644 esphome/components/rp2040_pwm/__init__.py create mode 100644 esphome/components/rp2040_pwm/output.py create mode 100644 esphome/components/rp2040_pwm/rp2040_pwm.cpp create mode 100644 esphome/components/rp2040_pwm/rp2040_pwm.h create mode 100644 esphome/components/wifi/wifi_component_pico_w.cpp create mode 100644 tests/test6.yaml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 671b74d118..8e09fef069 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -48,6 +48,10 @@ jobs: file: tests/test5.yaml name: Test tests/test5.yaml pio_cache_key: test5 + - id: test + file: tests/test6.yaml + name: Test tests/test6.yaml + pio_cache_key: test6 - id: pytest name: Run pytest - id: clang-format diff --git a/CODEOWNERS b/CODEOWNERS index 78624ecdbe..ac793e19af 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -184,6 +184,8 @@ esphome/components/rc522_spi/* @glmnet esphome/components/restart/* @esphome/core esphome/components/rf_bridge/* @jesserockz esphome/components/rgbct/* @jesserockz +esphome/components/rp2040/* @jesserockz +esphome/components/rp2040_pwm/* @jesserockz esphome/components/rtttl/* @glmnet esphome/components/safe_mode/* @jsuanet @paulmonigatti esphome/components/scd4x/* @martgras @sjtrny diff --git a/esphome/__main__.py b/esphome/__main__.py index c336336f18..cf2d161d04 100644 --- a/esphome/__main__.py +++ b/esphome/__main__.py @@ -4,6 +4,7 @@ import logging import os import re import sys +import time from datetime import datetime from esphome import const, writer, yaml_util @@ -22,6 +23,9 @@ from esphome.const import ( CONF_ESPHOME, CONF_PLATFORMIO_OPTIONS, CONF_SUBSTITUTIONS, + PLATFORM_ESP32, + PLATFORM_ESP8266, + PLATFORM_RP2040, SECRETS_FILES, ) from esphome.core import CORE, EsphomeError, coroutine @@ -101,11 +105,11 @@ def run_miniterm(config, port): if CONF_LOGGER not in config: _LOGGER.info("Logger is not enabled. Not starting UART logs.") - return + return 1 baud_rate = config["logger"][CONF_BAUD_RATE] if baud_rate == 0: _LOGGER.info("UART logging is disabled (baud_rate=0). Not starting UART logs.") - return + return 1 _LOGGER.info("Starting log output from %s with baud rate %s", port, baud_rate) backtrace_state = False @@ -119,25 +123,34 @@ def run_miniterm(config, port): ser.dtr = False ser.rts = False - with ser: - while True: - try: - raw = ser.readline() - except serial.SerialException: - _LOGGER.error("Serial port closed!") - return - line = ( - raw.replace(b"\r", b"") - .replace(b"\n", b"") - .decode("utf8", "backslashreplace") - ) - time = datetime.now().time().strftime("[%H:%M:%S]") - message = time + line - safe_print(message) + tries = 0 + while tries < 5: + try: + with ser: + while True: + try: + raw = ser.readline() + except serial.SerialException: + _LOGGER.error("Serial port closed!") + return 0 + line = ( + raw.replace(b"\r", b"") + .replace(b"\n", b"") + .decode("utf8", "backslashreplace") + ) + time_str = datetime.now().time().strftime("[%H:%M:%S]") + message = time_str + line + safe_print(message) - backtrace_state = platformio_api.process_stacktrace( - config, line, backtrace_state=backtrace_state - ) + backtrace_state = platformio_api.process_stacktrace( + config, line, backtrace_state=backtrace_state + ) + except serial.SerialException: + tries += 1 + time.sleep(1) + if tries >= 5: + _LOGGER.error("Could not connect to serial port %s", port) + return 1 def wrap_to_code(name, comp): @@ -258,9 +271,21 @@ def upload_using_esptool(config, port): def upload_program(config, args, host): - # if upload is to a serial port use platformio, otherwise assume ota if get_port_type(host) == "SERIAL": - return upload_using_esptool(config, host) + if CORE.target_platform in (PLATFORM_ESP32, PLATFORM_ESP8266): + return upload_using_esptool(config, host) + + if CORE.target_platform in (PLATFORM_RP2040): + from esphome import platformio_api + + upload_args = ["-t", "upload"] + if args.device is not None: + upload_args += ["--upload-port", args.device] + return platformio_api.run_platformio_cli_run( + config, CORE.verbose, *upload_args + ) + + return 1 # Unknown target platform from esphome import espota2 @@ -280,8 +305,7 @@ def show_logs(config, args, port): if "logger" not in config: raise EsphomeError("Logger is not configured!") if get_port_type(port) == "SERIAL": - run_miniterm(config, port) - return 0 + return run_miniterm(config, port) if get_port_type(port) == "NETWORK" and "api" in config: from esphome.components.api.client import run_logs diff --git a/esphome/components/logger/__init__.py b/esphome/components/logger/__init__.py index 43d87bcefe..333e109379 100644 --- a/esphome/components/logger/__init__.py +++ b/esphome/components/logger/__init__.py @@ -77,6 +77,8 @@ UART_SELECTION_ESP8266 = [UART0, UART0_SWAP, UART1] ESP_IDF_UARTS = [USB_CDC, USB_SERIAL_JTAG] +UART_SELECTION_RP2040 = [UART0, UART1] + HARDWARE_UART_TO_UART_SELECTION = { UART0: logger_ns.UART_SELECTION_UART0, UART0_SWAP: logger_ns.UART_SELECTION_UART0_SWAP, @@ -106,6 +108,8 @@ def uart_selection(value): return cv.one_of(*UART_SELECTION_ESP32[variant], upper=True)(value) if CORE.is_esp8266: return cv.one_of(*UART_SELECTION_ESP8266, upper=True)(value) + if CORE.is_rp2040: + return cv.one_of(*UART_SELECTION_RP2040, upper=True)(value) raise NotImplementedError @@ -158,12 +162,13 @@ CONFIG_SCHEMA = cv.All( @coroutine_with_priority(90.0) async def to_code(config): baud_rate = config[CONF_BAUD_RATE] - rhs = Logger.new( - baud_rate, - config[CONF_TX_BUFFER_SIZE], - HARDWARE_UART_TO_UART_SELECTION[config[CONF_HARDWARE_UART]], - ) - log = cg.Pvariable(config[CONF_ID], rhs) + log = cg.new_Pvariable(config[CONF_ID], baud_rate, config[CONF_TX_BUFFER_SIZE]) + if CONF_HARDWARE_UART in config: + cg.add( + log.set_uart_selection( + HARDWARE_UART_TO_UART_SELECTION[config[CONF_HARDWARE_UART]] + ) + ) cg.add(log.pre_setup()) for tag, level in config[CONF_LOGS].items(): diff --git a/esphome/components/logger/logger.cpp b/esphome/components/logger/logger.cpp index c97677c887..271f99ba58 100644 --- a/esphome/components/logger/logger.cpp +++ b/esphome/components/logger/logger.cpp @@ -148,8 +148,7 @@ void HOT Logger::log_message_(int level, const char *tag, int offset) { this->log_callback_.call(level, tag, msg); } -Logger::Logger(uint32_t baud_rate, size_t tx_buffer_size, UARTSelection uart) - : baud_rate_(baud_rate), tx_buffer_size_(tx_buffer_size), uart_(uart) { +Logger::Logger(uint32_t baud_rate, size_t tx_buffer_size) : baud_rate_(baud_rate), tx_buffer_size_(tx_buffer_size) { // add 1 to buffer size for null terminator this->tx_buffer_ = new char[this->tx_buffer_size_ + 1]; // NOLINT } @@ -270,6 +269,9 @@ const char *const UART_SELECTIONS[] = { #endif // USE_ESP32 #ifdef USE_ESP8266 const char *const UART_SELECTIONS[] = {"UART0", "UART1", "UART0_SWAP"}; +#endif +#ifdef USE_RP2040 +const char *const UART_SELECTIONS[] = {"UART0", "UART1"}; #endif // USE_ESP8266 void Logger::dump_config() { ESP_LOGCONFIG(TAG, "Logger:"); diff --git a/esphome/components/logger/logger.h b/esphome/components/logger/logger.h index 307c13a91f..0251faf987 100644 --- a/esphome/components/logger/logger.h +++ b/esphome/components/logger/logger.h @@ -7,8 +7,15 @@ #include #ifdef USE_ARDUINO +#if defined(USE_ESP8266) || defined(USE_ESP32) #include -#endif +#endif // USE_ESP8266 || USE_ESP32 +#ifdef USE_RP2040 +#include +#include +#endif // USE_RP2040 +#endif // USE_ARDUINO + #ifdef USE_ESP_IDF #include #endif @@ -44,7 +51,7 @@ enum UARTSelection { class Logger : public Component { public: - explicit Logger(uint32_t baud_rate, size_t tx_buffer_size, UARTSelection uart); + explicit Logger(uint32_t baud_rate, size_t tx_buffer_size); /// Manually set the baud rate for serial, set to 0 to disable. void set_baud_rate(uint32_t baud_rate); @@ -56,6 +63,7 @@ class Logger : public Component { uart_port_t get_uart_num() const { return uart_num_; } #endif + void set_uart_selection(UARTSelection uart_selection) { uart_ = uart_selection; } /// Get the UART used by the logger. UARTSelection get_uart() const; diff --git a/esphome/components/md5/md5.cpp b/esphome/components/md5/md5.cpp index 8d4bac1fd2..620b6749f3 100644 --- a/esphome/components/md5/md5.cpp +++ b/esphome/components/md5/md5.cpp @@ -6,7 +6,7 @@ namespace esphome { namespace md5 { -#ifdef USE_ARDUINO +#if defined(USE_ARDUINO) && !defined(USE_RP2040) void MD5Digest::init() { memset(this->digest_, 0, 16); MD5Init(&this->ctx_); @@ -15,7 +15,7 @@ void MD5Digest::init() { void MD5Digest::add(const uint8_t *data, size_t len) { MD5Update(&this->ctx_, data, len); } void MD5Digest::calculate() { MD5Final(this->digest_, &this->ctx_); } -#endif // USE_ARDUINO +#endif // USE_ARDUINO && !USE_RP2040 #ifdef USE_ESP_IDF void MD5Digest::init() { @@ -28,6 +28,17 @@ void MD5Digest::add(const uint8_t *data, size_t len) { esp_rom_md5_update(&this- void MD5Digest::calculate() { esp_rom_md5_final(this->digest_, &this->ctx_); } #endif // USE_ESP_IDF +#ifdef USE_RP2040 +void MD5Digest::init() { + memset(this->digest_, 0, 16); + br_md5_init(&this->ctx_); +} + +void MD5Digest::add(const uint8_t *data, size_t len) { br_md5_update(&this->ctx_, data, len); } + +void MD5Digest::calculate() { br_md5_out(&this->ctx_, this->digest_); } +#endif // USE_RP2040 + void MD5Digest::get_bytes(uint8_t *output) { memcpy(output, this->digest_, 16); } void MD5Digest::get_hex(char *output) { diff --git a/esphome/components/md5/md5.h b/esphome/components/md5/md5.h index a9628c9242..738a312267 100644 --- a/esphome/components/md5/md5.h +++ b/esphome/components/md5/md5.h @@ -17,6 +17,11 @@ #define MD5_CTX_TYPE md5_context_t #endif +#ifdef USE_RP2040 +#include +#define MD5_CTX_TYPE br_md5_context +#endif + namespace esphome { namespace md5 { diff --git a/esphome/components/mdns/mdns_component.cpp b/esphome/components/mdns/mdns_component.cpp index e1fcf320ed..a3f38322b3 100644 --- a/esphome/components/mdns/mdns_component.cpp +++ b/esphome/components/mdns/mdns_component.cpp @@ -38,6 +38,9 @@ void MDNSComponent::compile_records_() { #endif #ifdef USE_ESP32 platform = "ESP32"; +#endif +#ifdef USE_RP2040 + platform = "RP2040"; #endif if (platform != nullptr) { service.txt_records.push_back({"platform", platform}); diff --git a/esphome/components/mdns/mdns_rp2040.cpp b/esphome/components/mdns/mdns_rp2040.cpp new file mode 100644 index 0000000000..b153ececcc --- /dev/null +++ b/esphome/components/mdns/mdns_rp2040.cpp @@ -0,0 +1,40 @@ +#ifdef USE_RP2040 + +#include "esphome/components/network/ip_address.h" +#include "esphome/components/network/util.h" +#include "esphome/core/log.h" +#include "mdns_component.h" + +namespace esphome { +namespace mdns { + +void MDNSComponent::setup() { + this->compile_records_(); + + network::IPAddress addr = network::get_ip_address(); + // MDNS.begin(this->hostname_.c_str(), (uint32_t) addr); + + // for (const auto &service : this->services_) { + // // Strip the leading underscore from the proto and service_type. While it is + // // part of the wire protocol to have an underscore, and for example ESP-IDF + // // expects the underscore to be there, the ESP8266 implementation always adds + // // the underscore itself. + // auto *proto = service.proto.c_str(); + // while (*proto == '_') { + // proto++; + // } + // auto *service_type = service.service_type.c_str(); + // while (*service_type == '_') { + // service_type++; + // } + // MDNS.addService(service_type, proto, service.port); + // for (const auto &record : service.txt_records) { + // MDNS.addServiceTxt(service_type, proto, record.key.c_str(), record.value.c_str()); + // } + // } +} + +} // namespace mdns +} // namespace esphome + +#endif diff --git a/esphome/components/ota/__init__.py b/esphome/components/ota/__init__.py index 1bc4012ce2..32ea1fd363 100644 --- a/esphome/components/ota/__init__.py +++ b/esphome/components/ota/__init__.py @@ -39,7 +39,7 @@ CONFIG_SCHEMA = cv.Schema( { cv.GenerateID(): cv.declare_id(OTAComponent), cv.Optional(CONF_SAFE_MODE, default=True): cv.boolean, - cv.SplitDefault(CONF_PORT, esp8266=8266, esp32=3232): cv.port, + cv.SplitDefault(CONF_PORT, esp8266=8266, esp32=3232, rp2040=2040): cv.port, cv.Optional(CONF_PASSWORD): cv.string, cv.Optional( CONF_REBOOT_TIMEOUT, default="5min" @@ -94,6 +94,9 @@ async def to_code(config): if CORE.is_esp32 and CORE.using_arduino: cg.add_library("Update", None) + if CORE.is_rp2040 and CORE.using_arduino: + cg.add_library("Updater", None) + use_state_callback = False for conf in config.get(CONF_ON_STATE_CHANGE, []): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) diff --git a/esphome/components/ota/ota_backend_arduino_rp2040.cpp b/esphome/components/ota/ota_backend_arduino_rp2040.cpp new file mode 100644 index 0000000000..5a46b8f07e --- /dev/null +++ b/esphome/components/ota/ota_backend_arduino_rp2040.cpp @@ -0,0 +1,55 @@ +#include "esphome/core/defines.h" +#ifdef USE_ARDUINO +#ifdef USE_RP2040 + +#include "esphome/components/rp2040/preferences.h" +#include "ota_backend.h" +#include "ota_backend_arduino_rp2040.h" +#include "ota_component.h" + +#include + +namespace esphome { +namespace ota { + +OTAResponseTypes ArduinoRP2040OTABackend::begin(size_t image_size) { + bool ret = Update.begin(image_size, U_FLASH); + if (ret) { + return OTA_RESPONSE_OK; + } + + uint8_t error = Update.getError(); + if (error == UPDATE_ERROR_BOOTSTRAP) + return OTA_RESPONSE_ERROR_INVALID_BOOTSTRAPPING; + if (error == UPDATE_ERROR_NEW_FLASH_CONFIG) + return OTA_RESPONSE_ERROR_WRONG_NEW_FLASH_CONFIG; + if (error == UPDATE_ERROR_FLASH_CONFIG) + return OTA_RESPONSE_ERROR_WRONG_CURRENT_FLASH_CONFIG; + if (error == UPDATE_ERROR_SPACE) + return OTA_RESPONSE_ERROR_RP2040_NOT_ENOUGH_SPACE; + return OTA_RESPONSE_ERROR_UNKNOWN; +} + +void ArduinoRP2040OTABackend::set_update_md5(const char *md5) { Update.setMD5(md5); } + +OTAResponseTypes ArduinoRP2040OTABackend::write(uint8_t *data, size_t len) { + size_t written = Update.write(data, len); + if (written != len) { + return OTA_RESPONSE_ERROR_WRITING_FLASH; + } + return OTA_RESPONSE_OK; +} + +OTAResponseTypes ArduinoRP2040OTABackend::end() { + if (!Update.end()) + return OTA_RESPONSE_ERROR_UPDATE_END; + return OTA_RESPONSE_OK; +} + +void ArduinoRP2040OTABackend::abort() { Update.end(); } + +} // namespace ota +} // namespace esphome + +#endif // USE_RP2040 +#endif // USE_ARDUINO diff --git a/esphome/components/ota/ota_backend_arduino_rp2040.h b/esphome/components/ota/ota_backend_arduino_rp2040.h new file mode 100644 index 0000000000..5aa2ec9435 --- /dev/null +++ b/esphome/components/ota/ota_backend_arduino_rp2040.h @@ -0,0 +1,27 @@ +#pragma once +#include "esphome/core/defines.h" +#ifdef USE_ARDUINO +#ifdef USE_RP2040 + +#include "esphome/core/macros.h" +#include "ota_backend.h" +#include "ota_component.h" + +namespace esphome { +namespace ota { + +class ArduinoRP2040OTABackend : public OTABackend { + public: + OTAResponseTypes begin(size_t image_size) override; + void set_update_md5(const char *md5) override; + OTAResponseTypes write(uint8_t *data, size_t len) override; + OTAResponseTypes end() override; + void abort() override; + bool supports_compression() override { return false; } +}; + +} // namespace ota +} // namespace esphome + +#endif // USE_RP2040 +#endif // USE_ARDUINO diff --git a/esphome/components/ota/ota_component.cpp b/esphome/components/ota/ota_component.cpp index a02d64cd08..1f1ecd9867 100644 --- a/esphome/components/ota/ota_component.cpp +++ b/esphome/components/ota/ota_component.cpp @@ -2,6 +2,7 @@ #include "ota_backend.h" #include "ota_backend_arduino_esp32.h" #include "ota_backend_arduino_esp8266.h" +#include "ota_backend_arduino_rp2040.h" #include "ota_backend_esp_idf.h" #include "esphome/core/log.h" @@ -35,6 +36,9 @@ std::unique_ptr make_ota_backend() { #ifdef USE_ESP_IDF return make_unique(); #endif // USE_ESP_IDF +#ifdef USE_RP2040 + return make_unique(); +#endif // USE_RP2040 } OTAComponent::OTAComponent() { global_ota_component = this; } diff --git a/esphome/components/ota/ota_component.h b/esphome/components/ota/ota_component.h index 9a1c92f727..50d095be6c 100644 --- a/esphome/components/ota/ota_component.h +++ b/esphome/components/ota/ota_component.h @@ -33,6 +33,7 @@ enum OTAResponseTypes { OTA_RESPONSE_ERROR_ESP32_NOT_ENOUGH_SPACE = 137, OTA_RESPONSE_ERROR_NO_UPDATE_PARTITION = 138, OTA_RESPONSE_ERROR_MD5_MISMATCH = 139, + OTA_RESPONSE_ERROR_RP2040_NOT_ENOUGH_SPACE = 140, OTA_RESPONSE_ERROR_UNKNOWN = 255, }; diff --git a/esphome/components/rp2040/__init__.py b/esphome/components/rp2040/__init__.py new file mode 100644 index 0000000000..d526228f41 --- /dev/null +++ b/esphome/components/rp2040/__init__.py @@ -0,0 +1,157 @@ +import logging + +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.const import ( + CONF_BOARD, + CONF_FRAMEWORK, + CONF_SOURCE, + CONF_VERSION, + KEY_CORE, + KEY_FRAMEWORK_VERSION, + KEY_TARGET_FRAMEWORK, + KEY_TARGET_PLATFORM, +) +from esphome.core import CORE, coroutine_with_priority + +from .const import KEY_BOARD, KEY_RP2040, rp2040_ns + +# force import gpio to register pin schema +from .gpio import rp2040_pin_to_code # noqa + +_LOGGER = logging.getLogger(__name__) +CODEOWNERS = ["@jesserockz"] +AUTO_LOAD = [] + + +def set_core_data(config): + CORE.data[KEY_RP2040] = {} + CORE.data[KEY_CORE][KEY_TARGET_PLATFORM] = "rp2040" + CORE.data[KEY_CORE][KEY_TARGET_FRAMEWORK] = "arduino" + CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] = cv.Version.parse( + config[CONF_FRAMEWORK][CONF_VERSION] + ) + CORE.data[KEY_RP2040][KEY_BOARD] = config[CONF_BOARD] + + return config + + +def _format_framework_arduino_version(ver: cv.Version) -> str: + # format the given arduino (https://github.com/earlephilhower/arduino-pico/releases) version to + # a PIO earlephilhower/framework-arduinopico value + # List of package versions: https://api.registry.platformio.org/v3/packages/earlephilhower/tool/framework-arduinopico + return f"~1.{ver.major}{ver.minor:02d}{ver.patch:02d}.0" + + +# NOTE: Keep this in mind when updating the recommended version: +# * The new version needs to be thoroughly validated before changing the +# recommended version as otherwise a bunch of devices could be bricked +# * For all constants below, update platformio.ini (in this repo) +# and platformio.ini/platformio-lint.ini in the esphome-docker-base repository + +# The default/recommended arduino framework version +# - https://github.com/earlephilhower/arduino-pico/releases +# - https://api.registry.platformio.org/v3/packages/earlephilhower/tool/framework-arduinopico +RECOMMENDED_ARDUINO_FRAMEWORK_VERSION = cv.Version(2, 4, 0) + +# The platformio/raspberrypi version to use for arduino frameworks +# - https://github.com/platformio/platform-raspberrypi/releases +# - https://api.registry.platformio.org/v3/packages/platformio/platform/raspberrypi +ARDUINO_PLATFORM_VERSION = cv.Version(1, 7, 0) + + +def _arduino_check_versions(value): + value = value.copy() + lookups = { + "dev": (cv.Version(2, 4, 0), "https://github.com/earlephilhower/arduino-pico"), + "latest": (cv.Version(2, 4, 0), None), + "recommended": (RECOMMENDED_ARDUINO_FRAMEWORK_VERSION, None), + } + + if value[CONF_VERSION] in lookups: + if CONF_SOURCE in value: + raise cv.Invalid( + "Framework version needs to be explicitly specified when custom source is used." + ) + + version, source = lookups[value[CONF_VERSION]] + else: + version = cv.Version.parse(cv.version_number(value[CONF_VERSION])) + source = value.get(CONF_SOURCE, None) + + value[CONF_VERSION] = str(version) + value[CONF_SOURCE] = source or _format_framework_arduino_version(version) + + value[CONF_PLATFORM_VERSION] = value.get( + CONF_PLATFORM_VERSION, _parse_platform_version(str(ARDUINO_PLATFORM_VERSION)) + ) + + if version != RECOMMENDED_ARDUINO_FRAMEWORK_VERSION: + _LOGGER.warning( + "The selected Arduino framework version is not the recommended one." + ) + + return value + + +def _parse_platform_version(value): + try: + # if platform version is a valid version constraint, prefix the default package + cv.platformio_version_constraint(value) + return f"platformio/raspberrypi @ {value}" + except cv.Invalid: + return value + + +CONF_PLATFORM_VERSION = "platform_version" + +ARDUINO_FRAMEWORK_SCHEMA = cv.All( + cv.Schema( + { + cv.Optional(CONF_VERSION, default="recommended"): cv.string_strict, + cv.Optional(CONF_SOURCE): cv.string_strict, + cv.Optional(CONF_PLATFORM_VERSION): _parse_platform_version, + } + ), + _arduino_check_versions, +) + +CONFIG_SCHEMA = cv.All( + cv.Schema( + { + cv.Required(CONF_BOARD): cv.string_strict, + cv.Optional(CONF_FRAMEWORK, default={}): ARDUINO_FRAMEWORK_SCHEMA, + } + ), + set_core_data, +) + + +@coroutine_with_priority(1000) +async def to_code(config): + cg.add(rp2040_ns.setup_preferences()) + + cg.add_platformio_option("board", config[CONF_BOARD]) + cg.add_build_flag("-DUSE_RP2040") + cg.add_define("ESPHOME_BOARD", config[CONF_BOARD]) + cg.add_define("ESPHOME_VARIANT", "RP2040") + + conf = config[CONF_FRAMEWORK] + cg.add_platformio_option("framework", "arduino") + cg.add_build_flag("-DUSE_ARDUINO") + cg.add_build_flag("-DUSE_RP2040_FRAMEWORK_ARDUINO") + # cg.add_build_flag("-DPICO_BOARD=pico_w") + cg.add_platformio_option("platform", conf[CONF_PLATFORM_VERSION]) + cg.add_platformio_option( + "platform_packages", + [f"earlephilhower/framework-arduinopico @ {conf[CONF_SOURCE]}"], + ) + + cg.add_platformio_option("board_build.core", "earlephilhower") + cg.add_platformio_option("board_build.filesystem_size", "0.5m") + + ver: cv.Version = CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] + cg.add_define( + "USE_ARDUINO_VERSION_CODE", + cg.RawExpression(f"VERSION_CODE({ver.major}, {ver.minor}, {ver.patch})"), + ) diff --git a/esphome/components/rp2040/boards.py b/esphome/components/rp2040/boards.py new file mode 100644 index 0000000000..be3d05369a --- /dev/null +++ b/esphome/components/rp2040/boards.py @@ -0,0 +1,7 @@ +RP2040_BASE_PINS = {} + +RP2040_BOARD_PINS = { + "pico": {"LED": 25}, + "rpipico": "pico", + "rpipicow": {}, +} diff --git a/esphome/components/rp2040/const.py b/esphome/components/rp2040/const.py new file mode 100644 index 0000000000..e09016ca31 --- /dev/null +++ b/esphome/components/rp2040/const.py @@ -0,0 +1,6 @@ +import esphome.codegen as cg + +KEY_BOARD = "board" +KEY_RP2040 = "rp2040" + +rp2040_ns = cg.esphome_ns.namespace("rp2040") diff --git a/esphome/components/rp2040/core.cpp b/esphome/components/rp2040/core.cpp new file mode 100644 index 0000000000..2a1ce5a4d3 --- /dev/null +++ b/esphome/components/rp2040/core.cpp @@ -0,0 +1,32 @@ +#ifdef USE_RP2040 + +#include "core.h" +#include "esphome/core/hal.h" +#include "esphome/core/helpers.h" + +#include "hardware/watchdog.h" + +namespace esphome { + +void IRAM_ATTR HOT yield() { ::yield(); } +uint32_t IRAM_ATTR HOT millis() { return ::millis(); } +void IRAM_ATTR HOT delay(uint32_t ms) { ::delay(ms); } +uint32_t IRAM_ATTR HOT micros() { return ::micros(); } +void IRAM_ATTR HOT delayMicroseconds(uint32_t us) { delay_microseconds_safe(us); } +void arch_restart() { + while (true) { // NOLINT(clang-diagnostic-unreachable-code) + yield(); + } +} +void arch_init() { watchdog_enable(0x7fffff, false); } +void IRAM_ATTR HOT arch_feed_wdt() { watchdog_update(); } + +uint8_t progmem_read_byte(const uint8_t *addr) { + return pgm_read_byte(addr); // NOLINT +} +uint32_t IRAM_ATTR HOT arch_get_cpu_cycle_count() { return ulMainGetRunTimeCounterValue(); } +uint32_t arch_get_cpu_freq_hz() { return RP2040::f_cpu(); } + +} // namespace esphome + +#endif // USE_RP2040 diff --git a/esphome/components/rp2040/core.h b/esphome/components/rp2040/core.h new file mode 100644 index 0000000000..92fc4f824e --- /dev/null +++ b/esphome/components/rp2040/core.h @@ -0,0 +1,14 @@ +#pragma once + +#ifdef USE_RP2040 + +#include +#include + +extern "C" unsigned long ulMainGetRunTimeCounterValue(); + +namespace esphome { +namespace rp2040 {} // namespace rp2040 +} // namespace esphome + +#endif // USE_RP2040 diff --git a/esphome/components/rp2040/gpio.cpp b/esphome/components/rp2040/gpio.cpp new file mode 100644 index 0000000000..e32b93b5c2 --- /dev/null +++ b/esphome/components/rp2040/gpio.cpp @@ -0,0 +1,103 @@ +#ifdef USE_RP2040 + +#include "gpio.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace rp2040 { + +static const char *const TAG = "rp2040"; + +static int IRAM_ATTR flags_to_mode(gpio::Flags flags, uint8_t pin) { + if (flags == gpio::FLAG_INPUT) { // NOLINT(bugprone-branch-clone) + return INPUT; + } else if (flags == gpio::FLAG_OUTPUT) { + return OUTPUT; + } else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLUP)) { + return INPUT_PULLUP; + } else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLDOWN)) { + return INPUT_PULLDOWN; + // } else if (flags == (gpio::FLAG_OUTPUT | gpio::FLAG_OPEN_DRAIN)) { + // return OpenDrain; + } else { + return 0; + } +} + +struct ISRPinArg { + uint8_t pin; + bool inverted; +}; + +ISRInternalGPIOPin RP2040GPIOPin::to_isr() const { + auto *arg = new ISRPinArg{}; // NOLINT(cppcoreguidelines-owning-memory) + arg->pin = pin_; + arg->inverted = inverted_; + return ISRInternalGPIOPin((void *) arg); +} + +void RP2040GPIOPin::attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const { + PinStatus arduino_mode = LOW; + switch (type) { + case gpio::INTERRUPT_RISING_EDGE: + arduino_mode = inverted_ ? FALLING : RISING; + break; + case gpio::INTERRUPT_FALLING_EDGE: + arduino_mode = inverted_ ? RISING : FALLING; + break; + case gpio::INTERRUPT_ANY_EDGE: + arduino_mode = CHANGE; + break; + case gpio::INTERRUPT_LOW_LEVEL: + arduino_mode = inverted_ ? HIGH : LOW; + break; + case gpio::INTERRUPT_HIGH_LEVEL: + arduino_mode = inverted_ ? LOW : HIGH; + break; + } + + attachInterrupt(pin_, func, arduino_mode, arg); +} +void RP2040GPIOPin::pin_mode(gpio::Flags flags) { + pinMode(pin_, flags_to_mode(flags, pin_)); // NOLINT +} + +std::string RP2040GPIOPin::dump_summary() const { + char buffer[32]; + snprintf(buffer, sizeof(buffer), "GPIO%u", pin_); + return buffer; +} + +bool RP2040GPIOPin::digital_read() { + return bool(digitalRead(pin_)) != inverted_; // NOLINT +} +void RP2040GPIOPin::digital_write(bool value) { + digitalWrite(pin_, value != inverted_ ? 1 : 0); // NOLINT +} +void RP2040GPIOPin::detach_interrupt() const { detachInterrupt(pin_); } + +} // namespace rp2040 + +using namespace rp2040; + +bool IRAM_ATTR ISRInternalGPIOPin::digital_read() { + auto *arg = reinterpret_cast(arg_); + return bool(digitalRead(arg->pin)) != arg->inverted; // NOLINT +} +void IRAM_ATTR ISRInternalGPIOPin::digital_write(bool value) { + auto *arg = reinterpret_cast(arg_); + digitalWrite(arg->pin, value != arg->inverted ? 1 : 0); // NOLINT +} +void IRAM_ATTR ISRInternalGPIOPin::clear_interrupt() { + // TODO: implement + // auto *arg = reinterpret_cast(arg_); + // GPIO_REG_WRITE(GPIO_STATUS_W1TC_ADDRESS, 1UL << arg->pin); +} +void IRAM_ATTR ISRInternalGPIOPin::pin_mode(gpio::Flags flags) { + auto *arg = reinterpret_cast(arg_); + pinMode(arg->pin, flags_to_mode(flags, arg->pin)); // NOLINT +} + +} // namespace esphome + +#endif // USE_RP2040 diff --git a/esphome/components/rp2040/gpio.h b/esphome/components/rp2040/gpio.h new file mode 100644 index 0000000000..ef9500d5dd --- /dev/null +++ b/esphome/components/rp2040/gpio.h @@ -0,0 +1,38 @@ +#pragma once + +#ifdef USE_RP2040 + +#include +#include "esphome/core/hal.h" + +namespace esphome { +namespace rp2040 { + +class RP2040GPIOPin : public InternalGPIOPin { + public: + void set_pin(uint8_t pin) { pin_ = pin; } + void set_inverted(bool inverted) { inverted_ = inverted; } + void set_flags(gpio::Flags flags) { flags_ = flags; } + + void setup() override { pin_mode(flags_); } + void pin_mode(gpio::Flags flags) override; + bool digital_read() override; + void digital_write(bool value) override; + std::string dump_summary() const override; + void detach_interrupt() const override; + ISRInternalGPIOPin to_isr() const override; + uint8_t get_pin() const override { return pin_; } + bool is_inverted() const override { return inverted_; } + + protected: + void attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const override; + + uint8_t pin_; + bool inverted_; + gpio::Flags flags_; +}; + +} // namespace rp2040 +} // namespace esphome + +#endif // USE_RP2040 diff --git a/esphome/components/rp2040/gpio.py b/esphome/components/rp2040/gpio.py new file mode 100644 index 0000000000..4bc6dbee1c --- /dev/null +++ b/esphome/components/rp2040/gpio.py @@ -0,0 +1,91 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.const import ( + CONF_ID, + CONF_INPUT, + CONF_INVERTED, + CONF_MODE, + CONF_NUMBER, + CONF_OPEN_DRAIN, + CONF_OUTPUT, + CONF_PULLDOWN, + CONF_PULLUP, +) +from esphome.core import CORE +from esphome import pins + +from . import boards +from .const import KEY_BOARD, KEY_RP2040, rp2040_ns + +RP2040GPIOPin = rp2040_ns.class_("RP2040GPIOPin", cg.InternalGPIOPin) + + +def _lookup_pin(value): + board = CORE.data[KEY_RP2040][KEY_BOARD] + board_pins = boards.RP2040_BOARD_PINS.get(board, {}) + + while isinstance(board_pins, str): + board_pins = boards.RP2040_BOARD_PINS[board_pins] + + if value in board_pins: + return board_pins[value] + if value in boards.RP2040_BASE_PINS: + return boards.RP2040_BASE_PINS[value] + raise cv.Invalid(f"Cannot resolve pin name '{value}' for board {board}.") + + +def _translate_pin(value): + if isinstance(value, dict) or value is None: + raise cv.Invalid( + "This variable only supports pin numbers, not full pin schemas " + "(with inverted and mode)." + ) + if isinstance(value, int): + return value + try: + return int(value) + except ValueError: + pass + if value.startswith("GPIO"): + return cv.int_(value[len("GPIO") :].strip()) + return _lookup_pin(value) + + +def validate_gpio_pin(value): + value = _translate_pin(value) + if value < 0 or value > 29: + raise cv.Invalid(f"RP2040: Invalid pin number: {value}") + return value + + +CONF_ANALOG = "analog" + +RP2040_PIN_SCHEMA = cv.All( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(RP2040GPIOPin), + cv.Required(CONF_NUMBER): validate_gpio_pin, + cv.Optional(CONF_MODE, default={}): cv.Schema( + { + cv.Optional(CONF_ANALOG, default=False): cv.boolean, + cv.Optional(CONF_INPUT, default=False): cv.boolean, + cv.Optional(CONF_OUTPUT, default=False): cv.boolean, + cv.Optional(CONF_OPEN_DRAIN, default=False): cv.boolean, + cv.Optional(CONF_PULLUP, default=False): cv.boolean, + cv.Optional(CONF_PULLDOWN, default=False): cv.boolean, + } + ), + cv.Optional(CONF_INVERTED, default=False): cv.boolean, + } + ) +) + + +@pins.PIN_SCHEMA_REGISTRY.register("rp2040", RP2040_PIN_SCHEMA) +async def rp2040_pin_to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + num = config[CONF_NUMBER] + cg.add(var.set_pin(num)) + cg.add(var.set_inverted(config[CONF_INVERTED])) + cg.add(var.set_flags(pins.gpio_flags_expr(config[CONF_MODE]))) + return var diff --git a/esphome/components/rp2040/preferences.cpp b/esphome/components/rp2040/preferences.cpp new file mode 100644 index 0000000000..58ec57df2a --- /dev/null +++ b/esphome/components/rp2040/preferences.cpp @@ -0,0 +1,49 @@ +#ifdef USE_RP2040 + +#include "preferences.h" + +#include +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" +#include "esphome/core/preferences.h" + +namespace esphome { +namespace rp2040 { + +static const char *const TAG = "rp2040.preferences"; + +class RP2040PreferenceBackend : public ESPPreferenceBackend { + public: + bool save(const uint8_t *data, size_t len) override { return true; } + bool load(uint8_t *data, size_t len) override { return false; } +}; + +class RP2040Preferences : public ESPPreferences { + public: + ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash) override { + auto *pref = new RP2040PreferenceBackend(); // NOLINT(cppcoreguidelines-owning-memory) + return ESPPreferenceObject(pref); + } + + ESPPreferenceObject make_preference(size_t length, uint32_t type) override { + auto *pref = new RP2040PreferenceBackend(); // NOLINT(cppcoreguidelines-owning-memory) + return ESPPreferenceObject(pref); + } + + bool sync() override { return true; } + + bool reset() override { return true; } +}; + +void setup_preferences() { + auto *prefs = new RP2040Preferences(); // NOLINT(cppcoreguidelines-owning-memory) + global_preferences = prefs; +} + +} // namespace rp2040 + +ESPPreferences *global_preferences; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) + +} // namespace esphome + +#endif // USE_RP2040 diff --git a/esphome/components/rp2040/preferences.h b/esphome/components/rp2040/preferences.h new file mode 100644 index 0000000000..3c740a41c1 --- /dev/null +++ b/esphome/components/rp2040/preferences.h @@ -0,0 +1,13 @@ +#pragma once + +#ifdef USE_RP2040 + +namespace esphome { +namespace rp2040 { + +void setup_preferences(); + +} // namespace rp2040 +} // namespace esphome + +#endif // USE_RP2040 diff --git a/esphome/components/rp2040_pwm/__init__.py b/esphome/components/rp2040_pwm/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esphome/components/rp2040_pwm/output.py b/esphome/components/rp2040_pwm/output.py new file mode 100644 index 0000000000..8f2972d4a0 --- /dev/null +++ b/esphome/components/rp2040_pwm/output.py @@ -0,0 +1,55 @@ +from esphome import pins, automation +from esphome.components import output +import esphome.config_validation as cv +import esphome.codegen as cg +from esphome.const import ( + CONF_FREQUENCY, + CONF_ID, + CONF_PIN, +) + +CODEOWNERS = ["@jesserockz"] +DEPENDENCIES = ["rp2040"] + + +rp2040_pwm_ns = cg.esphome_ns.namespace("rp2040_pwm") +RP2040PWM = rp2040_pwm_ns.class_("RP2040PWM", output.FloatOutput, cg.Component) +SetFrequencyAction = rp2040_pwm_ns.class_("SetFrequencyAction", automation.Action) +validate_frequency = cv.All(cv.frequency, cv.Range(min=1.0e-6)) + +CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend( + { + cv.Required(CONF_ID): cv.declare_id(RP2040PWM), + cv.Required(CONF_PIN): pins.internal_gpio_output_pin_schema, + cv.Optional(CONF_FREQUENCY, default="1kHz"): validate_frequency, + } +).extend(cv.COMPONENT_SCHEMA) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await output.register_output(var, config) + + pin = await cg.gpio_pin_expression(config[CONF_PIN]) + cg.add(var.set_pin(pin)) + + cg.add(var.set_frequency(config[CONF_FREQUENCY])) + + +@automation.register_action( + "output.rp2040_pwm.set_frequency", + SetFrequencyAction, + cv.Schema( + { + cv.Required(CONF_ID): cv.use_id(RP2040PWM), + cv.Required(CONF_FREQUENCY): cv.templatable(validate_frequency), + } + ), +) +async def rp2040_set_frequency_to_code(config, action_id, template_arg, args): + paren = await cg.get_variable(config[CONF_ID]) + var = cg.new_Pvariable(action_id, template_arg, paren) + template_ = await cg.templatable(config[CONF_FREQUENCY], args, float) + cg.add(var.set_frequency(template_)) + return var diff --git a/esphome/components/rp2040_pwm/rp2040_pwm.cpp b/esphome/components/rp2040_pwm/rp2040_pwm.cpp new file mode 100644 index 0000000000..bf2a446edf --- /dev/null +++ b/esphome/components/rp2040_pwm/rp2040_pwm.cpp @@ -0,0 +1,45 @@ +#ifdef USE_RP2040 + +#include "rp2040_pwm.h" +#include "esphome/core/defines.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" +#include "esphome/core/macros.h" + +#include + +namespace esphome { +namespace rp2040_pwm { + +static const char *const TAG = "rp2040_pwm"; + +void RP2040PWM::setup() { + ESP_LOGCONFIG(TAG, "Setting up RP2040 PWM Output..."); + this->pin_->setup(); + this->pwm_ = new mbed::PwmOut((PinName) this->pin_->get_pin()); + this->turn_off(); +} +void RP2040PWM::dump_config() { + ESP_LOGCONFIG(TAG, "RP2040 PWM:"); + LOG_PIN(" Pin: ", this->pin_); + ESP_LOGCONFIG(TAG, " Frequency: %.1f Hz", this->frequency_); + LOG_FLOAT_OUTPUT(this); +} +void HOT RP2040PWM::write_state(float state) { + this->last_output_ = state; + + // Also check pin inversion + if (this->pin_->is_inverted()) { + state = 1.0f - state; + } + + auto total_time_us = static_cast(roundf(1e6f / this->frequency_)); + + this->pwm_->period_us(total_time_us); + this->pwm_->write(state); +} + +} // namespace rp2040_pwm +} // namespace esphome + +#endif diff --git a/esphome/components/rp2040_pwm/rp2040_pwm.h b/esphome/components/rp2040_pwm/rp2040_pwm.h new file mode 100644 index 0000000000..e348f131c2 --- /dev/null +++ b/esphome/components/rp2040_pwm/rp2040_pwm.h @@ -0,0 +1,57 @@ +#pragma once + +#ifdef USE_RP2040 + +#include "esphome/components/output/float_output.h" +#include "esphome/core/automation.h" +#include "esphome/core/component.h" +#include "esphome/core/hal.h" + +#include "drivers/PwmOut.h" + +namespace esphome { +namespace rp2040_pwm { + +class RP2040PWM : public output::FloatOutput, public Component { + public: + void set_pin(InternalGPIOPin *pin) { pin_ = pin; } + void set_frequency(float frequency) { this->frequency_ = frequency; } + /// Dynamically update frequency + void update_frequency(float frequency) override { + this->set_frequency(frequency); + this->write_state(this->last_output_); + } + + /// Initialize pin + void setup() override; + void dump_config() override; + /// HARDWARE setup_priority + float get_setup_priority() const override { return setup_priority::HARDWARE; } + + protected: + void write_state(float state) override; + + InternalGPIOPin *pin_; + mbed::PwmOut *pwm_; + float frequency_{1000.0}; + /// Cache last output level for dynamic frequency updating + float last_output_{0.0}; +}; + +template class SetFrequencyAction : public Action { + public: + SetFrequencyAction(RP2040PWM *parent) : parent_(parent) {} + TEMPLATABLE_VALUE(float, frequency); + + void play(Ts... x) { + float freq = this->frequency_.value(x...); + this->parent_->update_frequency(freq); + } + + RP2040PWM *parent_; +}; + +} // namespace rp2040_pwm +} // namespace esphome + +#endif // USE_RP2040 diff --git a/esphome/components/socket/__init__.py b/esphome/components/socket/__init__.py index 8e9502be6d..81203fdc31 100644 --- a/esphome/components/socket/__init__.py +++ b/esphome/components/socket/__init__.py @@ -13,6 +13,7 @@ CONFIG_SCHEMA = cv.Schema( CONF_IMPLEMENTATION, esp8266=IMPLEMENTATION_LWIP_TCP, esp32=IMPLEMENTATION_BSD_SOCKETS, + rp2040=IMPLEMENTATION_LWIP_TCP, ): cv.one_of( IMPLEMENTATION_LWIP_TCP, IMPLEMENTATION_BSD_SOCKETS, lower=True, space="_" ), diff --git a/esphome/components/socket/headers.h b/esphome/components/socket/headers.h index a383c0071d..1e79c8a1ab 100644 --- a/esphome/components/socket/headers.h +++ b/esphome/components/socket/headers.h @@ -91,7 +91,7 @@ struct iovec { size_t iov_len; }; -#ifdef USE_ESP8266 +#if defined(USE_ESP8266) || defined(USE_RP2040) // arduino-esp8266 declares a global vars called INADDR_NONE/ANY which are invalid with the define #ifdef INADDR_ANY #undef INADDR_ANY diff --git a/esphome/components/spi/__init__.py b/esphome/components/spi/__init__.py index c917fe1ad8..eeaf37985b 100644 --- a/esphome/components/spi/__init__.py +++ b/esphome/components/spi/__init__.py @@ -46,9 +46,7 @@ async def to_code(config): mosi = await cg.gpio_pin_expression(config[CONF_MOSI_PIN]) cg.add(var.set_mosi(mosi)) - if CORE.is_esp32 and CORE.using_arduino: - cg.add_library("SPI", None) - if CORE.is_esp8266: + if CORE.using_arduino: cg.add_library("SPI", None) diff --git a/esphome/components/spi/spi.h b/esphome/components/spi/spi.h index 7f0b0f481a..bc183be54b 100644 --- a/esphome/components/spi/spi.h +++ b/esphome/components/spi/spi.h @@ -105,7 +105,11 @@ class SPIComponent : public Component { void write_byte(uint8_t data) { #ifdef USE_SPI_ARDUINO_BACKEND if (this->hw_spi_ != nullptr) { +#ifdef USE_RP2040 + this->hw_spi_->transfer(data); +#else this->hw_spi_->write(data); +#endif return; } #endif // USE_SPI_ARDUINO_BACKEND @@ -116,7 +120,11 @@ class SPIComponent : public Component { void write_byte16(const uint16_t data) { #ifdef USE_SPI_ARDUINO_BACKEND if (this->hw_spi_ != nullptr) { +#ifdef USE_RP2040 + this->hw_spi_->transfer16(data); +#else this->hw_spi_->write16(data); +#endif return; } #endif // USE_SPI_ARDUINO_BACKEND @@ -130,7 +138,11 @@ class SPIComponent : public Component { #ifdef USE_SPI_ARDUINO_BACKEND if (this->hw_spi_ != nullptr) { for (size_t i = 0; i < length; i++) { +#ifdef USE_RP2040 + this->hw_spi_->transfer16(data[i]); +#else this->hw_spi_->write16(data[i]); +#endif } return; } @@ -145,7 +157,11 @@ class SPIComponent : public Component { #ifdef USE_SPI_ARDUINO_BACKEND if (this->hw_spi_ != nullptr) { auto *data_c = const_cast(data); +#ifdef USE_RP2040 + this->hw_spi_->transfer(data_c, length); +#else this->hw_spi_->writeBytes(data_c, length); +#endif return; } #endif // USE_SPI_ARDUINO_BACKEND @@ -178,7 +194,11 @@ class SPIComponent : public Component { if (this->miso_ != nullptr) { this->hw_spi_->transfer(data, length); } else { +#ifdef USE_RP2040 + this->hw_spi_->transfer(data, length); +#else this->hw_spi_->writeBytes(data, length); +#endif } return; } diff --git a/esphome/components/wifi/__init__.py b/esphome/components/wifi/__init__.py index 93ea6b92a4..f5684f06f7 100644 --- a/esphome/components/wifi/__init__.py +++ b/esphome/components/wifi/__init__.py @@ -267,7 +267,7 @@ CONFIG_SCHEMA = cv.All( CONF_REBOOT_TIMEOUT, default="15min" ): cv.positive_time_period_milliseconds, cv.SplitDefault( - CONF_POWER_SAVE_MODE, esp8266="none", esp32="light" + CONF_POWER_SAVE_MODE, esp8266="none", esp32="light", rp2040="light" ): cv.enum(WIFI_POWER_SAVE_MODES, upper=True), cv.Optional(CONF_FAST_CONNECT, default=False): cv.boolean, cv.Optional(CONF_USE_ADDRESS): cv.string_strict, @@ -386,6 +386,8 @@ async def to_code(config): cg.add_library("ESP8266WiFi", None) elif CORE.is_esp32 and CORE.using_arduino: cg.add_library("WiFi", None) + elif CORE.is_rp2040: + cg.add_library("WiFi", None) if CORE.is_esp32 and CORE.using_esp_idf: if config[CONF_ENABLE_BTM] or config[CONF_ENABLE_RRM]: diff --git a/esphome/components/wifi/wifi_component.cpp b/esphome/components/wifi/wifi_component.cpp index 0103106222..1ed9fd060f 100644 --- a/esphome/components/wifi/wifi_component.cpp +++ b/esphome/components/wifi/wifi_component.cpp @@ -710,6 +710,8 @@ int8_t WiFiScanResult::get_rssi() const { return this->rssi_; } bool WiFiScanResult::get_with_auth() const { return this->with_auth_; } bool WiFiScanResult::get_is_hidden() const { return this->is_hidden_; } +bool WiFiScanResult::operator==(const WiFiScanResult &rhs) const { return this->bssid_ == rhs.bssid_; } + WiFiComponent *global_wifi_component; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) } // namespace wifi diff --git a/esphome/components/wifi/wifi_component.h b/esphome/components/wifi/wifi_component.h index 6c5202ed7a..dba0d48724 100644 --- a/esphome/components/wifi/wifi_component.h +++ b/esphome/components/wifi/wifi_component.h @@ -24,6 +24,16 @@ extern "C" { #endif #endif +#ifdef USE_RP2040 +extern "C" { +#include "cyw43.h" +#include "cyw43_country.h" +#include "pico/cyw43_arch.h" +} + +#include +#endif + namespace esphome { namespace wifi { @@ -138,6 +148,8 @@ class WiFiScanResult { float get_priority() const { return priority_; } void set_priority(float priority) { priority_ = priority; } + bool operator==(const WiFiScanResult &rhs) const; + protected: bool matches_{false}; bssid_t bssid_; @@ -310,6 +322,11 @@ class WiFiComponent : public Component { void wifi_process_event_(IDFWiFiEvent *data); #endif +#ifdef USE_RP2040 + static int s_wifi_scan_result(void *env, const cyw43_ev_scan_result_t *result); + void wifi_scan_result(void *env, const cyw43_ev_scan_result_t *result); +#endif + std::string use_address_; std::vector sta_; std::vector sta_priorities_; diff --git a/esphome/components/wifi/wifi_component_pico_w.cpp b/esphome/components/wifi/wifi_component_pico_w.cpp new file mode 100644 index 0000000000..0ab143a9de --- /dev/null +++ b/esphome/components/wifi/wifi_component_pico_w.cpp @@ -0,0 +1,204 @@ + +#include "wifi_component.h" + +#ifdef USE_RP2040 + +#include "lwip/dns.h" +#include "lwip/err.h" +#include "lwip/netif.h" + +#include "esphome/core/application.h" +#include "esphome/core/hal.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" +#include "esphome/core/util.h" + +namespace esphome { +namespace wifi { + +static const char *const TAG = "wifi_pico_w"; + +bool WiFiComponent::wifi_mode_(optional sta, optional ap) { + if (sta.has_value()) { + if (sta.value()) { + cyw43_wifi_set_up(&cyw43_state, CYW43_ITF_STA, true, CYW43_COUNTRY_WORLDWIDE); + } + } + return true; +} + +bool WiFiComponent::wifi_apply_power_save_() { + uint32_t pm; + switch (this->power_save_) { + case WIFI_POWER_SAVE_NONE: + pm = CYW43_PERFORMANCE_PM; + break; + case WIFI_POWER_SAVE_LIGHT: + pm = CYW43_DEFAULT_PM; + break; + case WIFI_POWER_SAVE_HIGH: + pm = CYW43_AGGRESSIVE_PM; + break; + } + int ret = cyw43_wifi_pm(&cyw43_state, pm); + return ret == 0; +} + +// TODO: The driver doesnt seem to have an API for this +bool WiFiComponent::wifi_apply_output_power_(float output_power) { return true; } + +bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) { + if (!this->wifi_sta_ip_config_(ap.get_manual_ip())) + return false; + + auto ret = WiFi.begin(ap.get_ssid().c_str(), ap.get_password().c_str()); + if (ret != WL_CONNECTED) + return false; + + return true; +} + +bool WiFiComponent::wifi_sta_pre_setup_() { return this->wifi_mode_(true, {}); } + +bool WiFiComponent::wifi_sta_ip_config_(optional manual_ip) { + if (!manual_ip.has_value()) { + return true; + } + + IPAddress ip_address = IPAddress(manual_ip->static_ip); + IPAddress gateway = IPAddress(manual_ip->gateway); + IPAddress subnet = IPAddress(manual_ip->subnet); + + IPAddress dns = IPAddress(manual_ip->dns1); + + WiFi.config(ip_address, dns, gateway, subnet); + return true; +} + +bool WiFiComponent::wifi_apply_hostname_() { + WiFi.setHostname(App.get_name().c_str()); + return true; +} +const char *get_auth_mode_str(uint8_t mode) { + // TODO: + return "UNKNOWN"; +} +const char *get_disconnect_reason_str(uint8_t reason) { + // TODO: + return "UNKNOWN"; +} + +WiFiSTAConnectStatus WiFiComponent::wifi_sta_connect_status_() { + int status = cyw43_tcpip_link_status(&cyw43_state, CYW43_ITF_STA); + switch (status) { + case CYW43_LINK_JOIN: + case CYW43_LINK_NOIP: + return WiFiSTAConnectStatus::CONNECTING; + case CYW43_LINK_UP: + return WiFiSTAConnectStatus::CONNECTED; + case CYW43_LINK_FAIL: + case CYW43_LINK_BADAUTH: + return WiFiSTAConnectStatus::ERROR_CONNECT_FAILED; + case CYW43_LINK_NONET: + return WiFiSTAConnectStatus::ERROR_NETWORK_NOT_FOUND; + } + return WiFiSTAConnectStatus::IDLE; +} + +int WiFiComponent::s_wifi_scan_result(void *env, const cyw43_ev_scan_result_t *result) { + global_wifi_component->wifi_scan_result(env, result); + return 0; +} + +void WiFiComponent::wifi_scan_result(void *env, const cyw43_ev_scan_result_t *result) { + bssid_t bssid; + std::copy(result->bssid, result->bssid + 6, bssid.begin()); + std::string ssid(reinterpret_cast(result->ssid)); + WiFiScanResult res(bssid, ssid, result->channel, result->rssi, result->auth_mode != CYW43_AUTH_OPEN, ssid.empty()); + if (std::find(this->scan_result_.begin(), this->scan_result_.end(), res) == this->scan_result_.end()) { + this->scan_result_.push_back(res); + } +} + +bool WiFiComponent::wifi_scan_start_() { + this->scan_result_.clear(); + this->scan_done_ = false; + cyw43_wifi_scan_options_t scan_options = {0}; + int err = cyw43_wifi_scan(&cyw43_state, &scan_options, nullptr, &s_wifi_scan_result); + if (err) { + ESP_LOGV(TAG, "cyw43_wifi_scan failed!"); + } + return err == 0; + return true; +} + +bool WiFiComponent::wifi_ap_ip_config_(optional manual_ip) { + // TODO: + return false; +} + +bool WiFiComponent::wifi_start_ap_(const WiFiAP &ap) { + if (!this->wifi_mode_({}, true)) + return false; + + if (ap.get_channel().has_value()) { + cyw43_wifi_ap_set_channel(&cyw43_state, ap.get_channel().value()); + } + + const char *ssid = ap.get_ssid().c_str(); + + cyw43_wifi_ap_set_ssid(&cyw43_state, strlen(ssid), (const uint8_t *) ssid); + + if (!ap.get_password().empty()) { + const char *password = ap.get_password().c_str(); + cyw43_wifi_ap_set_password(&cyw43_state, strlen(password), (const uint8_t *) password); + cyw43_wifi_ap_set_auth(&cyw43_state, CYW43_AUTH_WPA2_MIXED_PSK); + } else { + cyw43_wifi_ap_set_auth(&cyw43_state, CYW43_AUTH_OPEN); + } + cyw43_wifi_set_up(&cyw43_state, CYW43_ITF_AP, true, CYW43_COUNTRY_WORLDWIDE); + + return true; +} +network::IPAddress WiFiComponent::wifi_soft_ap_ip() { return {WiFi.localIP()}; } + +bool WiFiComponent::wifi_disconnect_() { + int err = cyw43_wifi_leave(&cyw43_state, CYW43_ITF_STA); + return err == 0; +} +// NOTE: The driver does not provide an interface to get this +bssid_t WiFiComponent::wifi_bssid() { + bssid_t bssid{}; + uint8_t raw_bssid[6]; + WiFi.BSSID(raw_bssid); + for (size_t i = 0; i < bssid.size(); i++) + bssid[i] = raw_bssid[i]; + return bssid; +} +// NOTE: The driver does not provide an interface to get this +std::string WiFiComponent::wifi_ssid() { return WiFi.SSID(); } +// NOTE: The driver does not provide an interface to get this +int8_t WiFiComponent::wifi_rssi() { return WiFi.RSSI(); } +// NOTE: The driver does not provide an interface to get this +int32_t WiFiComponent::wifi_channel_() { return 0; } +network::IPAddress WiFiComponent::wifi_sta_ip() { return {WiFi.localIP()}; } +network::IPAddress WiFiComponent::wifi_subnet_mask_() { return {WiFi.subnetMask()}; } +network::IPAddress WiFiComponent::wifi_gateway_ip_() { return {WiFi.gatewayIP()}; } +network::IPAddress WiFiComponent::wifi_dns_ip_(int num) { + const ip_addr_t *dns_ip = dns_getserver(num); + return {dns_ip->addr}; +} + +void WiFiComponent::wifi_loop_() { + if (this->state_ == WIFI_COMPONENT_STATE_STA_SCANNING && !cyw43_wifi_scan_active(&cyw43_state)) { + this->scan_done_ = true; + ESP_LOGV(TAG, "Scan done!"); + } +} + +void WiFiComponent::wifi_pre_setup_() {} + +} // namespace wifi +} // namespace esphome + +#endif diff --git a/esphome/config_validation.py b/esphome/config_validation.py index 09436c1fbf..19d4747575 100644 --- a/esphome/config_validation.py +++ b/esphome/config_validation.py @@ -1440,6 +1440,7 @@ class SplitDefault(Optional): esp32=vol.UNDEFINED, esp32_arduino=vol.UNDEFINED, esp32_idf=vol.UNDEFINED, + rp2040=vol.UNDEFINED, ): super().__init__(key) self._esp8266_default = vol.default_factory(esp8266) @@ -1449,6 +1450,7 @@ class SplitDefault(Optional): self._esp32_idf_default = vol.default_factory( esp32_idf if esp32 is vol.UNDEFINED else esp32 ) + self._rp2040_default = vol.default_factory(rp2040) @property def default(self): @@ -1458,6 +1460,8 @@ class SplitDefault(Optional): return self._esp32_arduino_default if CORE.is_esp32 and CORE.using_esp_idf: return self._esp32_idf_default + if CORE.is_rp2040: + return self._rp2040_default raise NotImplementedError @default.setter diff --git a/esphome/const.py b/esphome/const.py index f2d31bd21f..f8551f81ce 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -6,8 +6,9 @@ ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" PLATFORM_ESP32 = "esp32" PLATFORM_ESP8266 = "esp8266" +PLATFORM_RP2040 = "rp2040" -TARGET_PLATFORMS = [PLATFORM_ESP32, PLATFORM_ESP8266] +TARGET_PLATFORMS = [PLATFORM_ESP32, PLATFORM_ESP8266, PLATFORM_RP2040] SOURCE_FILE_EXTENSIONS = {".cpp", ".hpp", ".h", ".c", ".tcc", ".ino"} HEADER_FILE_EXTENSIONS = {".h", ".hpp", ".tcc"} diff --git a/esphome/core/__init__.py b/esphome/core/__init__.py index a422cd9507..ef44b31fa3 100644 --- a/esphome/core/__init__.py +++ b/esphome/core/__init__.py @@ -594,6 +594,10 @@ class EsphomeCore: def is_esp32(self): return self.target_platform == "esp32" + @property + def is_rp2040(self): + return self.target_platform == "rp2040" + @property def target_framework(self): return self.data[KEY_CORE][KEY_TARGET_FRAMEWORK] diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index 3aca944a36..79e706cf0d 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -21,6 +21,8 @@ #include "esp_system.h" #include #include +#elif defined(USE_RP2040) && defined(USE_WIFI) +#include #endif #ifdef USE_ESP32_IGNORE_EFUSE_MAC_CRC @@ -91,6 +93,8 @@ uint32_t random_uint32() { return esp_random(); #elif defined(USE_ESP8266) return os_random(); +#elif defined(USE_RP2040) + return ((uint32_t) rand()) << 16 + ((uint32_t) rand()); #else #error "No random source available for this configuration." #endif @@ -102,6 +106,8 @@ bool random_bytes(uint8_t *data, size_t len) { return true; #elif defined(USE_ESP8266) return os_get_random(data, len) == 0; +#elif defined(USE_RP2040) + return false; #else #error "No random source available for this configuration." #endif @@ -409,6 +415,8 @@ void get_mac_address_raw(uint8_t *mac) { #endif #elif defined(USE_ESP8266) wifi_get_macaddr(STATION_IF, mac); +#elif defined(USE_RP2040) && defined(USE_WIFI) + WiFi.macAddress(mac); #endif } std::string get_mac_address() { diff --git a/esphome/dashboard/dashboard.py b/esphome/dashboard/dashboard.py index 0367101023..d551c9da4b 100644 --- a/esphome/dashboard/dashboard.py +++ b/esphome/dashboard/dashboard.py @@ -428,21 +428,27 @@ class DownloadBinaryRequestHandler(BaseHandler): def get(self, configuration=None): type = self.get_argument("type", "firmware.bin") - if type == "firmware.bin": - storage_path = ext_storage_path(settings.config_dir, configuration) - storage_json = StorageJSON.load(storage_path) - if storage_json is None: - self.send_error(404) - return + storage_path = ext_storage_path(settings.config_dir, configuration) + storage_json = StorageJSON.load(storage_path) + if storage_json is None: + self.send_error(404) + return + + if storage_json.target_platform.lower() == const.PLATFORM_RP2040: + filename = f"{storage_json.name}.uf2" + path = storage_json.firmware_bin_path.replace( + "firmware.bin", "firmware.uf2" + ) + + elif storage_json.target_platform.lower() == const.PLATFORM_ESP8266: + filename = f"{storage_json.name}.bin" + path = storage_json.firmware_bin_path + + elif type == "firmware.bin": filename = f"{storage_json.name}.bin" path = storage_json.firmware_bin_path elif type == "firmware-factory.bin": - storage_path = ext_storage_path(settings.config_dir, configuration) - storage_json = StorageJSON.load(storage_path) - if storage_json is None: - self.send_error(404) - return filename = f"{storage_json.name}-factory.bin" path = storage_json.firmware_bin_path.replace( "firmware.bin", "firmware-factory.bin" diff --git a/esphome/storage_json.py b/esphome/storage_json.py index af71d4583c..1cc2180747 100644 --- a/esphome/storage_json.py +++ b/esphome/storage_json.py @@ -119,16 +119,16 @@ class StorageJSON: ) @staticmethod - def from_wizard(name: str, address: str, esp_platform: str) -> "StorageJSON": + def from_wizard(name: str, address: str, platform: str) -> "StorageJSON": return StorageJSON( storage_version=1, name=name, comment=None, - esphome_version=const.__version__, + esphome_version=None, src_version=1, address=address, web_port=None, - target_platform=esp_platform, + target_platform=platform, build_path=None, firmware_bin_path=None, loaded_integrations=[], diff --git a/esphome/wizard.py b/esphome/wizard.py index 602f4ecf04..6273eec25d 100644 --- a/esphome/wizard.py +++ b/esphome/wizard.py @@ -81,11 +81,20 @@ esp32: type: esp-idf """ +RP2040_CONFIG = """ +rp2040: + board: {board} + framework: + # Required until https://github.com/platformio/platform-raspberrypi/pull/36 is merged + platform_version: https://github.com/maxgerhardt/platform-raspberrypi.git +""" + HARDWARE_BASE_CONFIGS = { "ESP8266": ESP8266_CONFIG, "ESP32": ESP32_CONFIG, "ESP32S2": ESP32S2_CONFIG, "ESP32C3": ESP32C3_CONFIG, + "RP2040": RP2040_CONFIG, } @@ -164,6 +173,7 @@ captive_portal: def wizard_write(path, **kwargs): from esphome.components.esp8266 import boards as esp8266_boards + from esphome.components.rp2040 import boards as rp2040_boards name = kwargs["name"] board = kwargs["board"] @@ -173,9 +183,13 @@ def wizard_write(path, **kwargs): kwargs[key] = sanitize_double_quotes(kwargs[key]) if "platform" not in kwargs: - kwargs["platform"] = ( - "ESP8266" if board in esp8266_boards.ESP8266_BOARD_PINS else "ESP32" - ) + if board in esp8266_boards.ESP8266_BOARD_PINS: + platform = "ESP8266" + elif board in rp2040_boards.RP2040_BOARD_PINS: + platform = "RP2040" + else: + platform = "ESP32" + kwargs["platform"] = platform hardware = kwargs["platform"] write_file(path, wizard_file(**kwargs)) diff --git a/platformio.ini b/platformio.ini index 9b943242f7..86617586ae 100644 --- a/platformio.ini +++ b/platformio.ini @@ -146,6 +146,24 @@ build_flags = -DUSE_ESP32_FRAMEWORK_ESP_IDF extra_scripts = post:esphome/components/esp32/post_build.py.script +; These are common settings for the RP2040 using Arduino. +[common:rp2040-arduino] +extends = common:arduino +board_build.core = earlephilhower +board_build.filesystem_size = 0.5m + +platform = https://github.com/maxgerhardt/platform-raspberrypi.git +platform_packages = + earlephilhower/framework-arduinopico @ ~1.20400.0 + +framework = arduino +lib_deps = + ${common:arduino.lib_deps} +build_flags = + ${common:arduino.build_flags} + -DUSE_RP2040 + -DUSE_RP2040_FRAMEWORK_ARDUINO + ; All the actual environments are defined below. [env:esp8266-arduino] extends = common:esp8266-arduino @@ -222,3 +240,10 @@ board_build.esp-idf.sdkconfig_path = .temp/sdkconfig-esp32s2-idf-tidy build_flags = ${common:esp32-idf.build_flags} ${flags:clangtidy.build_flags} + +[env:rp2040-pico-arduino] +extends = common:rp2040-arduino +board = pico +build_flags = + ${common:rp2040-arduino.build_flags} + ${flags:runtime.build_flags} diff --git a/script/ci-custom.py b/script/ci-custom.py index 6f69b55d2c..f95039576b 100755 --- a/script/ci-custom.py +++ b/script/ci-custom.py @@ -534,6 +534,7 @@ def lint_relative_py_import(fname): "esphome/components/socket/headers.h", "esphome/components/esp32/core.cpp", "esphome/components/esp8266/core.cpp", + "esphome/components/rp2040/core.cpp", ], ) def lint_namespace(fname, content): diff --git a/tests/README.md b/tests/README.md index 546025526f..ed78b3e7d1 100644 --- a/tests/README.md +++ b/tests/README.md @@ -24,3 +24,4 @@ Current test_.yaml file contents. | test3.yaml | ESP8266 | wifi | N/A | test4.yaml | ESP32 | ethernet | None | test5.yaml | ESP32 | wifi | ble_server +| test6.yaml | RP2040 | wifi | N/A diff --git a/tests/dummy_main.cpp b/tests/dummy_main.cpp index d956387665..8cc1838d94 100644 --- a/tests/dummy_main.cpp +++ b/tests/dummy_main.cpp @@ -13,8 +13,9 @@ using namespace esphome; void setup() { App.pre_setup("livingroom", __DATE__ ", " __TIME__, false); - auto *log = new logger::Logger(115200, 512, logger::UART_SELECTION_UART0); // NOLINT + auto *log = new logger::Logger(115200, 512); // NOLINT log->pre_setup(); + log->set_uart_selection(logger::UART_SELECTION_UART0); App.register_component(log); auto *wifi = new wifi::WiFiComponent(); // NOLINT diff --git a/tests/test6.yaml b/tests/test6.yaml new file mode 100644 index 0000000000..264773331e --- /dev/null +++ b/tests/test6.yaml @@ -0,0 +1,39 @@ +--- +esphome: + name: test6 + project: + name: esphome.test6_project + version: "1.0.0" + +rp2040: + board: rpipicow + framework: + # Waiting for https://github.com/platformio/platform-raspberrypi/pull/36 + platform_version: https://github.com/maxgerhardt/platform-raspberrypi.git + + +wifi: + networks: + - ssid: "MySSID" + password: "password1" + +api: + +ota: + +logger: + +binary_sensor: + - platform: gpio + pin: GPIO5 + id: pin_5_button + +output: + - platform: gpio + pin: GPIO4 + id: pin_4 + +switch: + - platform: output + output: pin_4 + id: pin_4_switch