🏗 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:
Otto Winter
2019-04-17 12:06:00 +02:00
committed by GitHub
parent 049807e3ab
commit 6682c43dfa
817 changed files with 54156 additions and 10830 deletions
+185
View File
@@ -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)
+149
View File
@@ -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
+85
View File
@@ -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