mirror of
https://github.com/Threnklyn/esphome-dev.git
synced 2026-06-03 19:38:30 +02:00
🏗 Merge C++ into python codebase (#504)
## Description: Move esphome-core codebase into esphome (and a bunch of other refactors). See https://github.com/esphome/feature-requests/issues/97 Yes this is a shit ton of work and no there's no way to automate it :( But it will be worth it 👍 Progress: - Core support (file copy etc): 80% - Base Abstractions (light, switch): ~50% - Integrations: ~10% - Working? Yes, (but only with ported components). Other refactors: - Moves all codegen related stuff into a single class: `esphome.codegen` (imported as `cg`) - Rework coroutine syntax - Move from `component/platform.py` to `domain/component.py` structure as with HA - Move all defaults out of C++ and into config validation. - Remove `make_...` helpers from Application class. Reason: Merge conflicts with every single new integration. - Pointer Variables are stored globally instead of locally in setup(). Reason: stack size limit. Future work: - Rework const.py - Move all `CONF_...` into a conf class (usage `conf.UPDATE_INTERVAL` vs `CONF_UPDATE_INTERVAL`). Reason: Less convoluted import block - Enable loading from `custom_components` folder. **Related issue (if applicable):** https://github.com/esphome/feature-requests/issues/97 **Pull request in [esphome-docs](https://github.com/esphome/esphome-docs) with documentation (if applicable):** esphome/esphome-docs#<esphome-docs PR number goes here> ## Checklist: - [ ] The code change is tested and works locally. - [ ] Tests have been added to verify that the new code works (under `tests/` folder). If user exposed functionality or configuration variables are added/changed: - [ ] Documentation added/updated in [esphomedocs](https://github.com/OttoWinter/esphomedocs).
This commit is contained in:
@@ -0,0 +1,185 @@
|
||||
import re
|
||||
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.automation import ACTION_REGISTRY, LambdaAction
|
||||
from esphome.const import CONF_ARGS, CONF_BAUD_RATE, CONF_FORMAT, CONF_HARDWARE_UART, CONF_ID, \
|
||||
CONF_LEVEL, CONF_LOGS, CONF_TAG, CONF_TX_BUFFER_SIZE
|
||||
from esphome.core import CORE, EsphomeError, Lambda, coroutine_with_priority
|
||||
from esphome.py_compat import text_type
|
||||
|
||||
logger_ns = cg.esphome_ns.namespace('logger')
|
||||
LOG_LEVELS = {
|
||||
'NONE': cg.global_ns.ESPHOME_LOG_LEVEL_NONE,
|
||||
'ERROR': cg.global_ns.ESPHOME_LOG_LEVEL_ERROR,
|
||||
'WARN': cg.global_ns.ESPHOME_LOG_LEVEL_WARN,
|
||||
'INFO': cg.global_ns.ESPHOME_LOG_LEVEL_INFO,
|
||||
'DEBUG': cg.global_ns.ESPHOME_LOG_LEVEL_DEBUG,
|
||||
'VERBOSE': cg.global_ns.ESPHOME_LOG_LEVEL_VERBOSE,
|
||||
'VERY_VERBOSE': cg.global_ns.ESPHOME_LOG_LEVEL_VERY_VERBOSE,
|
||||
}
|
||||
|
||||
LOG_LEVEL_TO_ESP_LOG = {
|
||||
'ERROR': cg.global_ns.ESP_LOGE,
|
||||
'WARN': cg.global_ns.ESP_LOGW,
|
||||
'INFO': cg.global_ns.ESP_LOGI,
|
||||
'DEBUG': cg.global_ns.ESP_LOGD,
|
||||
'VERBOSE': cg.global_ns.ESP_LOGV,
|
||||
'VERY_VERBOSE': cg.global_ns.ESP_LOGVV,
|
||||
}
|
||||
|
||||
LOG_LEVEL_SEVERITY = ['NONE', 'ERROR', 'WARN', 'INFO', 'DEBUG', 'VERBOSE', 'VERY_VERBOSE']
|
||||
|
||||
UART_SELECTION_ESP32 = ['UART0', 'UART1', 'UART2']
|
||||
|
||||
UART_SELECTION_ESP8266 = ['UART0', 'UART0_SWAP', 'UART1']
|
||||
|
||||
HARDWARE_UART_TO_UART_SELECTION = {
|
||||
'UART0': logger_ns.UART_SELECTION_UART0,
|
||||
'UART0_SWAP': logger_ns.UART_SELECTION_UART0_SWAP,
|
||||
'UART1': logger_ns.UART_SELECTION_UART1,
|
||||
'UART2': logger_ns.UART_SELECTION_UART2,
|
||||
}
|
||||
|
||||
HARDWARE_UART_TO_SERIAL = {
|
||||
'UART0': cg.global_ns.Serial,
|
||||
'UART0_SWAP': cg.global_ns.Serial,
|
||||
'UART1': cg.global_ns.Serial1,
|
||||
'UART2': cg.global_ns.Serial2,
|
||||
}
|
||||
|
||||
is_log_level = cv.one_of(*LOG_LEVELS, upper=True)
|
||||
|
||||
|
||||
def uart_selection(value):
|
||||
if CORE.is_esp32:
|
||||
return cv.one_of(*UART_SELECTION_ESP32, upper=True)(value)
|
||||
if CORE.is_esp8266:
|
||||
return cv.one_of(*UART_SELECTION_ESP8266, upper=True)(value)
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
def validate_local_no_higher_than_global(value):
|
||||
global_level = value.get(CONF_LEVEL, 'DEBUG')
|
||||
for tag, level in value.get(CONF_LOGS, {}).items():
|
||||
if LOG_LEVEL_SEVERITY.index(level) > LOG_LEVEL_SEVERITY.index(global_level):
|
||||
raise EsphomeError(u"The local log level {} for {} must be less severe than the "
|
||||
u"global log level {}.".format(level, tag, global_level))
|
||||
return value
|
||||
|
||||
|
||||
Logger = logger_ns.class_('Logger', cg.Component)
|
||||
|
||||
CONFIG_SCHEMA = cv.All(cv.Schema({
|
||||
cv.GenerateID(): cv.declare_variable_id(Logger),
|
||||
cv.Optional(CONF_BAUD_RATE, default=115200): cv.positive_int,
|
||||
cv.Optional(CONF_TX_BUFFER_SIZE, default=512): cv.validate_bytes,
|
||||
cv.Optional(CONF_HARDWARE_UART, default='UART0'): uart_selection,
|
||||
cv.Optional(CONF_LEVEL, default='DEBUG'): is_log_level,
|
||||
cv.Optional(CONF_LOGS, default={}): cv.Schema({
|
||||
cv.string: is_log_level,
|
||||
})
|
||||
}).extend(cv.COMPONENT_SCHEMA), validate_local_no_higher_than_global)
|
||||
|
||||
|
||||
@coroutine_with_priority(90.0)
|
||||
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)
|
||||
cg.add(log.pre_setup())
|
||||
|
||||
for tag, level in config[CONF_LOGS].items():
|
||||
cg.add(log.set_log_level(tag, LOG_LEVELS[level]))
|
||||
|
||||
level = config[CONF_LEVEL]
|
||||
cg.add_define('USE_LOGGER')
|
||||
this_severity = LOG_LEVEL_SEVERITY.index(level)
|
||||
cg.add_build_flag('-DESPHOME_LOG_LEVEL={}'.format(LOG_LEVELS[level]))
|
||||
|
||||
verbose_severity = LOG_LEVEL_SEVERITY.index('VERBOSE')
|
||||
very_verbose_severity = LOG_LEVEL_SEVERITY.index('VERY_VERBOSE')
|
||||
is_at_least_verbose = this_severity >= verbose_severity
|
||||
is_at_least_very_verbose = this_severity >= very_verbose_severity
|
||||
has_serial_logging = baud_rate != 0
|
||||
|
||||
if CORE.is_esp8266 and has_serial_logging and is_at_least_verbose:
|
||||
debug_serial_port = HARDWARE_UART_TO_SERIAL[config.get(CONF_HARDWARE_UART)]
|
||||
cg.add_build_flag("-DDEBUG_ESP_PORT{}".format(debug_serial_port))
|
||||
cg.add_build_flag("-DLWIP_DEBUG")
|
||||
DEBUG_COMPONENTS = {
|
||||
'HTTP_CLIENT',
|
||||
'HTTP_SERVER',
|
||||
'HTTP_UPDATE',
|
||||
'OTA',
|
||||
'SSL',
|
||||
'TLS_MEM',
|
||||
'UPDATER',
|
||||
'WIFI',
|
||||
}
|
||||
for comp in DEBUG_COMPONENTS:
|
||||
cg.add_build_flag("-DDEBUG_ESP_{}".format(comp))
|
||||
if CORE.is_esp32 and is_at_least_verbose:
|
||||
cg.add_build_flag('-DCORE_DEBUG_LEVEL=5')
|
||||
if CORE.is_esp32 and is_at_least_very_verbose:
|
||||
cg.add_build_flag('-DENABLE_I2C_DEBUG_BUFFER')
|
||||
if CORE.is_esp8266:
|
||||
cg.add_build_flag('-DUSE_STORE_LOG_STR_IN_FLASH')
|
||||
|
||||
# Register at end for safe mode
|
||||
yield cg.register_component(log, config)
|
||||
|
||||
|
||||
def maybe_simple_message(schema):
|
||||
def validator(value):
|
||||
if isinstance(value, dict):
|
||||
return cv.Schema(schema)(value)
|
||||
return cv.Schema(schema)({CONF_FORMAT: value})
|
||||
|
||||
return validator
|
||||
|
||||
|
||||
def validate_printf(value):
|
||||
# https://stackoverflow.com/questions/30011379/how-can-i-parse-a-c-format-string-in-python
|
||||
# pylint: disable=anomalous-backslash-in-string
|
||||
cfmt = u"""\
|
||||
( # start of capture group 1
|
||||
% # literal "%"
|
||||
(?: # first option
|
||||
(?:[-+0 #]{0,5}) # optional flags
|
||||
(?:\d+|\*)? # width
|
||||
(?:\.(?:\d+|\*))? # precision
|
||||
(?:h|l|ll|w|I|I32|I64)? # size
|
||||
[cCdiouxXeEfgGaAnpsSZ] # type
|
||||
) | # OR
|
||||
%%) # literal "%%"
|
||||
""" # noqa
|
||||
matches = re.findall(cfmt, value[CONF_FORMAT], flags=re.X)
|
||||
if len(matches) != len(value[CONF_ARGS]):
|
||||
raise cv.Invalid(u"Found {} printf-patterns ({}), but {} args were given!"
|
||||
u"".format(len(matches), u', '.join(matches), len(value[CONF_ARGS])))
|
||||
return value
|
||||
|
||||
|
||||
CONF_LOGGER_LOG = 'logger.log'
|
||||
LOGGER_LOG_ACTION_SCHEMA = cv.All(maybe_simple_message({
|
||||
cv.Required(CONF_FORMAT): cv.string,
|
||||
cv.Optional(CONF_ARGS, default=list): cv.ensure_list(cv.lambda_),
|
||||
cv.Optional(CONF_LEVEL, default="DEBUG"): cv.one_of(*LOG_LEVEL_TO_ESP_LOG, upper=True),
|
||||
cv.Optional(CONF_TAG, default="main"): cv.string,
|
||||
}), validate_printf)
|
||||
|
||||
|
||||
@ACTION_REGISTRY.register(CONF_LOGGER_LOG, LOGGER_LOG_ACTION_SCHEMA)
|
||||
def logger_log_action_to_code(config, action_id, template_arg, args):
|
||||
esp_log = LOG_LEVEL_TO_ESP_LOG[config[CONF_LEVEL]]
|
||||
args_ = [cg.RawExpression(text_type(x)) for x in config[CONF_ARGS]]
|
||||
|
||||
text = text_type(cg.statement(esp_log(config[CONF_TAG], config[CONF_FORMAT], *args_)))
|
||||
|
||||
lambda_ = yield cg.process_lambda(Lambda(text), args, return_type=cg.void)
|
||||
rhs = LambdaAction.new(template_arg, lambda_)
|
||||
type = LambdaAction.template(template_arg)
|
||||
yield cg.Pvariable(action_id, rhs, type=type)
|
||||
@@ -0,0 +1,149 @@
|
||||
#include "esphome/components/logger/logger.h"
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#include <esp_log.h>
|
||||
#endif
|
||||
#include <HardwareSerial.h>
|
||||
|
||||
namespace esphome {
|
||||
namespace logger {
|
||||
|
||||
static const char *TAG = "logger";
|
||||
|
||||
int HOT Logger::log_vprintf_(int level, const char *tag, const char *format, va_list args) { // NOLINT
|
||||
if (level > this->level_for(tag))
|
||||
return 0;
|
||||
|
||||
int ret = vsnprintf(this->tx_buffer_.data(), this->tx_buffer_.capacity(), format, args);
|
||||
this->log_message_(level, tag, this->tx_buffer_.data(), ret);
|
||||
return ret;
|
||||
}
|
||||
#ifdef USE_STORE_LOG_STR_IN_FLASH
|
||||
int Logger::log_vprintf_(int level, const char *tag, const __FlashStringHelper *format, va_list args) { // NOLINT
|
||||
if (level > this->level_for(tag))
|
||||
return 0;
|
||||
|
||||
// copy format string
|
||||
const char *format_pgm_p = (PGM_P) format;
|
||||
size_t len = 0;
|
||||
char *write = this->tx_buffer_.data();
|
||||
char ch = '.';
|
||||
while (len < this->tx_buffer_.capacity() && ch != '\0') {
|
||||
*write++ = ch = pgm_read_byte(format_pgm_p++);
|
||||
len++;
|
||||
}
|
||||
if (len == this->tx_buffer_.capacity())
|
||||
return -1;
|
||||
|
||||
// now apply vsnprintf
|
||||
size_t offset = len + 1;
|
||||
size_t remaining = this->tx_buffer_.capacity() - offset;
|
||||
char *msg = this->tx_buffer_.data() + offset;
|
||||
int ret = vsnprintf(msg, remaining, this->tx_buffer_.data(), args);
|
||||
this->log_message_(level, tag, msg, ret);
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
|
||||
int HOT Logger::level_for(const char *tag) {
|
||||
// Uses std::vector<> for low memory footprint, though the vector
|
||||
// could be sorted to minimize lookup times. This feature isn't used that
|
||||
// much anyway so it doesn't matter too much.
|
||||
for (auto &it : this->log_levels_) {
|
||||
if (it.tag == tag) {
|
||||
return it.level;
|
||||
}
|
||||
}
|
||||
return this->global_log_level_;
|
||||
}
|
||||
void HOT Logger::log_message_(int level, const char *tag, char *msg, int ret) {
|
||||
if (ret <= 0)
|
||||
return;
|
||||
// remove trailing newline
|
||||
if (msg[ret - 1] == '\n') {
|
||||
msg[ret - 1] = '\0';
|
||||
}
|
||||
if (this->baud_rate_ > 0)
|
||||
this->hw_serial_->println(msg);
|
||||
this->log_callback_.call(level, tag, msg);
|
||||
}
|
||||
|
||||
Logger::Logger(uint32_t baud_rate, size_t tx_buffer_size, UARTSelection uart) : baud_rate_(baud_rate), uart_(uart) {
|
||||
this->set_tx_buffer_size(tx_buffer_size);
|
||||
}
|
||||
|
||||
void Logger::pre_setup() {
|
||||
if (this->baud_rate_ > 0) {
|
||||
switch (this->uart_) {
|
||||
case UART_SELECTION_UART0:
|
||||
#ifdef ARDUINO_ARCH_ESP8266
|
||||
case UART_SELECTION_UART0_SWAP:
|
||||
#endif
|
||||
this->hw_serial_ = &Serial;
|
||||
break;
|
||||
case UART_SELECTION_UART1:
|
||||
this->hw_serial_ = &Serial1;
|
||||
break;
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
case UART_SELECTION_UART2:
|
||||
this->hw_serial_ = &Serial2;
|
||||
break;
|
||||
#endif
|
||||
}
|
||||
|
||||
this->hw_serial_->begin(this->baud_rate_);
|
||||
#ifdef ARDUINO_ARCH_ESP8266
|
||||
if (this->uart_ == UART_SELECTION_UART0_SWAP) {
|
||||
this->hw_serial_->swap();
|
||||
}
|
||||
this->hw_serial_->setDebugOutput(this->global_log_level_ >= ESPHOME_LOG_LEVEL_VERBOSE);
|
||||
#endif
|
||||
}
|
||||
#ifdef ARDUINO_ARCH_ESP8266
|
||||
else {
|
||||
uart_set_debug(UART_NO);
|
||||
}
|
||||
#endif
|
||||
|
||||
global_logger = this;
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
esp_log_set_vprintf(esp_idf_log_vprintf_);
|
||||
if (this->global_log_level_ >= ESPHOME_LOG_LEVEL_VERBOSE) {
|
||||
esp_log_level_set("*", ESP_LOG_VERBOSE);
|
||||
}
|
||||
#endif
|
||||
|
||||
ESP_LOGI(TAG, "Log initialized");
|
||||
}
|
||||
void Logger::set_baud_rate(uint32_t baud_rate) { this->baud_rate_ = baud_rate; }
|
||||
void Logger::set_global_log_level(int log_level) { this->global_log_level_ = log_level; }
|
||||
void Logger::set_log_level(const std::string &tag, int log_level) {
|
||||
this->log_levels_.push_back(LogLevelOverride{tag, log_level});
|
||||
}
|
||||
void Logger::set_tx_buffer_size(size_t tx_buffer_size) { this->tx_buffer_.reserve(tx_buffer_size); }
|
||||
UARTSelection Logger::get_uart() const { return this->uart_; }
|
||||
void Logger::add_on_log_callback(std::function<void(int, const char *, const char *)> &&callback) {
|
||||
this->log_callback_.add(std::move(callback));
|
||||
}
|
||||
float Logger::get_setup_priority() const { return setup_priority::HARDWARE - 1.0f; }
|
||||
const char *LOG_LEVELS[] = {"NONE", "ERROR", "WARN", "INFO", "DEBUG", "VERBOSE", "VERY_VERBOSE"};
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
const char *UART_SELECTIONS[] = {"UART0", "UART1", "UART2"};
|
||||
#endif
|
||||
#ifdef ARDUINO_ARCH_ESP8266
|
||||
const char *UART_SELECTIONS[] = {"UART0", "UART1", "UART0_SWAP"};
|
||||
#endif
|
||||
void Logger::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "Logger:");
|
||||
ESP_LOGCONFIG(TAG, " Level: %s", LOG_LEVELS[this->global_log_level_]);
|
||||
ESP_LOGCONFIG(TAG, " Log Baud Rate: %u", this->baud_rate_);
|
||||
ESP_LOGCONFIG(TAG, " Hardware UART: %s", UART_SELECTIONS[this->uart_]);
|
||||
for (auto &it : this->log_levels_) {
|
||||
ESP_LOGCONFIG(TAG, " Level for '%s': %s", it.tag.c_str(), LOG_LEVELS[it.level]);
|
||||
}
|
||||
}
|
||||
|
||||
Logger *global_logger = nullptr;
|
||||
|
||||
} // namespace logger
|
||||
} // namespace esphome
|
||||
@@ -0,0 +1,85 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/defines.h"
|
||||
|
||||
namespace esphome {
|
||||
|
||||
namespace logger {
|
||||
|
||||
/** Enum for logging UART selection
|
||||
*
|
||||
* Advanced configuration (pin selection, etc) is not supported.
|
||||
*/
|
||||
enum UARTSelection {
|
||||
UART_SELECTION_UART0 = 0,
|
||||
UART_SELECTION_UART1,
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
UART_SELECTION_UART2
|
||||
#endif
|
||||
#ifdef ARDUINO_ARCH_ESP8266
|
||||
UART_SELECTION_UART0_SWAP
|
||||
#endif
|
||||
};
|
||||
|
||||
class Logger : public Component {
|
||||
public:
|
||||
explicit Logger(uint32_t baud_rate, size_t tx_buffer_size, UARTSelection uart);
|
||||
|
||||
/// Manually set the baud rate for serial, set to 0 to disable.
|
||||
void set_baud_rate(uint32_t baud_rate);
|
||||
|
||||
/// Set the buffer size that's used for constructing log messages. Log messages longer than this will be truncated.
|
||||
void set_tx_buffer_size(size_t tx_buffer_size);
|
||||
|
||||
/// Get the UART used by the logger.
|
||||
UARTSelection get_uart() const;
|
||||
|
||||
/// Set the global log level. Note: Use the ESPHOME_LOG_LEVEL define to also remove the logs from the build.
|
||||
void set_global_log_level(int log_level);
|
||||
int get_global_log_level() const { return this->global_log_level_; }
|
||||
|
||||
/// Set the log level of the specified tag.
|
||||
void set_log_level(const std::string &tag, int log_level);
|
||||
|
||||
// ========== INTERNAL METHODS ==========
|
||||
// (In most use cases you won't need these)
|
||||
/// Set up this component.
|
||||
void pre_setup();
|
||||
void dump_config() override;
|
||||
|
||||
int level_for(const char *tag);
|
||||
|
||||
/// Register a callback that will be called for every log message sent
|
||||
void add_on_log_callback(std::function<void(int, const char *, const char *)> &&callback);
|
||||
|
||||
float get_setup_priority() const override;
|
||||
|
||||
int log_vprintf_(int level, const char *tag, const char *format, va_list args); // NOLINT
|
||||
#ifdef USE_STORE_LOG_STR_IN_FLASH
|
||||
int log_vprintf_(int level, const char *tag, const __FlashStringHelper *format, va_list args); // NOLINT
|
||||
#endif
|
||||
|
||||
protected:
|
||||
void log_message_(int level, const char *tag, char *msg, int ret);
|
||||
|
||||
uint32_t baud_rate_;
|
||||
std::vector<char> tx_buffer_;
|
||||
int global_log_level_{ESPHOME_LOG_LEVEL};
|
||||
UARTSelection uart_{UART_SELECTION_UART0};
|
||||
HardwareSerial *hw_serial_{nullptr};
|
||||
struct LogLevelOverride {
|
||||
std::string tag;
|
||||
int level;
|
||||
};
|
||||
std::vector<LogLevelOverride> log_levels_;
|
||||
CallbackManager<void(int, const char *, const char *)> log_callback_{};
|
||||
};
|
||||
|
||||
extern Logger *global_logger;
|
||||
|
||||
} // namespace logger
|
||||
|
||||
} // namespace esphome
|
||||
Reference in New Issue
Block a user