From c835b67bace40e69cecdee15206a8c814e10404b Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 10 May 2023 11:38:18 +1200 Subject: [PATCH] Add host target platform (#4783) Co-authored-by: Otto winter --- CODEOWNERS | 1 + esphome/components/api/api_connection.cpp | 2 + esphome/components/host/__init__.py | 38 +++++++++++ esphome/components/host/const.py | 5 ++ esphome/components/host/core.cpp | 77 +++++++++++++++++++++++ esphome/components/host/gpio.cpp | 59 +++++++++++++++++ esphome/components/host/gpio.h | 37 +++++++++++ esphome/components/host/gpio.py | 73 +++++++++++++++++++++ esphome/components/host/preferences.cpp | 36 +++++++++++ esphome/components/host/preferences.h | 13 ++++ esphome/components/logger/__init__.py | 8 ++- esphome/components/logger/logger.cpp | 10 +++ esphome/components/logger/logger.h | 7 ++- esphome/components/mdns/mdns_host.cpp | 18 ++++++ esphome/components/network/util.cpp | 3 + esphome/components/socket/__init__.py | 1 + esphome/components/socket/headers.h | 7 +++ esphome/config_validation.py | 4 ++ esphome/const.py | 3 +- esphome/core/__init__.py | 4 ++ esphome/core/defines.h | 4 ++ esphome/core/helpers.cpp | 37 +++++++++-- platformio.ini | 9 +++ script/ci-custom.py | 1 + 24 files changed, 448 insertions(+), 9 deletions(-) create mode 100644 esphome/components/host/__init__.py create mode 100644 esphome/components/host/const.py create mode 100644 esphome/components/host/core.cpp create mode 100644 esphome/components/host/gpio.cpp create mode 100644 esphome/components/host/gpio.h create mode 100644 esphome/components/host/gpio.py create mode 100644 esphome/components/host/preferences.cpp create mode 100644 esphome/components/host/preferences.h create mode 100644 esphome/components/mdns/mdns_host.cpp diff --git a/CODEOWNERS b/CODEOWNERS index 410a3b1e3c..de6488c3d3 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -109,6 +109,7 @@ esphome/components/heatpumpir/* @rob-deutsch esphome/components/hitachi_ac424/* @sourabhjaiswal esphome/components/homeassistant/* @OttoWinter esphome/components/honeywellabp/* @RubyBailey +esphome/components/host/* @esphome/core esphome/components/hrxl_maxsonar_wr/* @netmikey esphome/components/hte501/* @Stock-M esphome/components/hydreon_rgxx/* @functionpointer diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index a08cb39a4a..013b46695d 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -978,6 +978,8 @@ DeviceInfoResponse APIConnection::device_info(const DeviceInfoRequest &msg) { resp.manufacturer = "Espressif"; #elif defined(USE_RP2040) resp.manufacturer = "Raspberry Pi"; +#elif defined(USE_HOST) + resp.manufacturer = "Host"; #endif resp.model = ESPHOME_BOARD; #ifdef USE_DEEP_SLEEP diff --git a/esphome/components/host/__init__.py b/esphome/components/host/__init__.py new file mode 100644 index 0000000000..46f763d255 --- /dev/null +++ b/esphome/components/host/__init__.py @@ -0,0 +1,38 @@ +from esphome.const import ( + KEY_CORE, + KEY_FRAMEWORK_VERSION, + KEY_TARGET_FRAMEWORK, + KEY_TARGET_PLATFORM, +) +from esphome.core import CORE +import esphome.config_validation as cv +import esphome.codegen as cg + +from .const import KEY_HOST + +# force import gpio to register pin schema +from .gpio import host_pin_to_code # noqa + + +CODEOWNERS = ["@esphome/core"] +AUTO_LOAD = ["network"] + + +def set_core_data(config): + CORE.data[KEY_HOST] = {} + CORE.data[KEY_CORE][KEY_TARGET_PLATFORM] = "host" + CORE.data[KEY_CORE][KEY_TARGET_FRAMEWORK] = "host" + CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] = cv.Version(1, 0, 0) + return config + + +CONFIG_SCHEMA = cv.All( + cv.Schema({}), + set_core_data, +) + + +async def to_code(config): + cg.add_build_flag("-DUSE_HOST") + cg.add_define("ESPHOME_BOARD", "host") + cg.add_platformio_option("platform", "platformio/native") diff --git a/esphome/components/host/const.py b/esphome/components/host/const.py new file mode 100644 index 0000000000..b6f4c4e277 --- /dev/null +++ b/esphome/components/host/const.py @@ -0,0 +1,5 @@ +import esphome.codegen as cg + +KEY_HOST = "host" + +host_ns = cg.esphome_ns.namespace("host") diff --git a/esphome/components/host/core.cpp b/esphome/components/host/core.cpp new file mode 100644 index 0000000000..164d622dd4 --- /dev/null +++ b/esphome/components/host/core.cpp @@ -0,0 +1,77 @@ +#ifdef USE_HOST + +#include "esphome/core/hal.h" +#include "esphome/core/helpers.h" +#include "preferences.h" + +#include +#include +#include +#include + +namespace esphome { + +void IRAM_ATTR HOT yield() { ::sched_yield(); } +uint32_t IRAM_ATTR HOT millis() { + struct timespec spec; + clock_gettime(CLOCK_MONOTONIC, &spec); + time_t seconds = spec.tv_sec; + uint32_t ms = round(spec.tv_nsec / 1e6); + return ((uint32_t) seconds) * 1000U + ms; +} +void IRAM_ATTR HOT delay(uint32_t ms) { + struct timespec ts; + ts.tv_sec = ms / 1000; + ts.tv_nsec = (ms % 1000) * 1000000; + int res; + do { + res = nanosleep(&ts, &ts); + } while (res != 0 && errno == EINTR); +} +uint32_t IRAM_ATTR HOT micros() { + struct timespec spec; + clock_gettime(CLOCK_MONOTONIC, &spec); + time_t seconds = spec.tv_sec; + uint32_t us = round(spec.tv_nsec / 1e3); + return ((uint32_t) seconds) * 1000000U + us; +} +void IRAM_ATTR HOT delayMicroseconds(uint32_t us) { + struct timespec ts; + ts.tv_sec = us / 1000000U; + ts.tv_nsec = (us % 1000000U) * 1000U; + int res; + do { + res = nanosleep(&ts, &ts); + } while (res != 0 && errno == EINTR); +} +void arch_restart() { exit(0); } +void arch_init() { + // pass +} +void IRAM_ATTR HOT arch_feed_wdt() { + // pass +} + +uint8_t progmem_read_byte(const uint8_t *addr) { return *addr; } +uint32_t arch_get_cpu_cycle_count() { + struct timespec spec; + clock_gettime(CLOCK_MONOTONIC, &spec); + time_t seconds = spec.tv_sec; + uint32_t us = spec.tv_nsec; + return ((uint32_t) seconds) * 1000000000U + us; +} +uint32_t arch_get_cpu_freq_hz() { return 1000000000U; } + +} // namespace esphome + +void setup(); +void loop(); +int main() { + esphome::host::setup_preferences(); + setup(); + while (true) { + loop(); + } +} + +#endif // USE_HOST diff --git a/esphome/components/host/gpio.cpp b/esphome/components/host/gpio.cpp new file mode 100644 index 0000000000..e46f158513 --- /dev/null +++ b/esphome/components/host/gpio.cpp @@ -0,0 +1,59 @@ +#ifdef USE_HOST + +#include "gpio.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace host { + +static const char *const TAG = "host"; + +struct ISRPinArg { + uint8_t pin; + bool inverted; +}; + +ISRInternalGPIOPin HostGPIOPin::to_isr() const { + auto *arg = new ISRPinArg{}; // NOLINT(cppcoreguidelines-owning-memory) + arg->pin = pin_; + arg->inverted = inverted_; + return ISRInternalGPIOPin((void *) arg); +} + +void HostGPIOPin::attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const { + ESP_LOGD(TAG, "Attaching interrupt %p to pin %d and mode %d", func, pin_, (uint32_t) type); +} +void HostGPIOPin::pin_mode(gpio::Flags flags) { ESP_LOGD(TAG, "Setting pin %d mode to %02X", pin_, (uint32_t) flags); } + +std::string HostGPIOPin::dump_summary() const { + char buffer[32]; + snprintf(buffer, sizeof(buffer), "GPIO%u", pin_); + return buffer; +} + +bool HostGPIOPin::digital_read() { return inverted_; } +void HostGPIOPin::digital_write(bool value) { + // pass + ESP_LOGD(TAG, "Setting pin %d to %s", pin_, value != inverted_ ? "HIGH" : "LOW"); +} +void HostGPIOPin::detach_interrupt() const {} + +} // namespace host + +using namespace host; + +bool IRAM_ATTR ISRInternalGPIOPin::digital_read() { + auto *arg = reinterpret_cast(arg_); + return arg->inverted; +} +void IRAM_ATTR ISRInternalGPIOPin::digital_write(bool value) { + // pass +} +void IRAM_ATTR ISRInternalGPIOPin::clear_interrupt() { + auto *arg = reinterpret_cast(arg_); + ESP_LOGD(TAG, "Clearing interrupt for pin %d", arg->pin); +} + +} // namespace esphome + +#endif // USE_HOST diff --git a/esphome/components/host/gpio.h b/esphome/components/host/gpio.h new file mode 100644 index 0000000000..c0920467d6 --- /dev/null +++ b/esphome/components/host/gpio.h @@ -0,0 +1,37 @@ +#pragma once + +#ifdef USE_HOST + +#include "esphome/core/hal.h" + +namespace esphome { +namespace host { + +class HostGPIOPin : 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 host +} // namespace esphome + +#endif // USE_HOST diff --git a/esphome/components/host/gpio.py b/esphome/components/host/gpio.py new file mode 100644 index 0000000000..d523d28ee5 --- /dev/null +++ b/esphome/components/host/gpio.py @@ -0,0 +1,73 @@ +import logging + +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 import pins +import esphome.config_validation as cv +import esphome.codegen as cg + +from .const import host_ns + + +_LOGGER = logging.getLogger(__name__) + + +HostGPIOPin = host_ns.class_("HostGPIOPin", 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 + if value.startswith("GPIO"): + return cv.int_(value[len("GPIO") :].strip()) + return value + + +def validate_gpio_pin(value): + return _translate_pin(value) + + +HOST_PIN_SCHEMA = cv.All( + { + cv.GenerateID(): cv.declare_id(HostGPIOPin), + cv.Required(CONF_NUMBER): validate_gpio_pin, + cv.Optional(CONF_MODE, default={}): cv.Schema( + { + 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("host", HOST_PIN_SCHEMA) +async def host_pin_to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + num = config[CONF_NUMBER] + cg.add(var.set_pin(num)) + cg.add(var.set_inverted(config[CONF_INVERTED])) + cg.add(var.set_flags(pins.gpio_flags_expr(config[CONF_MODE]))) + return var diff --git a/esphome/components/host/preferences.cpp b/esphome/components/host/preferences.cpp new file mode 100644 index 0000000000..bf45893e40 --- /dev/null +++ b/esphome/components/host/preferences.cpp @@ -0,0 +1,36 @@ +#ifdef USE_HOST + +#include "preferences.h" +#include +#include "esphome/core/preferences.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" +#include "esphome/core/defines.h" + +namespace esphome { +namespace host { + +static const char *const TAG = "host.preferences"; + +class HostPreferences : public ESPPreferences { + public: + ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash) override { return {}; } + + ESPPreferenceObject make_preference(size_t length, uint32_t type) override { return {}; } + + bool sync() override { return true; } + bool reset() override { return true; } +}; + +void setup_preferences() { + auto *pref = new HostPreferences(); // NOLINT(cppcoreguidelines-owning-memory) + global_preferences = pref; +} + +} // namespace host + +ESPPreferences *global_preferences; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) + +} // namespace esphome + +#endif // USE_HOST diff --git a/esphome/components/host/preferences.h b/esphome/components/host/preferences.h new file mode 100644 index 0000000000..7462360ec3 --- /dev/null +++ b/esphome/components/host/preferences.h @@ -0,0 +1,13 @@ +#pragma once + +#ifdef USE_HOST + +namespace esphome { +namespace host { + +void setup_preferences(); + +} // namespace host +} // namespace esphome + +#endif // USE_HOST diff --git a/esphome/components/logger/__init__.py b/esphome/components/logger/__init__.py index 9f0fa22fc0..af96b03c8e 100644 --- a/esphome/components/logger/__init__.py +++ b/esphome/components/logger/__init__.py @@ -17,6 +17,9 @@ from esphome.const import ( CONF_TAG, CONF_TRIGGER_ID, CONF_TX_BUFFER_SIZE, + PLATFORM_ESP32, + PLATFORM_ESP8266, + PLATFORM_RP2040, ) from esphome.core import CORE, EsphomeError, Lambda, coroutine_with_priority from esphome.components.esp32 import add_idf_sdkconfig_option, get_esp32_variant @@ -141,7 +144,10 @@ CONFIG_SCHEMA = cv.All( esp8266=UART0, esp32=UART0, rp2040=USB_CDC, - ): uart_selection, + ): cv.All( + cv.only_on([PLATFORM_ESP8266, PLATFORM_ESP32, PLATFORM_RP2040]), + uart_selection, + ), cv.Optional(CONF_LEVEL, default="DEBUG"): is_log_level, cv.Optional(CONF_LOGS, default={}): cv.Schema( { diff --git a/esphome/components/logger/logger.cpp b/esphome/components/logger/logger.cpp index c77e280711..8fd39265fd 100644 --- a/esphome/components/logger/logger.cpp +++ b/esphome/components/logger/logger.cpp @@ -145,6 +145,9 @@ void HOT Logger::log_message_(int level, const char *tag, int offset) { if (xPortGetFreeHeapSize() < 2048) return; #endif +#ifdef USE_HOST + puts(msg); +#endif this->log_callback_.call(level, tag, msg); } @@ -262,7 +265,11 @@ void Logger::set_baud_rate(uint32_t baud_rate) { this->baud_rate_ = baud_rate; } void Logger::set_log_level(const std::string &tag, int log_level) { this->log_levels_.push_back(LogLevelOverride{tag, log_level}); } + +#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) UARTSelection Logger::get_uart() const { return this->uart_; } +#endif + void Logger::add_on_log_callback(std::function &&callback) { this->log_callback_.add(std::move(callback)); } @@ -294,7 +301,10 @@ void Logger::dump_config() { ESP_LOGCONFIG(TAG, "Logger:"); ESP_LOGCONFIG(TAG, " Level: %s", LOG_LEVELS[ESPHOME_LOG_LEVEL]); ESP_LOGCONFIG(TAG, " Log Baud Rate: %" PRIu32, this->baud_rate_); +#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) ESP_LOGCONFIG(TAG, " Hardware UART: %s", UART_SELECTIONS[this->uart_]); +#endif + for (auto &it : this->log_levels_) { ESP_LOGCONFIG(TAG, " Level for '%s': %s", it.tag.c_str(), LOG_LEVELS[it.level]); } diff --git a/esphome/components/logger/logger.h b/esphome/components/logger/logger.h index 88d5e8ee97..54a5236cd8 100644 --- a/esphome/components/logger/logger.h +++ b/esphome/components/logger/logger.h @@ -25,6 +25,7 @@ namespace esphome { namespace logger { +#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) /** Enum for logging UART selection * * Advanced configuration (pin selection, etc) is not supported. @@ -52,6 +53,7 @@ enum UARTSelection { UART_SELECTION_USB_CDC, #endif // USE_RP2040 }; +#endif // USE_ESP32 || USE_ESP8266 class Logger : public Component { public: @@ -66,10 +68,11 @@ class Logger : public Component { #ifdef USE_ESP_IDF uart_port_t get_uart_num() const { return uart_num_; } #endif - +#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) void set_uart_selection(UARTSelection uart_selection) { uart_ = uart_selection; } /// Get the UART used by the logger. UARTSelection get_uart() const; +#endif /// Set the log level of the specified tag. void set_log_level(const std::string &tag, int log_level); @@ -139,7 +142,9 @@ class Logger : public Component { char *tx_buffer_{nullptr}; int tx_buffer_at_{0}; int tx_buffer_size_{0}; +#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) UARTSelection uart_{UART_SELECTION_UART0}; +#endif #ifdef USE_ARDUINO Stream *hw_serial_{nullptr}; #endif diff --git a/esphome/components/mdns/mdns_host.cpp b/esphome/components/mdns/mdns_host.cpp new file mode 100644 index 0000000000..3f89146f02 --- /dev/null +++ b/esphome/components/mdns/mdns_host.cpp @@ -0,0 +1,18 @@ +#ifdef USE_HOST + +#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_(); } + +void MDNSComponent::on_shutdown() {} + +} // namespace mdns +} // namespace esphome + +#endif diff --git a/esphome/components/network/util.cpp b/esphome/components/network/util.cpp index f7ac6b543e..941102d6c1 100644 --- a/esphome/components/network/util.cpp +++ b/esphome/components/network/util.cpp @@ -23,6 +23,9 @@ bool is_connected() { return wifi::global_wifi_component->is_connected(); #endif +#ifdef USE_HOST + return true; // Assume its connected +#endif return false; } diff --git a/esphome/components/socket/__init__.py b/esphome/components/socket/__init__.py index 81203fdc31..1757ec4668 100644 --- a/esphome/components/socket/__init__.py +++ b/esphome/components/socket/__init__.py @@ -14,6 +14,7 @@ CONFIG_SCHEMA = cv.Schema( esp8266=IMPLEMENTATION_LWIP_TCP, esp32=IMPLEMENTATION_BSD_SOCKETS, rp2040=IMPLEMENTATION_LWIP_TCP, + host=IMPLEMENTATION_BSD_SOCKETS, ): cv.one_of( IMPLEMENTATION_LWIP_TCP, IMPLEMENTATION_BSD_SOCKETS, lower=True, space="_" ), diff --git a/esphome/components/socket/headers.h b/esphome/components/socket/headers.h index 20d8fdb8c9..1922885ac0 100644 --- a/esphome/components/socket/headers.h +++ b/esphome/components/socket/headers.h @@ -130,6 +130,13 @@ struct iovec { #include #include +#ifdef USE_HOST +#include +#include +#include +#include +#endif // USE_HOST + #ifdef USE_ARDUINO // arduino-esp32 declares a global var called INADDR_NONE which is replaced // by the define diff --git a/esphome/config_validation.py b/esphome/config_validation.py index 4b822b46c9..2482e5471c 100644 --- a/esphome/config_validation.py +++ b/esphome/config_validation.py @@ -1455,6 +1455,7 @@ class SplitDefault(Optional): esp32_arduino=vol.UNDEFINED, esp32_idf=vol.UNDEFINED, rp2040=vol.UNDEFINED, + host=vol.UNDEFINED, ): super().__init__(key) self._esp8266_default = vol.default_factory(esp8266) @@ -1465,6 +1466,7 @@ class SplitDefault(Optional): esp32_idf if esp32 is vol.UNDEFINED else esp32 ) self._rp2040_default = vol.default_factory(rp2040) + self._host_default = vol.default_factory(host) @property def default(self): @@ -1476,6 +1478,8 @@ class SplitDefault(Optional): return self._esp32_idf_default if CORE.is_rp2040: return self._rp2040_default + if CORE.is_host: + return self._host_default raise NotImplementedError @default.setter diff --git a/esphome/const.py b/esphome/const.py index 79f7f6cf68..46e5545078 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -7,8 +7,9 @@ ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" PLATFORM_ESP32 = "esp32" PLATFORM_ESP8266 = "esp8266" PLATFORM_RP2040 = "rp2040" +PLATFORM_HOST = "host" -TARGET_PLATFORMS = [PLATFORM_ESP32, PLATFORM_ESP8266, PLATFORM_RP2040] +TARGET_PLATFORMS = [PLATFORM_ESP32, PLATFORM_ESP8266, PLATFORM_RP2040, PLATFORM_HOST] SOURCE_FILE_EXTENSIONS = {".cpp", ".hpp", ".h", ".c", ".tcc", ".ino"} HEADER_FILE_EXTENSIONS = {".h", ".hpp", ".tcc"} diff --git a/esphome/core/__init__.py b/esphome/core/__init__.py index 1866f9c9f5..891936adc3 100644 --- a/esphome/core/__init__.py +++ b/esphome/core/__init__.py @@ -602,6 +602,10 @@ class EsphomeCore: def is_rp2040(self): return self.target_platform == "rp2040" + @property + def is_host(self): + return self.target_platform == "host" + @property def target_framework(self): return self.data[KEY_CORE][KEY_TARGET_FRAMEWORK] diff --git a/esphome/core/defines.h b/esphome/core/defines.h index ef08eecbe5..64edabc878 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -102,6 +102,10 @@ #endif +#ifdef USE_HOST +#define USE_SOCKET_IMPL_BSD_SOCKETS +#endif + // Disabled feature flags //#define USE_BSEC // Requires a library with proprietary license. diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index 7e8ba41987..4c6ee84dde 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -2,13 +2,14 @@ #include "esphome/core/defines.h" #include "esphome/core/hal.h" +#include "esphome/core/log.h" -#include #include #include #include -#include #include +#include +#include #if defined(USE_ESP8266) #include @@ -18,17 +19,20 @@ #elif defined(USE_ESP32_FRAMEWORK_ARDUINO) #include #elif defined(USE_ESP_IDF) +#include +#include #include "esp_mac.h" #include "esp_random.h" #include "esp_system.h" -#include -#include #elif defined(USE_RP2040) #if defined(USE_WIFI) #include #endif #include #include +#elif defined(USE_HOST) +#include +#include #endif #ifdef USE_ESP32_IGNORE_EFUSE_MAC_CRC @@ -38,6 +42,8 @@ namespace esphome { +static const char *const TAG = "helpers"; + // STL backports #if _GLIBCXX_RELEASE < 7 @@ -106,6 +112,11 @@ uint32_t random_uint32() { result |= rosc_hw->randombit; } return result; +#elif defined(USE_HOST) + std::random_device dev; + std::mt19937 rng(dev()); + std::uniform_int_distribution dist(0, std::numeric_limits::max()); + return dist(rng); #else #error "No random source available for this configuration." #endif @@ -127,6 +138,19 @@ bool random_bytes(uint8_t *data, size_t len) { *data++ = result; } return true; +#elif defined(USE_HOST) + FILE *fp = fopen("/dev/urandom", "r"); + if (fp == nullptr) { + ESP_LOGW(TAG, "Could not open /dev/urandom, errno=%d", errno); + exit(1); + } + size_t read = fread(data, 1, len, fp); + if (read != len) { + ESP_LOGW(TAG, "Not enough data from /dev/urandom"); + exit(1); + } + fclose(fp); + return true; #else #error "No random source available for this configuration." #endif @@ -145,7 +169,7 @@ std::string str_truncate(const std::string &str, size_t length) { return str.length() > length ? str.substr(0, length) : str; } std::string str_until(const char *str, char ch) { - char *pos = strchr(str, ch); + const char *pos = strchr(str, ch); return pos == nullptr ? std::string(str) : std::string(str, pos - str); } std::string str_until(const std::string &str, char ch) { return str.substr(0, str.find(ch)); } @@ -395,7 +419,7 @@ void hsv_to_rgb(int hue, float saturation, float value, float &red, float &green } // System APIs -#if defined(USE_ESP8266) || defined(USE_RP2040) +#if defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_HOST) // ESP8266 doesn't have mutexes, but that shouldn't be an issue as it's single-core and non-preemptive OS. Mutex::Mutex() {} void Mutex::lock() {} @@ -469,6 +493,7 @@ void set_mac_address(uint8_t *mac) { esp_base_mac_addr_set(mac); } void delay_microseconds_safe(uint32_t us) { // avoids CPU locks that could trigger WDT or affect WiFi/BT stability uint32_t start = micros(); + const uint32_t lag = 5000; // microseconds, specifies the maximum time for a CPU busy-loop. // it must be larger than the worst-case duration of a delay(1) call (hardware tasks) // 5ms is conservative, it could be reduced when exact BT/WiFi stack delays are known diff --git a/platformio.ini b/platformio.ini index 19ba9c2790..6a7dd467b5 100644 --- a/platformio.ini +++ b/platformio.ini @@ -251,3 +251,12 @@ board = rpipico build_flags = ${common:rp2040-arduino.build_flags} ${flags:runtime.build_flags} + +[env:host] +extends = common +platform = platformio/native +lib_deps = + esphome/noise-c@0.1.1 ; used by api +build_flags = + ${common.build_flags} + -DUSE_HOST diff --git a/script/ci-custom.py b/script/ci-custom.py index f95039576b..20f607f987 100755 --- a/script/ci-custom.py +++ b/script/ci-custom.py @@ -535,6 +535,7 @@ def lint_relative_py_import(fname): "esphome/components/esp32/core.cpp", "esphome/components/esp8266/core.cpp", "esphome/components/rp2040/core.cpp", + "esphome/components/host/core.cpp", ], ) def lint_namespace(fname, content):