[nrf52, core] nrf52 core based on zephyr (#7049)

Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
Co-authored-by: Samuel Sieb <samuel-github@sieb.net>
Co-authored-by: Tomasz Duda <tomaszduda23@gmai.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
tomaszduda23 2025-07-16 03:00:21 +02:00 committed by GitHub
parent eb81b8a1c8
commit 5d9cba3dce
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
32 changed files with 1250 additions and 17 deletions

View File

@ -324,6 +324,7 @@ esphome/components/nextion/text_sensor/* @senexcrenshaw
esphome/components/nfc/* @jesserockz @kbx81
esphome/components/noblex/* @AGalfra
esphome/components/npi19/* @bakerkj
esphome/components/nrf52/* @tomaszduda23
esphome/components/number/* @esphome/core
esphome/components/one_wire/* @ssieb
esphome/components/online_image/* @clydebarrow @guillempages
@ -535,5 +536,6 @@ esphome/components/xiaomi_xmwsdj04mmc/* @medusalix
esphome/components/xl9535/* @mreditor97
esphome/components/xpt2046/touchscreen/* @nielsnl68 @numo68
esphome/components/xxtea/* @clydebarrow
esphome/components/zephyr/* @tomaszduda23
esphome/components/zhlt01/* @cfeenstra1024
esphome/components/zio_ultrasonic/* @kahrendt

View File

@ -21,6 +21,11 @@ from esphome.components.libretiny.const import (
COMPONENT_LN882X,
COMPONENT_RTL87XX,
)
from esphome.components.zephyr import (
zephyr_add_cdc_acm,
zephyr_add_overlay,
zephyr_add_prj_conf,
)
from esphome.config_helpers import filter_source_files_from_platform
import esphome.config_validation as cv
from esphome.const import (
@ -41,6 +46,7 @@ from esphome.const import (
PLATFORM_ESP32,
PLATFORM_ESP8266,
PLATFORM_LN882X,
PLATFORM_NRF52,
PLATFORM_RP2040,
PLATFORM_RTL87XX,
PlatformFramework,
@ -115,6 +121,8 @@ ESP_ARDUINO_UNSUPPORTED_USB_UARTS = [USB_SERIAL_JTAG]
UART_SELECTION_RP2040 = [USB_CDC, UART0, UART1]
UART_SELECTION_NRF52 = [USB_CDC, UART0]
HARDWARE_UART_TO_UART_SELECTION = {
UART0: logger_ns.UART_SELECTION_UART0,
UART0_SWAP: logger_ns.UART_SELECTION_UART0_SWAP,
@ -167,6 +175,8 @@ def uart_selection(value):
return cv.one_of(*UART_SELECTION_LIBRETINY[component], upper=True)(value)
if CORE.is_host:
raise cv.Invalid("Uart selection not valid for host platform")
if CORE.is_nrf52:
return cv.one_of(*UART_SELECTION_NRF52, upper=True)(value)
raise NotImplementedError
@ -186,6 +196,7 @@ LoggerMessageTrigger = logger_ns.class_(
automation.Trigger.template(cg.int_, cg.const_char_ptr, cg.const_char_ptr),
)
CONF_ESP8266_STORE_LOG_STRINGS_IN_FLASH = "esp8266_store_log_strings_in_flash"
CONFIG_SCHEMA = cv.All(
cv.Schema(
@ -227,6 +238,7 @@ CONFIG_SCHEMA = cv.All(
bk72xx=DEFAULT,
ln882x=DEFAULT,
rtl87xx=DEFAULT,
nrf52=USB_CDC,
): cv.All(
cv.only_on(
[
@ -236,6 +248,7 @@ CONFIG_SCHEMA = cv.All(
PLATFORM_BK72XX,
PLATFORM_LN882X,
PLATFORM_RTL87XX,
PLATFORM_NRF52,
]
),
uart_selection,
@ -358,6 +371,15 @@ async def to_code(config):
except cv.Invalid:
pass
if CORE.using_zephyr:
if config[CONF_HARDWARE_UART] == UART0:
zephyr_add_overlay("""&uart0 { status = "okay";};""")
if config[CONF_HARDWARE_UART] == UART1:
zephyr_add_overlay("""&uart1 { status = "okay";};""")
if config[CONF_HARDWARE_UART] == USB_CDC:
zephyr_add_prj_conf("UART_LINE_CTRL", True)
zephyr_add_cdc_acm(config, 0)
# Register at end for safe mode
await cg.register_component(log, config)
@ -462,6 +484,7 @@ FILTER_SOURCE_FILES = filter_source_files_from_platform(
PlatformFramework.RTL87XX_ARDUINO,
PlatformFramework.LN882X_ARDUINO,
},
"logger_zephyr.cpp": {PlatformFramework.NRF52_ZEPHYR},
"task_log_buffer.cpp": {
PlatformFramework.ESP32_ARDUINO,
PlatformFramework.ESP32_IDF,

View File

@ -4,9 +4,9 @@
#include <memory> // For unique_ptr
#endif
#include "esphome/core/application.h"
#include "esphome/core/hal.h"
#include "esphome/core/log.h"
#include "esphome/core/application.h"
namespace esphome {
namespace logger {
@ -160,6 +160,8 @@ Logger::Logger(uint32_t baud_rate, size_t tx_buffer_size) : baud_rate_(baud_rate
this->tx_buffer_ = new char[this->tx_buffer_size_ + 1]; // NOLINT
#if defined(USE_ESP32) || defined(USE_LIBRETINY)
this->main_task_ = xTaskGetCurrentTaskHandle();
#elif defined(USE_ZEPHYR)
this->main_task_ = k_current_get();
#endif
}
#ifdef USE_ESPHOME_TASK_LOG_BUFFER
@ -172,6 +174,7 @@ void Logger::init_log_buffer(size_t total_buffer_size) {
}
#endif
#ifndef USE_ZEPHYR
#if defined(USE_LOGGER_USB_CDC) || defined(USE_ESP32)
void Logger::loop() {
#if defined(USE_LOGGER_USB_CDC) && defined(USE_ARDUINO)
@ -185,8 +188,13 @@ void Logger::loop() {
}
opened = !opened;
}
#endif
this->process_messages_();
}
#endif
#endif
void Logger::process_messages_() {
#ifdef USE_ESPHOME_TASK_LOG_BUFFER
// Process any buffered messages when available
if (this->log_buffer_->has_messages()) {
@ -227,12 +235,11 @@ void Logger::loop() {
}
#endif
}
#endif
void Logger::set_baud_rate(uint32_t baud_rate) { this->baud_rate_ = baud_rate; }
void Logger::set_log_level(const std::string &tag, uint8_t log_level) { this->log_levels_[tag] = log_level; }
#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_LIBRETINY)
#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR)
UARTSelection Logger::get_uart() const { return this->uart_; }
#endif

View File

@ -29,6 +29,11 @@
#include <driver/uart.h>
#endif // USE_ESP_IDF
#ifdef USE_ZEPHYR
#include <zephyr/kernel.h>
struct device;
#endif
namespace esphome {
namespace logger {
@ -56,7 +61,7 @@ static const char *const LOG_LEVEL_LETTERS[] = {
"VV", // VERY_VERBOSE
};
#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_LIBRETINY)
#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR)
/** Enum for logging UART selection
*
* Advanced configuration (pin selection, etc) is not supported.
@ -82,7 +87,7 @@ enum UARTSelection : uint8_t {
UART_SELECTION_UART0_SWAP,
#endif // USE_ESP8266
};
#endif // USE_ESP32 || USE_ESP8266 || USE_RP2040 || USE_LIBRETINY
#endif // USE_ESP32 || USE_ESP8266 || USE_RP2040 || USE_LIBRETINY || USE_ZEPHYR
/**
* @brief Logger component for all ESPHome logging.
@ -107,7 +112,7 @@ class Logger : public Component {
#ifdef USE_ESPHOME_TASK_LOG_BUFFER
void init_log_buffer(size_t total_buffer_size);
#endif
#if defined(USE_LOGGER_USB_CDC) || defined(USE_ESP32)
#if defined(USE_LOGGER_USB_CDC) || defined(USE_ESP32) || defined(USE_ZEPHYR)
void loop() override;
#endif
/// Manually set the baud rate for serial, set to 0 to disable.
@ -122,7 +127,7 @@ class Logger : public Component {
#ifdef USE_ESP32
void create_pthread_key() { pthread_key_create(&log_recursion_key_, nullptr); }
#endif
#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_LIBRETINY)
#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR)
void set_uart_selection(UARTSelection uart_selection) { uart_ = uart_selection; }
/// Get the UART used by the logger.
UARTSelection get_uart() const;
@ -157,6 +162,7 @@ class Logger : public Component {
#endif
protected:
void process_messages_();
void write_msg_(const char *msg);
// Format a log message with printf-style arguments and write it to a buffer with header, footer, and null terminator
@ -164,7 +170,7 @@ class Logger : public Component {
inline void HOT format_log_to_buffer_with_terminator_(uint8_t level, const char *tag, int line, const char *format,
va_list args, char *buffer, uint16_t *buffer_at,
uint16_t buffer_size) {
#if defined(USE_ESP32) || defined(USE_LIBRETINY)
#if defined(USE_ESP32) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR)
this->write_header_to_buffer_(level, tag, line, this->get_thread_name_(), buffer, buffer_at, buffer_size);
#else
this->write_header_to_buffer_(level, tag, line, nullptr, buffer, buffer_at, buffer_size);
@ -231,7 +237,10 @@ class Logger : public Component {
#ifdef USE_ARDUINO
Stream *hw_serial_{nullptr};
#endif
#if defined(USE_ESP32) || defined(USE_LIBRETINY)
#if defined(USE_ZEPHYR)
const device *uart_dev_{nullptr};
#endif
#if defined(USE_ESP32) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR)
void *main_task_ = nullptr; // Only used for thread name identification
#endif
#ifdef USE_ESP32
@ -256,7 +265,7 @@ class Logger : public Component {
uint16_t tx_buffer_at_{0};
uint16_t tx_buffer_size_{0};
uint8_t current_level_{ESPHOME_LOG_LEVEL_VERY_VERBOSE};
#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040)
#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_ZEPHYR)
UARTSelection uart_{UART_SELECTION_UART0};
#endif
#ifdef USE_LIBRETINY
@ -268,9 +277,13 @@ class Logger : public Component {
bool global_recursion_guard_{false}; // Simple global recursion guard for single-task platforms
#endif
#if defined(USE_ESP32) || defined(USE_LIBRETINY)
#if defined(USE_ESP32) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR)
const char *HOT get_thread_name_() {
#ifdef USE_ZEPHYR
k_tid_t current_task = k_current_get();
#else
TaskHandle_t current_task = xTaskGetCurrentTaskHandle();
#endif
if (current_task == main_task_) {
return nullptr; // Main task
} else {
@ -278,6 +291,8 @@ class Logger : public Component {
return pcTaskGetName(current_task);
#elif defined(USE_LIBRETINY)
return pcTaskGetTaskName(current_task);
#elif defined(USE_ZEPHYR)
return k_thread_name_get(current_task);
#endif
}
}
@ -319,7 +334,7 @@ class Logger : public Component {
const char *color = esphome::logger::LOG_LEVEL_COLORS[level];
const char *letter = esphome::logger::LOG_LEVEL_LETTERS[level];
#if defined(USE_ESP32) || defined(USE_LIBRETINY)
#if defined(USE_ESP32) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR)
if (thread_name != nullptr) {
// Non-main task with thread name
this->printf_to_buffer_(buffer, buffer_at, buffer_size, "%s[%s][%s:%03u]%s[%s]%s: ", color, letter, tag, line,

View File

@ -0,0 +1,88 @@
#ifdef USE_ZEPHYR
#include "esphome/core/application.h"
#include "esphome/core/log.h"
#include "logger.h"
#include <zephyr/device.h>
#include <zephyr/drivers/uart.h>
#include <zephyr/usb/usb_device.h>
namespace esphome {
namespace logger {
static const char *const TAG = "logger";
void Logger::loop() {
#ifdef USE_LOGGER_USB_CDC
if (this->uart_ != UART_SELECTION_USB_CDC || nullptr == this->uart_dev_) {
return;
}
static bool opened = false;
uint32_t dtr = 0;
uart_line_ctrl_get(this->uart_dev_, UART_LINE_CTRL_DTR, &dtr);
/* Poll if the DTR flag was set, optional */
if (opened == dtr) {
return;
}
if (!opened) {
App.schedule_dump_config();
}
opened = !opened;
#endif
this->process_messages_();
}
void Logger::pre_setup() {
if (this->baud_rate_ > 0) {
static const struct device *uart_dev = nullptr;
switch (this->uart_) {
case UART_SELECTION_UART0:
uart_dev = DEVICE_DT_GET_OR_NULL(DT_NODELABEL(uart0));
break;
case UART_SELECTION_UART1:
uart_dev = DEVICE_DT_GET_OR_NULL(DT_NODELABEL(uart1));
break;
#ifdef USE_LOGGER_USB_CDC
case UART_SELECTION_USB_CDC:
uart_dev = DEVICE_DT_GET_OR_NULL(DT_NODELABEL(cdc_acm_uart0));
if (device_is_ready(uart_dev)) {
usb_enable(nullptr);
}
break;
#endif
}
if (!device_is_ready(uart_dev)) {
ESP_LOGE(TAG, "%s is not ready.", get_uart_selection_());
} else {
this->uart_dev_ = uart_dev;
}
}
global_logger = this;
ESP_LOGI(TAG, "Log initialized");
}
void HOT Logger::write_msg_(const char *msg) {
#ifdef CONFIG_PRINTK
printk("%s\n", msg);
#endif
if (nullptr == this->uart_dev_) {
return;
}
while (*msg) {
uart_poll_out(this->uart_dev_, *msg);
++msg;
}
uart_poll_out(this->uart_dev_, '\n');
}
const char *const UART_SELECTIONS[] = {"UART0", "UART1", "USB_CDC"};
const char *Logger::get_uart_selection_() { return UART_SELECTIONS[this->uart_]; }
} // namespace logger
} // namespace esphome
#endif

View File

@ -0,0 +1,218 @@
from __future__ import annotations
from pathlib import Path
import esphome.codegen as cg
from esphome.components.zephyr import (
copy_files as zephyr_copy_files,
zephyr_add_pm_static,
zephyr_set_core_data,
zephyr_to_code,
)
from esphome.components.zephyr.const import (
BOOTLOADER_MCUBOOT,
KEY_BOOTLOADER,
KEY_ZEPHYR,
)
import esphome.config_validation as cv
from esphome.const import (
CONF_BOARD,
CONF_FRAMEWORK,
KEY_CORE,
KEY_FRAMEWORK_VERSION,
KEY_TARGET_FRAMEWORK,
KEY_TARGET_PLATFORM,
PLATFORM_NRF52,
)
from esphome.core import CORE, EsphomeError, coroutine_with_priority
from esphome.storage_json import StorageJSON
from esphome.types import ConfigType
from .boards import BOARDS_ZEPHYR, BOOTLOADER_CONFIG
from .const import (
BOOTLOADER_ADAFRUIT,
BOOTLOADER_ADAFRUIT_NRF52_SD132,
BOOTLOADER_ADAFRUIT_NRF52_SD140_V6,
BOOTLOADER_ADAFRUIT_NRF52_SD140_V7,
)
# force import gpio to register pin schema
from .gpio import nrf52_pin_to_code # noqa
CODEOWNERS = ["@tomaszduda23"]
AUTO_LOAD = ["zephyr"]
IS_TARGET_PLATFORM = True
def set_core_data(config: ConfigType) -> ConfigType:
zephyr_set_core_data(config)
CORE.data[KEY_CORE][KEY_TARGET_PLATFORM] = PLATFORM_NRF52
CORE.data[KEY_CORE][KEY_TARGET_FRAMEWORK] = KEY_ZEPHYR
CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] = cv.Version(2, 6, 1)
if config[KEY_BOOTLOADER] in BOOTLOADER_CONFIG:
zephyr_add_pm_static(BOOTLOADER_CONFIG[config[KEY_BOOTLOADER]])
return config
BOOTLOADERS = [
BOOTLOADER_ADAFRUIT,
BOOTLOADER_ADAFRUIT_NRF52_SD132,
BOOTLOADER_ADAFRUIT_NRF52_SD140_V6,
BOOTLOADER_ADAFRUIT_NRF52_SD140_V7,
BOOTLOADER_MCUBOOT,
]
def _detect_bootloader(config: ConfigType) -> ConfigType:
"""Detect the bootloader for the given board."""
config = config.copy()
bootloaders: list[str] = []
board = config[CONF_BOARD]
if board in BOARDS_ZEPHYR and KEY_BOOTLOADER in BOARDS_ZEPHYR[board]:
# this board have bootloaders config available
bootloaders = BOARDS_ZEPHYR[board][KEY_BOOTLOADER]
if KEY_BOOTLOADER not in config:
if bootloaders:
# there is no bootloader in config -> take first one
config[KEY_BOOTLOADER] = bootloaders[0]
else:
# make mcuboot as default if there is no configuration for that board
config[KEY_BOOTLOADER] = BOOTLOADER_MCUBOOT
elif bootloaders and config[KEY_BOOTLOADER] not in bootloaders:
raise cv.Invalid(
f"{board} does not support {config[KEY_BOOTLOADER]}, select one of: {', '.join(bootloaders)}"
)
return config
CONFIG_SCHEMA = cv.All(
cv.Schema(
{
cv.Required(CONF_BOARD): cv.string_strict,
cv.Optional(KEY_BOOTLOADER): cv.one_of(*BOOTLOADERS, lower=True),
}
),
_detect_bootloader,
set_core_data,
)
@coroutine_with_priority(1000)
async def to_code(config: ConfigType) -> None:
"""Convert the configuration to code."""
cg.add_platformio_option("board", config[CONF_BOARD])
cg.add_build_flag("-DUSE_NRF52")
cg.add_define("ESPHOME_BOARD", config[CONF_BOARD])
cg.add_define("ESPHOME_VARIANT", "NRF52")
cg.add_platformio_option(CONF_FRAMEWORK, CORE.data[KEY_CORE][KEY_TARGET_FRAMEWORK])
cg.add_platformio_option(
"platform",
"https://github.com/tomaszduda23/platform-nordicnrf52/archive/refs/tags/v10.3.0-1.zip",
)
cg.add_platformio_option(
"platform_packages",
[
"platformio/framework-zephyr@https://github.com/tomaszduda23/framework-sdk-nrf/archive/refs/tags/v2.6.1-4.zip",
"platformio/toolchain-gccarmnoneeabi@https://github.com/tomaszduda23/toolchain-sdk-ng/archive/refs/tags/v0.16.1-1.zip",
],
)
if config[KEY_BOOTLOADER] == BOOTLOADER_ADAFRUIT:
# make sure that firmware.zip is created
# for Adafruit_nRF52_Bootloader
cg.add_platformio_option("board_upload.protocol", "nrfutil")
cg.add_platformio_option("board_upload.use_1200bps_touch", "true")
cg.add_platformio_option("board_upload.require_upload_port", "true")
cg.add_platformio_option("board_upload.wait_for_upload_port", "true")
zephyr_to_code(config)
def copy_files() -> None:
"""Copy files to the build directory."""
zephyr_copy_files()
def get_download_types(storage_json: StorageJSON) -> list[dict[str, str]]:
"""Get the download types for the firmware."""
types = []
UF2_PATH = "zephyr/zephyr.uf2"
DFU_PATH = "firmware.zip"
HEX_PATH = "zephyr/zephyr.hex"
HEX_MERGED_PATH = "zephyr/merged.hex"
APP_IMAGE_PATH = "zephyr/app_update.bin"
build_dir = Path(storage_json.firmware_bin_path).parent
if (build_dir / UF2_PATH).is_file():
types = [
{
"title": "UF2 package (recommended)",
"description": "For flashing via Adafruit nRF52 Bootloader as a flash drive.",
"file": UF2_PATH,
"download": f"{storage_json.name}.uf2",
},
{
"title": "DFU package",
"description": "For flashing via adafruit-nrfutil using USB CDC.",
"file": DFU_PATH,
"download": f"dfu-{storage_json.name}.zip",
},
]
else:
types = [
{
"title": "HEX package",
"description": "For flashing via pyocd using SWD.",
"file": (
HEX_MERGED_PATH
if (build_dir / HEX_MERGED_PATH).is_file()
else HEX_PATH
),
"download": f"{storage_json.name}.hex",
},
]
if (build_dir / APP_IMAGE_PATH).is_file():
types += [
{
"title": "App update package",
"description": "For flashing via mcumgr-web using BLE or smpclient using USB CDC.",
"file": APP_IMAGE_PATH,
"download": f"app-{storage_json.name}.img",
},
]
return types
def _upload_using_platformio(
config: ConfigType, port: str, upload_args: list[str]
) -> int | str:
from esphome import platformio_api
if port is not None:
upload_args += ["--upload-port", port]
return platformio_api.run_platformio_cli_run(config, CORE.verbose, *upload_args)
def upload_program(config: ConfigType, args, host: str) -> bool:
from esphome.__main__ import check_permissions, get_port_type
result = 0
handled = False
if get_port_type(host) == "SERIAL":
check_permissions(host)
result = _upload_using_platformio(config, host, ["-t", "upload"])
handled = True
if host == "PYOCD":
result = _upload_using_platformio(config, host, ["-t", "flash_pyocd"])
handled = True
if result != 0:
raise EsphomeError(f"Upload failed with result: {result}")
return handled

View File

@ -0,0 +1,34 @@
from esphome.components.zephyr import Section
from esphome.components.zephyr.const import KEY_BOOTLOADER
from .const import (
BOOTLOADER_ADAFRUIT,
BOOTLOADER_ADAFRUIT_NRF52_SD132,
BOOTLOADER_ADAFRUIT_NRF52_SD140_V6,
BOOTLOADER_ADAFRUIT_NRF52_SD140_V7,
)
BOARDS_ZEPHYR = {
"adafruit_itsybitsy_nrf52840": {
KEY_BOOTLOADER: [
BOOTLOADER_ADAFRUIT,
BOOTLOADER_ADAFRUIT_NRF52_SD132,
BOOTLOADER_ADAFRUIT_NRF52_SD140_V6,
BOOTLOADER_ADAFRUIT_NRF52_SD140_V7,
]
},
}
# https://github.com/ffenix113/zigbee_home/blob/17bb7b9e9d375e756da9e38913f53303937fb66a/types/board/known_boards.go
# https://learn.adafruit.com/introducing-the-adafruit-nrf52840-feather?view=all#hathach-memory-map
BOOTLOADER_CONFIG = {
BOOTLOADER_ADAFRUIT_NRF52_SD132: [
Section("empty_app_offset", 0x0, 0x26000, "flash_primary"),
],
BOOTLOADER_ADAFRUIT_NRF52_SD140_V6: [
Section("empty_app_offset", 0x0, 0x26000, "flash_primary"),
],
BOOTLOADER_ADAFRUIT_NRF52_SD140_V7: [
Section("empty_app_offset", 0x0, 0x27000, "flash_primary"),
],
}

View File

@ -0,0 +1,4 @@
BOOTLOADER_ADAFRUIT = "adafruit"
BOOTLOADER_ADAFRUIT_NRF52_SD132 = "adafruit_nrf52_sd132"
BOOTLOADER_ADAFRUIT_NRF52_SD140_V6 = "adafruit_nrf52_sd140_v6"
BOOTLOADER_ADAFRUIT_NRF52_SD140_V7 = "adafruit_nrf52_sd140_v7"

View File

@ -0,0 +1,53 @@
from esphome import pins
import esphome.codegen as cg
from esphome.components.zephyr.const import zephyr_ns
import esphome.config_validation as cv
from esphome.const import CONF_ID, CONF_INVERTED, CONF_MODE, CONF_NUMBER, PLATFORM_NRF52
ZephyrGPIOPin = zephyr_ns.class_("ZephyrGPIOPin", cg.InternalGPIOPin)
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
# e.g. P0.27
if len(value) >= len("P0.0") and value[0] == "P" and value[2] == ".":
return cv.int_(value[len("P")].strip()) * 32 + cv.int_(
value[len("P0.") :].strip()
)
raise cv.Invalid(f"Invalid pin: {value}")
def validate_gpio_pin(value):
value = _translate_pin(value)
if value < 0 or value > (32 + 16):
raise cv.Invalid(f"NRF52: Invalid pin number: {value}")
return value
NRF52_PIN_SCHEMA = cv.All(
pins.gpio_base_schema(
ZephyrGPIOPin,
validate_gpio_pin,
modes=pins.GPIO_STANDARD_MODES,
),
)
@pins.PIN_SCHEMA_REGISTRY.register(PLATFORM_NRF52, NRF52_PIN_SCHEMA)
async def nrf52_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

@ -6,6 +6,7 @@ import tzlocal
from esphome import automation
from esphome.automation import Condition
import esphome.codegen as cg
from esphome.components.zephyr import zephyr_add_prj_conf
import esphome.config_validation as cv
from esphome.const import (
CONF_AT,
@ -25,7 +26,7 @@ from esphome.const import (
CONF_TIMEZONE,
CONF_TRIGGER_ID,
)
from esphome.core import coroutine_with_priority
from esphome.core import CORE, coroutine_with_priority
_LOGGER = logging.getLogger(__name__)
@ -341,6 +342,8 @@ async def register_time(time_var, config):
@coroutine_with_priority(100.0)
async def to_code(config):
if CORE.using_zephyr:
zephyr_add_prj_conf("POSIX_CLOCK", True)
cg.add_define("USE_TIME")
cg.add_global(time_ns.using)

View File

@ -2,13 +2,15 @@
#include "esphome/core/log.h"
#ifdef USE_HOST
#include <sys/time.h>
#elif defined(USE_ZEPHYR)
#include <zephyr/posix/time.h>
#else
#include "lwip/opt.h"
#endif
#ifdef USE_ESP8266
#include "sys/time.h"
#endif
#ifdef USE_RP2040
#if defined(USE_RP2040) || defined(USE_ZEPHYR)
#include <sys/time.h>
#endif
#include <cerrno>
@ -22,11 +24,22 @@ static const char *const TAG = "time";
RealTimeClock::RealTimeClock() = default;
void RealTimeClock::synchronize_epoch_(uint32_t epoch) {
ESP_LOGVV(TAG, "Got epoch %" PRIu32, epoch);
// Update UTC epoch time.
#ifdef USE_ZEPHYR
struct timespec ts;
ts.tv_nsec = 0;
ts.tv_sec = static_cast<time_t>(epoch);
int ret = clock_settime(CLOCK_REALTIME, &ts);
if (ret != 0) {
ESP_LOGW(TAG, "clock_settime() failed with code %d", ret);
}
#else
struct timeval timev {
.tv_sec = static_cast<time_t>(epoch), .tv_usec = 0,
};
ESP_LOGVV(TAG, "Got epoch %" PRIu32, epoch);
struct timezone tz = {0, 0};
int ret = settimeofday(&timev, &tz);
if (ret == EINVAL) {
@ -43,7 +56,7 @@ void RealTimeClock::synchronize_epoch_(uint32_t epoch) {
if (ret != 0) {
ESP_LOGW(TAG, "setimeofday() failed with code %d", ret);
}
#endif
auto time = this->now();
ESP_LOGD(TAG, "Synchronized time: %04d-%02d-%02d %02d:%02d:%02d", time.year, time.month, time.day_of_month, time.hour,
time.minute, time.second);

View File

@ -0,0 +1,231 @@
import os
from typing import Final, TypedDict
import esphome.codegen as cg
from esphome.const import CONF_BOARD
from esphome.core import CORE
from esphome.helpers import copy_file_if_changed, write_file_if_changed
from .const import (
BOOTLOADER_MCUBOOT,
KEY_BOOTLOADER,
KEY_EXTRA_BUILD_FILES,
KEY_OVERLAY,
KEY_PM_STATIC,
KEY_PRJ_CONF,
KEY_ZEPHYR,
zephyr_ns,
)
CODEOWNERS = ["@tomaszduda23"]
AUTO_LOAD = ["preferences"]
KEY_BOARD: Final = "board"
PrjConfValueType = bool | str | int
class Section:
def __init__(self, name, address, size, region):
self.name = name
self.address = address
self.size = size
self.region = region
self.end_address = self.address + self.size
def __str__(self):
return (
f"{self.name}:\n"
f" address: 0x{self.address:X}\n"
f" end_address: 0x{self.end_address:X}\n"
f" region: {self.region}\n"
f" size: 0x{self.size:X}"
)
class ZephyrData(TypedDict):
board: str
bootloader: str
prj_conf: dict[str, tuple[PrjConfValueType, bool]]
overlay: str
extra_build_files: dict[str, str]
pm_static: list[Section]
def zephyr_set_core_data(config):
CORE.data[KEY_ZEPHYR] = ZephyrData(
board=config[CONF_BOARD],
bootloader=config[KEY_BOOTLOADER],
prj_conf={},
overlay="",
extra_build_files={},
pm_static=[],
)
return config
def zephyr_data() -> ZephyrData:
return CORE.data[KEY_ZEPHYR]
def zephyr_add_prj_conf(
name: str, value: PrjConfValueType, required: bool = True
) -> None:
"""Set an zephyr prj conf value."""
if not name.startswith("CONFIG_"):
name = "CONFIG_" + name
prj_conf = zephyr_data()[KEY_PRJ_CONF]
if name not in prj_conf:
prj_conf[name] = (value, required)
return
old_value, old_required = prj_conf[name]
if old_value != value and old_required:
raise ValueError(
f"{name} already set with value '{old_value}', cannot set again to '{value}'"
)
if required:
prj_conf[name] = (value, required)
def zephyr_add_overlay(content):
zephyr_data()[KEY_OVERLAY] += content
def add_extra_build_file(filename: str, path: str) -> bool:
"""Add an extra build file to the project."""
extra_build_files = zephyr_data()[KEY_EXTRA_BUILD_FILES]
if filename not in extra_build_files:
extra_build_files[filename] = path
return True
return False
def add_extra_script(stage: str, filename: str, path: str):
"""Add an extra script to the project."""
key = f"{stage}:{filename}"
if add_extra_build_file(filename, path):
cg.add_platformio_option("extra_scripts", [key])
def zephyr_to_code(config):
cg.add(zephyr_ns.setup_preferences())
cg.add_build_flag("-DUSE_ZEPHYR")
cg.set_cpp_standard("gnu++20")
# build is done by west so bypass board checking in platformio
cg.add_platformio_option("boards_dir", CORE.relative_build_path("boards"))
# c++ support
zephyr_add_prj_conf("NEWLIB_LIBC", True)
zephyr_add_prj_conf("CONFIG_FPU", True)
zephyr_add_prj_conf("NEWLIB_LIBC_FLOAT_PRINTF", True)
zephyr_add_prj_conf("CPLUSPLUS", True)
zephyr_add_prj_conf("CONFIG_STD_CPP20", True)
zephyr_add_prj_conf("LIB_CPLUSPLUS", True)
# preferences
zephyr_add_prj_conf("SETTINGS", True)
zephyr_add_prj_conf("NVS", True)
zephyr_add_prj_conf("FLASH_MAP", True)
zephyr_add_prj_conf("CONFIG_FLASH", True)
# watchdog
zephyr_add_prj_conf("WATCHDOG", True)
zephyr_add_prj_conf("WDT_DISABLE_AT_BOOT", False)
# disable console
zephyr_add_prj_conf("UART_CONSOLE", False)
zephyr_add_prj_conf("CONSOLE", False, False)
# use NFC pins as GPIO
zephyr_add_prj_conf("NFCT_PINS_AS_GPIOS", True)
# <err> os: ***** USAGE FAULT *****
# <err> os: Illegal load of EXC_RETURN into PC
zephyr_add_prj_conf("MAIN_STACK_SIZE", 2048)
add_extra_script(
"pre",
"pre_build.py",
os.path.join(os.path.dirname(__file__), "pre_build.py.script"),
)
def _format_prj_conf_val(value: PrjConfValueType) -> str:
if isinstance(value, bool):
return "y" if value else "n"
if isinstance(value, int):
return str(value)
if isinstance(value, str):
return f'"{value}"'
raise ValueError
def zephyr_add_cdc_acm(config, id):
zephyr_add_prj_conf("USB_DEVICE_STACK", True)
zephyr_add_prj_conf("USB_CDC_ACM", True)
# prevent device to go to susspend, without this communication stop working in python
# there should be a way to solve it
zephyr_add_prj_conf("USB_DEVICE_REMOTE_WAKEUP", False)
# prevent logging when buffer is full
zephyr_add_prj_conf("USB_CDC_ACM_LOG_LEVEL_WRN", True)
zephyr_add_overlay(
f"""
&zephyr_udc0 {{
cdc_acm_uart{id}: cdc_acm_uart{id} {{
compatible = "zephyr,cdc-acm-uart";
}};
}};
"""
)
def zephyr_add_pm_static(section: Section):
CORE.data[KEY_ZEPHYR][KEY_PM_STATIC].extend(section)
def copy_files():
want_opts = zephyr_data()[KEY_PRJ_CONF]
prj_conf = (
"\n".join(
f"{name}={_format_prj_conf_val(value[0])}"
for name, value in sorted(want_opts.items())
)
+ "\n"
)
write_file_if_changed(CORE.relative_build_path("zephyr/prj.conf"), prj_conf)
write_file_if_changed(
CORE.relative_build_path("zephyr/app.overlay"),
zephyr_data()[KEY_OVERLAY],
)
if zephyr_data()[KEY_BOOTLOADER] == BOOTLOADER_MCUBOOT or zephyr_data()[
KEY_BOARD
] in ["xiao_ble"]:
fake_board_manifest = """
{
"frameworks": [
"zephyr"
],
"name": "esphome nrf52",
"upload": {
"maximum_ram_size": 248832,
"maximum_size": 815104
},
"url": "https://esphome.io/",
"vendor": "esphome"
}
"""
write_file_if_changed(
CORE.relative_build_path(f"boards/{zephyr_data()[KEY_BOARD]}.json"),
fake_board_manifest,
)
for filename, path in zephyr_data()[KEY_EXTRA_BUILD_FILES].items():
copy_file_if_changed(
path,
CORE.relative_build_path(filename),
)
pm_static = "\n".join(str(item) for item in zephyr_data()[KEY_PM_STATIC])
if pm_static:
write_file_if_changed(
CORE.relative_build_path("zephyr/pm_static.yml"), pm_static
)

View File

@ -0,0 +1,14 @@
from typing import Final
import esphome.codegen as cg
BOOTLOADER_MCUBOOT = "mcuboot"
KEY_BOOTLOADER: Final = "bootloader"
KEY_EXTRA_BUILD_FILES: Final = "extra_build_files"
KEY_OVERLAY: Final = "overlay"
KEY_PM_STATIC: Final = "pm_static"
KEY_PRJ_CONF: Final = "prj_conf"
KEY_ZEPHYR = "zephyr"
zephyr_ns = cg.esphome_ns.namespace("zephyr")

View File

@ -0,0 +1,86 @@
#ifdef USE_ZEPHYR
#include <zephyr/kernel.h>
#include <zephyr/drivers/watchdog.h>
#include <zephyr/sys/reboot.h>
#include <zephyr/random/rand32.h>
#include "esphome/core/hal.h"
#include "esphome/core/helpers.h"
namespace esphome {
static int wdt_channel_id = -1; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
static const device *const WDT = DEVICE_DT_GET(DT_ALIAS(watchdog0));
void yield() { ::k_yield(); }
uint32_t millis() { return k_ticks_to_ms_floor32(k_uptime_ticks()); }
uint32_t micros() { return k_ticks_to_us_floor32(k_uptime_ticks()); }
void delayMicroseconds(uint32_t us) { ::k_usleep(us); }
void delay(uint32_t ms) { ::k_msleep(ms); }
void arch_init() {
if (device_is_ready(WDT)) {
static wdt_timeout_cfg wdt_config{};
wdt_config.flags = WDT_FLAG_RESET_SOC;
wdt_config.window.max = 2000;
wdt_channel_id = wdt_install_timeout(WDT, &wdt_config);
if (wdt_channel_id >= 0) {
wdt_setup(WDT, WDT_OPT_PAUSE_HALTED_BY_DBG | WDT_OPT_PAUSE_IN_SLEEP);
}
}
}
void arch_feed_wdt() {
if (wdt_channel_id >= 0) {
wdt_feed(WDT, wdt_channel_id);
}
}
void arch_restart() { sys_reboot(SYS_REBOOT_COLD); }
uint32_t arch_get_cpu_cycle_count() { return k_cycle_get_32(); }
uint32_t arch_get_cpu_freq_hz() { return sys_clock_hw_cycles_per_sec(); }
uint8_t progmem_read_byte(const uint8_t *addr) { return *addr; }
Mutex::Mutex() {
auto *mutex = new k_mutex();
this->handle_ = mutex;
k_mutex_init(mutex);
}
Mutex::~Mutex() { delete static_cast<k_mutex *>(this->handle_); }
void Mutex::lock() { k_mutex_lock(static_cast<k_mutex *>(this->handle_), K_FOREVER); }
bool Mutex::try_lock() { return k_mutex_lock(static_cast<k_mutex *>(this->handle_), K_NO_WAIT) == 0; }
void Mutex::unlock() { k_mutex_unlock(static_cast<k_mutex *>(this->handle_)); }
IRAM_ATTR InterruptLock::InterruptLock() { state_ = irq_lock(); }
IRAM_ATTR InterruptLock::~InterruptLock() { irq_unlock(state_); }
uint32_t random_uint32() { return rand(); } // NOLINT(cert-msc30-c, cert-msc50-cpp)
bool random_bytes(uint8_t *data, size_t len) {
sys_rand_get(data, len);
return true;
}
void get_mac_address_raw(uint8_t *mac) { // NOLINT(readability-non-const-parameter)
mac[0] = ((NRF_FICR->DEVICEADDR[1] & 0xFFFF) >> 8) | 0xC0;
mac[1] = NRF_FICR->DEVICEADDR[1] & 0xFFFF;
mac[2] = NRF_FICR->DEVICEADDR[0] >> 24;
mac[3] = NRF_FICR->DEVICEADDR[0] >> 16;
mac[4] = NRF_FICR->DEVICEADDR[0] >> 8;
mac[5] = NRF_FICR->DEVICEADDR[0];
}
} // namespace esphome
void setup();
void loop();
int main() {
setup();
while (true) {
loop();
esphome::yield();
}
return 0;
}
#endif

View File

@ -0,0 +1,120 @@
#ifdef USE_ZEPHYR
#include "gpio.h"
#include <zephyr/drivers/gpio.h>
#include "esphome/core/log.h"
namespace esphome {
namespace zephyr {
static const char *const TAG = "zephyr";
static int flags_to_mode(gpio::Flags flags, bool inverted, bool value) {
int ret = 0;
if (flags & gpio::FLAG_INPUT) {
ret |= GPIO_INPUT;
}
if (flags & gpio::FLAG_OUTPUT) {
ret |= GPIO_OUTPUT;
if (value != inverted) {
ret |= GPIO_OUTPUT_INIT_HIGH;
} else {
ret |= GPIO_OUTPUT_INIT_LOW;
}
}
if (flags & gpio::FLAG_PULLUP) {
ret |= GPIO_PULL_UP;
}
if (flags & gpio::FLAG_PULLDOWN) {
ret |= GPIO_PULL_DOWN;
}
if (flags & gpio::FLAG_OPEN_DRAIN) {
ret |= GPIO_OPEN_DRAIN;
}
return ret;
}
struct ISRPinArg {
uint8_t pin;
bool inverted;
};
ISRInternalGPIOPin ZephyrGPIOPin::to_isr() const {
auto *arg = new ISRPinArg{}; // NOLINT(cppcoreguidelines-owning-memory)
arg->pin = this->pin_;
arg->inverted = this->inverted_;
return ISRInternalGPIOPin((void *) arg);
}
void ZephyrGPIOPin::attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const {
// TODO
}
void ZephyrGPIOPin::setup() {
const struct device *gpio = nullptr;
if (this->pin_ < 32) {
#define GPIO0 DT_NODELABEL(gpio0)
#if DT_NODE_HAS_STATUS(GPIO0, okay)
gpio = DEVICE_DT_GET(GPIO0);
#else
#error "gpio0 is disabled"
#endif
} else {
#define GPIO1 DT_NODELABEL(gpio1)
#if DT_NODE_HAS_STATUS(GPIO1, okay)
gpio = DEVICE_DT_GET(GPIO1);
#else
#error "gpio1 is disabled"
#endif
}
if (device_is_ready(gpio)) {
this->gpio_ = gpio;
} else {
ESP_LOGE(TAG, "gpio %u is not ready.", this->pin_);
return;
}
this->pin_mode(this->flags_);
}
void ZephyrGPIOPin::pin_mode(gpio::Flags flags) {
if (nullptr == this->gpio_) {
return;
}
gpio_pin_configure(this->gpio_, this->pin_ % 32, flags_to_mode(flags, this->inverted_, this->value_));
}
std::string ZephyrGPIOPin::dump_summary() const {
char buffer[32];
snprintf(buffer, sizeof(buffer), "GPIO%u, P%u.%u", this->pin_, this->pin_ / 32, this->pin_ % 32);
return buffer;
}
bool ZephyrGPIOPin::digital_read() {
if (nullptr == this->gpio_) {
return false;
}
return bool(gpio_pin_get(this->gpio_, this->pin_ % 32) != this->inverted_);
}
void ZephyrGPIOPin::digital_write(bool value) {
// make sure that value is not ignored since it can be inverted e.g. on switch side
// that way init state should be correct
this->value_ = value;
if (nullptr == this->gpio_) {
return;
}
gpio_pin_set(this->gpio_, this->pin_ % 32, value != this->inverted_ ? 1 : 0);
}
void ZephyrGPIOPin::detach_interrupt() const {
// TODO
}
} // namespace zephyr
bool IRAM_ATTR ISRInternalGPIOPin::digital_read() {
// TODO
return false;
}
} // namespace esphome
#endif

View File

@ -0,0 +1,38 @@
#pragma once
#ifdef USE_ZEPHYR
#include "esphome/core/hal.h"
struct device;
namespace esphome {
namespace zephyr {
class ZephyrGPIOPin : public InternalGPIOPin {
public:
void set_pin(uint8_t pin) { this->pin_ = pin; }
void set_inverted(bool inverted) { this->inverted_ = inverted; }
void set_flags(gpio::Flags flags) { this->flags_ = flags; }
void setup() override;
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 this->pin_; }
bool is_inverted() const override { return this->inverted_; }
gpio::Flags get_flags() const override { return flags_; }
protected:
void attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const override;
uint8_t pin_;
bool inverted_;
gpio::Flags flags_;
const device *gpio_ = nullptr;
bool value_ = false;
};
} // namespace zephyr
} // namespace esphome
#endif // USE_ZEPHYR

View File

@ -0,0 +1,4 @@
Import("env")
board_config = env.BoardConfig()
board_config.update("frameworks", ["arduino", "zephyr"])

View File

@ -0,0 +1,156 @@
#ifdef USE_ZEPHYR
#include <zephyr/kernel.h>
#include "esphome/core/preferences.h"
#include "esphome/core/log.h"
#include <zephyr/settings/settings.h>
namespace esphome {
namespace zephyr {
static const char *const TAG = "zephyr.preferences";
#define ESPHOME_SETTINGS_KEY "esphome"
class ZephyrPreferenceBackend : public ESPPreferenceBackend {
public:
ZephyrPreferenceBackend(uint32_t type) { this->type_ = type; }
ZephyrPreferenceBackend(uint32_t type, std::vector<uint8_t> &&data) : data(std::move(data)) { this->type_ = type; }
bool save(const uint8_t *data, size_t len) override {
this->data.resize(len);
std::memcpy(this->data.data(), data, len);
ESP_LOGVV(TAG, "save key: %u, len: %d", this->type_, len);
return true;
}
bool load(uint8_t *data, size_t len) override {
if (len != this->data.size()) {
ESP_LOGE(TAG, "size of setting key %s changed, from: %u, to: %u", get_key().c_str(), this->data.size(), len);
return false;
}
std::memcpy(data, this->data.data(), len);
ESP_LOGVV(TAG, "load key: %u, len: %d", this->type_, len);
return true;
}
uint32_t get_type() const { return this->type_; }
std::string get_key() const { return str_sprintf(ESPHOME_SETTINGS_KEY "/%" PRIx32, this->type_); }
std::vector<uint8_t> data;
protected:
uint32_t type_ = 0;
};
class ZephyrPreferences : public ESPPreferences {
public:
void open() {
int err = settings_subsys_init();
if (err) {
ESP_LOGE(TAG, "Failed to initialize settings subsystem, err: %d", err);
return;
}
static struct settings_handler settings_cb = {
.name = ESPHOME_SETTINGS_KEY,
.h_set = load_setting,
.h_export = export_settings,
};
err = settings_register(&settings_cb);
if (err) {
ESP_LOGE(TAG, "setting_register failed, err, %d", err);
return;
}
err = settings_load_subtree(ESPHOME_SETTINGS_KEY);
if (err) {
ESP_LOGE(TAG, "Cannot load settings, err: %d", err);
return;
}
ESP_LOGD(TAG, "Loaded %u settings.", this->backends_.size());
}
ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash) override {
return make_preference(length, type);
}
ESPPreferenceObject make_preference(size_t length, uint32_t type) override {
for (auto *backend : this->backends_) {
if (backend->get_type() == type) {
return ESPPreferenceObject(backend);
}
}
printf("type %u size %u\n", type, this->backends_.size());
auto *pref = new ZephyrPreferenceBackend(type); // NOLINT(cppcoreguidelines-owning-memory)
ESP_LOGD(TAG, "Add new setting %s.", pref->get_key().c_str());
this->backends_.push_back(pref);
return ESPPreferenceObject(pref);
}
bool sync() override {
ESP_LOGD(TAG, "Save settings");
int err = settings_save();
if (err) {
ESP_LOGE(TAG, "Cannot save settings, err: %d", err);
return false;
}
return true;
}
bool reset() override {
ESP_LOGD(TAG, "Reset settings");
for (auto *backend : this->backends_) {
// save empty delete data
backend->data.clear();
}
sync();
return true;
}
protected:
std::vector<ZephyrPreferenceBackend *> backends_;
static int load_setting(const char *name, size_t len, settings_read_cb read_cb, void *cb_arg) {
auto type = parse_hex<uint32_t>(name);
if (!type.has_value()) {
std::string full_name(ESPHOME_SETTINGS_KEY);
full_name += "/";
full_name += name;
// Delete unusable keys. Otherwise it will stay in flash forever.
settings_delete(full_name.c_str());
return 1;
}
std::vector<uint8_t> data(len);
int err = read_cb(cb_arg, data.data(), len);
ESP_LOGD(TAG, "load setting, name: %s(%u), len %u, err %u", name, *type, len, err);
auto *pref = new ZephyrPreferenceBackend(*type, std::move(data)); // NOLINT(cppcoreguidelines-owning-memory)
static_cast<ZephyrPreferences *>(global_preferences)->backends_.push_back(pref);
return 0;
}
static int export_settings(int (*cb)(const char *name, const void *value, size_t val_len)) {
for (auto *backend : static_cast<ZephyrPreferences *>(global_preferences)->backends_) {
auto name = backend->get_key();
int err = cb(name.c_str(), backend->data.data(), backend->data.size());
ESP_LOGD(TAG, "save in flash, name %s, len %u, err %d", name.c_str(), backend->data.size(), err);
}
return 0;
}
};
void setup_preferences() {
auto *prefs = new ZephyrPreferences(); // NOLINT(cppcoreguidelines-owning-memory)
global_preferences = prefs;
prefs->open();
}
} // namespace zephyr
ESPPreferences *global_preferences; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
} // namespace esphome
#endif

View File

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

View File

@ -21,6 +21,7 @@ class Platform(StrEnum):
HOST = "host"
LIBRETINY_OLDSTYLE = "libretiny"
LN882X = "ln882x"
NRF52 = "nrf52"
RP2040 = "rp2040"
RTL87XX = "rtl87xx"
@ -31,6 +32,7 @@ class Framework(StrEnum):
ARDUINO = "arduino"
ESP_IDF = "esp-idf"
NATIVE = "host"
ZEPHYR = "zephyr"
class PlatformFramework(Enum):
@ -47,6 +49,9 @@ class PlatformFramework(Enum):
RTL87XX_ARDUINO = (Platform.RTL87XX, Framework.ARDUINO)
LN882X_ARDUINO = (Platform.LN882X, Framework.ARDUINO)
# Zephyr framework platforms
NRF52_ZEPHYR = (Platform.NRF52, Framework.ZEPHYR)
# Host platform (native)
HOST_NATIVE = (Platform.HOST, Framework.NATIVE)
@ -58,6 +63,7 @@ PLATFORM_ESP8266 = Platform.ESP8266
PLATFORM_HOST = Platform.HOST
PLATFORM_LIBRETINY_OLDSTYLE = Platform.LIBRETINY_OLDSTYLE
PLATFORM_LN882X = Platform.LN882X
PLATFORM_NRF52 = Platform.NRF52
PLATFORM_RP2040 = Platform.RP2040
PLATFORM_RTL87XX = Platform.RTL87XX

View File

@ -21,6 +21,7 @@ from esphome.const import (
PLATFORM_ESP8266,
PLATFORM_HOST,
PLATFORM_LN882X,
PLATFORM_NRF52,
PLATFORM_RP2040,
PLATFORM_RTL87XX,
)
@ -670,6 +671,10 @@ class EsphomeCore:
def is_libretiny(self):
return self.is_bk72xx or self.is_rtl87xx or self.is_ln882x
@property
def is_nrf52(self):
return self.target_platform == PLATFORM_NRF52
@property
def is_host(self):
return self.target_platform == PLATFORM_HOST
@ -686,6 +691,10 @@ class EsphomeCore:
def using_esp_idf(self):
return self.target_framework == "esp-idf"
@property
def using_zephyr(self):
return self.target_framework == "zephyr"
def add_job(self, func, *args, **kwargs) -> None:
self.event_loop.add_job(func, *args, **kwargs)

View File

@ -1,5 +1,6 @@
#pragma once
#include <array>
#include <cmath>
#include <cstdint>
#include <cstring>
@ -678,7 +679,7 @@ class InterruptLock {
~InterruptLock();
protected:
#if defined(USE_ESP8266) || defined(USE_RP2040)
#if defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_ZEPHYR)
uint32_t state_;
#endif
};

View File

@ -0,0 +1,14 @@
binary_sensor:
- platform: gpio
pin: 2
id: gpio_binary_sensor
output:
- platform: gpio
pin: 3
id: gpio_output
switch:
- platform: gpio
pin: 4
id: gpio_switch

View File

@ -0,0 +1,14 @@
binary_sensor:
- platform: gpio
pin: 2
id: gpio_binary_sensor
output:
- platform: gpio
pin: 3
id: gpio_output
switch:
- platform: gpio
pin: 4
id: gpio_switch

View File

@ -0,0 +1,7 @@
esphome:
on_boot:
then:
- logger.log: Hello world
logger:
level: DEBUG

View File

@ -0,0 +1,7 @@
esphome:
on_boot:
then:
- logger.log: Hello world
logger:
level: DEBUG

View File

@ -0,0 +1 @@
time:

View File

@ -0,0 +1 @@
time:

View File

@ -0,0 +1,10 @@
sensor:
- platform: uptime
name: Uptime Sensor
- platform: uptime
name: Uptime Sensor Seconds
type: seconds
text_sensor:
- platform: uptime
name: Uptime Text

View File

@ -0,0 +1,10 @@
sensor:
- platform: uptime
name: Uptime Sensor
- platform: uptime
name: Uptime Sensor Seconds
type: seconds
text_sensor:
- platform: uptime
name: Uptime Text

View File

@ -0,0 +1,16 @@
esphome:
name: componenttestnrf52
friendly_name: $component_name
nrf52:
board: adafruit_itsybitsy_nrf52840
bootloader: adafruit_nrf52_sd140_v6
logger:
level: VERY_VERBOSE
packages:
component_under_test: !include
file: $component_test_file
vars:
component_test_file: $component_test_file

View File

@ -0,0 +1,15 @@
esphome:
name: componenttestnrf52
friendly_name: $component_name
nrf52:
board: adafruit_feather_nrf52840
logger:
level: VERY_VERBOSE
packages:
component_under_test: !include
file: $component_test_file
vars:
component_test_file: $component_test_file