Merge branch 'dev' into beta

This commit is contained in:
Otto Winter
2019-10-19 17:07:36 +02:00
380 changed files with 17501 additions and 5297 deletions
+51 -12
View File
@@ -1,5 +1,6 @@
#include "esphome/core/application.h"
#include "esphome/core/log.h"
#include "esphome/core/version.h"
#ifdef USE_STATUS_LED
#include "esphome/components/status_led/status_led.h"
@@ -32,10 +33,9 @@ void Application::setup() {
for (uint32_t i = 0; i < this->components_.size(); i++) {
Component *component = this->components_[i];
if (component->is_failed())
continue;
component->call_setup();
component->call();
this->scheduler.process_to_add();
if (component->can_proceed())
continue;
@@ -44,10 +44,9 @@ void Application::setup() {
do {
uint32_t new_app_state = STATUS_LED_WARNING;
this->scheduler.call();
for (uint32_t j = 0; j <= i; j++) {
if (!this->components_[j]->is_failed()) {
this->components_[j]->call_loop();
}
this->components_[j]->call();
new_app_state |= this->components_[j]->get_component_state();
this->app_state_ |= new_app_state;
}
@@ -57,13 +56,53 @@ void Application::setup() {
}
ESP_LOGI(TAG, "setup() finished successfully!");
this->dump_config();
this->schedule_dump_config();
}
void Application::dump_config() {
ESP_LOGI(TAG, "esphome version " ESPHOME_VERSION " compiled on %s", this->compilation_time_.c_str());
void Application::loop() {
uint32_t new_app_state = 0;
const uint32_t start = millis();
for (auto component : this->components_) {
component->dump_config();
this->scheduler.call();
for (Component *component : this->components_) {
component->call();
new_app_state |= component->get_component_state();
this->app_state_ |= new_app_state;
this->feed_wdt();
}
this->app_state_ = new_app_state;
const uint32_t end = millis();
if (end - start > 200) {
ESP_LOGV(TAG, "A component took a long time in a loop() cycle (%.1f s).", (end - start) / 1e3f);
ESP_LOGV(TAG, "Components should block for at most 20-30ms in loop().");
ESP_LOGV(TAG, "This will become a warning soon.");
}
const uint32_t now = millis();
if (HighFrequencyLoopRequester::is_high_frequency()) {
yield();
} else {
uint32_t delay_time = this->loop_interval_;
if (now - this->last_loop_ < this->loop_interval_)
delay_time = this->loop_interval_ - (now - this->last_loop_);
uint32_t next_schedule = this->scheduler.next_schedule_in().value_or(delay_time);
// next_schedule is max 0.5*delay_time
// otherwise interval=0 schedules result in constant looping with almost no sleep
next_schedule = std::max(next_schedule, delay_time / 2);
delay_time = std::min(next_schedule, delay_time);
delay(delay_time);
}
this->last_loop_ = now;
if (this->dump_config_at_ >= 0 && this->dump_config_at_ < this->components_.size()) {
if (this->dump_config_at_ == 0) {
ESP_LOGI(TAG, "esphome version " ESPHOME_VERSION " compiled on %s", this->compilation_time_.c_str());
}
this->components_[this->dump_config_at_]->dump_config();
this->dump_config_at_++;
}
}
void Application::loop() {
@@ -116,7 +155,7 @@ void ICACHE_RAM_ATTR HOT Application::feed_wdt() {
LAST_FEED = now;
#ifdef USE_STATUS_LED
if (status_led::global_status_led != nullptr) {
status_led::global_status_led->call_loop();
status_led::global_status_led->call();
}
#endif
}
+11 -5
View File
@@ -6,6 +6,7 @@
#include "esphome/core/preferences.h"
#include "esphome/core/component.h"
#include "esphome/core/helpers.h"
#include "esphome/core/scheduler.h"
#ifdef USE_BINARY_SENSOR
#include "esphome/components/binary_sensor/binary_sensor.h"
@@ -39,7 +40,7 @@ class Application {
void pre_setup(const std::string &name, const char *compilation_time) {
this->name_ = name;
this->compilation_time_ = compilation_time;
global_preferences.begin(this->name_);
global_preferences.begin();
}
#ifdef USE_BINARY_SENSOR
@@ -109,8 +110,7 @@ class Application {
*/
void set_loop_interval(uint32_t loop_interval) { this->loop_interval_ = loop_interval; }
void dump_config();
void schedule_dump_config() { this->dump_config_scheduled_ = true; }
void schedule_dump_config() { this->dump_config_at_ = 0; }
void feed_wdt();
@@ -119,8 +119,12 @@ class Application {
void safe_reboot();
void run_safe_shutdown_hooks() {
for (auto *comp : this->components_)
for (auto *comp : this->components_) {
comp->on_safe_shutdown();
}
for (auto *comp : this->components_) {
comp->on_shutdown();
}
}
uint32_t get_app_state() const { return this->app_state_; }
@@ -198,6 +202,8 @@ class Application {
}
#endif
Scheduler scheduler;
protected:
friend Component;
@@ -234,7 +240,7 @@ class Application {
std::string compilation_time_;
uint32_t last_loop_{0};
uint32_t loop_interval_{16};
bool dump_config_scheduled_{false};
int dump_config_at_{-1};
uint32_t app_state_{0};
};
+9
View File
@@ -17,6 +17,15 @@ namespace esphome {
#define TEMPLATABLE_VALUE(type, name) TEMPLATABLE_VALUE_(type, name)
#define TEMPLATABLE_STRING_VALUE_(name) \
protected: \
TemplatableStringValue<Ts...> name##_{}; \
\
public: \
template<typename V> void set_##name(V name) { this->name##_ = name; }
#define TEMPLATABLE_STRING_VALUE(name) TEMPLATABLE_STRING_VALUE_(name)
/** Base class for all automation conditions.
*
* @tparam Ts The template parameters to pass when executing.
+1 -1
View File
@@ -73,7 +73,7 @@ template<typename... Ts> class ForCondition : public Condition<Ts...>, public Co
bool check(Ts... x) override {
if (!this->check_internal())
return false;
return millis() - this->last_inactive_ < this->time_.value(x...);
return millis() - this->last_inactive_ >= this->time_.value(x...);
}
protected:
+41 -120
View File
@@ -1,5 +1,3 @@
#include <algorithm>
#include "esphome/core/component.h"
#include "esphome/core/helpers.h"
#include "esphome/core/esphal.h"
@@ -45,109 +43,50 @@ void Component::setup() {}
void Component::loop() {}
void Component::set_interval(const std::string &name, uint32_t interval, std::function<void()> &&f) { // NOLINT
const uint32_t now = millis();
// only put offset in lower half
uint32_t offset = 0;
if (interval != 0)
offset = (random_uint32() % interval) / 2;
ESP_LOGVV(TAG, "set_interval(name='%s', interval=%u, offset=%u)", name.c_str(), interval, offset);
if (!name.empty()) {
this->cancel_interval(name);
}
struct TimeFunction function = {
.name = name,
.type = TimeFunction::INTERVAL,
.interval = interval,
.last_execution = now - interval - offset,
.f = std::move(f),
.remove = false,
};
this->time_functions_.push_back(function);
App.scheduler.set_interval(this, name, interval, std::move(f));
}
bool Component::cancel_interval(const std::string &name) { // NOLINT
return this->cancel_time_function_(name, TimeFunction::INTERVAL);
return App.scheduler.cancel_interval(this, name);
}
void Component::set_timeout(const std::string &name, uint32_t timeout, std::function<void()> &&f) { // NOLINT
const uint32_t now = millis();
ESP_LOGVV(TAG, "set_timeout(name='%s', timeout=%u)", name.c_str(), timeout);
if (!name.empty()) {
this->cancel_timeout(name);
}
struct TimeFunction function = {
.name = name,
.type = TimeFunction::TIMEOUT,
.interval = timeout,
.last_execution = now,
.f = std::move(f),
.remove = false,
};
this->time_functions_.push_back(function);
return App.scheduler.set_timeout(this, name, timeout, std::move(f));
}
bool Component::cancel_timeout(const std::string &name) { // NOLINT
return this->cancel_time_function_(name, TimeFunction::TIMEOUT);
return App.scheduler.cancel_timeout(this, name);
}
void Component::call_loop() {
this->loop_internal_();
this->loop();
}
void Component::call_loop() { this->loop(); }
bool Component::cancel_time_function_(const std::string &name, TimeFunction::Type type) {
// NOLINTNEXTLINE
for (auto iter = this->time_functions_.begin(); iter != this->time_functions_.end(); iter++) {
if (!iter->remove && iter->name == name && iter->type == type) {
ESP_LOGVV(TAG, "Removing old time function %s.", iter->name.c_str());
iter->remove = true;
return true;
}
}
return false;
}
void Component::call_setup() {
this->setup_internal_();
this->setup();
}
void Component::call_setup() { this->setup(); }
uint32_t Component::get_component_state() const { return this->component_state_; }
void Component::loop_internal_() {
this->component_state_ &= ~COMPONENT_STATE_MASK;
this->component_state_ |= COMPONENT_STATE_LOOP;
for (unsigned int i = 0; i < this->time_functions_.size(); i++) { // NOLINT
const uint32_t now = millis();
TimeFunction *tf = &this->time_functions_[i];
if (tf->should_run(now)) {
#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE
const char *type =
tf->type == TimeFunction::INTERVAL ? "interval" : (tf->type == TimeFunction::TIMEOUT ? "timeout" : "defer");
ESP_LOGVV(TAG, "Running %s '%s':%u with interval=%u last_execution=%u (now=%u)", type, tf->name.c_str(), i,
tf->interval, tf->last_execution, now);
#endif
tf->f();
// The vector might have reallocated due to new items
tf = &this->time_functions_[i];
if (tf->type == TimeFunction::INTERVAL && tf->interval != 0) {
const uint32_t amount = (now - tf->last_execution) / tf->interval;
tf->last_execution += (amount * tf->interval);
} else if (tf->type == TimeFunction::DEFER || tf->type == TimeFunction::TIMEOUT) {
tf->remove = true;
}
}
void Component::call() {
uint32_t state = this->component_state_ & COMPONENT_STATE_MASK;
switch (state) {
case COMPONENT_STATE_CONSTRUCTION:
// State Construction: Call setup and set state to setup
this->component_state_ &= ~COMPONENT_STATE_MASK;
this->component_state_ |= COMPONENT_STATE_SETUP;
this->call_setup();
break;
case COMPONENT_STATE_SETUP:
// State setup: Call first loop and set state to loop
this->component_state_ &= ~COMPONENT_STATE_MASK;
this->component_state_ |= COMPONENT_STATE_LOOP;
this->call_loop();
break;
case COMPONENT_STATE_LOOP:
// State loop: Call loop
this->call_loop();
break;
case COMPONENT_STATE_FAILED:
// State failed: Do nothing
break;
default:
break;
}
this->time_functions_.erase(std::remove_if(this->time_functions_.begin(), this->time_functions_.end(),
[](const TimeFunction &tf) -> bool { return tf.remove; }),
this->time_functions_.end());
}
void Component::setup_internal_() {
this->component_state_ &= ~COMPONENT_STATE_MASK;
this->component_state_ |= COMPONENT_STATE_SETUP;
}
void Component::mark_failed() {
ESP_LOGE(TAG, "Component was marked as failed.");
@@ -155,29 +94,20 @@ void Component::mark_failed() {
this->component_state_ |= COMPONENT_STATE_FAILED;
this->status_set_error();
}
void Component::defer(std::function<void()> &&f) { this->defer("", std::move(f)); } // NOLINT
bool Component::cancel_defer(const std::string &name) { // NOLINT
return this->cancel_time_function_(name, TimeFunction::DEFER);
void Component::defer(std::function<void()> &&f) { // NOLINT
App.scheduler.set_timeout(this, "", 0, std::move(f));
}
bool Component::cancel_defer(const std::string &name) { // NOLINT
return App.scheduler.cancel_timeout(this, name);
}
void Component::defer(const std::string &name, std::function<void()> &&f) { // NOLINT
if (!name.empty()) {
this->cancel_defer(name);
}
struct TimeFunction function = {
.name = name,
.type = TimeFunction::DEFER,
.interval = 0,
.last_execution = 0,
.f = std::move(f),
.remove = false,
};
this->time_functions_.push_back(function);
App.scheduler.set_timeout(this, name, 0, std::move(f));
}
void Component::set_timeout(uint32_t timeout, std::function<void()> &&f) { // NOLINT
this->set_timeout("", timeout, std::move(f));
App.scheduler.set_timeout(this, "", timeout, std::move(f));
}
void Component::set_interval(uint32_t interval, std::function<void()> &&f) { // NOLINT
this->set_interval("", interval, std::move(f));
App.scheduler.set_interval(this, "", interval, std::move(f));
}
bool Component::is_failed() { return (this->component_state_ & COMPONENT_STATE_MASK) == COMPONENT_STATE_FAILED; }
bool Component::can_proceed() { return true; }
@@ -203,16 +133,15 @@ void Component::status_momentary_error(const std::string &name, uint32_t length)
}
void Component::dump_config() {}
float Component::get_actual_setup_priority() const {
return this->setup_priority_override_.value_or(this->get_setup_priority());
if (isnan(this->setup_priority_override_))
return this->get_setup_priority();
return this->setup_priority_override_;
}
void Component::set_setup_priority(float priority) { this->setup_priority_override_ = priority; }
PollingComponent::PollingComponent(uint32_t update_interval) : Component(), update_interval_(update_interval) {}
void PollingComponent::call_setup() {
// Call component internal setup.
this->setup_internal_();
// Let the polling component subclass setup their HW.
this->setup();
@@ -240,12 +169,4 @@ void Nameable::calc_object_id_() {
}
uint32_t Nameable::get_object_id_hash() { return this->object_id_hash_; }
bool Component::TimeFunction::should_run(uint32_t now) const {
if (this->remove)
return false;
if (this->type == DEFER)
return true;
return this->interval != 4294967295UL && now - this->last_execution > this->interval;
}
} // namespace esphome
+5 -40
View File
@@ -2,7 +2,7 @@
#include <string>
#include <functional>
#include <vector>
#include "Arduino.h"
#include "esphome/core/optional.h"
@@ -91,18 +91,7 @@ class Component {
*/
virtual float get_loop_priority() const;
/** Public loop() functions. These will be called by the Application instance.
*
* Note: This should normally not be overriden, unless you know what you're doing.
* They're basically to make creating custom components easier. For example the
* SensorComponent can override these methods to not have the user call some super
* methods within their custom sensors. These methods should ALWAYS call the loop_internal()
* and setup_internal() methods.
*
* Basically, it handles stuff like interval/timeout functions and eventually calls loop().
*/
virtual void call_loop();
virtual void call_setup();
void call();
virtual void on_shutdown() {}
virtual void on_safe_shutdown() {}
@@ -138,6 +127,8 @@ class Component {
void status_momentary_error(const std::string &name, uint32_t length = 5000);
protected:
virtual void call_loop();
virtual void call_setup();
/** Set an interval function with a unique name. Empty name means no cancelling possible.
*
* This will call f every interval ms. Can be cancelled via CancelInterval().
@@ -204,34 +195,8 @@ class Component {
/// Cancel a defer callback using the specified name, name must not be empty.
bool cancel_defer(const std::string &name); // NOLINT
void loop_internal_();
void setup_internal_();
/// Internal struct for storing timeout/interval functions.
struct TimeFunction {
std::string name; ///< The name/id of this TimeFunction.
enum Type { TIMEOUT, INTERVAL, DEFER } type; ///< The type of this TimeFunction. Either TIMEOUT, INTERVAL or DEFER.
uint32_t interval; ///< The interval/timeout of this function.
/// The last execution for interval functions and the time, SetInterval was called, for timeout functions.
uint32_t last_execution;
std::function<void()> f; ///< The function (or callback) itself.
bool remove;
bool should_run(uint32_t now) const;
};
/// Cancel an only time function. If name is empty, won't do anything.
bool cancel_time_function_(const std::string &name, TimeFunction::Type type);
/** Storage for interval/timeout functions.
*
* Intentionally a vector despite its map-like nature, because of the
* memory overhead.
*/
std::vector<TimeFunction> time_functions_;
uint32_t component_state_{0x0000}; ///< State of this component.
optional<float> setup_priority_override_;
float setup_priority_override_{NAN};
};
/** This class simplifies creating components that periodically check a state.
+1 -2
View File
@@ -1,8 +1,6 @@
#pragma once
// This file is auto-generated! Do not edit!
#define ESPHOME_VERSION "dev"
#define USE_API
#define USE_LOGGER
#define USE_BINARY_SENSOR
@@ -24,3 +22,4 @@
#endif
#define USE_TIME
#define USE_DEEP_SLEEP
#define USE_CAPTIVE_PORTAL
+1 -1
View File
@@ -148,7 +148,7 @@ void ICACHE_RAM_ATTR HOT GPIOPin::digital_write(bool value) {
}
#endif
}
void ISRInternalGPIOPin::digital_write(bool value) {
void ICACHE_RAM_ATTR HOT ISRInternalGPIOPin::digital_write(bool value) {
#ifdef ARDUINO_ARCH_ESP8266
if (this->pin_ != 16) {
if (value != this->inverted_) {
+18 -2
View File
@@ -4,6 +4,7 @@
#include <functional>
#include <vector>
#include <memory>
#include <type_traits>
#include "esphome/core/optional.h"
#include "esphome/core/esphal.h"
@@ -160,6 +161,11 @@ template<int...> struct seq {}; // NOLINT
template<int N, int... S> struct gens : gens<N - 1, N - 1, S...> {}; // NOLINT
template<int... S> struct gens<0, S...> { using type = seq<S...>; }; // NOLINT
template<bool B, class T = void> using enable_if_t = typename std::enable_if<B, T>::type;
template<typename T, enable_if_t<!std::is_pointer<T>::value, int> = 0> T id(T value) { return value; }
template<typename T, enable_if_t<std::is_pointer<T *>::value, int> = 0> T &id(T *value) { return *value; }
template<typename... X> class CallbackManager;
/** Simple helper class to allow having multiple subscribers to a signal.
@@ -192,8 +198,6 @@ struct is_callable // NOLINT
static constexpr auto value = decltype(test<T>(nullptr))::value; // NOLINT
};
template<bool B, class T = void> using enable_if_t = typename std::enable_if<B, T>::type;
template<typename T, typename... X> class TemplatableValue {
public:
TemplatableValue() : type_(EMPTY) {}
@@ -239,6 +243,18 @@ template<typename T, typename... X> class TemplatableValue {
std::function<T(X...)> f_;
};
template<typename... X> class TemplatableStringValue : public TemplatableValue<std::string, X...> {
public:
TemplatableStringValue() : TemplatableValue<std::string, X...>() {}
template<typename F, enable_if_t<!is_callable<F, X...>::value, int> = 0>
TemplatableStringValue(F value) : TemplatableValue<std::string, X...>(value) {}
template<typename F, enable_if_t<is_callable<F, X...>::value, int> = 0>
TemplatableStringValue(F f)
: TemplatableValue<std::string, X...>([f](X... x) -> std::string { return to_string(f(x...)); }) {}
};
void delay_microseconds_accurate(uint32_t usec);
template<typename T> class Deduplicator {
+28 -23
View File
@@ -1,6 +1,6 @@
#include "esphome/core/log.h"
#include "esphome/core/defines.h"
#include "esphome/core/helpers.h"
#include "log.h"
#include "defines.h"
#include "helpers.h"
#ifdef USE_LOGGER
#include "esphome/components/logger/logger.h"
@@ -8,60 +8,65 @@
namespace esphome {
int HOT esp_log_printf_(int level, const char *tag, const char *format, ...) { // NOLINT
void HOT esp_log_printf_(int level, const char *tag, int line, const char *format, ...) { // NOLINT
va_list arg;
va_start(arg, format);
int ret = esp_log_vprintf_(level, tag, format, arg);
esp_log_vprintf_(level, tag, line, format, arg);
va_end(arg);
return ret;
}
#ifdef USE_STORE_LOG_STR_IN_FLASH
int HOT esp_log_printf_(int level, const char *tag, const __FlashStringHelper *format, ...) {
void HOT esp_log_printf_(int level, const char *tag, int line, const __FlashStringHelper *format, ...) {
va_list arg;
va_start(arg, format);
int ret = esp_log_vprintf_(level, tag, format, arg);
esp_log_vprintf_(level, tag, line, format, arg);
va_end(arg);
return ret;
return 0;
}
#endif
int HOT esp_log_vprintf_(int level, const char *tag, const char *format, va_list args) { // NOLINT
void HOT esp_log_vprintf_(int level, const char *tag, int line, const char *format, va_list args) { // NOLINT
#ifdef USE_LOGGER
auto *log = logger::global_logger;
if (log == nullptr)
return 0;
return;
return log->log_vprintf_(level, tag, format, args);
#else
return 0;
log->log_vprintf_(level, tag, line, format, args);
#endif
}
#ifdef USE_STORE_LOG_STR_IN_FLASH
int HOT esp_log_vprintf_(int level, const char *tag, const __FlashStringHelper *format, va_list args) { // NOLINT
void HOT esp_log_vprintf_(int level, const char *tag, int line, const __FlashStringHelper *format,
va_list args) { // NOLINT
#ifdef USE_LOGGER
auto *log = logger::global_logger;
if (log == nullptr)
return 0;
return;
return log->log_vprintf_(level, tag, format, args);
#else
return 0;
log->log_vprintf_(level, tag, line, format, args);
#endif
}
#endif
#ifdef ARDUINO_ARCH_ESP32
int HOT esp_idf_log_vprintf_(const char *format, va_list args) { // NOLINT
#ifdef USE_LOGGER
auto *log = logger::global_logger;
if (log == nullptr)
return 0;
return log->log_vprintf_(log->get_global_log_level(), "", format, args);
#else
return 0;
size_t len = strlen(format);
if (format[len - 1] == '\n') {
// Remove trailing newline from format
// Use locally stored
static std::string FORMAT_COPY;
FORMAT_COPY.clear();
FORMAT_COPY.insert(0, format, len - 1);
format = FORMAT_COPY.c_str();
}
log->log_vprintf_(ESPHOME_LOG_LEVEL, "esp-idf", 0, format, args);
#endif
return 0;
}
#endif
} // namespace esphome
+20 -29
View File
@@ -20,9 +20,10 @@ namespace esphome {
#define ESPHOME_LOG_LEVEL_ERROR 1
#define ESPHOME_LOG_LEVEL_WARN 2
#define ESPHOME_LOG_LEVEL_INFO 3
#define ESPHOME_LOG_LEVEL_DEBUG 4
#define ESPHOME_LOG_LEVEL_VERBOSE 5
#define ESPHOME_LOG_LEVEL_VERY_VERBOSE 6
#define ESPHOME_LOG_LEVEL_CONFIG 4
#define ESPHOME_LOG_LEVEL_DEBUG 5
#define ESPHOME_LOG_LEVEL_VERBOSE 6
#define ESPHOME_LOG_LEVEL_VERY_VERBOSE 7
#ifndef ESPHOME_LOG_LEVEL
#define ESPHOME_LOG_LEVEL ESPHOME_LOG_LEVEL_DEBUG
@@ -43,38 +44,30 @@ namespace esphome {
#define ESPHOME_LOG_COLOR(COLOR) "\033[0;" COLOR "m"
#define ESPHOME_LOG_BOLD(COLOR) "\033[1;" COLOR "m"
#define ESPHOME_LOG_COLOR_E ESPHOME_LOG_BOLD(ESPHOME_LOG_COLOR_RED)
#define ESPHOME_LOG_COLOR_W ESPHOME_LOG_COLOR(ESPHOME_LOG_COLOR_YELLOW)
#define ESPHOME_LOG_COLOR_I ESPHOME_LOG_COLOR(ESPHOME_LOG_COLOR_GREEN)
#define ESPHOME_LOG_COLOR_C ESPHOME_LOG_COLOR(ESPHOME_LOG_COLOR_MAGENTA)
#define ESPHOME_LOG_COLOR_D ESPHOME_LOG_COLOR(ESPHOME_LOG_COLOR_CYAN)
#define ESPHOME_LOG_COLOR_V ESPHOME_LOG_COLOR(ESPHOME_LOG_COLOR_GRAY)
#define ESPHOME_LOG_COLOR_VV ESPHOME_LOG_COLOR(ESPHOME_LOG_COLOR_WHITE)
#define ESPHOME_LOG_RESET_COLOR "\033[0m"
int esp_log_printf_(int level, const char *tag, const char *format, ...) // NOLINT
__attribute__((format(printf, 3, 4)));
void esp_log_printf_(int level, const char *tag, int line, const char *format, ...) // NOLINT
__attribute__((format(printf, 4, 5)));
#ifdef USE_STORE_LOG_STR_IN_FLASH
int esp_log_printf_(int level, const char *tag, const __FlashStringHelper *format, ...);
void esp_log_printf_(int level, const char *tag, int line, const __FlashStringHelper *format, ...);
#endif
int esp_log_vprintf_(int level, const char *tag, const char *format, va_list args); // NOLINT
void esp_log_vprintf_(int level, const char *tag, int line, const char *format, va_list args); // NOLINT
#ifdef USE_STORE_LOG_STR_IN_FLASH
int esp_log_vprintf_(int level, const char *tag, const __FlashStringHelper *format, va_list args);
void esp_log_vprintf_(int level, const char *tag, int line, const __FlashStringHelper *format, va_list args);
#endif
#ifdef ARDUINO_ARCH_ESP32
int esp_idf_log_vprintf_(const char *format, va_list args); // NOLINT
#endif
#ifdef USE_STORE_LOG_STR_IN_FLASH
#define ESPHOME_LOG_FORMAT(tag, letter, format) \
F(ESPHOME_LOG_COLOR_##letter "[" #letter "][%s:%03u]: " format ESPHOME_LOG_RESET_COLOR), tag, __LINE__
#define ESPHOME_LOG_FORMAT(format) F(format)
#else
#define ESPHOME_LOG_FORMAT(tag, letter, format) \
ESPHOME_LOG_COLOR_##letter "[" #letter "][%s:%03u]: " format ESPHOME_LOG_RESET_COLOR, tag, __LINE__
#define ESPHOME_LOG_FORMAT(format) format
#endif
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE
#define esph_log_vv(tag, format, ...) \
esp_log_printf_(ESPHOME_LOG_LEVEL_VERY_VERBOSE, tag, ESPHOME_LOG_FORMAT(tag, VV, format), ##__VA_ARGS__)
esp_log_printf_(ESPHOME_LOG_LEVEL_VERY_VERBOSE, tag, __LINE__, ESPHOME_LOG_FORMAT(format), ##__VA_ARGS__)
#define ESPHOME_LOG_HAS_VERY_VERBOSE
#else
@@ -83,7 +76,7 @@ int esp_idf_log_vprintf_(const char *format, va_list args); // NOLINT
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
#define esph_log_v(tag, format, ...) \
esp_log_printf_(ESPHOME_LOG_LEVEL_VERBOSE, tag, ESPHOME_LOG_FORMAT(tag, V, format), ##__VA_ARGS__)
esp_log_printf_(ESPHOME_LOG_LEVEL_VERBOSE, tag, __LINE__, ESPHOME_LOG_FORMAT(format), ##__VA_ARGS__)
#define ESPHOME_LOG_HAS_VERBOSE
#else
@@ -92,22 +85,20 @@ int esp_idf_log_vprintf_(const char *format, va_list args); // NOLINT
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_DEBUG
#define esph_log_d(tag, format, ...) \
esp_log_printf_(ESPHOME_LOG_LEVEL_DEBUG, tag, ESPHOME_LOG_FORMAT(tag, D, format), ##__VA_ARGS__)
esp_log_printf_(ESPHOME_LOG_LEVEL_DEBUG, tag, __LINE__, ESPHOME_LOG_FORMAT(format), ##__VA_ARGS__)
#define esph_log_config(tag, format, ...) \
esp_log_printf_(ESPHOME_LOG_LEVEL_DEBUG, tag, ESPHOME_LOG_FORMAT(tag, C, format), ##__VA_ARGS__)
esp_log_printf_(ESPHOME_LOG_LEVEL_CONFIG, tag, __LINE__, ESPHOME_LOG_FORMAT(format), ##__VA_ARGS__)
#define ESPHOME_LOG_HAS_DEBUG
#define ESPHOME_LOG_HAS_CONFIG
#else
#define esph_log_d(tag, format, ...)
#define esph_log_config(tag, format, ...)
#endif
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_INFO
#define esph_log_i(tag, format, ...) \
esp_log_printf_(ESPHOME_LOG_LEVEL_INFO, tag, ESPHOME_LOG_FORMAT(tag, I, format), ##__VA_ARGS__)
esp_log_printf_(ESPHOME_LOG_LEVEL_INFO, tag, __LINE__, ESPHOME_LOG_FORMAT(format), ##__VA_ARGS__)
#define ESPHOME_LOG_HAS_INFO
#else
@@ -116,7 +107,7 @@ int esp_idf_log_vprintf_(const char *format, va_list args); // NOLINT
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_WARN
#define esph_log_w(tag, format, ...) \
esp_log_printf_(ESPHOME_LOG_LEVEL_WARN, tag, ESPHOME_LOG_FORMAT(tag, W, format), ##__VA_ARGS__)
esp_log_printf_(ESPHOME_LOG_LEVEL_WARN, tag, __LINE__, ESPHOME_LOG_FORMAT(format), ##__VA_ARGS__)
#define ESPHOME_LOG_HAS_WARN
#else
@@ -125,7 +116,7 @@ int esp_idf_log_vprintf_(const char *format, va_list args); // NOLINT
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_ERROR
#define esph_log_e(tag, format, ...) \
esp_log_printf_(ESPHOME_LOG_LEVEL_ERROR, tag, ESPHOME_LOG_FORMAT(tag, E, format), ##__VA_ARGS__)
esp_log_printf_(ESPHOME_LOG_LEVEL_ERROR, tag, __LINE__, ESPHOME_LOG_FORMAT(format), ##__VA_ARGS__)
#define ESPHOME_LOG_HAS_ERROR
#else
+122 -51
View File
@@ -1,20 +1,25 @@
#include "esphome/core/preferences.h"
#include "esphome/core/log.h"
#include "esphome/core/helpers.h"
#include "esphome/core/application.h"
#ifdef USE_ESP8266_PREFERENCES_FLASH
#ifdef ARDUINO_ARCH_ESP8266
extern "C" {
#include "spi_flash.h"
}
#endif
#ifdef ARDUINO_ARCH_ESP32
#include "nvs.h"
#include "nvs_flash.h"
#endif
namespace esphome {
static const char *TAG = "preferences";
ESPPreferenceObject::ESPPreferenceObject() : rtc_offset_(0), length_words_(0), type_(0), data_(nullptr) {}
ESPPreferenceObject::ESPPreferenceObject(size_t rtc_offset, size_t length, uint32_t type)
: rtc_offset_(rtc_offset), length_words_(length), type_(type) {
ESPPreferenceObject::ESPPreferenceObject() : offset_(0), length_words_(0), type_(0), data_(nullptr) {}
ESPPreferenceObject::ESPPreferenceObject(size_t offset, size_t length, uint32_t type)
: offset_(offset), length_words_(length), type_(type) {
this->data_ = new uint32_t[this->length_words_ + 1];
for (uint32_t i = 0; i < this->length_words_ + 1; i++)
this->data_[i] = 0;
@@ -29,7 +34,7 @@ bool ESPPreferenceObject::load_() {
bool valid = this->data_[this->length_words_] == this->calculate_crc_();
ESP_LOGVV(TAG, "LOAD %u: valid=%s, 0=0x%08X 1=0x%08X (Type=%u, CRC=0x%08X)", this->rtc_offset_, // NOLINT
ESP_LOGVV(TAG, "LOAD %u: valid=%s, 0=0x%08X 1=0x%08X (Type=%u, CRC=0x%08X)", this->offset_, // NOLINT
YESNO(valid), this->data_[0], this->data_[1], this->type_, this->calculate_crc_());
return valid;
}
@@ -42,7 +47,7 @@ bool ESPPreferenceObject::save_() {
this->data_[this->length_words_] = this->calculate_crc_();
if (!this->save_internal_())
return false;
ESP_LOGVV(TAG, "SAVE %u: 0=0x%08X 1=0x%08X (Type=%u, CRC=0x%08X)", this->rtc_offset_, // NOLINT
ESP_LOGVV(TAG, "SAVE %u: 0=0x%08X 1=0x%08X (Type=%u, CRC=0x%08X)", this->offset_, // NOLINT
this->data_[0], this->data_[1], this->type_, this->calculate_crc_());
return true;
}
@@ -54,6 +59,12 @@ bool ESPPreferenceObject::save_() {
#define ESP_RTC_USER_MEM_SIZE_WORDS 128
#define ESP_RTC_USER_MEM_SIZE_BYTES ESP_RTC_USER_MEM_SIZE_WORDS * 4
#ifdef USE_ESP8266_PREFERENCES_FLASH
#define ESP8266_FLASH_STORAGE_SIZE 128
#else
#define ESP8266_FLASH_STORAGE_SIZE 64
#endif
static inline bool esp_rtc_user_mem_read(uint32_t index, uint32_t *dest) {
if (index >= ESP_RTC_USER_MEM_SIZE_WORDS) {
return false;
@@ -62,9 +73,7 @@ static inline bool esp_rtc_user_mem_read(uint32_t index, uint32_t *dest) {
return true;
}
#ifdef USE_ESP8266_PREFERENCES_FLASH
static bool esp8266_preferences_modified = false;
#endif
static bool esp8266_flash_dirty = false;
static inline bool esp_rtc_user_mem_write(uint32_t index, uint32_t value) {
if (index >= ESP_RTC_USER_MEM_SIZE_WORDS) {
@@ -75,29 +84,24 @@ static inline bool esp_rtc_user_mem_write(uint32_t index, uint32_t value) {
}
auto *ptr = &ESP_RTC_USER_MEM[index];
#ifdef USE_ESP8266_PREFERENCES_FLASH
if (*ptr != value) {
esp8266_preferences_modified = true;
}
#endif
*ptr = value;
return true;
}
#ifdef USE_ESP8266_PREFERENCES_FLASH
extern "C" uint32_t _SPIFFS_end;
static const uint32_t get_esp8266_flash_sector() { return (uint32_t(&_SPIFFS_end) - 0x40200000) / SPI_FLASH_SEC_SIZE; }
static const uint32_t get_esp8266_flash_sector() {
union {
uint32_t *ptr;
uint32_t uint;
} data{};
data.ptr = &_SPIFFS_end;
return (data.uint - 0x40200000) / SPI_FLASH_SEC_SIZE;
}
static const uint32_t get_esp8266_flash_address() { return get_esp8266_flash_sector() * SPI_FLASH_SEC_SIZE; }
static void load_esp8266_flash() {
ESP_LOGVV(TAG, "Loading preferences from flash...");
disable_interrupts();
spi_flash_read(get_esp8266_flash_address(), ESP_RTC_USER_MEM, ESP_RTC_USER_MEM_SIZE_BYTES);
enable_interrupts();
}
static void save_esp8266_flash() {
if (!esp8266_preferences_modified)
void ESPPreferences::save_esp8266_flash_() {
if (!esp8266_flash_dirty)
return;
ESP_LOGVV(TAG, "Saving preferences to flash...");
@@ -109,31 +113,53 @@ static void save_esp8266_flash() {
return;
}
auto write_res = spi_flash_write(get_esp8266_flash_address(), ESP_RTC_USER_MEM, ESP_RTC_USER_MEM_SIZE_BYTES);
auto write_res = spi_flash_write(get_esp8266_flash_address(), this->flash_storage_, ESP8266_FLASH_STORAGE_SIZE * 4);
enable_interrupts();
if (write_res != SPI_FLASH_RESULT_OK) {
ESP_LOGV(TAG, "Write ESP8266 flash failed!");
return;
}
esp8266_preferences_modified = false;
esp8266_flash_dirty = false;
}
#endif
bool ESPPreferenceObject::save_internal_() {
if (this->in_flash_) {
for (uint32_t i = 0; i <= this->length_words_; i++) {
uint32_t j = this->offset_ + i;
if (j >= ESP8266_FLASH_STORAGE_SIZE)
return false;
uint32_t v = this->data_[i];
uint32_t *ptr = &global_preferences.flash_storage_[j];
if (*ptr != v)
esp8266_flash_dirty = true;
*ptr = v;
}
global_preferences.save_esp8266_flash_();
return true;
}
for (uint32_t i = 0; i <= this->length_words_; i++) {
if (!esp_rtc_user_mem_write(this->rtc_offset_ + i, this->data_[i]))
if (!esp_rtc_user_mem_write(this->offset_ + i, this->data_[i]))
return false;
}
#ifdef USE_ESP8266_PREFERENCES_FLASH
save_esp8266_flash();
#endif
return true;
}
bool ESPPreferenceObject::load_internal_() {
if (this->in_flash_) {
for (uint32_t i = 0; i <= this->length_words_; i++) {
uint32_t j = this->offset_ + i;
if (j >= ESP8266_FLASH_STORAGE_SIZE)
return false;
this->data_[i] = global_preferences.flash_storage_[j];
}
return true;
}
for (uint32_t i = 0; i <= this->length_words_; i++) {
if (!esp_rtc_user_mem_read(this->rtc_offset_ + i, &this->data_[i]))
if (!esp_rtc_user_mem_read(this->offset_ + i, &this->data_[i]))
return false;
}
return true;
@@ -144,13 +170,26 @@ ESPPreferences::ESPPreferences()
// which will be reset each time OTA occurs
: current_offset_(0) {}
void ESPPreferences::begin(const std::string &name) {
#ifdef USE_ESP8266_PREFERENCES_FLASH
load_esp8266_flash();
#endif
void ESPPreferences::begin() {
this->flash_storage_ = new uint32_t[ESP8266_FLASH_STORAGE_SIZE];
ESP_LOGVV(TAG, "Loading preferences from flash...");
disable_interrupts();
spi_flash_read(get_esp8266_flash_address(), this->flash_storage_, ESP8266_FLASH_STORAGE_SIZE * 4);
enable_interrupts();
}
ESPPreferenceObject ESPPreferences::make_preference(size_t length, uint32_t type) {
ESPPreferenceObject ESPPreferences::make_preference(size_t length, uint32_t type, bool in_flash) {
if (in_flash) {
uint32_t start = this->current_flash_offset_;
uint32_t end = start + length + 1;
if (end > ESP8266_FLASH_STORAGE_SIZE)
return {};
auto pref = ESPPreferenceObject(start, length, type);
pref.in_flash_ = true;
this->current_flash_offset_ = end;
return pref;
}
uint32_t start = this->current_offset_;
uint32_t end = start + length + 1;
bool in_normal = start < 96;
@@ -165,7 +204,7 @@ ESPPreferenceObject ESPPreferences::make_preference(size_t length, uint32_t type
if (end > 128) {
// Doesn't fit in data, return uninitialized preference obj.
return ESPPreferenceObject();
return {};
}
uint32_t rtc_offset;
@@ -185,35 +224,67 @@ bool ESPPreferences::is_prevent_write() { return this->prevent_write_; }
#ifdef ARDUINO_ARCH_ESP32
bool ESPPreferenceObject::save_internal_() {
if (global_preferences.nvs_handle_ == 0)
return false;
char key[32];
sprintf(key, "%u", this->rtc_offset_);
sprintf(key, "%u", this->offset_);
uint32_t len = (this->length_words_ + 1) * 4;
size_t ret = global_preferences.preferences_.putBytes(key, this->data_, len);
if (ret != len) {
ESP_LOGV(TAG, "putBytes failed!");
esp_err_t err = nvs_set_blob(global_preferences.nvs_handle_, key, this->data_, len);
if (err) {
ESP_LOGV(TAG, "nvs_set_blob('%s', len=%u) failed: %s", key, len, esp_err_to_name(err));
return false;
}
err = nvs_commit(global_preferences.nvs_handle_);
if (err) {
ESP_LOGV(TAG, "nvs_commit('%s', len=%u) failed: %s", key, len, esp_err_to_name(err));
return false;
}
return true;
}
bool ESPPreferenceObject::load_internal_() {
if (global_preferences.nvs_handle_ == 0)
return false;
char key[32];
sprintf(key, "%u", this->rtc_offset_);
sprintf(key, "%u", this->offset_);
uint32_t len = (this->length_words_ + 1) * 4;
size_t ret = global_preferences.preferences_.getBytes(key, this->data_, len);
if (ret != len) {
ESP_LOGV(TAG, "getBytes failed!");
uint32_t actual_len;
esp_err_t err = nvs_get_blob(global_preferences.nvs_handle_, key, nullptr, &actual_len);
if (err) {
ESP_LOGV(TAG, "nvs_get_blob('%s'): %s - the key might not be set yet", key, esp_err_to_name(err));
return false;
}
if (actual_len != len) {
ESP_LOGVV(TAG, "NVS length does not match. Assuming key changed (%u!=%u)", actual_len, len);
return false;
}
err = nvs_get_blob(global_preferences.nvs_handle_, key, this->data_, &len);
if (err) {
ESP_LOGV(TAG, "nvs_get_blob('%s') failed: %s", key, esp_err_to_name(err));
return false;
}
return true;
}
ESPPreferences::ESPPreferences() : current_offset_(0) {}
void ESPPreferences::begin(const std::string &name) {
const std::string key = truncate_string(name, 15);
ESP_LOGV(TAG, "Opening preferences with key '%s'", key.c_str());
this->preferences_.begin(key.c_str());
void ESPPreferences::begin() {
auto ns = truncate_string(App.get_name(), 15);
esp_err_t err = nvs_open(ns.c_str(), NVS_READWRITE, &this->nvs_handle_);
if (err) {
ESP_LOGW(TAG, "nvs_open failed: %s - erasing NVS...", esp_err_to_name(err));
nvs_flash_deinit();
nvs_flash_erase();
nvs_flash_init();
err = nvs_open(ns.c_str(), NVS_READWRITE, &this->nvs_handle_);
if (err) {
this->nvs_handle_ = 0;
}
}
}
ESPPreferenceObject ESPPreferences::make_preference(size_t length, uint32_t type) {
ESPPreferenceObject ESPPreferences::make_preference(size_t length, uint32_t type, bool in_flash) {
auto pref = ESPPreferenceObject(this->current_offset_, length, type);
this->current_offset_++;
return pref;
+29 -12
View File
@@ -2,18 +2,15 @@
#include <string>
#ifdef ARDUINO_ARCH_ESP32
#include <Preferences.h>
#endif
#include "esphome/core/esphal.h"
#include "esphome/core/defines.h"
namespace esphome {
class ESPPreferenceObject {
public:
ESPPreferenceObject();
ESPPreferenceObject(size_t rtc_offset, size_t length, uint32_t type);
ESPPreferenceObject(size_t offset, size_t length, uint32_t type);
template<typename T> bool save(T *src);
@@ -22,6 +19,8 @@ class ESPPreferenceObject {
bool is_initialized() const;
protected:
friend class ESPPreferences;
bool save_();
bool load_();
bool save_internal_();
@@ -29,18 +28,33 @@ class ESPPreferenceObject {
uint32_t calculate_crc_() const;
size_t rtc_offset_;
size_t offset_;
size_t length_words_;
uint32_t type_;
uint32_t *data_;
#ifdef ARDUINO_ARCH_ESP8266
bool in_flash_{false};
#endif
};
#ifdef ARDUINO_ARCH_ESP8266
#ifdef USE_ESP8266_PREFERENCES_FLASH
static bool DEFAULT_IN_FLASH = true;
#else
static bool DEFAULT_IN_FLASH = false;
#endif
#endif
#ifdef ARDUINO_ARCH_ESP32
static bool DEFAULT_IN_FLASH = true;
#endif
class ESPPreferences {
public:
ESPPreferences();
void begin(const std::string &name);
ESPPreferenceObject make_preference(size_t length, uint32_t type);
template<typename T> ESPPreferenceObject make_preference(uint32_t type);
void begin();
ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash = DEFAULT_IN_FLASH);
template<typename T> ESPPreferenceObject make_preference(uint32_t type, bool in_flash = DEFAULT_IN_FLASH);
#ifdef ARDUINO_ARCH_ESP8266
/** On the ESP8266, we can't override the first 128 bytes during OTA uploads
@@ -59,17 +73,20 @@ class ESPPreferences {
uint32_t current_offset_;
#ifdef ARDUINO_ARCH_ESP32
Preferences preferences_;
uint32_t nvs_handle_;
#endif
#ifdef ARDUINO_ARCH_ESP8266
void save_esp8266_flash_();
bool prevent_write_{false};
uint32_t *flash_storage_;
uint32_t current_flash_offset_;
#endif
};
extern ESPPreferences global_preferences;
template<typename T> ESPPreferenceObject ESPPreferences::make_preference(uint32_t type) {
return this->make_preference((sizeof(T) + 3) / 4, type);
template<typename T> ESPPreferenceObject ESPPreferences::make_preference(uint32_t type, bool in_flash) {
return this->make_preference((sizeof(T) + 3) / 4, type, in_flash);
}
template<typename T> bool ESPPreferenceObject::save(T *src) {
+229
View File
@@ -0,0 +1,229 @@
#include "scheduler.h"
#include "esphome/core/log.h"
#include "esphome/core/helpers.h"
#include <algorithm>
namespace esphome {
static const char *TAG = "scheduler";
static const uint32_t SCHEDULER_DONT_RUN = 4294967295UL;
void HOT Scheduler::set_timeout(Component *component, const std::string &name, uint32_t timeout,
std::function<void()> &&func) {
const uint32_t now = this->millis_();
if (!name.empty())
this->cancel_timeout(component, name);
if (timeout == SCHEDULER_DONT_RUN)
return;
ESP_LOGVV(TAG, "set_timeout(name='%s', timeout=%u)", name.c_str(), timeout);
auto *item = new SchedulerItem();
item->component = component;
item->name = name;
item->type = SchedulerItem::TIMEOUT;
item->timeout = timeout;
item->last_execution = now;
item->last_execution_major = this->millis_major_;
item->f = std::move(func);
item->remove = false;
this->push_(item);
}
bool HOT Scheduler::cancel_timeout(Component *component, const std::string &name) {
return this->cancel_item_(component, name, SchedulerItem::TIMEOUT);
}
void HOT Scheduler::set_interval(Component *component, const std::string &name, uint32_t interval,
std::function<void()> &&func) {
const uint32_t now = this->millis_();
if (!name.empty())
this->cancel_interval(component, name);
if (interval == SCHEDULER_DONT_RUN)
return;
// only put offset in lower half
uint32_t offset = 0;
if (interval != 0)
offset = (random_uint32() % interval) / 2;
ESP_LOGVV(TAG, "set_interval(name='%s', interval=%u, offset=%u)", name.c_str(), interval, offset);
auto *item = new SchedulerItem();
item->component = component;
item->name = name;
item->type = SchedulerItem::INTERVAL;
item->interval = interval;
item->last_execution = now - offset;
item->last_execution_major = this->millis_major_;
if (item->last_execution > now)
item->last_execution_major--;
item->f = std::move(func);
item->remove = false;
this->push_(item);
}
bool HOT Scheduler::cancel_interval(Component *component, const std::string &name) {
return this->cancel_item_(component, name, SchedulerItem::INTERVAL);
}
optional<uint32_t> HOT Scheduler::next_schedule_in() {
if (this->empty_())
return {};
auto *item = this->items_[0];
const uint32_t now = this->millis_();
uint32_t next_time = item->last_execution + item->interval;
if (next_time < now)
return 0;
return next_time - now;
}
void ICACHE_RAM_ATTR HOT Scheduler::call() {
const uint32_t now = this->millis_();
this->process_to_add();
// Uncomment for debugging the scheduler:
// if (random_uint32() % 400 == 0) {
// std::vector<SchedulerItem *> old_items = this->items_;
// ESP_LOGVV(TAG, "Items: count=%u, now=%u", this->items_.size(), now);
// while (!this->empty_()) {
// auto *item = this->items_[0];
// const char *type = item->type == SchedulerItem::INTERVAL ? "interval" : "timeout";
// ESP_LOGVV(TAG, " %s '%s' interval=%u last_execution=%u (%u) next=%u",
// type, item->name.c_str(), item->interval, item->last_execution, item->last_execution_major,
// item->last_execution + item->interval);
// this->pop_raw_();
// }
// ESP_LOGVV(TAG, "\n");
// this->items_ = old_items;
//}
while (!this->empty_()) {
// Don't copy-by value yet
auto *item = this->items_[0];
if ((now - item->last_execution) < item->interval)
// Not reached timeout yet, done for this call
break;
uint8_t major = item->last_execution_major;
if (item->last_execution + item->interval < item->last_execution)
major++;
if (major != this->millis_major_)
break;
// Don't run on failed components
if (item->component != nullptr && item->component->is_failed()) {
this->pop_raw_();
delete item;
continue;
}
#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE
const char *type = item->type == SchedulerItem::INTERVAL ? "interval" : "timeout";
ESP_LOGVV(TAG, "Running %s '%s' with interval=%u last_execution=%u (now=%u)", type, item->name.c_str(),
item->interval, item->last_execution, now);
#endif
// Warning: During f(), a lot of stuff can happen, including:
// - timeouts/intervals get added, potentially invalidating vector pointers
// - timeouts/intervals get cancelled
item->f();
// Only pop after function call, this ensures we were reachable
// during the function call and know if we were cancelled.
this->pop_raw_();
if (item->remove) {
// We were removed/cancelled in the function call, stop
delete item;
continue;
}
if (item->type == SchedulerItem::INTERVAL) {
if (item->interval != 0) {
const uint32_t before = item->last_execution;
const uint32_t amount = (now - item->last_execution) / item->interval;
item->last_execution += amount * item->interval;
if (item->last_execution < before)
item->last_execution_major++;
}
this->push_(item);
} else {
delete item;
}
}
this->process_to_add();
}
void HOT Scheduler::process_to_add() {
for (auto *it : this->to_add_) {
if (it->remove) {
delete it;
continue;
}
this->items_.push_back(it);
std::push_heap(this->items_.begin(), this->items_.end(), SchedulerItem::cmp);
}
this->to_add_.clear();
}
void HOT Scheduler::cleanup_() {
while (!this->items_.empty()) {
auto item = this->items_[0];
if (!item->remove)
return;
delete item;
this->pop_raw_();
}
}
void HOT Scheduler::pop_raw_() {
std::pop_heap(this->items_.begin(), this->items_.end(), SchedulerItem::cmp);
this->items_.pop_back();
}
void HOT Scheduler::push_(Scheduler::SchedulerItem *item) { this->to_add_.push_back(item); }
bool HOT Scheduler::cancel_item_(Component *component, const std::string &name, Scheduler::SchedulerItem::Type type) {
bool ret = false;
for (auto *it : this->items_)
if (it->component == component && it->name == name && it->type == type) {
it->remove = true;
ret = true;
}
for (auto *it : this->to_add_)
if (it->component == component && it->name == name && it->type == type) {
it->remove = true;
ret = true;
}
return ret;
}
uint32_t Scheduler::millis_() {
const uint32_t now = millis();
if (now < this->last_millis_) {
ESP_LOGD(TAG, "Incrementing scheduler major");
this->millis_major_++;
}
return now;
}
bool HOT Scheduler::SchedulerItem::cmp(Scheduler::SchedulerItem *a, Scheduler::SchedulerItem *b) {
// min-heap
// return true if *a* will happen after *b*
uint32_t a_next_exec = a->last_execution + a->timeout;
uint8_t a_next_exec_major = a->last_execution_major;
if (a_next_exec < a->last_execution)
a_next_exec_major++;
uint32_t b_next_exec = b->last_execution + b->timeout;
uint8_t b_next_exec_major = b->last_execution_major;
if (b_next_exec < b->last_execution)
b_next_exec_major++;
if (a_next_exec_major != b_next_exec_major) {
return a_next_exec_major > b_next_exec_major;
}
return a_next_exec > b_next_exec;
}
} // namespace esphome
+56
View File
@@ -0,0 +1,56 @@
#pragma once
#include "esphome/core/component.h"
#include <vector>
namespace esphome {
class Component;
class Scheduler {
public:
void set_timeout(Component *component, const std::string &name, uint32_t timeout, std::function<void()> &&func);
bool cancel_timeout(Component *component, const std::string &name);
void set_interval(Component *component, const std::string &name, uint32_t interval, std::function<void()> &&func);
bool cancel_interval(Component *component, const std::string &name);
optional<uint32_t> next_schedule_in();
void call();
void process_to_add();
protected:
struct SchedulerItem {
Component *component;
std::string name;
enum Type { TIMEOUT, INTERVAL } type;
union {
uint32_t interval;
uint32_t timeout;
};
uint32_t last_execution;
std::function<void()> f;
bool remove;
uint8_t last_execution_major;
static bool cmp(SchedulerItem *a, SchedulerItem *b);
};
uint32_t millis_();
void cleanup_();
void pop_raw_();
void push_(SchedulerItem *item);
bool cancel_item_(Component *component, const std::string &name, SchedulerItem::Type type);
bool empty_() {
this->cleanup_();
return this->items_.empty();
}
std::vector<SchedulerItem *> items_;
std::vector<SchedulerItem *> to_add_;
uint32_t last_millis_{0};
uint8_t millis_major_{0};
};
} // namespace esphome
+46 -72
View File
@@ -1,6 +1,8 @@
#include "esphome/core/util.h"
#include "esphome/core/defines.h"
#include "esphome/core/application.h"
#include "esphome/core/version.h"
#include "esphome/core/log.h"
#ifdef USE_WIFI
#include "esphome/components/wifi/wifi_component.h"
@@ -37,84 +39,56 @@ bool network_is_connected() {
return false;
}
void network_setup() {
bool ready = true;
#ifdef USE_ETHERNET
if (ethernet::global_eth_component != nullptr) {
ethernet::global_eth_component->call_setup();
ready = false;
}
#endif
#ifdef USE_WIFI
if (wifi::global_wifi_component != nullptr) {
wifi::global_wifi_component->call_setup();
ready = false;
}
#endif
while (!ready) {
#ifdef USE_ETHERNET
if (ethernet::global_eth_component != nullptr) {
ethernet::global_eth_component->call_loop();
ready = ready || ethernet::global_eth_component->can_proceed();
}
#endif
#ifdef USE_WIFI
if (wifi::global_wifi_component != nullptr) {
wifi::global_wifi_component->call_loop();
ready = ready || wifi::global_wifi_component->can_proceed();
}
#endif
App.feed_wdt();
}
}
void network_tick() {
#ifdef USE_ETHERNET
if (ethernet::global_eth_component != nullptr)
ethernet::global_eth_component->call_loop();
#endif
#ifdef USE_WIFI
if (wifi::global_wifi_component != nullptr)
wifi::global_wifi_component->call_loop();
#endif
}
void network_setup_mdns() {
MDNS.begin(App.get_name().c_str());
#ifdef USE_API
if (api::global_api_server != nullptr) {
MDNS.addService("esphomelib", "tcp", api::global_api_server->get_port());
// DNS-SD (!=mDNS !) requires at least one TXT record for service discovery - let's add version
MDNS.addServiceTxt("esphomelib", "tcp", "version", ESPHOME_VERSION);
MDNS.addServiceTxt("esphomelib", "tcp", "address", network_get_address().c_str());
} else {
#endif
// Publish "http" service if not using native API.
// This is just to have *some* mDNS service so that .local resolution works
MDNS.addService("http", "tcp", 80);
MDNS.addServiceTxt("http", "tcp", "version", ESPHOME_VERSION);
#ifdef USE_API
}
#endif
}
void network_tick_mdns() {
#ifdef ARDUINO_ARCH_ESP8266
MDNS.update();
bool mdns_setup;
#endif
}
std::string network_get_address() {
#ifdef ARDUINO_ARCH_ESP8266
void network_setup_mdns(IPAddress address, int interface) {
// Latest arduino framework breaks mDNS for AP interface
// see https://github.com/esp8266/Arduino/issues/6114
if (interface == 1)
return;
MDNS.begin(App.get_name().c_str(), address);
mdns_setup = true;
#endif
#ifdef ARDUINO_ARCH_ESP32
void network_setup_mdns() {
MDNS.begin(App.get_name().c_str());
#endif
#ifdef USE_API
if (api::global_api_server != nullptr) {
MDNS.addService("esphomelib", "tcp", api::global_api_server->get_port());
// DNS-SD (!=mDNS !) requires at least one TXT record for service discovery - let's add version
MDNS.addServiceTxt("esphomelib", "tcp", "version", ESPHOME_VERSION);
MDNS.addServiceTxt("esphomelib", "tcp", "address", network_get_address().c_str());
} else {
#endif
// Publish "http" service if not using native API.
// This is just to have *some* mDNS service so that .local resolution works
MDNS.addService("http", "tcp", 80);
MDNS.addServiceTxt("http", "tcp", "version", ESPHOME_VERSION);
#ifdef USE_API
}
#endif
}
void network_tick_mdns() {
#ifdef ARDUINO_ARCH_ESP8266
if (mdns_setup)
MDNS.update();
#endif
}
std::string network_get_address() {
#ifdef USE_ETHERNET
if (ethernet::global_eth_component != nullptr)
return ethernet::global_eth_component->get_use_address();
if (ethernet::global_eth_component != nullptr)
return ethernet::global_eth_component->get_use_address();
#endif
#ifdef USE_WIFI
if (wifi::global_wifi_component != nullptr)
return wifi::global_wifi_component->get_use_address();
if (wifi::global_wifi_component != nullptr)
return wifi::global_wifi_component->get_use_address();
#endif
return "";
}
return "";
}
} // namespace esphome
+6 -2
View File
@@ -1,6 +1,7 @@
#pragma once
#include <string>
#include "IPAddress.h"
namespace esphome {
@@ -10,9 +11,12 @@ bool network_is_connected();
std::string network_get_address();
/// Manually set up the network stack (outside of the App.setup() loop, for example in OTA safe mode)
void network_setup();
void network_tick();
#ifdef ARDUINO_ARCH_ESP8266
void network_setup_mdns(IPAddress address, int interface);
#endif
#ifdef ARDUINO_ARCH_ESP32
void network_setup_mdns();
#endif
void network_tick_mdns();
} // namespace esphome
+3
View File
@@ -0,0 +1,3 @@
#pragma once
// This file is auto-generated! Do not edit!
#define ESPHOME_VERSION "dev"