Initial Support for RP2040 platform (#3284)

Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
This commit is contained in:
Jesse Hills 2022-10-20 16:50:39 +13:00 committed by GitHub
parent e87edcc77a
commit 6153bcc6ad
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
49 changed files with 1270 additions and 61 deletions

View file

@ -48,6 +48,10 @@ jobs:
file: tests/test5.yaml file: tests/test5.yaml
name: Test tests/test5.yaml name: Test tests/test5.yaml
pio_cache_key: test5 pio_cache_key: test5
- id: test
file: tests/test6.yaml
name: Test tests/test6.yaml
pio_cache_key: test6
- id: pytest - id: pytest
name: Run pytest name: Run pytest
- id: clang-format - id: clang-format

View file

@ -184,6 +184,8 @@ esphome/components/rc522_spi/* @glmnet
esphome/components/restart/* @esphome/core esphome/components/restart/* @esphome/core
esphome/components/rf_bridge/* @jesserockz esphome/components/rf_bridge/* @jesserockz
esphome/components/rgbct/* @jesserockz esphome/components/rgbct/* @jesserockz
esphome/components/rp2040/* @jesserockz
esphome/components/rp2040_pwm/* @jesserockz
esphome/components/rtttl/* @glmnet esphome/components/rtttl/* @glmnet
esphome/components/safe_mode/* @jsuanet @paulmonigatti esphome/components/safe_mode/* @jsuanet @paulmonigatti
esphome/components/scd4x/* @martgras @sjtrny esphome/components/scd4x/* @martgras @sjtrny

View file

@ -4,6 +4,7 @@ import logging
import os import os
import re import re
import sys import sys
import time
from datetime import datetime from datetime import datetime
from esphome import const, writer, yaml_util from esphome import const, writer, yaml_util
@ -22,6 +23,9 @@ from esphome.const import (
CONF_ESPHOME, CONF_ESPHOME,
CONF_PLATFORMIO_OPTIONS, CONF_PLATFORMIO_OPTIONS,
CONF_SUBSTITUTIONS, CONF_SUBSTITUTIONS,
PLATFORM_ESP32,
PLATFORM_ESP8266,
PLATFORM_RP2040,
SECRETS_FILES, SECRETS_FILES,
) )
from esphome.core import CORE, EsphomeError, coroutine from esphome.core import CORE, EsphomeError, coroutine
@ -101,11 +105,11 @@ def run_miniterm(config, port):
if CONF_LOGGER not in config: if CONF_LOGGER not in config:
_LOGGER.info("Logger is not enabled. Not starting UART logs.") _LOGGER.info("Logger is not enabled. Not starting UART logs.")
return return 1
baud_rate = config["logger"][CONF_BAUD_RATE] baud_rate = config["logger"][CONF_BAUD_RATE]
if baud_rate == 0: if baud_rate == 0:
_LOGGER.info("UART logging is disabled (baud_rate=0). Not starting UART logs.") _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) _LOGGER.info("Starting log output from %s with baud rate %s", port, baud_rate)
backtrace_state = False backtrace_state = False
@ -119,25 +123,34 @@ def run_miniterm(config, port):
ser.dtr = False ser.dtr = False
ser.rts = False ser.rts = False
with ser: tries = 0
while True: while tries < 5:
try: try:
raw = ser.readline() with ser:
except serial.SerialException: while True:
_LOGGER.error("Serial port closed!") try:
return raw = ser.readline()
line = ( except serial.SerialException:
raw.replace(b"\r", b"") _LOGGER.error("Serial port closed!")
.replace(b"\n", b"") return 0
.decode("utf8", "backslashreplace") line = (
) raw.replace(b"\r", b"")
time = datetime.now().time().strftime("[%H:%M:%S]") .replace(b"\n", b"")
message = time + line .decode("utf8", "backslashreplace")
safe_print(message) )
time_str = datetime.now().time().strftime("[%H:%M:%S]")
message = time_str + line
safe_print(message)
backtrace_state = platformio_api.process_stacktrace( backtrace_state = platformio_api.process_stacktrace(
config, line, backtrace_state=backtrace_state 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): def wrap_to_code(name, comp):
@ -258,9 +271,21 @@ def upload_using_esptool(config, port):
def upload_program(config, args, host): def upload_program(config, args, host):
# if upload is to a serial port use platformio, otherwise assume ota
if get_port_type(host) == "SERIAL": 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 from esphome import espota2
@ -280,8 +305,7 @@ def show_logs(config, args, port):
if "logger" not in config: if "logger" not in config:
raise EsphomeError("Logger is not configured!") raise EsphomeError("Logger is not configured!")
if get_port_type(port) == "SERIAL": if get_port_type(port) == "SERIAL":
run_miniterm(config, port) return run_miniterm(config, port)
return 0
if get_port_type(port) == "NETWORK" and "api" in config: if get_port_type(port) == "NETWORK" and "api" in config:
from esphome.components.api.client import run_logs from esphome.components.api.client import run_logs

View file

@ -77,6 +77,8 @@ UART_SELECTION_ESP8266 = [UART0, UART0_SWAP, UART1]
ESP_IDF_UARTS = [USB_CDC, USB_SERIAL_JTAG] ESP_IDF_UARTS = [USB_CDC, USB_SERIAL_JTAG]
UART_SELECTION_RP2040 = [UART0, UART1]
HARDWARE_UART_TO_UART_SELECTION = { HARDWARE_UART_TO_UART_SELECTION = {
UART0: logger_ns.UART_SELECTION_UART0, UART0: logger_ns.UART_SELECTION_UART0,
UART0_SWAP: logger_ns.UART_SELECTION_UART0_SWAP, 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) return cv.one_of(*UART_SELECTION_ESP32[variant], upper=True)(value)
if CORE.is_esp8266: if CORE.is_esp8266:
return cv.one_of(*UART_SELECTION_ESP8266, upper=True)(value) 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 raise NotImplementedError
@ -158,12 +162,13 @@ CONFIG_SCHEMA = cv.All(
@coroutine_with_priority(90.0) @coroutine_with_priority(90.0)
async def to_code(config): async def to_code(config):
baud_rate = config[CONF_BAUD_RATE] baud_rate = config[CONF_BAUD_RATE]
rhs = Logger.new( log = cg.new_Pvariable(config[CONF_ID], baud_rate, config[CONF_TX_BUFFER_SIZE])
baud_rate, if CONF_HARDWARE_UART in config:
config[CONF_TX_BUFFER_SIZE], cg.add(
HARDWARE_UART_TO_UART_SELECTION[config[CONF_HARDWARE_UART]], log.set_uart_selection(
) HARDWARE_UART_TO_UART_SELECTION[config[CONF_HARDWARE_UART]]
log = cg.Pvariable(config[CONF_ID], rhs) )
)
cg.add(log.pre_setup()) cg.add(log.pre_setup())
for tag, level in config[CONF_LOGS].items(): for tag, level in config[CONF_LOGS].items():

View file

@ -148,8 +148,7 @@ void HOT Logger::log_message_(int level, const char *tag, int offset) {
this->log_callback_.call(level, tag, msg); this->log_callback_.call(level, tag, msg);
} }
Logger::Logger(uint32_t baud_rate, size_t tx_buffer_size, UARTSelection uart) Logger::Logger(uint32_t baud_rate, size_t tx_buffer_size) : baud_rate_(baud_rate), tx_buffer_size_(tx_buffer_size) {
: baud_rate_(baud_rate), tx_buffer_size_(tx_buffer_size), uart_(uart) {
// add 1 to buffer size for null terminator // add 1 to buffer size for null terminator
this->tx_buffer_ = new char[this->tx_buffer_size_ + 1]; // NOLINT this->tx_buffer_ = new char[this->tx_buffer_size_ + 1]; // NOLINT
} }
@ -270,6 +269,9 @@ const char *const UART_SELECTIONS[] = {
#endif // USE_ESP32 #endif // USE_ESP32
#ifdef USE_ESP8266 #ifdef USE_ESP8266
const char *const UART_SELECTIONS[] = {"UART0", "UART1", "UART0_SWAP"}; const char *const UART_SELECTIONS[] = {"UART0", "UART1", "UART0_SWAP"};
#endif
#ifdef USE_RP2040
const char *const UART_SELECTIONS[] = {"UART0", "UART1"};
#endif // USE_ESP8266 #endif // USE_ESP8266
void Logger::dump_config() { void Logger::dump_config() {
ESP_LOGCONFIG(TAG, "Logger:"); ESP_LOGCONFIG(TAG, "Logger:");

View file

@ -7,8 +7,15 @@
#include <cstdarg> #include <cstdarg>
#ifdef USE_ARDUINO #ifdef USE_ARDUINO
#if defined(USE_ESP8266) || defined(USE_ESP32)
#include <HardwareSerial.h> #include <HardwareSerial.h>
#endif #endif // USE_ESP8266 || USE_ESP32
#ifdef USE_RP2040
#include <HardwareSerial.h>
#include <SerialUSB.h>
#endif // USE_RP2040
#endif // USE_ARDUINO
#ifdef USE_ESP_IDF #ifdef USE_ESP_IDF
#include <driver/uart.h> #include <driver/uart.h>
#endif #endif
@ -44,7 +51,7 @@ enum UARTSelection {
class Logger : public Component { class Logger : public Component {
public: 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. /// Manually set the baud rate for serial, set to 0 to disable.
void set_baud_rate(uint32_t baud_rate); 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_; } uart_port_t get_uart_num() const { return uart_num_; }
#endif #endif
void set_uart_selection(UARTSelection uart_selection) { uart_ = uart_selection; }
/// Get the UART used by the logger. /// Get the UART used by the logger.
UARTSelection get_uart() const; UARTSelection get_uart() const;

View file

@ -6,7 +6,7 @@
namespace esphome { namespace esphome {
namespace md5 { namespace md5 {
#ifdef USE_ARDUINO #if defined(USE_ARDUINO) && !defined(USE_RP2040)
void MD5Digest::init() { void MD5Digest::init() {
memset(this->digest_, 0, 16); memset(this->digest_, 0, 16);
MD5Init(&this->ctx_); 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::add(const uint8_t *data, size_t len) { MD5Update(&this->ctx_, data, len); }
void MD5Digest::calculate() { MD5Final(this->digest_, &this->ctx_); } void MD5Digest::calculate() { MD5Final(this->digest_, &this->ctx_); }
#endif // USE_ARDUINO #endif // USE_ARDUINO && !USE_RP2040
#ifdef USE_ESP_IDF #ifdef USE_ESP_IDF
void MD5Digest::init() { 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_); } void MD5Digest::calculate() { esp_rom_md5_final(this->digest_, &this->ctx_); }
#endif // USE_ESP_IDF #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_bytes(uint8_t *output) { memcpy(output, this->digest_, 16); }
void MD5Digest::get_hex(char *output) { void MD5Digest::get_hex(char *output) {

View file

@ -17,6 +17,11 @@
#define MD5_CTX_TYPE md5_context_t #define MD5_CTX_TYPE md5_context_t
#endif #endif
#ifdef USE_RP2040
#include <MD5Builder.h>
#define MD5_CTX_TYPE br_md5_context
#endif
namespace esphome { namespace esphome {
namespace md5 { namespace md5 {

View file

@ -38,6 +38,9 @@ void MDNSComponent::compile_records_() {
#endif #endif
#ifdef USE_ESP32 #ifdef USE_ESP32
platform = "ESP32"; platform = "ESP32";
#endif
#ifdef USE_RP2040
platform = "RP2040";
#endif #endif
if (platform != nullptr) { if (platform != nullptr) {
service.txt_records.push_back({"platform", platform}); service.txt_records.push_back({"platform", platform});

View file

@ -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

View file

@ -39,7 +39,7 @@ CONFIG_SCHEMA = cv.Schema(
{ {
cv.GenerateID(): cv.declare_id(OTAComponent), cv.GenerateID(): cv.declare_id(OTAComponent),
cv.Optional(CONF_SAFE_MODE, default=True): cv.boolean, 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_PASSWORD): cv.string,
cv.Optional( cv.Optional(
CONF_REBOOT_TIMEOUT, default="5min" CONF_REBOOT_TIMEOUT, default="5min"
@ -94,6 +94,9 @@ async def to_code(config):
if CORE.is_esp32 and CORE.using_arduino: if CORE.is_esp32 and CORE.using_arduino:
cg.add_library("Update", None) cg.add_library("Update", None)
if CORE.is_rp2040 and CORE.using_arduino:
cg.add_library("Updater", None)
use_state_callback = False use_state_callback = False
for conf in config.get(CONF_ON_STATE_CHANGE, []): for conf in config.get(CONF_ON_STATE_CHANGE, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)

View file

@ -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 <Updater.h>
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

View file

@ -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

View file

@ -2,6 +2,7 @@
#include "ota_backend.h" #include "ota_backend.h"
#include "ota_backend_arduino_esp32.h" #include "ota_backend_arduino_esp32.h"
#include "ota_backend_arduino_esp8266.h" #include "ota_backend_arduino_esp8266.h"
#include "ota_backend_arduino_rp2040.h"
#include "ota_backend_esp_idf.h" #include "ota_backend_esp_idf.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
@ -35,6 +36,9 @@ std::unique_ptr<OTABackend> make_ota_backend() {
#ifdef USE_ESP_IDF #ifdef USE_ESP_IDF
return make_unique<IDFOTABackend>(); return make_unique<IDFOTABackend>();
#endif // USE_ESP_IDF #endif // USE_ESP_IDF
#ifdef USE_RP2040
return make_unique<ArduinoRP2040OTABackend>();
#endif // USE_RP2040
} }
OTAComponent::OTAComponent() { global_ota_component = this; } OTAComponent::OTAComponent() { global_ota_component = this; }

View file

@ -33,6 +33,7 @@ enum OTAResponseTypes {
OTA_RESPONSE_ERROR_ESP32_NOT_ENOUGH_SPACE = 137, OTA_RESPONSE_ERROR_ESP32_NOT_ENOUGH_SPACE = 137,
OTA_RESPONSE_ERROR_NO_UPDATE_PARTITION = 138, OTA_RESPONSE_ERROR_NO_UPDATE_PARTITION = 138,
OTA_RESPONSE_ERROR_MD5_MISMATCH = 139, OTA_RESPONSE_ERROR_MD5_MISMATCH = 139,
OTA_RESPONSE_ERROR_RP2040_NOT_ENOUGH_SPACE = 140,
OTA_RESPONSE_ERROR_UNKNOWN = 255, OTA_RESPONSE_ERROR_UNKNOWN = 255,
}; };

View file

@ -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})"),
)

View file

@ -0,0 +1,7 @@
RP2040_BASE_PINS = {}
RP2040_BOARD_PINS = {
"pico": {"LED": 25},
"rpipico": "pico",
"rpipicow": {},
}

View file

@ -0,0 +1,6 @@
import esphome.codegen as cg
KEY_BOARD = "board"
KEY_RP2040 = "rp2040"
rp2040_ns = cg.esphome_ns.namespace("rp2040")

View file

@ -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

View file

@ -0,0 +1,14 @@
#pragma once
#ifdef USE_RP2040
#include <Arduino.h>
#include <pico.h>
extern "C" unsigned long ulMainGetRunTimeCounterValue();
namespace esphome {
namespace rp2040 {} // namespace rp2040
} // namespace esphome
#endif // USE_RP2040

View file

@ -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<ISRPinArg *>(arg_);
return bool(digitalRead(arg->pin)) != arg->inverted; // NOLINT
}
void IRAM_ATTR ISRInternalGPIOPin::digital_write(bool value) {
auto *arg = reinterpret_cast<ISRPinArg *>(arg_);
digitalWrite(arg->pin, value != arg->inverted ? 1 : 0); // NOLINT
}
void IRAM_ATTR ISRInternalGPIOPin::clear_interrupt() {
// TODO: implement
// auto *arg = reinterpret_cast<ISRPinArg *>(arg_);
// GPIO_REG_WRITE(GPIO_STATUS_W1TC_ADDRESS, 1UL << arg->pin);
}
void IRAM_ATTR ISRInternalGPIOPin::pin_mode(gpio::Flags flags) {
auto *arg = reinterpret_cast<ISRPinArg *>(arg_);
pinMode(arg->pin, flags_to_mode(flags, arg->pin)); // NOLINT
}
} // namespace esphome
#endif // USE_RP2040

View file

@ -0,0 +1,38 @@
#pragma once
#ifdef USE_RP2040
#include <Arduino.h>
#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

View file

@ -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

View file

@ -0,0 +1,49 @@
#ifdef USE_RP2040
#include "preferences.h"
#include <cstring>
#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

View file

@ -0,0 +1,13 @@
#pragma once
#ifdef USE_RP2040
namespace esphome {
namespace rp2040 {
void setup_preferences();
} // namespace rp2040
} // namespace esphome
#endif // USE_RP2040

View file

@ -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

View file

@ -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 <PinNames.h>
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<uint32_t>(roundf(1e6f / this->frequency_));
this->pwm_->period_us(total_time_us);
this->pwm_->write(state);
}
} // namespace rp2040_pwm
} // namespace esphome
#endif

View file

@ -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<typename... Ts> class SetFrequencyAction : public Action<Ts...> {
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

View file

@ -13,6 +13,7 @@ CONFIG_SCHEMA = cv.Schema(
CONF_IMPLEMENTATION, CONF_IMPLEMENTATION,
esp8266=IMPLEMENTATION_LWIP_TCP, esp8266=IMPLEMENTATION_LWIP_TCP,
esp32=IMPLEMENTATION_BSD_SOCKETS, esp32=IMPLEMENTATION_BSD_SOCKETS,
rp2040=IMPLEMENTATION_LWIP_TCP,
): cv.one_of( ): cv.one_of(
IMPLEMENTATION_LWIP_TCP, IMPLEMENTATION_BSD_SOCKETS, lower=True, space="_" IMPLEMENTATION_LWIP_TCP, IMPLEMENTATION_BSD_SOCKETS, lower=True, space="_"
), ),

View file

@ -91,7 +91,7 @@ struct iovec {
size_t iov_len; 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 // arduino-esp8266 declares a global vars called INADDR_NONE/ANY which are invalid with the define
#ifdef INADDR_ANY #ifdef INADDR_ANY
#undef INADDR_ANY #undef INADDR_ANY

View file

@ -46,9 +46,7 @@ async def to_code(config):
mosi = await cg.gpio_pin_expression(config[CONF_MOSI_PIN]) mosi = await cg.gpio_pin_expression(config[CONF_MOSI_PIN])
cg.add(var.set_mosi(mosi)) cg.add(var.set_mosi(mosi))
if CORE.is_esp32 and CORE.using_arduino: if CORE.using_arduino:
cg.add_library("SPI", None)
if CORE.is_esp8266:
cg.add_library("SPI", None) cg.add_library("SPI", None)

View file

@ -105,7 +105,11 @@ class SPIComponent : public Component {
void write_byte(uint8_t data) { void write_byte(uint8_t data) {
#ifdef USE_SPI_ARDUINO_BACKEND #ifdef USE_SPI_ARDUINO_BACKEND
if (this->hw_spi_ != nullptr) { if (this->hw_spi_ != nullptr) {
#ifdef USE_RP2040
this->hw_spi_->transfer(data);
#else
this->hw_spi_->write(data); this->hw_spi_->write(data);
#endif
return; return;
} }
#endif // USE_SPI_ARDUINO_BACKEND #endif // USE_SPI_ARDUINO_BACKEND
@ -116,7 +120,11 @@ class SPIComponent : public Component {
void write_byte16(const uint16_t data) { void write_byte16(const uint16_t data) {
#ifdef USE_SPI_ARDUINO_BACKEND #ifdef USE_SPI_ARDUINO_BACKEND
if (this->hw_spi_ != nullptr) { if (this->hw_spi_ != nullptr) {
#ifdef USE_RP2040
this->hw_spi_->transfer16(data);
#else
this->hw_spi_->write16(data); this->hw_spi_->write16(data);
#endif
return; return;
} }
#endif // USE_SPI_ARDUINO_BACKEND #endif // USE_SPI_ARDUINO_BACKEND
@ -130,7 +138,11 @@ class SPIComponent : public Component {
#ifdef USE_SPI_ARDUINO_BACKEND #ifdef USE_SPI_ARDUINO_BACKEND
if (this->hw_spi_ != nullptr) { if (this->hw_spi_ != nullptr) {
for (size_t i = 0; i < length; i++) { for (size_t i = 0; i < length; i++) {
#ifdef USE_RP2040
this->hw_spi_->transfer16(data[i]);
#else
this->hw_spi_->write16(data[i]); this->hw_spi_->write16(data[i]);
#endif
} }
return; return;
} }
@ -145,7 +157,11 @@ class SPIComponent : public Component {
#ifdef USE_SPI_ARDUINO_BACKEND #ifdef USE_SPI_ARDUINO_BACKEND
if (this->hw_spi_ != nullptr) { if (this->hw_spi_ != nullptr) {
auto *data_c = const_cast<uint8_t *>(data); auto *data_c = const_cast<uint8_t *>(data);
#ifdef USE_RP2040
this->hw_spi_->transfer(data_c, length);
#else
this->hw_spi_->writeBytes(data_c, length); this->hw_spi_->writeBytes(data_c, length);
#endif
return; return;
} }
#endif // USE_SPI_ARDUINO_BACKEND #endif // USE_SPI_ARDUINO_BACKEND
@ -178,7 +194,11 @@ class SPIComponent : public Component {
if (this->miso_ != nullptr) { if (this->miso_ != nullptr) {
this->hw_spi_->transfer(data, length); this->hw_spi_->transfer(data, length);
} else { } else {
#ifdef USE_RP2040
this->hw_spi_->transfer(data, length);
#else
this->hw_spi_->writeBytes(data, length); this->hw_spi_->writeBytes(data, length);
#endif
} }
return; return;
} }

View file

@ -267,7 +267,7 @@ CONFIG_SCHEMA = cv.All(
CONF_REBOOT_TIMEOUT, default="15min" CONF_REBOOT_TIMEOUT, default="15min"
): cv.positive_time_period_milliseconds, ): cv.positive_time_period_milliseconds,
cv.SplitDefault( 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.enum(WIFI_POWER_SAVE_MODES, upper=True),
cv.Optional(CONF_FAST_CONNECT, default=False): cv.boolean, cv.Optional(CONF_FAST_CONNECT, default=False): cv.boolean,
cv.Optional(CONF_USE_ADDRESS): cv.string_strict, cv.Optional(CONF_USE_ADDRESS): cv.string_strict,
@ -386,6 +386,8 @@ async def to_code(config):
cg.add_library("ESP8266WiFi", None) cg.add_library("ESP8266WiFi", None)
elif CORE.is_esp32 and CORE.using_arduino: elif CORE.is_esp32 and CORE.using_arduino:
cg.add_library("WiFi", None) cg.add_library("WiFi", None)
elif CORE.is_rp2040:
cg.add_library("WiFi", None)
if CORE.is_esp32 and CORE.using_esp_idf: if CORE.is_esp32 and CORE.using_esp_idf:
if config[CONF_ENABLE_BTM] or config[CONF_ENABLE_RRM]: if config[CONF_ENABLE_BTM] or config[CONF_ENABLE_RRM]:

View file

@ -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_with_auth() const { return this->with_auth_; }
bool WiFiScanResult::get_is_hidden() const { return this->is_hidden_; } 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) WiFiComponent *global_wifi_component; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
} // namespace wifi } // namespace wifi

View file

@ -24,6 +24,16 @@ extern "C" {
#endif #endif
#endif #endif
#ifdef USE_RP2040
extern "C" {
#include "cyw43.h"
#include "cyw43_country.h"
#include "pico/cyw43_arch.h"
}
#include <WiFi.h>
#endif
namespace esphome { namespace esphome {
namespace wifi { namespace wifi {
@ -138,6 +148,8 @@ class WiFiScanResult {
float get_priority() const { return priority_; } float get_priority() const { return priority_; }
void set_priority(float priority) { priority_ = priority; } void set_priority(float priority) { priority_ = priority; }
bool operator==(const WiFiScanResult &rhs) const;
protected: protected:
bool matches_{false}; bool matches_{false};
bssid_t bssid_; bssid_t bssid_;
@ -310,6 +322,11 @@ class WiFiComponent : public Component {
void wifi_process_event_(IDFWiFiEvent *data); void wifi_process_event_(IDFWiFiEvent *data);
#endif #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::string use_address_;
std::vector<WiFiAP> sta_; std::vector<WiFiAP> sta_;
std::vector<WiFiSTAPriority> sta_priorities_; std::vector<WiFiSTAPriority> sta_priorities_;

View file

@ -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<bool> sta, optional<bool> 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<ManualIP> 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<const char *>(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<ManualIP> 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

View file

@ -1440,6 +1440,7 @@ class SplitDefault(Optional):
esp32=vol.UNDEFINED, esp32=vol.UNDEFINED,
esp32_arduino=vol.UNDEFINED, esp32_arduino=vol.UNDEFINED,
esp32_idf=vol.UNDEFINED, esp32_idf=vol.UNDEFINED,
rp2040=vol.UNDEFINED,
): ):
super().__init__(key) super().__init__(key)
self._esp8266_default = vol.default_factory(esp8266) self._esp8266_default = vol.default_factory(esp8266)
@ -1449,6 +1450,7 @@ class SplitDefault(Optional):
self._esp32_idf_default = vol.default_factory( self._esp32_idf_default = vol.default_factory(
esp32_idf if esp32 is vol.UNDEFINED else esp32 esp32_idf if esp32 is vol.UNDEFINED else esp32
) )
self._rp2040_default = vol.default_factory(rp2040)
@property @property
def default(self): def default(self):
@ -1458,6 +1460,8 @@ class SplitDefault(Optional):
return self._esp32_arduino_default return self._esp32_arduino_default
if CORE.is_esp32 and CORE.using_esp_idf: if CORE.is_esp32 and CORE.using_esp_idf:
return self._esp32_idf_default return self._esp32_idf_default
if CORE.is_rp2040:
return self._rp2040_default
raise NotImplementedError raise NotImplementedError
@default.setter @default.setter

View file

@ -6,8 +6,9 @@ ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
PLATFORM_ESP32 = "esp32" PLATFORM_ESP32 = "esp32"
PLATFORM_ESP8266 = "esp8266" 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"} SOURCE_FILE_EXTENSIONS = {".cpp", ".hpp", ".h", ".c", ".tcc", ".ino"}
HEADER_FILE_EXTENSIONS = {".h", ".hpp", ".tcc"} HEADER_FILE_EXTENSIONS = {".h", ".hpp", ".tcc"}

View file

@ -594,6 +594,10 @@ class EsphomeCore:
def is_esp32(self): def is_esp32(self):
return self.target_platform == "esp32" return self.target_platform == "esp32"
@property
def is_rp2040(self):
return self.target_platform == "rp2040"
@property @property
def target_framework(self): def target_framework(self):
return self.data[KEY_CORE][KEY_TARGET_FRAMEWORK] return self.data[KEY_CORE][KEY_TARGET_FRAMEWORK]

View file

@ -21,6 +21,8 @@
#include "esp_system.h" #include "esp_system.h"
#include <freertos/FreeRTOS.h> #include <freertos/FreeRTOS.h>
#include <freertos/portmacro.h> #include <freertos/portmacro.h>
#elif defined(USE_RP2040) && defined(USE_WIFI)
#include <WiFi.h>
#endif #endif
#ifdef USE_ESP32_IGNORE_EFUSE_MAC_CRC #ifdef USE_ESP32_IGNORE_EFUSE_MAC_CRC
@ -91,6 +93,8 @@ uint32_t random_uint32() {
return esp_random(); return esp_random();
#elif defined(USE_ESP8266) #elif defined(USE_ESP8266)
return os_random(); return os_random();
#elif defined(USE_RP2040)
return ((uint32_t) rand()) << 16 + ((uint32_t) rand());
#else #else
#error "No random source available for this configuration." #error "No random source available for this configuration."
#endif #endif
@ -102,6 +106,8 @@ bool random_bytes(uint8_t *data, size_t len) {
return true; return true;
#elif defined(USE_ESP8266) #elif defined(USE_ESP8266)
return os_get_random(data, len) == 0; return os_get_random(data, len) == 0;
#elif defined(USE_RP2040)
return false;
#else #else
#error "No random source available for this configuration." #error "No random source available for this configuration."
#endif #endif
@ -409,6 +415,8 @@ void get_mac_address_raw(uint8_t *mac) {
#endif #endif
#elif defined(USE_ESP8266) #elif defined(USE_ESP8266)
wifi_get_macaddr(STATION_IF, mac); wifi_get_macaddr(STATION_IF, mac);
#elif defined(USE_RP2040) && defined(USE_WIFI)
WiFi.macAddress(mac);
#endif #endif
} }
std::string get_mac_address() { std::string get_mac_address() {

View file

@ -428,21 +428,27 @@ class DownloadBinaryRequestHandler(BaseHandler):
def get(self, configuration=None): def get(self, configuration=None):
type = self.get_argument("type", "firmware.bin") type = self.get_argument("type", "firmware.bin")
if type == "firmware.bin": storage_path = ext_storage_path(settings.config_dir, configuration)
storage_path = ext_storage_path(settings.config_dir, configuration) storage_json = StorageJSON.load(storage_path)
storage_json = StorageJSON.load(storage_path) if storage_json is None:
if storage_json is None: self.send_error(404)
self.send_error(404) return
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" filename = f"{storage_json.name}.bin"
path = storage_json.firmware_bin_path path = storage_json.firmware_bin_path
elif type == "firmware-factory.bin": 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" filename = f"{storage_json.name}-factory.bin"
path = storage_json.firmware_bin_path.replace( path = storage_json.firmware_bin_path.replace(
"firmware.bin", "firmware-factory.bin" "firmware.bin", "firmware-factory.bin"

View file

@ -119,16 +119,16 @@ class StorageJSON:
) )
@staticmethod @staticmethod
def from_wizard(name: str, address: str, esp_platform: str) -> "StorageJSON": def from_wizard(name: str, address: str, platform: str) -> "StorageJSON":
return StorageJSON( return StorageJSON(
storage_version=1, storage_version=1,
name=name, name=name,
comment=None, comment=None,
esphome_version=const.__version__, esphome_version=None,
src_version=1, src_version=1,
address=address, address=address,
web_port=None, web_port=None,
target_platform=esp_platform, target_platform=platform,
build_path=None, build_path=None,
firmware_bin_path=None, firmware_bin_path=None,
loaded_integrations=[], loaded_integrations=[],

View file

@ -81,11 +81,20 @@ esp32:
type: esp-idf 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 = { HARDWARE_BASE_CONFIGS = {
"ESP8266": ESP8266_CONFIG, "ESP8266": ESP8266_CONFIG,
"ESP32": ESP32_CONFIG, "ESP32": ESP32_CONFIG,
"ESP32S2": ESP32S2_CONFIG, "ESP32S2": ESP32S2_CONFIG,
"ESP32C3": ESP32C3_CONFIG, "ESP32C3": ESP32C3_CONFIG,
"RP2040": RP2040_CONFIG,
} }
@ -164,6 +173,7 @@ captive_portal:
def wizard_write(path, **kwargs): def wizard_write(path, **kwargs):
from esphome.components.esp8266 import boards as esp8266_boards from esphome.components.esp8266 import boards as esp8266_boards
from esphome.components.rp2040 import boards as rp2040_boards
name = kwargs["name"] name = kwargs["name"]
board = kwargs["board"] board = kwargs["board"]
@ -173,9 +183,13 @@ def wizard_write(path, **kwargs):
kwargs[key] = sanitize_double_quotes(kwargs[key]) kwargs[key] = sanitize_double_quotes(kwargs[key])
if "platform" not in kwargs: if "platform" not in kwargs:
kwargs["platform"] = ( if board in esp8266_boards.ESP8266_BOARD_PINS:
"ESP8266" if board in esp8266_boards.ESP8266_BOARD_PINS else "ESP32" platform = "ESP8266"
) elif board in rp2040_boards.RP2040_BOARD_PINS:
platform = "RP2040"
else:
platform = "ESP32"
kwargs["platform"] = platform
hardware = kwargs["platform"] hardware = kwargs["platform"]
write_file(path, wizard_file(**kwargs)) write_file(path, wizard_file(**kwargs))

View file

@ -146,6 +146,24 @@ build_flags =
-DUSE_ESP32_FRAMEWORK_ESP_IDF -DUSE_ESP32_FRAMEWORK_ESP_IDF
extra_scripts = post:esphome/components/esp32/post_build.py.script 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. ; All the actual environments are defined below.
[env:esp8266-arduino] [env:esp8266-arduino]
extends = common:esp8266-arduino extends = common:esp8266-arduino
@ -222,3 +240,10 @@ board_build.esp-idf.sdkconfig_path = .temp/sdkconfig-esp32s2-idf-tidy
build_flags = build_flags =
${common:esp32-idf.build_flags} ${common:esp32-idf.build_flags}
${flags:clangtidy.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}

View file

@ -534,6 +534,7 @@ def lint_relative_py_import(fname):
"esphome/components/socket/headers.h", "esphome/components/socket/headers.h",
"esphome/components/esp32/core.cpp", "esphome/components/esp32/core.cpp",
"esphome/components/esp8266/core.cpp", "esphome/components/esp8266/core.cpp",
"esphome/components/rp2040/core.cpp",
], ],
) )
def lint_namespace(fname, content): def lint_namespace(fname, content):

View file

@ -24,3 +24,4 @@ Current test_.yaml file contents.
| test3.yaml | ESP8266 | wifi | N/A | test3.yaml | ESP8266 | wifi | N/A
| test4.yaml | ESP32 | ethernet | None | test4.yaml | ESP32 | ethernet | None
| test5.yaml | ESP32 | wifi | ble_server | test5.yaml | ESP32 | wifi | ble_server
| test6.yaml | RP2040 | wifi | N/A

View file

@ -13,8 +13,9 @@ using namespace esphome;
void setup() { void setup() {
App.pre_setup("livingroom", __DATE__ ", " __TIME__, false); 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->pre_setup();
log->set_uart_selection(logger::UART_SELECTION_UART0);
App.register_component(log); App.register_component(log);
auto *wifi = new wifi::WiFiComponent(); // NOLINT auto *wifi = new wifi::WiFiComponent(); // NOLINT

39
tests/test6.yaml Normal file
View file

@ -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