Compare commits

...

3 Commits

Author SHA1 Message Date
Jesse Hills
7f0dc7b07b
Merge branch 'dev' into host-target 2022-03-08 08:21:39 +13:00
Jesse Hills
1ab4928959
Merge branch 'dev' into host-target 2021-10-12 12:07:45 +13:00
Otto winter
52b86e3440
Add host target platform 2021-09-20 16:22:04 +02:00
19 changed files with 412 additions and 11 deletions

View File

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

View File

@ -0,0 +1,5 @@
import esphome.codegen as cg
KEY_HOST = "host"
host_ns = cg.esphome_ns.namespace("host")

View File

@ -0,0 +1,74 @@
#ifdef USE_HOST
#include "esphome/core/hal.h"
#include "esphome/core/helpers.h"
#include "preferences.h"
#include <sched.h>
#include <time.h>
#include <cmath>
#include <cstdlib>
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 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

View File

@ -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<ISRPinArg *>(arg_);
return arg->inverted;
}
void IRAM_ATTR ISRInternalGPIOPin::digital_write(bool value) {
// pass
}
void IRAM_ATTR ISRInternalGPIOPin::clear_interrupt() {
auto *arg = reinterpret_cast<ISRPinArg *>(arg_);
ESP_LOGD(TAG, "Clearing interrupt for pin %d", arg->pin);
}
} // namespace esphome
#endif // USE_HOST

View File

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

View File

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

View File

@ -0,0 +1,33 @@
#ifdef USE_HOST
#include "preferences.h"
#include <cstring>
#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 {}; }
};
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

View File

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

View File

@ -113,7 +113,9 @@ CONFIG_SCHEMA = cv.All(
cv.Optional(CONF_BAUD_RATE, default=115200): cv.positive_int,
cv.Optional(CONF_TX_BUFFER_SIZE, default=512): cv.validate_bytes,
cv.Optional(CONF_DEASSERT_RTS_DTR, default=False): cv.boolean,
cv.Optional(CONF_HARDWARE_UART, default="UART0"): uart_selection,
cv.SplitDefault(CONF_HARDWARE_UART, esp32="UART0", esp8266="UART0"): cv.All(
cv.only_on(["esp32", "esp8266"]), uart_selection
),
cv.Optional(CONF_LEVEL, default="DEBUG"): is_log_level,
cv.Optional(CONF_LOGS, default={}): cv.Schema(
{
@ -138,12 +140,9 @@ CONFIG_SCHEMA = cv.All(
@coroutine_with_priority(90.0)
async def to_code(config):
baud_rate = config[CONF_BAUD_RATE]
rhs = Logger.new(
baud_rate,
config[CONF_TX_BUFFER_SIZE],
HARDWARE_UART_TO_UART_SELECTION[config[CONF_HARDWARE_UART]],
)
log = cg.Pvariable(config[CONF_ID], rhs)
log = cg.new_Pvariable(config[CONF_ID], baud_rate, config[CONF_TX_BUFFER_SIZE])
if CONF_HARDWARE_UART in config:
cg.add(log.set_uart_selection(config[CONF_HARDWARE_UART]))
cg.add(log.pre_setup())
for tag, level in config[CONF_LOGS].items():

View File

@ -130,12 +130,14 @@ 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);
}
Logger::Logger(uint32_t baud_rate, size_t tx_buffer_size, UARTSelection uart)
: baud_rate_(baud_rate), tx_buffer_size_(tx_buffer_size), uart_(uart) {
Logger::Logger(uint32_t baud_rate, size_t tx_buffer_size) : baud_rate_(baud_rate), tx_buffer_size_(tx_buffer_size) {
// add 1 to buffer size for null terminator
this->tx_buffer_ = new char[this->tx_buffer_size_ + 1]; // NOLINT
}
@ -217,7 +219,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)
UARTSelection Logger::get_uart() const { return this->uart_; }
#endif
void Logger::add_on_log_callback(std::function<void(int, const char *, const char *)> &&callback) {
this->log_callback_.add(std::move(callback));
}
@ -233,7 +239,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: %u", this->baud_rate_);
#if defined(USE_ESP32) || defined(USE_ESP8266)
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]);
}

View File

@ -17,6 +17,7 @@ namespace esphome {
namespace logger {
#if defined(USE_ESP32) || defined(USE_ESP8266)
/** Enum for logging UART selection
*
* Advanced configuration (pin selection, etc) is not supported.
@ -31,10 +32,11 @@ enum UARTSelection {
UART_SELECTION_UART0_SWAP,
#endif
};
#endif // USE_ESP32 || USE_ESP8266
class Logger : public Component {
public:
explicit Logger(uint32_t baud_rate, size_t tx_buffer_size, UARTSelection uart);
explicit Logger(uint32_t baud_rate, size_t tx_buffer_size);
/// Manually set the baud rate for serial, set to 0 to disable.
void set_baud_rate(uint32_t baud_rate);
@ -46,8 +48,11 @@ class Logger : public Component {
uart_port_t get_uart_num() const { return uart_num_; }
#endif
#if defined(USE_ESP32) || defined(USE_ESP8266)
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);
@ -117,7 +122,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)
UARTSelection uart_{UART_SELECTION_UART0};
#endif
#ifdef USE_ARDUINO
HardwareSerial *hw_serial_{nullptr};
#endif

View File

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

View File

@ -119,6 +119,12 @@ struct iovec {
#include <sys/uio.h>
#include <unistd.h>
#ifdef USE_HOST
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#endif // USE_HOST
#ifdef USE_ARDUINO
// arduino-esp32 declares a global var called INADDR_NONE which is replaced
// by the define

View File

@ -1426,6 +1426,7 @@ class SplitDefault(Optional):
esp32=vol.UNDEFINED,
esp32_arduino=vol.UNDEFINED,
esp32_idf=vol.UNDEFINED,
host=vol.UNDEFINED,
):
super().__init__(key)
self._esp8266_default = vol.default_factory(esp8266)
@ -1435,6 +1436,7 @@ class SplitDefault(Optional):
self._esp32_idf_default = vol.default_factory(
esp32_idf if esp32 is vol.UNDEFINED else esp32
)
self._host_default = vol.default_factory(host)
@property
def default(self):
@ -1444,6 +1446,8 @@ class SplitDefault(Optional):
return self._esp32_arduino_default
if CORE.is_esp32 and CORE.using_esp_idf:
return self._esp32_idf_default
if CORE.is_host:
return self._host_default
raise NotImplementedError
@default.setter

View File

@ -4,10 +4,13 @@ __version__ = "2022.3.0-dev"
ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
TARGET_FRAMEWORKS = ["arduino", "esp-idf"]
PLATFORM_ESP32 = "esp32"
PLATFORM_ESP8266 = "esp8266"
PLATFORM_HOST = "host"
TARGET_PLATFORMS = [PLATFORM_ESP32, PLATFORM_ESP8266]
TARGET_PLATFORMS = [PLATFORM_ESP32, PLATFORM_ESP8266, PLATFORM_HOST]
SOURCE_FILE_EXTENSIONS = {".cpp", ".hpp", ".h", ".c", ".tcc", ".ino"}
HEADER_FILE_EXTENSIONS = {".h", ".hpp", ".tcc"}

View File

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

View File

@ -83,6 +83,10 @@
#define USE_SOCKET_IMPL_LWIP_TCP
#endif
#ifdef USE_HOST
#define USE_SOCKET_IMPL_BSD_SOCKETS
#endif
// Disabled feature flags
//#define USE_BSEC // Requires a library with proprietary license.

View File

@ -21,6 +21,10 @@
#include "esp_system.h"
#include <freertos/FreeRTOS.h>
#include <freertos/portmacro.h>
#elif defined(USE_HOST)
#include <cstdio>
#include <random>
#include <limits>
#endif
#ifdef USE_ESP32_IGNORE_EFUSE_MAC_CRC
@ -76,6 +80,11 @@ uint32_t random_uint32() {
return esp_random();
#elif defined(USE_ESP8266)
return os_random();
#elif defined(USE_HOST)
std::random_device dev;
std::mt19937 rng(dev());
std::uniform_int_distribution<uint32_t> dist(0, std::numeric_limits<uint32_t>::max());
return dist(rng);
#else
#error "No random source available for this configuration."
#endif
@ -87,6 +96,19 @@ bool random_bytes(uint8_t *data, size_t len) {
return true;
#elif defined(USE_ESP8266)
return os_get_random(data, len) == 0;
#elif defined(USE_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

View File

@ -220,3 +220,13 @@ board_build.esp-idf.sdkconfig_path = .temp/sdkconfig-esp32s2-idf-tidy
build_flags =
${common:esp32-idf.build_flags}
${flags:clangtidy.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
${flags:runtime.build_flags}