🏗 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
+113
View File
@@ -0,0 +1,113 @@
#include "esphome/core/application.h"
#include "esphome/core/log.h"
#ifdef USE_STATUS_LED
#include "esphome/components/status_led/status_led.h"
#endif
namespace esphome {
static const char *TAG = "app";
void Application::register_component_(Component *comp) {
if (comp == nullptr) {
ESP_LOGW(TAG, "Tried to register null component!");
return;
}
for (auto *c : this->components_) {
if (comp == c) {
ESP_LOGW(TAG, "Component already registered! (%p)", c);
return;
}
}
this->components_.push_back(comp);
}
void Application::setup() {
ESP_LOGI(TAG, "Running through setup()...");
ESP_LOGV(TAG, "Sorting components by setup priority...");
std::stable_sort(this->components_.begin(), this->components_.end(), [](const Component *a, const Component *b) {
return a->get_actual_setup_priority() > b->get_actual_setup_priority();
});
for (uint32_t i = 0; i < this->components_.size(); i++) {
Component *component = this->components_[i];
if (component->is_failed())
continue;
component->call_setup();
if (component->can_proceed())
continue;
std::stable_sort(this->components_.begin(), this->components_.begin() + i + 1,
[](Component *a, Component *b) { return a->get_loop_priority() > b->get_loop_priority(); });
do {
uint32_t new_app_state = STATUS_LED_WARNING;
for (uint32_t j = 0; j <= i; j++) {
if (!this->components_[j]->is_failed()) {
this->components_[j]->call_loop();
}
new_app_state |= this->components_[j]->get_component_state();
this->app_state_ |= new_app_state;
}
this->app_state_ = new_app_state;
yield();
} while (!component->can_proceed());
}
ESP_LOGI(TAG, "setup() finished successfully!");
this->dump_config();
}
void Application::dump_config() {
ESP_LOGI(TAG, "esphome-core version " ESPHOME_VERSION " compiled on %s", this->compilation_time_.c_str());
for (auto component : this->components_) {
component->dump_config();
}
}
void ICACHE_RAM_ATTR HOT Application::feed_wdt() {
static uint32_t LAST_FEED = 0;
uint32_t now = millis();
if (now - LAST_FEED > 3) {
#ifdef ARDUINO_ARCH_ESP8266
ESP.wdtFeed();
#endif
#ifdef ARDUINO_ARCH_ESP32
yield();
#endif
LAST_FEED = now;
#ifdef USE_STATUS_LED
if (status_led::global_status_led != nullptr) {
status_led::global_status_led->call_loop();
}
#endif
}
}
void Application::reboot() {
ESP_LOGI(TAG, "Forcing a reboot...");
for (auto *comp : this->components_)
comp->on_shutdown();
ESP.restart();
// restart() doesn't always end execution
while (true) {
yield();
}
}
void Application::safe_reboot() {
ESP_LOGI(TAG, "Rebooting safely...");
for (auto *comp : this->components_)
comp->on_safe_shutdown();
for (auto *comp : this->components_)
comp->on_shutdown();
ESP.restart();
// restart() doesn't always end execution
while (true) {
yield();
}
}
Application App;
} // namespace esphome
+271
View File
@@ -0,0 +1,271 @@
#pragma once
#include <string>
#include <vector>
#include "esphome/core/defines.h"
#include "esphome/core/preferences.h"
#include "esphome/core/component.h"
#include "esphome/core/helpers.h"
#ifdef USE_BINARY_SENSOR
#include "esphome/components/binary_sensor/binary_sensor.h"
#endif
#ifdef USE_SENSOR
#include "esphome/components/sensor/sensor.h"
#endif
#ifdef USE_SWITCH
#include "esphome/components/switch/switch.h"
#endif
#ifdef USE_TEXT_SENSOR
#include "esphome/components/text_sensor/text_sensor.h"
#endif
#ifdef USE_FAN
#include "esphome/components/fan/fan_state.h"
#endif
#ifdef USE_CLIMATE
#include "esphome/components/climate/climate.h"
#endif
#ifdef USE_LIGHT
#include "esphome/components/light/light_state.h"
#endif
#ifdef USE_COVER
#include "esphome/components/cover/cover.h"
#endif
namespace esphome {
class Application {
public:
void pre_setup(const std::string &name, const char *compilation_time) {
this->name_ = name;
this->compilation_time_ = compilation_time;
global_preferences.begin(this->name_);
}
#ifdef USE_BINARY_SENSOR
void register_binary_sensor(binary_sensor::BinarySensor *binary_sensor) {
this->binary_sensors_.push_back(binary_sensor);
}
#endif
#ifdef USE_SENSOR
void register_sensor(sensor::Sensor *sensor) { this->sensors_.push_back(sensor); }
#endif
#ifdef USE_SWITCH
void register_switch(switch_::Switch *a_switch) { this->switches_.push_back(a_switch); }
#endif
#ifdef USE_TEXT_SENSOR
void register_text_sensor(text_sensor::TextSensor *sensor) { this->text_sensors_.push_back(sensor); }
#endif
#ifdef USE_FAN
void register_fan(fan::FanState *state) { this->fans_.push_back(state); }
#endif
#ifdef USE_COVER
void register_cover(cover::Cover *cover) { this->covers_.push_back(cover); }
#endif
#ifdef USE_CLIMATE
void register_climate(climate::Climate *climate) { this->climates_.push_back(climate); }
#endif
#ifdef USE_LIGHT
void register_light(light::LightState *light) { this->lights_.push_back(light); }
#endif
/// Register the component in this Application instance.
template<class C> C *register_component(C *c) {
static_assert(std::is_base_of<Component, C>::value, "Only Component subclasses can be registered");
this->register_component_((Component *) c);
return c;
}
/// Set up all the registered components. Call this at the end of your setup() function.
void setup();
/// Make a loop iteration. Call this in your loop() function.
void loop() {
uint32_t new_app_state = 0;
for (Component *component : this->components_) {
if (!component->is_failed()) {
component->call_loop();
}
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 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_);
delay(delay_time);
}
this->last_loop_ = now;
if (this->dump_config_scheduled_) {
this->dump_config();
this->dump_config_scheduled_ = false;
}
}
/// Get the name of this Application set by set_name().
const std::string &get_name() const { return this->name_; }
const std::string &get_compilation_time() const { return this->compilation_time_; }
/** Set the target interval with which to run the loop() calls.
* If the loop() method takes longer than the target interval, ESPHome won't
* sleep in loop(), but if the time spent in loop() is small than the target, ESPHome
* will delay at the end of the App.loop() method.
*
* This is done to conserve power: In most use-cases, high-speed loop() calls are not required
* and degrade power consumption.
*
* Each component can request a high frequency loop execution by using the HighFrequencyLoopRequester
* helper in helpers.h
*
* @param loop_interval The interval in milliseconds to run the core loop at. Defaults to 16 milliseconds.
*/
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 feed_wdt();
void reboot();
void safe_reboot();
void run_safe_shutdown_hooks() {
for (auto *comp : this->components_)
comp->on_safe_shutdown();
}
uint32_t get_app_state() const { return this->app_state_; }
#ifdef USE_BINARY_SENSOR
const std::vector<binary_sensor::BinarySensor *> &get_binary_sensors() { return this->binary_sensors_; }
binary_sensor::BinarySensor *get_binary_sensor_by_key(uint32_t key, bool include_internal = false) {
for (auto *obj : this->binary_sensors_)
if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal()))
return obj;
return nullptr;
}
#endif
#ifdef USE_SWITCH
const std::vector<switch_::Switch *> &get_switches() { return this->switches_; }
switch_::Switch *get_switch_by_key(uint32_t key, bool include_internal = false) {
for (auto *obj : this->switches_)
if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal()))
return obj;
return nullptr;
}
#endif
#ifdef USE_SENSOR
const std::vector<sensor::Sensor *> &get_sensors() { return this->sensors_; }
sensor::Sensor *get_sensor_by_key(uint32_t key, bool include_internal = false) {
for (auto *obj : this->sensors_)
if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal()))
return obj;
return nullptr;
}
#endif
#ifdef USE_TEXT_SENSOR
const std::vector<text_sensor::TextSensor *> &get_text_sensors() { return this->text_sensors_; }
text_sensor::TextSensor *get_text_sensor_by_key(uint32_t key, bool include_internal = false) {
for (auto *obj : this->text_sensors_)
if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal()))
return obj;
return nullptr;
}
#endif
#ifdef USE_FAN
const std::vector<fan::FanState *> &get_fans() { return this->fans_; }
fan::FanState *get_fan_by_key(uint32_t key, bool include_internal = false) {
for (auto *obj : this->fans_)
if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal()))
return obj;
return nullptr;
}
#endif
#ifdef USE_COVER
const std::vector<cover::Cover *> &get_covers() { return this->covers_; }
cover::Cover *get_cover_by_key(uint32_t key, bool include_internal = false) {
for (auto *obj : this->covers_)
if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal()))
return obj;
return nullptr;
}
#endif
#ifdef USE_LIGHT
const std::vector<light::LightState *> &get_lights() { return this->lights_; }
light::LightState *get_light_by_key(uint32_t key, bool include_internal = false) {
for (auto *obj : this->lights_)
if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal()))
return obj;
return nullptr;
}
#endif
#ifdef USE_CLIMATE
const std::vector<climate::Climate *> &get_climates() { return this->climates_; }
climate::Climate *get_climate_by_key(uint32_t key, bool include_internal = false) {
for (auto *obj : this->climates_)
if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal()))
return obj;
return nullptr;
}
#endif
protected:
friend Component;
void register_component_(Component *comp);
std::vector<Component *> components_{};
#ifdef USE_BINARY_SENSOR
std::vector<binary_sensor::BinarySensor *> binary_sensors_{};
#endif
#ifdef USE_SWITCH
std::vector<switch_::Switch *> switches_{};
#endif
#ifdef USE_SENSOR
std::vector<sensor::Sensor *> sensors_{};
#endif
#ifdef USE_TEXT_SENSOR
std::vector<text_sensor::TextSensor *> text_sensors_{};
#endif
#ifdef USE_FAN
std::vector<fan::FanState *> fans_{};
#endif
#ifdef USE_COVER
std::vector<cover::Cover *> covers_{};
#endif
#ifdef USE_CLIMATE
std::vector<climate::Climate *> climates_{};
#endif
#ifdef USE_LIGHT
std::vector<light::LightState *> lights_{};
#endif
std::string name_;
std::string compilation_time_;
uint32_t last_loop_{0};
uint32_t loop_interval_{16};
bool dump_config_scheduled_{false};
uint32_t app_state_{0};
};
/// Global storage of Application pointer - only one Application can exist.
extern Application App;
} // namespace esphome
+33
View File
@@ -0,0 +1,33 @@
#include "esphome/core/automation.h"
namespace esphome {
static const char *TAG = "automation";
void StartupTrigger::setup() { this->trigger(); }
float StartupTrigger::get_setup_priority() const {
// Run after everything is set up
return this->setup_priority_;
}
StartupTrigger::StartupTrigger(float setup_priority) : setup_priority_(setup_priority) {}
void ShutdownTrigger::on_shutdown() { this->trigger(); }
void LoopTrigger::loop() { this->trigger(); }
float LoopTrigger::get_setup_priority() const { return setup_priority::DATA; }
RangeCondition::RangeCondition() = default;
bool RangeCondition::check(float x) {
float min = this->min_.value(x);
float max = this->max_.value(x);
if (isnan(min)) {
return x >= max;
} else if (isnan(max)) {
return x >= min;
} else {
return min <= x && x <= max;
}
}
} // namespace esphome
+241
View File
@@ -0,0 +1,241 @@
#pragma once
#include <vector>
#include "esphome/core/component.h"
#include "esphome/core/helpers.h"
#include "esphome/core/defines.h"
#include "esphome/core/preferences.h"
namespace esphome {
#define TEMPLATABLE_VALUE_(type, name) \
protected: \
TemplatableValue<type, Ts...> name##_{}; \
\
public: \
template<typename V> void set_##name(V value_) { this->name##_ = value_; }
#define TEMPLATABLE_VALUE(type, name) TEMPLATABLE_VALUE_(type, name)
template<typename... Ts> class Condition {
public:
virtual bool check(Ts... x) = 0;
bool check_tuple(const std::tuple<Ts...> &tuple);
protected:
template<int... S> bool check_tuple_(const std::tuple<Ts...> &tuple, seq<S...>);
};
template<typename... Ts> class AndCondition : public Condition<Ts...> {
public:
explicit AndCondition(const std::vector<Condition<Ts...> *> &conditions);
bool check(Ts... x) override;
protected:
std::vector<Condition<Ts...> *> conditions_;
};
template<typename... Ts> class OrCondition : public Condition<Ts...> {
public:
explicit OrCondition(const std::vector<Condition<Ts...> *> &conditions);
bool check(Ts... x) override;
protected:
std::vector<Condition<Ts...> *> conditions_;
};
template<typename... Ts> class LambdaCondition : public Condition<Ts...> {
public:
explicit LambdaCondition(std::function<bool(Ts...)> &&f);
bool check(Ts... x) override;
protected:
std::function<bool(Ts...)> f_;
};
class RangeCondition : public Condition<float> {
public:
explicit RangeCondition();
bool check(float x) override;
template<typename V> void set_min(V value) { this->min_ = value; }
template<typename V> void set_max(V value) { this->max_ = value; }
protected:
TemplatableValue<float, float> min_{NAN};
TemplatableValue<float, float> max_{NAN};
};
template<typename... Ts> class Automation;
template<typename... Ts> class Trigger {
public:
void trigger(Ts... x);
void set_parent(Automation<Ts...> *parent);
void stop();
protected:
Automation<Ts...> *parent_{nullptr};
};
class StartupTrigger : public Trigger<>, public Component {
public:
explicit StartupTrigger(float setup_priority = setup_priority::LATE);
void setup() override;
float get_setup_priority() const override;
protected:
float setup_priority_;
};
class ShutdownTrigger : public Trigger<>, public Component {
public:
void on_shutdown() override;
};
class LoopTrigger : public Trigger<>, public Component {
public:
void loop() override;
float get_setup_priority() const override;
};
template<typename... Ts> class ActionList;
template<typename... Ts> class Action {
public:
virtual void play(Ts... x) = 0;
void play_next(Ts... x);
virtual void stop();
void stop_next();
void play_next_tuple(const std::tuple<Ts...> &tuple);
protected:
friend ActionList<Ts...>;
template<int... S> void play_next_tuple_(const std::tuple<Ts...> &tuple, seq<S...>);
Action<Ts...> *next_ = nullptr;
};
template<typename... Ts> class DelayAction : public Action<Ts...>, public Component {
public:
explicit DelayAction();
template<typename V> void set_delay(V value) { this->delay_ = value; }
void stop() override;
void play(Ts... x) override;
float get_setup_priority() const override;
protected:
TemplatableValue<uint32_t, Ts...> delay_{0};
};
template<typename... Ts> class LambdaAction : public Action<Ts...> {
public:
explicit LambdaAction(std::function<void(Ts...)> &&f);
void play(Ts... x) override;
protected:
std::function<void(Ts...)> f_;
};
template<typename... Ts> class IfAction : public Action<Ts...> {
public:
explicit IfAction(std::vector<Condition<Ts...> *> conditions);
void add_then(const std::vector<Action<Ts...> *> &actions);
void add_else(const std::vector<Action<Ts...> *> &actions);
void play(Ts... x) override;
void stop() override;
protected:
std::vector<Condition<Ts...> *> conditions_;
ActionList<Ts...> then_;
ActionList<Ts...> else_;
};
template<typename... Ts> class WhileAction : public Action<Ts...> {
public:
WhileAction(const std::vector<Condition<Ts...> *> &conditions);
void add_then(const std::vector<Action<Ts...> *> &actions);
void play(Ts... x) override;
void stop() override;
protected:
std::vector<Condition<Ts...> *> conditions_;
ActionList<Ts...> then_;
bool is_running_{false};
};
template<typename... Ts> class WaitUntilAction : public Action<Ts...>, public Component {
public:
WaitUntilAction(const std::vector<Condition<Ts...> *> &conditions);
void play(Ts... x) override;
void stop() override;
void loop() override;
float get_setup_priority() const override;
protected:
std::vector<Condition<Ts...> *> conditions_;
bool triggered_{false};
std::tuple<Ts...> var_{};
};
template<typename... Ts> class UpdateComponentAction : public Action<Ts...> {
public:
UpdateComponentAction(PollingComponent *component);
void play(Ts... x) override;
protected:
PollingComponent *component_;
};
template<typename... Ts> class ActionList {
public:
Action<Ts...> *add_action(Action<Ts...> *action);
void add_actions(const std::vector<Action<Ts...> *> &actions);
void play(Ts... x);
void stop();
bool empty() const;
protected:
Action<Ts...> *actions_begin_{nullptr};
Action<Ts...> *actions_end_{nullptr};
};
template<typename... Ts> class Automation {
public:
explicit Automation(Trigger<Ts...> *trigger);
Condition<Ts...> *add_condition(Condition<Ts...> *condition);
void add_conditions(const std::vector<Condition<Ts...> *> &conditions);
Action<Ts...> *add_action(Action<Ts...> *action);
void add_actions(const std::vector<Action<Ts...> *> &actions);
void stop();
void trigger(Ts... x);
protected:
Trigger<Ts...> *trigger_;
std::vector<Condition<Ts...> *> conditions_;
ActionList<Ts...> actions_;
};
} // namespace esphome
#include "esphome/core/automation.tcc"
+243
View File
@@ -0,0 +1,243 @@
#pragma once
#include "esphome/core/automation.h"
namespace esphome {
template<typename... Ts> bool Condition<Ts...>::check_tuple(const std::tuple<Ts...> &tuple) {
return this->check_tuple_(tuple, typename gens<sizeof...(Ts)>::type());
}
template<typename... Ts>
template<int... S>
bool Condition<Ts...>::check_tuple_(const std::tuple<Ts...> &tuple, seq<S...>) {
return this->check(std::get<S>(tuple)...);
}
template<typename... Ts> bool AndCondition<Ts...>::check(Ts... x) {
for (auto *condition : this->conditions_) {
if (!condition->check(x...))
return false;
}
return true;
}
template<typename... Ts>
AndCondition<Ts...>::AndCondition(const std::vector<Condition<Ts...> *> &conditions) : conditions_(conditions) {}
template<typename... Ts> bool OrCondition<Ts...>::check(Ts... x) {
for (auto *condition : this->conditions_) {
if (condition->check(x...))
return true;
}
return false;
}
template<typename... Ts>
OrCondition<Ts...>::OrCondition(const std::vector<Condition<Ts...> *> &conditions) : conditions_(conditions) {}
template<typename... Ts> void Trigger<Ts...>::set_parent(Automation<Ts...> *parent) { this->parent_ = parent; }
template<typename... Ts> void Trigger<Ts...>::trigger(Ts... x) {
if (this->parent_ == nullptr)
return;
this->parent_->trigger(x...);
}
template<typename... Ts> void Trigger<Ts...>::stop() {
if (this->parent_ == nullptr)
return;
this->parent_->stop();
}
template<typename... Ts> void Action<Ts...>::play_next(Ts... x) {
if (this->next_ != nullptr) {
this->next_->play(x...);
}
}
template<typename... Ts> void Action<Ts...>::stop() { this->stop_next(); }
template<typename... Ts> void Action<Ts...>::stop_next() {
if (this->next_ != nullptr) {
this->next_->stop();
}
}
template<typename... Ts> void Action<Ts...>::play_next_tuple(const std::tuple<Ts...> &tuple) {
this->play_next_tuple_(tuple, typename gens<sizeof...(Ts)>::type());
}
template<typename... Ts>
template<int... S>
void Action<Ts...>::play_next_tuple_(const std::tuple<Ts...> &tuple, seq<S...>) {
this->play_next(std::get<S>(tuple)...);
}
template<typename... Ts> DelayAction<Ts...>::DelayAction() = default;
template<typename... Ts> void DelayAction<Ts...>::play(Ts... x) {
auto f = std::bind(&DelayAction<Ts...>::play_next, this, x...);
this->set_timeout(this->delay_.value(x...), f);
}
template<typename... Ts> float DelayAction<Ts...>::get_setup_priority() const { return setup_priority::HARDWARE; }
template<typename... Ts> void DelayAction<Ts...>::stop() {
this->cancel_timeout("");
this->stop_next();
}
template<typename... Ts> Condition<Ts...> *Automation<Ts...>::add_condition(Condition<Ts...> *condition) {
this->conditions_.push_back(condition);
return condition;
}
template<typename... Ts> void Automation<Ts...>::add_conditions(const std::vector<Condition<Ts...> *> &conditions) {
for (auto *condition : conditions) {
this->add_condition(condition);
}
}
template<typename... Ts> Automation<Ts...>::Automation(Trigger<Ts...> *trigger) : trigger_(trigger) {
this->trigger_->set_parent(this);
}
template<typename... Ts> Action<Ts...> *Automation<Ts...>::add_action(Action<Ts...> *action) {
this->actions_.add_action(action);
}
template<typename... Ts> void Automation<Ts...>::add_actions(const std::vector<Action<Ts...> *> &actions) {
this->actions_.add_actions(actions);
}
template<typename... Ts> void Automation<Ts...>::trigger(Ts... x) {
for (auto *condition : this->conditions_) {
if (!condition->check(x...))
return;
}
this->actions_.play(x...);
}
template<typename... Ts> void Automation<Ts...>::stop() { this->actions_.stop(); }
template<typename... Ts> LambdaCondition<Ts...>::LambdaCondition(std::function<bool(Ts...)> &&f) : f_(std::move(f)) {}
template<typename... Ts> bool LambdaCondition<Ts...>::check(Ts... x) { return this->f_(x...); }
template<typename... Ts> LambdaAction<Ts...>::LambdaAction(std::function<void(Ts...)> &&f) : f_(std::move(f)) {}
template<typename... Ts> void LambdaAction<Ts...>::play(Ts... x) {
this->f_(x...);
this->play_next(x...);
}
template<typename... Ts> Action<Ts...> *ActionList<Ts...>::add_action(Action<Ts...> *action) {
if (this->actions_end_ == nullptr) {
this->actions_begin_ = action;
} else {
this->actions_end_->next_ = action;
}
return this->actions_end_ = action;
}
template<typename... Ts> void ActionList<Ts...>::add_actions(const std::vector<Action<Ts...> *> &actions) {
for (auto *action : actions) {
this->add_action(action);
}
}
template<typename... Ts> void ActionList<Ts...>::play(Ts... x) {
if (this->actions_begin_ != nullptr)
this->actions_begin_->play(x...);
}
template<typename... Ts> void ActionList<Ts...>::stop() {
if (this->actions_begin_ != nullptr)
this->actions_begin_->stop();
}
template<typename... Ts> bool ActionList<Ts...>::empty() const { return this->actions_begin_ == nullptr; }
template<typename... Ts>
IfAction<Ts...>::IfAction(const std::vector<Condition<Ts...> *> conditions) : conditions_(conditions) {}
template<typename... Ts> void IfAction<Ts...>::play(Ts... x) {
bool res = true;
for (auto *condition : this->conditions_) {
if (!condition->check(x...)) {
res = false;
break;
}
}
if (res) {
if (this->then_.empty()) {
this->play_next(x...);
} else {
this->then_.play(x...);
}
} else {
if (this->else_.empty()) {
this->play_next(x...);
} else {
this->else_.play(x...);
}
}
}
template<typename... Ts> void IfAction<Ts...>::add_then(const std::vector<Action<Ts...> *> &actions) {
this->then_.add_actions(actions);
this->then_.add_action(new LambdaAction<Ts...>([this](Ts... x) { this->play_next(x...); }));
}
template<typename... Ts> void IfAction<Ts...>::add_else(const std::vector<Action<Ts...> *> &actions) {
this->else_.add_actions(actions);
this->else_.add_action(new LambdaAction<Ts...>([this](Ts... x) { this->play_next(x...); }));
}
template<typename... Ts> void IfAction<Ts...>::stop() {
this->then_.stop();
this->else_.stop();
this->stop_next();
}
template<typename... Ts> void UpdateComponentAction<Ts...>::play(Ts... x) {
this->component_->update();
this->play_next(x...);
}
template<typename... Ts>
UpdateComponentAction<Ts...>::UpdateComponentAction(PollingComponent *component) : component_(component) {}
template<typename... Ts>
WhileAction<Ts...>::WhileAction(const std::vector<Condition<Ts...> *> &conditions) : conditions_(conditions) {}
template<typename... Ts> void WhileAction<Ts...>::add_then(const std::vector<Action<Ts...> *> &actions) {
this->then_.add_actions(actions);
this->then_.add_action(new LambdaAction<Ts...>([this](Ts... x) {
this->is_running_ = false;
this->play(x...);
}));
}
template<typename... Ts> void WhileAction<Ts...>::play(Ts... x) {
if (this->is_running_)
return;
for (auto *condition : this->conditions_) {
if (!condition->check(x...)) {
this->play_next(x...);
return;
}
}
this->is_running_ = true;
this->then_.play(x...);
}
template<typename... Ts> void WhileAction<Ts...>::stop() {
this->then_.stop();
this->is_running_ = false;
this->stop_next();
}
template<typename... Ts>
WaitUntilAction<Ts...>::WaitUntilAction(const std::vector<Condition<Ts...> *> &conditions) : conditions_(conditions) {}
template<typename... Ts> void WaitUntilAction<Ts...>::play(Ts... x) {
this->var_ = std::make_tuple(x...);
this->triggered_ = true;
this->loop();
}
template<typename... Ts> void WaitUntilAction<Ts...>::stop() {
this->triggered_ = false;
this->stop_next();
}
template<typename... Ts> void WaitUntilAction<Ts...>::loop() {
if (!this->triggered_)
return;
for (auto *condition : this->conditions_) {
if (!condition->check_tuple(this->var_)) {
return;
}
}
this->triggered_ = false;
this->play_next_tuple(this->var_);
}
template<typename... Ts> float WaitUntilAction<Ts...>::get_setup_priority() const { return setup_priority::DATA; }
} // namespace esphome
+251
View File
@@ -0,0 +1,251 @@
#include <algorithm>
#include "esphome/core/component.h"
#include "esphome/core/helpers.h"
#include "esphome/core/esphal.h"
#include "esphome/core/log.h"
#include "esphome/core/application.h"
namespace esphome {
static const char *TAG = "component";
namespace setup_priority {
const float BUS = 1000.0f;
const float IO = 900.0f;
const float HARDWARE = 800.0f;
const float DATA = 600.0f;
const float PROCESSOR = 400.0;
const float WIFI = 250.0f;
const float AFTER_WIFI = 200.0f;
const float AFTER_CONNECTION = 100.0f;
const float LATE = -100.0f;
} // namespace setup_priority
const uint32_t COMPONENT_STATE_MASK = 0xFF;
const uint32_t COMPONENT_STATE_CONSTRUCTION = 0x00;
const uint32_t COMPONENT_STATE_SETUP = 0x01;
const uint32_t COMPONENT_STATE_LOOP = 0x02;
const uint32_t COMPONENT_STATE_FAILED = 0x03;
const uint32_t STATUS_LED_MASK = 0xFF00;
const uint32_t STATUS_LED_OK = 0x0000;
const uint32_t STATUS_LED_WARNING = 0x0100;
const uint32_t STATUS_LED_ERROR = 0x0200;
uint32_t global_state = 0;
float Component::get_loop_priority() const { return 0.0f; }
float Component::get_setup_priority() const { return setup_priority::DATA; }
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);
}
bool Component::cancel_interval(const std::string &name) { // NOLINT
return this->cancel_time_function_(name, TimeFunction::INTERVAL);
}
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);
}
bool Component::cancel_timeout(const std::string &name) { // NOLINT
return this->cancel_time_function_(name, TimeFunction::TIMEOUT);
}
void Component::call_loop() {
this->loop_internal_();
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();
}
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;
}
}
}
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.");
this->component_state_ &= ~COMPONENT_STATE_MASK;
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(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);
}
void Component::set_timeout(uint32_t timeout, std::function<void()> &&f) { // NOLINT
this->set_timeout("", timeout, std::move(f));
}
void Component::set_interval(uint32_t interval, std::function<void()> &&f) { // NOLINT
this->set_interval("", interval, std::move(f));
}
bool Component::is_failed() { return (this->component_state_ & COMPONENT_STATE_MASK) == COMPONENT_STATE_FAILED; }
bool Component::can_proceed() { return true; }
bool Component::status_has_warning() { return this->component_state_ & STATUS_LED_WARNING; }
bool Component::status_has_error() { return this->component_state_ & STATUS_LED_ERROR; }
void Component::status_set_warning() {
this->component_state_ |= STATUS_LED_WARNING;
App.app_state_ |= STATUS_LED_WARNING;
}
void Component::status_set_error() {
this->component_state_ |= STATUS_LED_ERROR;
App.app_state_ |= STATUS_LED_ERROR;
}
void Component::status_clear_warning() { this->component_state_ &= ~STATUS_LED_WARNING; }
void Component::status_clear_error() { this->component_state_ &= ~STATUS_LED_ERROR; }
void Component::status_momentary_warning(const std::string &name, uint32_t length) {
this->status_set_warning();
this->set_timeout(name, length, [this]() { this->status_clear_warning(); });
}
void Component::status_momentary_error(const std::string &name, uint32_t length) {
this->status_set_error();
this->set_timeout(name, length, [this]() { this->status_clear_error(); });
}
void Component::dump_config() {}
float Component::get_actual_setup_priority() const {
return this->setup_priority_override_.value_or(this->get_setup_priority());
}
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();
// Register interval.
this->set_interval("update", this->get_update_interval(), [this]() { this->update(); });
}
uint32_t PollingComponent::get_update_interval() const { return this->update_interval_; }
void PollingComponent::set_update_interval(uint32_t update_interval) { this->update_interval_ = update_interval; }
const std::string &Nameable::get_name() const { return this->name_; }
void Nameable::set_name(const std::string &name) {
this->name_ = name;
this->calc_object_id_();
}
Nameable::Nameable(const std::string &name) : name_(name) { this->calc_object_id_(); }
const std::string &Nameable::get_object_id() { return this->object_id_; }
bool Nameable::is_internal() const { return this->internal_; }
void Nameable::set_internal(bool internal) { this->internal_ = internal; }
void Nameable::calc_object_id_() {
this->object_id_ = sanitize_string_whitelist(to_lowercase_underscore(this->name_), HOSTNAME_CHARACTER_WHITELIST);
// FNV-1 hash
this->object_id_hash_ = fnv1_hash(this->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
+298
View File
@@ -0,0 +1,298 @@
#pragma once
#include <string>
#include <functional>
#include <vector>
#include "esphome/core/optional.h"
namespace esphome {
/** Default setup priorities for components of different types.
*
* Components should return one of these setup priorities in get_setup_priority.
*/
namespace setup_priority {
/// For communication buses like i2c/spi
extern const float BUS;
/// For components that represent GPIO pins like PCF8573
extern const float IO;
/// For components that deal with hardware and are very important like GPIO switch
extern const float HARDWARE;
/// For components that import data from directly connected sensors like DHT.
extern const float DATA;
/// Alias for DATA (here for compatability reasons)
extern const float HARDWARE_LATE;
/// For components that use data from sensors like displays
extern const float PROCESSOR;
extern const float WIFI;
/// For components that should be initialized after WiFi is connected.
extern const float AFTER_WIFI;
/// For components that should be initialized after a data connection (API/MQTT) is connected.
extern const float AFTER_CONNECTION;
/// For components that should be initialized at the very end of the setup process.
extern const float LATE;
} // namespace setup_priority
#define LOG_UPDATE_INTERVAL(this) \
if (this->get_update_interval() < 100) { \
ESP_LOGCONFIG(TAG, " Update Interval: %.3fs", this->get_update_interval() / 1000.0f); \
} else { \
ESP_LOGCONFIG(TAG, " Update Interval: %.1fs", this->get_update_interval() / 1000.0f); \
}
extern const uint32_t COMPONENT_STATE_MASK;
extern const uint32_t COMPONENT_STATE_CONSTRUCTION;
extern const uint32_t COMPONENT_STATE_SETUP;
extern const uint32_t COMPONENT_STATE_LOOP;
extern const uint32_t COMPONENT_STATE_FAILED;
extern const uint32_t STATUS_LED_MASK;
extern const uint32_t STATUS_LED_OK;
extern const uint32_t STATUS_LED_WARNING;
extern const uint32_t STATUS_LED_ERROR;
class Component {
public:
/** Where the component's initialization should happen.
*
* Analogous to Arduino's setup(). This method is guaranteed to only be called once.
* Defaults to doing nothing.
*/
virtual void setup();
/** This method will be called repeatedly.
*
* Analogous to Arduino's loop(). setup() is guaranteed to be called before this.
* Defaults to doing nothing.
*/
virtual void loop();
virtual void dump_config();
/** priority of setup(). higher -> executed earlier
*
* Defaults to 0.
*
* @return The setup priority of this component
*/
virtual float get_setup_priority() const;
float get_actual_setup_priority() const;
void set_setup_priority(float priority);
/** priority of loop(). higher -> executed earlier
*
* Defaults to 0.
*
* @return The loop priority of this 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();
virtual void on_shutdown() {}
virtual void on_safe_shutdown() {}
uint32_t get_component_state() const;
/** Mark this component as failed. Any future timeouts/intervals/setup/loop will no longer be called.
*
* This might be useful if a component wants to indicate that a connection to its peripheral failed.
* For example, i2c based components can check if the remote device is responding and otherwise
* mark the component as failed. Eventually this will also enable smart status LEDs.
*/
virtual void mark_failed();
bool is_failed();
virtual bool can_proceed();
bool status_has_warning();
bool status_has_error();
void status_set_warning();
void status_set_error();
void status_clear_warning();
void status_clear_error();
void status_momentary_warning(const std::string &name, uint32_t length = 5000);
void status_momentary_error(const std::string &name, uint32_t length = 5000);
protected:
/** 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().
* Similar to javascript's setInterval().
*
* IMPORTANT: Do not rely on this having correct timing. This is only called from
* loop() and therefore can be significantly delay. If you need exact timing please
* use hardware timers.
*
* @param name The identifier for this interval function.
* @param interval The interval in ms.
* @param f The function (or lambda) that should be called
*
* @see cancel_interval()
*/
void set_interval(const std::string &name, uint32_t interval, std::function<void()> &&f); // NOLINT
void set_interval(uint32_t interval, std::function<void()> &&f); // NOLINT
/** Cancel an interval function.
*
* @param name The identifier for this interval function.
* @return Whether an interval functions was deleted.
*/
bool cancel_interval(const std::string &name); // NOLINT
void set_timeout(uint32_t timeout, std::function<void()> &&f); // NOLINT
/** Set a timeout function with a unique name.
*
* Similar to javascript's setTimeout(). Empty name means no cancelling possible.
*
* IMPORTANT: Do not rely on this having correct timing. This is only called from
* loop() and therefore can be significantly delay. If you need exact timing please
* use hardware timers.
*
* @param name The identifier for this timeout function.
* @param timeout The timeout in ms.
* @param f The function (or lambda) that should be called
*
* @see cancel_timeout()
*/
void set_timeout(const std::string &name, uint32_t timeout, std::function<void()> &&f); // NOLINT
/** Cancel a timeout function.
*
* @param name The identifier for this timeout function.
* @return Whether a timeout functions was deleted.
*/
bool cancel_timeout(const std::string &name); // NOLINT
/** Defer a callback to the next loop() call.
*
* If name is specified and a defer() object with the same name exists, the old one is first removed.
*
* @param name The name of the defer function.
* @param f The callback.
*/
void defer(const std::string &name, std::function<void()> &&f); // NOLINT
/// Defer a callback to the next loop() call.
void defer(std::function<void()> &&f); // NOLINT
/// 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_;
};
/** This class simplifies creating components that periodically check a state.
*
* You basically just need to implement the update() function, it will be called every update_interval ms
* after startup. Note that this class cannot guarantee a correct timing, as it's not using timers, just
* a software polling feature with set_interval() from Component.
*/
class PollingComponent : public Component {
public:
/** Initialize this polling component with the given update interval in ms.
*
* @param update_interval The update interval in ms.
*/
explicit PollingComponent(uint32_t update_interval);
/** Manually set the update interval in ms for this polling object.
*
* Override this if you want to do some validation for the update interval.
*
* @param update_interval The update interval in ms.
*/
virtual void set_update_interval(uint32_t update_interval);
// ========== OVERRIDE METHODS ==========
// (You'll only need this when creating your own custom sensor)
virtual void update() = 0;
// ========== INTERNAL METHODS ==========
// (In most use cases you won't need these)
void call_setup() override;
/// Get the update interval in ms of this sensor
virtual uint32_t get_update_interval() const;
protected:
uint32_t update_interval_;
};
/// Helper class that enables naming of objects so that it doesn't have to be re-implement every time.
class Nameable {
public:
explicit Nameable(const std::string &name);
const std::string &get_name() const;
void set_name(const std::string &name);
/// Get the sanitized name of this nameable as an ID. Caching it internally.
const std::string &get_object_id();
uint32_t get_object_id_hash();
bool is_internal() const;
void set_internal(bool internal);
protected:
virtual uint32_t hash_base() = 0;
void calc_object_id_();
std::string name_;
std::string object_id_;
uint32_t object_id_hash_;
bool internal_{false};
};
} // namespace esphome
+58
View File
@@ -0,0 +1,58 @@
#include "controller.h"
#include "esphome/core/log.h"
#include "esphome/core/application.h"
namespace esphome {
void Controller::setup_controller() {
#ifdef USE_BINARY_SENSOR
for (auto *obj : App.get_binary_sensors()) {
if (!obj->is_internal())
obj->add_on_state_callback([this, obj](bool state) { this->on_binary_sensor_update(obj, state); });
}
#endif
#ifdef USE_FAN
for (auto *obj : App.get_fans()) {
if (!obj->is_internal())
obj->add_on_state_callback([this, obj]() { this->on_fan_update(obj); });
}
#endif
#ifdef USE_LIGHT
for (auto *obj : App.get_lights()) {
if (!obj->is_internal())
obj->add_new_remote_values_callback([this, obj]() { this->on_light_update(obj); });
}
#endif
#ifdef USE_SENSOR
for (auto *obj : App.get_sensors()) {
if (!obj->is_internal())
obj->add_on_state_callback([this, obj](float state) { this->on_sensor_update(obj, state); });
}
#endif
#ifdef USE_SWITCH
for (auto *obj : App.get_switches()) {
if (!obj->is_internal())
obj->add_on_state_callback([this, obj](bool state) { this->on_switch_update(obj, state); });
}
#endif
#ifdef USE_COVER
for (auto *obj : App.get_covers()) {
if (!obj->is_internal())
obj->add_on_state_callback([this, obj]() { this->on_cover_update(obj); });
}
#endif
#ifdef USE_TEXT_SENSOR
for (auto *obj : App.get_text_sensors()) {
if (!obj->is_internal())
obj->add_on_state_callback([this, obj](std::string state) { this->on_text_sensor_update(obj, state); });
}
#endif
#ifdef USE_CLIMATE
for (auto *obj : App.get_climates()) {
if (!obj->is_internal())
obj->add_on_state_callback([this, obj]() { this->on_climate_update(obj); });
}
#endif
}
} // namespace esphome
+60
View File
@@ -0,0 +1,60 @@
#pragma once
#include "esphome/core/defines.h"
#ifdef USE_BINARY_SENSOR
#include "esphome/components/binary_sensor/binary_sensor.h"
#endif
#ifdef USE_FAN
#include "esphome/components/fan/fan_state.h"
#endif
#ifdef USE_LIGHT
#include "esphome/components/light/light_state.h"
#endif
#ifdef USE_COVER
#include "esphome/components/cover/cover.h"
#endif
#ifdef USE_SENSOR
#include "esphome/components/sensor/sensor.h"
#endif
#ifdef USE_TEXT_SENSOR
#include "esphome/components/text_sensor/text_sensor.h"
#endif
#ifdef USE_SWITCH
#include "esphome/components/switch/switch.h"
#endif
#ifdef USE_CLIMATE
#include "esphome/components/climate/climate.h"
#endif
namespace esphome {
class Controller {
public:
void setup_controller();
#ifdef USE_BINARY_SENSOR
virtual void on_binary_sensor_update(binary_sensor::BinarySensor *obj, bool state){};
#endif
#ifdef USE_FAN
virtual void on_fan_update(fan::FanState *obj){};
#endif
#ifdef USE_LIGHT
virtual void on_light_update(light::LightState *obj){};
#endif
#ifdef USE_SENSOR
virtual void on_sensor_update(sensor::Sensor *obj, float state){};
#endif
#ifdef USE_SWITCH
virtual void on_switch_update(switch_::Switch *obj, bool state){};
#endif
#ifdef USE_COVER
virtual void on_cover_update(cover::Cover *obj){};
#endif
#ifdef USE_TEXT_SENSOR
virtual void on_text_sensor_update(text_sensor::TextSensor *obj, std::string state){};
#endif
#ifdef USE_CLIMATE
virtual void on_climate_update(climate::Climate *obj){};
#endif
};
} // namespace esphome
+27
View File
@@ -0,0 +1,27 @@
#pragma once
// This file is auto-generated! Do not edit!
#define ESPHOME_VERSION "dev"
#define ESPHOME_LOG_LEVEL 6
#define USE_API
#define USE_LOGGER
#define USE_BINARY_SENSOR
#define USE_SENSOR
#define USE_SWITCH
#define USE_WIFI
#define USE_STATUS_LED
#define USE_TEXT_SENSOR
#define USE_FAN
#define USE_COVER
#define USE_LIGHT
#define USE_CLIMATE
#define USE_MQTT
#define USE_POWER_SUPPLY
#define USE_HOMEASSISTANT_TIME
#define USE_JSON
#ifdef ARDUINO_ARCH_ESP32
#define USE_ESP32_CAMERA
#endif
#define USE_TIME
#define USE_DEEP_SLEEP
+280
View File
@@ -0,0 +1,280 @@
#include "esphome/core/esphal.h"
#include "esphome/core/helpers.h"
#include "esphome/core/defines.h"
#include "esphome/core/log.h"
#ifdef ARDUINO_ARCH_ESP8266
extern "C" {
typedef struct { // NOLINT
void *interruptInfo; // NOLINT
void *functionInfo; // NOLINT
} ArgStructure;
void ICACHE_RAM_ATTR __attachInterruptArg(uint8_t pin, void (*)(void *), void *fp, // NOLINT
int mode);
};
#endif
namespace esphome {
static const char *TAG = "esphal";
GPIOPin::GPIOPin(uint8_t pin, uint8_t mode, bool inverted)
: pin_(pin),
mode_(mode),
inverted_(inverted),
#ifdef ARDUINO_ARCH_ESP8266
gpio_read_(pin < 16 ? &GPI : &GP16I),
gpio_mask_(pin < 16 ? (1UL << pin) : 1)
#endif
#ifdef ARDUINO_ARCH_ESP32
gpio_set_(pin < 32 ? &GPIO.out_w1ts : &GPIO.out1_w1ts.val),
gpio_clear_(pin < 32 ? &GPIO.out_w1tc : &GPIO.out1_w1tc.val),
gpio_read_(pin < 32 ? &GPIO.in : &GPIO.in1.val),
gpio_mask_(pin < 32 ? (1UL << pin) : (1UL << (pin - 32)))
#endif
{
}
const char *GPIOPin::get_pin_mode_name() const {
const char *mode_s;
switch (this->mode_) {
case INPUT:
mode_s = "INPUT";
break;
case OUTPUT:
mode_s = "OUTPUT";
break;
case INPUT_PULLUP:
mode_s = "INPUT_PULLUP";
break;
case OUTPUT_OPEN_DRAIN:
mode_s = "OUTPUT_OPEN_DRAIN";
break;
case SPECIAL:
mode_s = "SPECIAL";
break;
case FUNCTION_1:
mode_s = "FUNCTION_1";
break;
case FUNCTION_2:
mode_s = "FUNCTION_2";
break;
case FUNCTION_3:
mode_s = "FUNCTION_3";
break;
case FUNCTION_4:
mode_s = "FUNCTION_4";
break;
#ifdef ARDUINO_ARCH_ESP32
case PULLUP:
mode_s = "PULLUP";
break;
case PULLDOWN:
mode_s = "PULLDOWN";
break;
case INPUT_PULLDOWN:
mode_s = "INPUT_PULLDOWN";
break;
case OPEN_DRAIN:
mode_s = "OPEN_DRAIN";
break;
case FUNCTION_5:
mode_s = "FUNCTION_5";
break;
case FUNCTION_6:
mode_s = "FUNCTION_6";
break;
case ANALOG:
mode_s = "ANALOG";
break;
#endif
#ifdef ARDUINO_ARCH_ESP8266
case FUNCTION_0:
mode_s = "FUNCTION_0";
break;
case WAKEUP_PULLUP:
mode_s = "WAKEUP_PULLUP";
break;
case WAKEUP_PULLDOWN:
mode_s = "WAKEUP_PULLDOWN";
break;
case INPUT_PULLDOWN_16:
mode_s = "INPUT_PULLDOWN_16";
break;
#endif
default:
mode_s = "UNKNOWN";
break;
}
return mode_s;
}
unsigned char GPIOPin::get_pin() const { return this->pin_; }
unsigned char GPIOPin::get_mode() const { return this->mode_; }
bool GPIOPin::is_inverted() const { return this->inverted_; }
void GPIOPin::setup() { this->pin_mode(this->mode_); }
bool ICACHE_RAM_ATTR HOT GPIOPin::digital_read() {
return bool((*this->gpio_read_) & this->gpio_mask_) != this->inverted_;
}
bool ICACHE_RAM_ATTR HOT ISRInternalGPIOPin::digital_read() {
return bool((*this->gpio_read_) & this->gpio_mask_) != this->inverted_;
}
void ICACHE_RAM_ATTR HOT GPIOPin::digital_write(bool value) {
#ifdef ARDUINO_ARCH_ESP8266
if (this->pin_ != 16) {
if (value != this->inverted_) {
GPOS = this->gpio_mask_;
} else {
GPOC = this->gpio_mask_;
}
} else {
if (value != this->inverted_) {
GP16O |= 1;
} else {
GP16O &= ~1;
}
}
#endif
#ifdef ARDUINO_ARCH_ESP32
if (value != this->inverted_) {
(*this->gpio_set_) = this->gpio_mask_;
} else {
(*this->gpio_clear_) = this->gpio_mask_;
}
#endif
}
void ISRInternalGPIOPin::digital_write(bool value) {
#ifdef ARDUINO_ARCH_ESP8266
if (this->pin_ != 16) {
if (value != this->inverted_) {
GPOS = this->gpio_mask_;
} else {
GPOC = this->gpio_mask_;
}
} else {
if (value != this->inverted_) {
GP16O |= 1;
} else {
GP16O &= ~1;
}
}
#endif
#ifdef ARDUINO_ARCH_ESP32
if (value != this->inverted_) {
(*this->gpio_set_) = this->gpio_mask_;
} else {
(*this->gpio_clear_) = this->gpio_mask_;
}
#endif
}
ISRInternalGPIOPin::ISRInternalGPIOPin(uint8_t pin,
#ifdef ARDUINO_ARCH_ESP32
volatile uint32_t *gpio_clear, volatile uint32_t *gpio_set,
#endif
volatile uint32_t *gpio_read, uint32_t gpio_mask, bool inverted)
: pin_(pin),
inverted_(inverted),
gpio_read_(gpio_read),
gpio_mask_(gpio_mask)
#ifdef ARDUINO_ARCH_ESP32
,
gpio_clear_(gpio_clear),
gpio_set_(gpio_set)
#endif
{
}
void ICACHE_RAM_ATTR ISRInternalGPIOPin::clear_interrupt() {
#ifdef ARDUINO_ARCH_ESP8266
GPIO_REG_WRITE(GPIO_STATUS_W1TC_ADDRESS, this->gpio_mask_);
#endif
#ifdef ARDUINO_ARCH_ESP32
if (this->pin_ < 32) {
GPIO.status_w1tc = this->gpio_mask_;
} else {
GPIO.status1_w1tc.intr_st = this->gpio_mask_;
}
#endif
}
void ICACHE_RAM_ATTR HOT GPIOPin::pin_mode(uint8_t mode) {
#ifdef ARDUINO_ARCH_ESP8266
if (this->pin_ == 16 && mode == INPUT_PULLUP) {
// pullups are not available on GPIO16, manually override with
// input mode.
pinMode(16, INPUT);
return;
}
#endif
pinMode(this->pin_, mode);
}
#ifdef ARDUINO_ARCH_ESP8266
struct ESPHomeInterruptFuncInfo {
void (*func)(void *);
void *arg;
};
void ICACHE_RAM_ATTR interrupt_handler(void *arg) {
ArgStructure *as = static_cast<ArgStructure *>(arg);
auto *info = static_cast<ESPHomeInterruptFuncInfo *>(as->functionInfo);
info->func(info->arg);
}
#endif
void GPIOPin::attach_interrupt_(void (*func)(void *), void *arg, int mode) const {
if (this->inverted_) {
if (mode == RISING) {
mode = FALLING;
} else if (mode == FALLING) {
mode = RISING;
}
}
#ifdef ARDUINO_ARCH_ESP8266
ArgStructure *as = new ArgStructure;
as->interruptInfo = nullptr;
as->functionInfo = new ESPHomeInterruptFuncInfo{
.func = func,
.arg = arg,
};
__attachInterruptArg(this->pin_, interrupt_handler, as, mode);
#endif
#ifdef ARDUINO_ARCH_ESP32
// work around issue https://github.com/espressif/arduino-esp32/pull/1776 in arduino core
// yet again proves how horrible code is there :( - how could that have been accepted...
auto *attach = reinterpret_cast<void (*)(uint8_t, void (*)(void *), void *, int)>(attachInterruptArg);
attach(this->pin_, func, arg, mode);
#endif
}
ISRInternalGPIOPin *GPIOPin::to_isr() const {
return new ISRInternalGPIOPin(this->pin_,
#ifdef ARDUINO_ARCH_ESP32
this->gpio_clear_, this->gpio_set_,
#endif
this->gpio_read_, this->gpio_mask_, this->inverted_);
}
} // namespace esphome
#ifdef ARDUINO_ESP8266_RELEASE_2_3_0
// Fix 2.3.0 std missing memchr
extern "C" {
void *memchr(const void *s, int c, size_t n) {
if (n == 0)
return nullptr;
const uint8_t *p = reinterpret_cast<const uint8_t *>(s);
do {
if (*p++ == c)
return const_cast<void *>(reinterpret_cast<const void *>(p - 1));
} while (--n != 0);
return nullptr;
}
};
#endif
+118
View File
@@ -0,0 +1,118 @@
#pragma once
#include "Arduino.h"
#ifdef ARDUINO_ARCH_ESP32
#include <esp32-hal.h>
#endif
// Fix some arduino defs
#ifdef round
#undef round
#endif
#ifdef bool
#undef bool
#endif
#ifdef true
#undef true
#endif
#ifdef false
#undef false
#endif
#ifdef min
#undef min
#endif
#ifdef max
#undef max
#endif
#ifdef abs
#undef abs
#endif
namespace esphome {
#define LOG_PIN(prefix, pin) \
if ((pin) != nullptr) { \
ESP_LOGCONFIG(TAG, prefix LOG_PIN_PATTERN, LOG_PIN_ARGS(pin)); \
}
#define LOG_PIN_PATTERN "GPIO%u (Mode: %s%s)"
#define LOG_PIN_ARGS(pin) (pin)->get_pin(), (pin)->get_pin_mode_name(), ((pin)->is_inverted() ? ", INVERTED" : "")
/// Copy of GPIOPin that is safe to use from ISRs (with no virtual functions)
class ISRInternalGPIOPin {
public:
ISRInternalGPIOPin(uint8_t pin,
#ifdef ARDUINO_ARCH_ESP32
volatile uint32_t *gpio_clear, volatile uint32_t *gpio_set,
#endif
volatile uint32_t *gpio_read, uint32_t gpio_mask, bool inverted);
bool digital_read();
void digital_write(bool value);
void clear_interrupt();
protected:
const uint8_t pin_;
const bool inverted_;
volatile uint32_t *const gpio_read_;
const uint32_t gpio_mask_;
#ifdef ARDUINO_ARCH_ESP32
volatile uint32_t *const gpio_clear_;
volatile uint32_t *const gpio_set_;
#endif
};
/** A high-level abstraction class that can expose a pin together with useful options like pinMode.
*
* Set the parameters for this at construction time and use setup() to apply them. The inverted parameter will
* automatically invert the input/output for you.
*
* Use read_value() and write_value() to use digitalRead() and digitalWrite(), respectively.
*/
class GPIOPin {
public:
/** Construct the GPIOPin instance.
*
* @param pin The GPIO pin number of this instance.
* @param mode The Arduino pinMode that this pin should be put into at setup().
* @param inverted Whether all digitalRead/digitalWrite calls should be inverted.
*/
GPIOPin(uint8_t pin, uint8_t mode, bool inverted = false);
/// Setup the pin mode.
virtual void setup();
/// Read the binary value from this pin using digitalRead (and inverts automatically).
virtual bool digital_read();
/// Write the binary value to this pin using digitalWrite (and inverts automatically).
virtual void digital_write(bool value);
/// Set the pin mode
virtual void pin_mode(uint8_t mode);
/// Get the GPIO pin number.
uint8_t get_pin() const;
const char *get_pin_mode_name() const;
/// Get the pinMode of this pin.
uint8_t get_mode() const;
/// Return whether this pin shall be treated as inverted. (for example active-low)
bool is_inverted() const;
template<typename T> void attach_interrupt(void (*func)(T *), T *arg, int mode) const;
ISRInternalGPIOPin *to_isr() const;
protected:
void attach_interrupt_(void (*func)(void *), void *arg, int mode) const;
const uint8_t pin_;
const uint8_t mode_;
const bool inverted_;
#ifdef ARDUINO_ARCH_ESP32
volatile uint32_t *const gpio_set_;
volatile uint32_t *const gpio_clear_;
#endif
volatile uint32_t *const gpio_read_;
const uint32_t gpio_mask_;
};
template<typename T> void GPIOPin::attach_interrupt(void (*func)(T *), T *arg, int mode) const {
this->attach_interrupt_(reinterpret_cast<void (*)(void *)>(func), arg, mode);
}
} // namespace esphome
+307
View File
@@ -0,0 +1,307 @@
#include "esphome/core/helpers.h"
#include <cstdio>
#include <algorithm>
#ifdef ARDUINO_ARCH_ESP8266
#include <ESP8266WiFi.h>
#else
#include <Esp.h>
#endif
#include "esphome/core/log.h"
#include "esphome/core/esphal.h"
namespace esphome {
static const char *TAG = "helpers";
std::string get_mac_address() {
char tmp[20];
uint8_t mac[6];
#ifdef ARDUINO_ARCH_ESP32
esp_efuse_mac_get_default(mac);
#endif
#ifdef ARDUINO_ARCH_ESP8266
WiFi.macAddress(mac);
#endif
sprintf(tmp, "%02x%02x%02x%02x%02x%02x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
return std::string(tmp);
}
std::string get_mac_address_pretty() {
char tmp[20];
uint8_t mac[6];
#ifdef ARDUINO_ARCH_ESP32
esp_efuse_mac_get_default(mac);
#endif
#ifdef ARDUINO_ARCH_ESP8266
WiFi.macAddress(mac);
#endif
sprintf(tmp, "%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
return std::string(tmp);
}
std::string generate_hostname(const std::string &base) { return base + std::string("-") + get_mac_address(); }
uint32_t random_uint32() {
#ifdef ARDUINO_ARCH_ESP32
return esp_random();
#else
return os_random();
#endif
}
double random_double() { return random_uint32() / double(UINT32_MAX); }
float random_float() { return float(random_double()); }
static uint32_t fast_random_seed = 0;
void fast_random_set_seed(uint32_t seed) { fast_random_seed = seed; }
uint32_t fast_random_32() {
fast_random_seed = (fast_random_seed * 2654435769ULL) + 40503ULL;
return fast_random_seed;
}
uint16_t fast_random_16() {
uint32_t rand32 = fast_random_32();
return (rand32 & 0xFFFF) + (rand32 >> 16);
}
uint8_t fast_random_8() {
uint8_t rand32 = fast_random_32();
return (rand32 & 0xFF) + ((rand32 >> 8) & 0xFF);
}
float gamma_correct(float value, float gamma) {
if (value <= 0.0f)
return 0.0f;
if (gamma <= 0.0f)
return value;
return powf(value, gamma);
}
std::string to_lowercase_underscore(std::string s) {
std::transform(s.begin(), s.end(), s.begin(), ::tolower);
std::replace(s.begin(), s.end(), ' ', '_');
return s;
}
std::string sanitize_string_whitelist(const std::string &s, const std::string &whitelist) {
std::string out(s);
out.erase(std::remove_if(out.begin(), out.end(),
[&out, &whitelist](const char &c) { return whitelist.find(c) == std::string::npos; }),
out.end());
return out;
}
std::string sanitize_hostname(const std::string &hostname) {
std::string s = sanitize_string_whitelist(hostname, HOSTNAME_CHARACTER_WHITELIST);
return truncate_string(s, 63);
}
std::string truncate_string(const std::string &s, size_t length) {
if (s.length() > length)
return s.substr(0, length);
return s;
}
std::string value_accuracy_to_string(float value, int8_t accuracy_decimals) {
auto multiplier = float(pow10(accuracy_decimals));
float value_rounded = roundf(value * multiplier) / multiplier;
char tmp[32]; // should be enough, but we should maybe improve this at some point.
dtostrf(value_rounded, 0, uint8_t(std::max(0, int(accuracy_decimals))), tmp);
return std::string(tmp);
}
std::string uint64_to_string(uint64_t num) {
char buffer[17];
auto *address16 = reinterpret_cast<uint16_t *>(&num);
snprintf(buffer, sizeof(buffer), "%04X%04X%04X%04X", address16[3], address16[2], address16[1], address16[0]);
return std::string(buffer);
}
std::string uint32_to_string(uint32_t num) {
char buffer[9];
auto *address16 = reinterpret_cast<uint16_t *>(&num);
snprintf(buffer, sizeof(buffer), "%04X%04X", address16[1], address16[0]);
return std::string(buffer);
}
static char *global_json_build_buffer = nullptr;
static size_t global_json_build_buffer_size = 0;
void reserve_global_json_build_buffer(size_t required_size) {
if (global_json_build_buffer_size == 0 || global_json_build_buffer_size < required_size) {
delete[] global_json_build_buffer;
global_json_build_buffer_size = std::max(required_size, global_json_build_buffer_size * 2);
size_t remainder = global_json_build_buffer_size % 16U;
if (remainder != 0)
global_json_build_buffer_size += 16 - remainder;
global_json_build_buffer = new char[global_json_build_buffer_size];
}
}
ParseOnOffState parse_on_off(const char *str, const char *on, const char *off) {
if (on == nullptr && strcasecmp(str, "on") == 0)
return PARSE_ON;
if (on != nullptr && strcasecmp(str, on) == 0)
return PARSE_ON;
if (off == nullptr && strcasecmp(str, "off") == 0)
return PARSE_OFF;
if (off != nullptr && strcasecmp(str, off) == 0)
return PARSE_OFF;
if (strcasecmp(str, "toggle") == 0)
return PARSE_TOGGLE;
return PARSE_NONE;
}
const char *HOSTNAME_CHARACTER_WHITELIST = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_";
void disable_interrupts() {
#ifdef ARDUINO_ARCH_ESP32
portDISABLE_INTERRUPTS();
#else
noInterrupts();
#endif
}
void enable_interrupts() {
#ifdef ARDUINO_ARCH_ESP32
portENABLE_INTERRUPTS();
#else
interrupts();
#endif
}
uint8_t crc8(uint8_t *data, uint8_t len) {
uint8_t crc = 0;
while ((len--) != 0u) {
uint8_t inbyte = *data++;
for (uint8_t i = 8; i != 0u; i--) {
bool mix = (crc ^ inbyte) & 0x01;
crc >>= 1;
if (mix)
crc ^= 0x8C;
inbyte >>= 1;
}
}
return crc;
}
void delay_microseconds_accurate(uint32_t usec) {
if (usec == 0)
return;
if (usec <= 16383UL) {
delayMicroseconds(usec);
} else {
delay(usec / 1000UL);
delayMicroseconds(usec % 1000UL);
}
}
uint8_t reverse_bits_8(uint8_t x) {
x = ((x & 0xAA) >> 1) | ((x & 0x55) << 1);
x = ((x & 0xCC) >> 2) | ((x & 0x33) << 2);
x = ((x & 0xF0) >> 4) | ((x & 0x0F) << 4);
return x;
}
uint16_t reverse_bits_16(uint16_t x) {
return uint16_t(reverse_bits_8(x & 0xFF) << 8) | uint16_t(reverse_bits_8(x >> 8));
}
std::string to_string(const std::string &val) { return val; }
std::string to_string(int val) {
char buf[64];
sprintf(buf, "%d", val);
return buf;
}
std::string to_string(long val) {
char buf[64];
sprintf(buf, "%ld", val);
return buf;
}
std::string to_string(long long val) {
char buf[64];
sprintf(buf, "%lld", val);
return buf;
}
std::string to_string(unsigned val) {
char buf[64];
sprintf(buf, "%u", val);
return buf;
}
std::string to_string(unsigned long val) {
char buf[64];
sprintf(buf, "%lu", val);
return buf;
}
std::string to_string(unsigned long long val) {
char buf[64];
sprintf(buf, "%llu", val);
return buf;
}
std::string to_string(float val) {
char buf[64];
sprintf(buf, "%f", val);
return buf;
}
std::string to_string(double val) {
char buf[64];
sprintf(buf, "%f", val);
return buf;
}
std::string to_string(long double val) {
char buf[64];
sprintf(buf, "%Lf", val);
return buf;
}
optional<float> parse_float(const std::string &str) {
char *end;
float value = ::strtof(str.c_str(), &end);
if (end == nullptr)
return {};
return value;
}
uint32_t fnv1_hash(const std::string &str) {
uint32_t hash = 2166136261UL;
for (char c : str) {
hash *= 16777619UL;
hash ^= c;
}
return hash;
}
bool str_equals_case_insensitive(const std::string &a, const std::string &b) {
return strcasecmp(a.c_str(), b.c_str()) == 0;
}
template<uint32_t> uint32_t reverse_bits(uint32_t x) {
return uint32_t(reverse_bits_16(x & 0xFFFF) << 16) | uint32_t(reverse_bits_16(x >> 16));
}
static int high_freq_num_requests = 0;
void HighFrequencyLoopRequester::start() {
if (this->started_)
return;
high_freq_num_requests++;
this->started_ = true;
}
void HighFrequencyLoopRequester::stop() {
if (!this->started_)
return;
high_freq_num_requests--;
this->started_ = false;
}
bool HighFrequencyLoopRequester::is_high_frequency() { return high_freq_num_requests > 0; }
float clamp(float val, float min, float max) {
if (min > max)
std::swap(min, max);
if (val < min)
return min;
if (val > max)
return max;
return val;
}
float lerp(float completion, float start, float end) { return start + (end - start) * completion; }
} // namespace esphome
+256
View File
@@ -0,0 +1,256 @@
#pragma once
#include <string>
#include <functional>
#include <vector>
#include <memory>
#include "esphome/core/optional.h"
#include "esphome/core/esphal.h"
#ifdef CLANG_TIDY
#undef ICACHE_RAM_ATTR
#define ICACHE_RAM_ATTR
#undef ICACHE_RODATA_ATTR
#define ICACHE_RODATA_ATTR
#endif
#define HOT __attribute__((hot))
#define ESPDEPRECATED(msg) __attribute__((deprecated(msg)))
#define ALWAYS_INLINE __attribute__((always_inline))
namespace esphome {
/// The characters that are allowed in a hostname.
extern const char *HOSTNAME_CHARACTER_WHITELIST;
/// Gets the MAC address as a string, this can be used as way to identify this ESP.
std::string get_mac_address();
std::string get_mac_address_pretty();
std::string to_string(const std::string &val);
std::string to_string(int val);
std::string to_string(long val);
std::string to_string(long long val);
std::string to_string(unsigned val);
std::string to_string(unsigned long val);
std::string to_string(unsigned long long val);
std::string to_string(float val);
std::string to_string(double val);
std::string to_string(long double val);
optional<float> parse_float(const std::string &str);
/// Sanitize the hostname by removing characters that are not in the whitelist and truncating it to 63 chars.
std::string sanitize_hostname(const std::string &hostname);
/// Truncate a string to a specific length
std::string truncate_string(const std::string &s, size_t length);
/// Convert the string to lowercase_underscore.
std::string to_lowercase_underscore(std::string s);
/// Compare string a to string b (ignoring case) and return whether they are equal.
bool str_equals_case_insensitive(const std::string &a, const std::string &b);
class HighFrequencyLoopRequester {
public:
void start();
void stop();
static bool is_high_frequency();
protected:
bool started_{false};
};
/** Clamp the value between min and max.
*
* @param val The value.
* @param min The minimum value.
* @param max The maximum value.
* @return val clamped in between min and max.
*/
float clamp(float val, float min, float max);
/** Linearly interpolate between end start and end by completion.
*
* @tparam T The input/output typename.
* @param start The start value.
* @param end The end value.
* @param completion The completion. 0 is start value, 1 is end value.
* @return The linearly interpolated value.
*/
float lerp(float completion, float start, float end);
/// std::make_unique
template<typename T, typename... Args> std::unique_ptr<T> make_unique(Args &&... args) {
return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}
/// Return a random 32 bit unsigned integer.
uint32_t random_uint32();
/** Returns a random double between 0 and 1.
*
* Note: This function probably doesn't provide a truly uniform distribution.
*/
double random_double();
/// Returns a random float between 0 and 1. Essentially just casts random_double() to a float.
float random_float();
void fast_random_set_seed(uint32_t seed);
uint32_t fast_random_32();
uint16_t fast_random_16();
uint8_t fast_random_8();
/// Applies gamma correction with the provided gamma to value.
float gamma_correct(float value, float gamma);
/// Create a string from a value and an accuracy in decimals.
std::string value_accuracy_to_string(float value, int8_t accuracy_decimals);
/// Convert a uint64_t to a hex string
std::string uint64_to_string(uint64_t num);
/// Convert a uint32_t to a hex string
std::string uint32_to_string(uint32_t num);
/// Sanitizes the input string with the whitelist.
std::string sanitize_string_whitelist(const std::string &s, const std::string &whitelist);
uint8_t reverse_bits_8(uint8_t x);
uint16_t reverse_bits_16(uint16_t x);
uint32_t reverse_bits_32(uint32_t x);
/** Cross-platform method to disable interrupts.
*
* Useful when you need to do some timing-dependent communication.
*
* @see Do not forget to call `enable_interrupts()` again or otherwise things will go very wrong.
*/
void disable_interrupts();
/// Cross-platform method to enable interrupts after they have been disabled.
void enable_interrupts();
/// Calculate a crc8 of data with the provided data length.
uint8_t crc8(uint8_t *data, uint8_t len);
enum ParseOnOffState {
PARSE_NONE = 0,
PARSE_ON,
PARSE_OFF,
PARSE_TOGGLE,
};
ParseOnOffState parse_on_off(const char *str, const char *on = nullptr, const char *off = nullptr);
// https://stackoverflow.com/questions/7858817/unpacking-a-tuple-to-call-a-matching-function-pointer/7858971#7858971
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<typename... X> class CallbackManager;
/** Simple helper class to allow having multiple subscribers to a signal.
*
* @tparam Ts The arguments for the callback, wrapped in void().
*/
template<typename... Ts> class CallbackManager<void(Ts...)> {
public:
/// Add a callback to the internal callback list.
void add(std::function<void(Ts...)> &&callback) { this->callbacks_.push_back(std::move(callback)); }
/// Call all callbacks in this manager.
void call(Ts... args) {
for (auto &cb : this->callbacks_)
cb(args...);
}
protected:
std::vector<std::function<void(Ts...)>> callbacks_;
};
// https://stackoverflow.com/a/37161919/8924614
template<class T, class... Args>
struct is_callable // NOLINT
{
template<class U> static auto test(U *p) -> decltype((*p)(std::declval<Args>()...), void(), std::true_type());
template<class U> static auto test(...) -> decltype(std::false_type());
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) {}
template<typename F, enable_if_t<!is_callable<F, X...>::value, int> = 0>
TemplatableValue(F value) : type_(VALUE), value_(value) {}
template<typename F, enable_if_t<is_callable<F, X...>::value, int> = 0>
TemplatableValue(F f) : type_(LAMBDA), f_(f) {}
bool has_value() { return this->type_ != EMPTY; }
T value(X... x) {
if (this->type_ == LAMBDA) {
return this->f_(x...);
}
// return value also when empty
return this->value_;
}
optional<T> optional_value(X... x) {
if (!this->has_value()) {
return {};
}
return this->value(x...);
}
T value_or(X... x, T default_value) {
if (!this->has_value()) {
return default_value;
}
return this->value(x...);
}
protected:
enum {
EMPTY,
VALUE,
LAMBDA,
} type_;
T value_;
std::function<T(X...)> f_;
};
void delay_microseconds_accurate(uint32_t usec);
template<typename T> class Deduplicator {
public:
bool next(T value) {
if (this->has_value_) {
if (this->last_value_ == value)
return false;
}
this->has_value_ = true;
this->last_value_ = value;
return true;
}
bool has_value() const { return this->has_value_; }
protected:
bool has_value_{false};
T last_value_{};
};
uint32_t fnv1_hash(const std::string &str);
} // namespace esphome
+67
View File
@@ -0,0 +1,67 @@
#include "esphome/core/log.h"
#include "esphome/core/defines.h"
#include "esphome/core/helpers.h"
#ifdef USE_LOGGER
#include "esphome/components/logger/logger.h"
#endif
namespace esphome {
int HOT esp_log_printf_(int level, const char *tag, const char *format, ...) { // NOLINT
va_list arg;
va_start(arg, format);
int ret = esp_log_vprintf_(level, tag, 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, ...) {
va_list arg;
va_start(arg, format);
int ret = esp_log_vprintf_(level, tag, 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
#ifdef USE_LOGGER
auto *log = logger::global_logger;
if (log == nullptr)
return 0;
return log->log_vprintf_(level, tag, format, args);
#else
return 0;
#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
#ifdef USE_LOGGER
auto *log = logger::global_logger;
if (log == nullptr)
return 0;
return log->log_vprintf_(level, tag, format, args);
#else
return 0;
#endif
}
#endif
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;
#endif
}
} // namespace esphome
+173
View File
@@ -0,0 +1,173 @@
#pragma once
#include <cassert>
#include <cstdarg>
#include <string>
#ifdef USE_STORE_LOG_STR_IN_FLASH
#include "WString.h"
#endif
// avoid esp-idf redefining our macros
#include "esphome/core/esphal.h"
#ifdef ARDUINO_ARCH_ESP32
#include "esp_err.h"
#endif
namespace esphome {
#define ESPHOME_LOG_LEVEL_NONE 0
#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
#ifndef ESPHOME_LOG_LEVEL
#define ESPHOME_LOG_LEVEL ESPHOME_LOG_LEVEL_DEBUG
#endif
#define ESPHOME_LOG_COLOR_BLACK "30"
#define ESPHOME_LOG_COLOR_RED "31" // ERROR
#define ESPHOME_LOG_COLOR_GREEN "32" // INFO
#define ESPHOME_LOG_COLOR_YELLOW "33" // WARNING
#define ESPHOME_LOG_COLOR_BLUE "34"
#define ESPHOME_LOG_COLOR_MAGENTA "35" // CONFIG
#define ESPHOME_LOG_COLOR_CYAN "36" // DEBUG
#define ESPHOME_LOG_COLOR_GRAY "37" // VERBOSE
#define ESPHOME_LOG_COLOR_WHITE "38"
#define ESPHOME_LOG_SECRET_BEGIN "\033[5m"
#define ESPHOME_LOG_SECRET_END "\033[6m"
#define LOG_SECRET(x) ESPHOME_LOG_SECRET_BEGIN x ESPHOME_LOG_SECRET_END
#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)));
#ifdef USE_STORE_LOG_STR_IN_FLASH
int esp_log_printf_(int level, const char *tag, const __FlashStringHelper *format, ...);
#endif
int esp_log_vprintf_(int level, const char *tag, 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);
#endif
int esp_idf_log_vprintf_(const char *format, va_list args); // NOLINT
#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__
#else
#define ESPHOME_LOG_FORMAT(tag, letter, format) \
ESPHOME_LOG_COLOR_##letter "[" #letter "][%s:%03u]: " format ESPHOME_LOG_RESET_COLOR, tag, __LINE__
#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__)
#define ESPHOME_LOG_HAS_VERY_VERBOSE
#else
#define esph_log_vv(tag, format, ...)
#endif
#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__)
#define ESPHOME_LOG_HAS_VERBOSE
#else
#define esph_log_v(tag, format, ...)
#endif
#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__)
#define esph_log_config(tag, format, ...) \
esp_log_printf_(ESPHOME_LOG_LEVEL_DEBUG, tag, ESPHOME_LOG_FORMAT(tag, C, 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__)
#define ESPHOME_LOG_HAS_INFO
#else
#define esph_log_i(tag, format, ...)
#endif
#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__)
#define ESPHOME_LOG_HAS_WARN
#else
#define esph_log_w(tag, format, ...)
#endif
#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__)
#define ESPHOME_LOG_HAS_ERROR
#else
#define esph_log_e(tag, format, ...)
#endif
#ifdef ESP_LOGE
#undef ESP_LOGE
#endif
#ifdef ESP_LOGW
#undef ESP_LOGW
#endif
#ifdef ESP_LOGI
#undef ESP_LOGI
#endif
#ifdef ESP_LOGD
#undef ESP_LOGD
#endif
#ifdef ESP_LOGV
#undef ESP_LOGV
#endif
#define ESP_LOGE(tag, ...) esph_log_e(tag, __VA_ARGS__)
#define LOG_E(tag, ...) ESP_LOGE(tag, __VA__ARGS__)
#define ESP_LOGW(tag, ...) esph_log_w(tag, __VA_ARGS__)
#define LOG_W(tag, ...) ESP_LOGW(tag, __VA__ARGS__)
#define ESP_LOGI(tag, ...) esph_log_i(tag, __VA_ARGS__)
#define LOG_I(tag, ...) ESP_LOGI(tag, __VA__ARGS__)
#define ESP_LOGD(tag, ...) esph_log_d(tag, __VA_ARGS__)
#define LOG_D(tag, ...) ESP_LOGD(tag, __VA__ARGS__)
#define ESP_LOGCONFIG(tag, ...) esph_log_config(tag, __VA_ARGS__)
#define LOG_CONFIG(tag, ...) ESP_LOGCONFIG(tag, __VA__ARGS__)
#define ESP_LOGV(tag, ...) esph_log_v(tag, __VA_ARGS__)
#define LOG_V(tag, ...) ESP_LOGV(tag, __VA__ARGS__)
#define ESP_LOGVV(tag, ...) esph_log_vv(tag, __VA_ARGS__)
#define LOG_VV(tag, ...) ESP_LOGVV(tag, __VA__ARGS__)
#define BYTE_TO_BINARY_PATTERN "%c%c%c%c%c%c%c%c"
#define BYTE_TO_BINARY(byte) \
((byte) &0x80 ? '1' : '0'), ((byte) &0x40 ? '1' : '0'), ((byte) &0x20 ? '1' : '0'), ((byte) &0x10 ? '1' : '0'), \
((byte) &0x08 ? '1' : '0'), ((byte) &0x04 ? '1' : '0'), ((byte) &0x02 ? '1' : '0'), ((byte) &0x01 ? '1' : '0')
#define YESNO(b) ((b) ? "YES" : "NO")
#define ONOFF(b) ((b) ? "ON" : "OFF")
} // namespace esphome
+214
View File
@@ -0,0 +1,214 @@
#pragma once
//
// Copyright (c) 2017 Martin Moene
//
// https://github.com/martinmoene/optional-bare
//
// This code is licensed under the MIT License (MIT).
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
// Modified by Otto Winter on 18.05.18
namespace esphome {
// type for nullopt
struct nullopt_t { // NOLINT
struct init {}; // NOLINT
nullopt_t(init) {}
};
// extra parenthesis to prevent the most vexing parse:
const nullopt_t nullopt((nullopt_t::init())); // NOLINT
// Simplistic optional: requires T to be default constructible, copyable.
template<typename T> class optional { // NOLINT
private:
using safe_bool = void (optional::*)() const;
public:
using value_type = T;
optional() {}
optional(nullopt_t) {}
optional(T const &arg) : has_value_(true), value_(arg) {}
template<class U> optional(optional<U> const &other) : has_value_(other.has_value()), value_(other.value()) {}
optional &operator=(nullopt_t) {
reset();
return *this;
}
template<class U> optional &operator=(optional<U> const &other) {
has_value_ = other.has_value();
value_ = other.value();
return *this;
}
void swap(optional &rhs) {
using std::swap;
if (has_value() && rhs.has_value()) {
swap(**this, *rhs);
} else if (!has_value() && rhs.has_value()) {
initialize(*rhs);
rhs.reset();
} else if (has_value() && !rhs.has_value()) {
rhs.initialize(**this);
reset();
}
}
// observers
value_type const *operator->() const { return &value_; }
value_type *operator->() { return &value_; }
value_type const &operator*() const { return value_; }
value_type &operator*() { return value_; }
operator safe_bool() const { return has_value() ? &optional::this_type_does_not_support_comparisons : nullptr; }
bool has_value() const { return has_value_; }
value_type const &value() const { return value_; }
value_type &value() { return value_; }
template<class U> value_type value_or(U const &v) const { return has_value() ? value() : static_cast<value_type>(v); }
// modifiers
void reset() { has_value_ = false; }
private:
void this_type_does_not_support_comparisons() const {} // NOLINT
template<typename V> void initialize(V const &value) { // NOLINT
value_ = value;
has_value_ = true;
}
private:
bool has_value_{false}; // NOLINT
value_type value_; // NOLINT
};
// Relational operators
template<typename T, typename U> inline bool operator==(optional<T> const &x, optional<U> const &y) {
return bool(x) != bool(y) ? false : !bool(x) ? true : *x == *y;
}
template<typename T, typename U> inline bool operator!=(optional<T> const &x, optional<U> const &y) {
return !(x == y);
}
template<typename T, typename U> inline bool operator<(optional<T> const &x, optional<U> const &y) {
return (!y) ? false : (!x) ? true : *x < *y;
}
template<typename T, typename U> inline bool operator>(optional<T> const &x, optional<U> const &y) { return (y < x); }
template<typename T, typename U> inline bool operator<=(optional<T> const &x, optional<U> const &y) { return !(y < x); }
template<typename T, typename U> inline bool operator>=(optional<T> const &x, optional<U> const &y) { return !(x < y); }
// Comparison with nullopt
template<typename T> inline bool operator==(optional<T> const &x, nullopt_t) { return (!x); }
template<typename T> inline bool operator==(nullopt_t, optional<T> const &x) { return (!x); }
template<typename T> inline bool operator!=(optional<T> const &x, nullopt_t) { return bool(x); }
template<typename T> inline bool operator!=(nullopt_t, optional<T> const &x) { return bool(x); }
template<typename T> inline bool operator<(optional<T> const &, nullopt_t) { return false; }
template<typename T> inline bool operator<(nullopt_t, optional<T> const &x) { return bool(x); }
template<typename T> inline bool operator<=(optional<T> const &x, nullopt_t) { return (!x); }
template<typename T> inline bool operator<=(nullopt_t, optional<T> const &) { return true; }
template<typename T> inline bool operator>(optional<T> const &x, nullopt_t) { return bool(x); }
template<typename T> inline bool operator>(nullopt_t, optional<T> const &) { return false; }
template<typename T> inline bool operator>=(optional<T> const &, nullopt_t) { return true; }
template<typename T> inline bool operator>=(nullopt_t, optional<T> const &x) { return (!x); }
// Comparison with T
template<typename T, typename U> inline bool operator==(optional<T> const &x, U const &v) {
return bool(x) ? *x == v : false;
}
template<typename T, typename U> inline bool operator==(U const &v, optional<T> const &x) {
return bool(x) ? v == *x : false;
}
template<typename T, typename U> inline bool operator!=(optional<T> const &x, U const &v) {
return bool(x) ? *x != v : true;
}
template<typename T, typename U> inline bool operator!=(U const &v, optional<T> const &x) {
return bool(x) ? v != *x : true;
}
template<typename T, typename U> inline bool operator<(optional<T> const &x, U const &v) {
return bool(x) ? *x < v : true;
}
template<typename T, typename U> inline bool operator<(U const &v, optional<T> const &x) {
return bool(x) ? v < *x : false;
}
template<typename T, typename U> inline bool operator<=(optional<T> const &x, U const &v) {
return bool(x) ? *x <= v : true;
}
template<typename T, typename U> inline bool operator<=(U const &v, optional<T> const &x) {
return bool(x) ? v <= *x : false;
}
template<typename T, typename U> inline bool operator>(optional<T> const &x, U const &v) {
return bool(x) ? *x > v : false;
}
template<typename T, typename U> inline bool operator>(U const &v, optional<T> const &x) {
return bool(x) ? v > *x : true;
}
template<typename T, typename U> inline bool operator>=(optional<T> const &x, U const &v) {
return bool(x) ? *x >= v : false;
}
template<typename T, typename U> inline bool operator>=(U const &v, optional<T> const &x) {
return bool(x) ? v >= *x : true;
}
// Specialized algorithms
template<typename T> void swap(optional<T> &x, optional<T> &y) { x.swap(y); }
// Convenience function to create an optional.
template<typename T> inline optional<T> make_optional(T const &v) { return optional<T>(v); }
} // namespace esphome
+233
View File
@@ -0,0 +1,233 @@
#include "esphome/core/preferences.h"
#include "esphome/core/log.h"
#include "esphome/core/helpers.h"
#ifdef USE_ESP8266_PREFERENCES_FLASH
extern "C" {
#include "spi_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) {
this->data_ = new uint32_t[this->length_words_ + 1];
for (uint32_t i = 0; i < this->length_words_ + 1; i++)
this->data_[i] = 0;
}
bool ESPPreferenceObject::load_() {
if (!this->is_initialized()) {
ESP_LOGV(TAG, "Load Pref Not initialized!");
return false;
}
if (!this->load_internal_())
return false;
bool valid = this->data_[this->length_words_] == this->calculate_crc_();
ESP_LOGVV(TAG, "LOAD %zu: valid=%s, 0=0x%08X 1=0x%08X (Type=%u, CRC=0x%08X)", this->rtc_offset_, // NOLINT
YESNO(valid), this->data_[0], this->data_[1], this->type_, this->calculate_crc_());
return valid;
}
bool ESPPreferenceObject::save_() {
if (!this->is_initialized()) {
ESP_LOGV(TAG, "Save Pref Not initialized!");
return false;
}
this->data_[this->length_words_] = this->calculate_crc_();
if (!this->save_internal_())
return false;
ESP_LOGVV(TAG, "SAVE %zu: 0=0x%08X 1=0x%08X (Type=%u, CRC=0x%08X)", this->rtc_offset_, // NOLINT
this->data_[0], this->data_[1], this->type_, this->calculate_crc_());
return true;
}
#ifdef ARDUINO_ARCH_ESP8266
#define ESP_RTC_USER_MEM_START 0x60001200
#define ESP_RTC_USER_MEM ((uint32_t *) ESP_RTC_USER_MEM_START)
#define ESP_RTC_USER_MEM_SIZE_WORDS 128
#define ESP_RTC_USER_MEM_SIZE_BYTES ESP_RTC_USER_MEM_SIZE_WORDS * 4
static inline bool esp_rtc_user_mem_read(uint32_t index, uint32_t *dest) {
if (index >= ESP_RTC_USER_MEM_SIZE_WORDS) {
return false;
}
*dest = ESP_RTC_USER_MEM[index];
return true;
}
#ifdef USE_ESP8266_PREFERENCES_FLASH
static bool esp8266_preferences_modified = false;
#endif
static inline bool esp_rtc_user_mem_write(uint32_t index, uint32_t value) {
if (index >= ESP_RTC_USER_MEM_SIZE_WORDS) {
return false;
}
if (index < 32 && global_preferences.is_prevent_write()) {
return false;
}
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_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)
return;
ESP_LOGVV(TAG, "Saving preferences to flash...");
disable_interrupts();
auto erase_res = spi_flash_erase_sector(get_esp8266_flash_sector());
if (erase_res != SPI_FLASH_RESULT_OK) {
enable_interrupts();
ESP_LOGV(TAG, "Erase ESP8266 flash failed!");
return;
}
auto write_res = spi_flash_write(get_esp8266_flash_address(), ESP_RTC_USER_MEM, ESP_RTC_USER_MEM_SIZE_BYTES);
enable_interrupts();
if (write_res != SPI_FLASH_RESULT_OK) {
ESP_LOGV(TAG, "Write ESP8266 flash failed!");
return;
}
esp8266_preferences_modified = false;
}
#endif
bool ESPPreferenceObject::save_internal_() {
for (uint32_t i = 0; i <= this->length_words_; i++) {
if (!esp_rtc_user_mem_write(this->rtc_offset_ + i, this->data_[i]))
return false;
}
#ifdef USE_ESP8266_PREFERENCES_FLASH
save_esp8266_flash();
#endif
return true;
}
bool ESPPreferenceObject::load_internal_() {
for (uint32_t i = 0; i <= this->length_words_; i++) {
if (!esp_rtc_user_mem_read(this->rtc_offset_ + i, &this->data_[i]))
return false;
}
return true;
}
ESPPreferences::ESPPreferences()
// offset starts from start of user RTC mem (64 words before that are reserved for system),
// an additional 32 words at the start of user RTC are for eboot (OTA, see eboot_command.h),
// 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
}
ESPPreferenceObject ESPPreferences::make_preference(size_t length, uint32_t type) {
uint32_t start = this->current_offset_;
uint32_t end = start + length + 1;
bool in_normal = start < 96;
// Normal: offset 0-95 maps to RTC offset 32 - 127,
// Eboot: offset 96-127 maps to RTC offset 0 - 31 words
if (in_normal && end > 96) {
// start is in normal but end is not -> switch to Eboot
this->current_offset_ = start = 96;
end = start + length + 1;
in_normal = false;
}
if (end > 128) {
// Doesn't fit in data, return uninitialized preference obj.
return ESPPreferenceObject();
}
uint32_t rtc_offset;
if (in_normal) {
rtc_offset = start + 32;
} else {
rtc_offset = start - 96;
}
auto pref = ESPPreferenceObject(rtc_offset, length, type);
this->current_offset_ += length + 1;
return pref;
}
void ESPPreferences::prevent_write(bool prevent) { this->prevent_write_ = prevent; }
bool ESPPreferences::is_prevent_write() { return this->prevent_write_; }
#endif
#ifdef ARDUINO_ARCH_ESP32
bool ESPPreferenceObject::save_internal_() {
char key[32];
sprintf(key, "%u", this->rtc_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!");
return false;
}
return true;
}
bool ESPPreferenceObject::load_internal_() {
char key[32];
sprintf(key, "%u", this->rtc_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!");
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());
}
ESPPreferenceObject ESPPreferences::make_preference(size_t length, uint32_t type) {
auto pref = ESPPreferenceObject(this->current_offset_, length, type);
this->current_offset_++;
return pref;
}
#endif
uint32_t ESPPreferenceObject::calculate_crc_() const {
uint32_t crc = this->type_;
for (size_t i = 0; i < this->length_words_; i++) {
crc ^= (this->data_[i] * 2654435769UL) >> 1;
}
return crc;
}
bool ESPPreferenceObject::is_initialized() const { return this->data_ != nullptr; }
ESPPreferences global_preferences;
} // namespace esphome
+92
View File
@@ -0,0 +1,92 @@
#pragma once
#include <string>
#ifdef ARDUINO_ARCH_ESP32
#include <Preferences.h>
#endif
#include "esphome/core/esphal.h"
namespace esphome {
class ESPPreferenceObject {
public:
ESPPreferenceObject();
ESPPreferenceObject(size_t rtc_offset, size_t length, uint32_t type);
template<typename T> bool save(T *src);
template<typename T> bool load(T *dest);
bool is_initialized() const;
protected:
bool save_();
bool load_();
bool save_internal_();
bool load_internal_();
uint32_t calculate_crc_() const;
size_t rtc_offset_;
size_t length_words_;
uint32_t type_;
uint32_t *data_;
};
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);
#ifdef ARDUINO_ARCH_ESP8266
/** On the ESP8266, we can't override the first 128 bytes during OTA uploads
* as the eboot parameters are stored there. Writing there during an OTA upload
* would invalidate applying the new firmware. During normal operation, we use
* this part of the RTC user memory, but stop writing to it during OTA uploads.
*
* @param prevent Whether to prevent writing to the first 32 words of RTC user memory.
*/
void prevent_write(bool prevent);
bool is_prevent_write();
#endif
protected:
friend ESPPreferenceObject;
uint32_t current_offset_;
#ifdef ARDUINO_ARCH_ESP32
Preferences preferences_;
#endif
#ifdef ARDUINO_ARCH_ESP8266
bool prevent_write_{false};
#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> bool ESPPreferenceObject::save(T *src) {
if (!this->is_initialized())
return false;
memset(this->data_, 0, this->length_words_ * 4);
memcpy(this->data_, src, sizeof(T));
return this->save_();
}
template<typename T> bool ESPPreferenceObject::load(T *dest) {
memset(this->data_, 0, this->length_words_ * 4);
if (!this->load_())
return false;
memcpy(dest, this->data_, sizeof(T));
return true;
}
} // namespace esphome
+120
View File
@@ -0,0 +1,120 @@
#include "esphome/core/util.h"
#include "esphome/core/defines.h"
#include "esphome/core/application.h"
#ifdef USE_WIFI
#include "esphome/components/wifi/wifi_component.h"
#endif
#ifdef USE_API
#include "esphome/components/api/api_server.h"
#endif
#ifdef USE_ETHERNET
#include "esphome/components/ethernet/ethernet_component.h"
#endif
#ifdef ARDUINO_ARCH_ESP32
#include <ESPmDNS.h>
#endif
#ifdef ARDUINO_ARCH_ESP8266
#include <ESP8266mDNS.h>
#endif
namespace esphome {
bool network_is_connected() {
#ifdef USE_ETHERNET
if (ethernet::global_eth_component != nullptr && ethernet::global_eth_component->is_connected())
return true;
#endif
#ifdef USE_WIFI
if (wifi::global_wifi_component != nullptr)
return wifi::global_wifi_component->is_connected();
#endif
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();
#endif
}
std::string network_get_address() {
#ifdef USE_ETHERNET
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();
#endif
return "";
}
} // namespace esphome
+18
View File
@@ -0,0 +1,18 @@
#pragma once
#include <string>
namespace esphome {
/// Return whether the node is connected to the network (through wifi, eth, ...)
bool network_is_connected();
/// Get the active network hostname
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();
void network_setup_mdns();
void network_tick_mdns();
} // namespace esphome