1
0
mirror of https://github.com/esphome/esphome.git synced 2025-10-04 02:52:22 +01:00

🏗 Merge C++ into python codebase (#504)

## Description:

Move esphome-core codebase into esphome (and a bunch of other refactors). See https://github.com/esphome/feature-requests/issues/97

Yes this is a shit ton of work and no there's no way to automate it :( But it will be worth it 👍

Progress:
- Core support (file copy etc): 80%
- Base Abstractions (light, switch): ~50%
- Integrations: ~10%
- Working? Yes, (but only with ported components).

Other refactors:
- Moves all codegen related stuff into a single class: `esphome.codegen` (imported as `cg`)
- Rework coroutine syntax
- Move from `component/platform.py` to `domain/component.py` structure as with HA
- Move all defaults out of C++ and into config validation.
- Remove `make_...` helpers from Application class. Reason: Merge conflicts with every single new integration.
- Pointer Variables are stored globally instead of locally in setup(). Reason: stack size limit.

Future work:
- Rework const.py - Move all `CONF_...` into a conf class (usage `conf.UPDATE_INTERVAL` vs `CONF_UPDATE_INTERVAL`). Reason: Less convoluted import block
- Enable loading from `custom_components` folder.

**Related issue (if applicable):** https://github.com/esphome/feature-requests/issues/97

**Pull request in [esphome-docs](https://github.com/esphome/esphome-docs) with documentation (if applicable):** esphome/esphome-docs#<esphome-docs PR number goes here>

## Checklist:
  - [ ] The code change is tested and works locally.
  - [ ] Tests have been added to verify that the new code works (under `tests/` folder).

If user exposed functionality or configuration variables are added/changed:
  - [ ] Documentation added/updated in [esphomedocs](https://github.com/OttoWinter/esphomedocs).
This commit is contained in:
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

View File

@@ -1,125 +1,117 @@
import voluptuous as vol
from esphome.automation import ACTION_REGISTRY, maybe_simple_id
from esphome.components import mqtt
from esphome.components.mqtt import setup_mqtt_component
import esphome.config_validation as cv
import esphome.codegen as cg
from esphome.components import mqtt
from esphome.const import CONF_ID, CONF_INTERNAL, CONF_MQTT_ID, CONF_OSCILLATING, \
CONF_OSCILLATION_COMMAND_TOPIC, CONF_OSCILLATION_STATE_TOPIC, CONF_SPEED, \
CONF_SPEED_COMMAND_TOPIC, CONF_SPEED_STATE_TOPIC
from esphome.core import CORE
from esphome.cpp_generator import Pvariable, add, get_variable, templatable
from esphome.cpp_types import Action, Application, Component, Nameable, bool_, esphome_ns
CONF_SPEED_COMMAND_TOPIC, CONF_SPEED_STATE_TOPIC, CONF_NAME
from esphome.core import CORE, coroutine
PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA.extend({
IS_PLATFORM_COMPONENT = True
})
fan_ns = esphome_ns.namespace('fan')
FanState = fan_ns.class_('FanState', Nameable, Component)
MQTTFanComponent = fan_ns.class_('MQTTFanComponent', mqtt.MQTTComponent)
MakeFan = Application.struct('MakeFan')
fan_ns = cg.esphome_ns.namespace('fan')
FanState = fan_ns.class_('FanState', cg.Nameable, cg.Component)
MakeFan = cg.Application.struct('MakeFan')
# Actions
TurnOnAction = fan_ns.class_('TurnOnAction', Action)
TurnOffAction = fan_ns.class_('TurnOffAction', Action)
ToggleAction = fan_ns.class_('ToggleAction', Action)
TurnOnAction = fan_ns.class_('TurnOnAction', cg.Action)
TurnOffAction = fan_ns.class_('TurnOffAction', cg.Action)
ToggleAction = fan_ns.class_('ToggleAction', cg.Action)
FanSpeed = fan_ns.enum('FanSpeed')
FAN_SPEED_OFF = FanSpeed.FAN_SPEED_OFF
FAN_SPEED_LOW = FanSpeed.FAN_SPEED_LOW
FAN_SPEED_MEDIUM = FanSpeed.FAN_SPEED_MEDIUM
FAN_SPEED_HIGH = FanSpeed.FAN_SPEED_HIGH
FAN_SPEEDS = {
'OFF': FanSpeed.FAN_SPEED_OFF,
'LOW': FanSpeed.FAN_SPEED_LOW,
'MEDIuM': FanSpeed.FAN_SPEED_MEDIUM,
'HIGH': FanSpeed.FAN_SPEED_HIGH,
}
FAN_SCHEMA = cv.MQTT_COMMAND_COMPONENT_SCHEMA.extend({
cv.GenerateID(): cv.declare_variable_id(FanState),
cv.GenerateID(CONF_MQTT_ID): cv.declare_variable_id(MQTTFanComponent),
vol.Optional(CONF_OSCILLATION_STATE_TOPIC): vol.All(cv.requires_component('mqtt'),
cv.publish_topic),
vol.Optional(CONF_OSCILLATION_COMMAND_TOPIC): vol.All(cv.requires_component('mqtt'),
cv.subscribe_topic),
cv.OnlyWith(CONF_MQTT_ID, 'mqtt'): cv.declare_variable_id(mqtt.MQTTFanComponent),
cv.Optional(CONF_OSCILLATION_STATE_TOPIC): cv.All(cv.requires_component('mqtt'),
cv.publish_topic),
cv.Optional(CONF_OSCILLATION_COMMAND_TOPIC): cv.All(cv.requires_component('mqtt'),
cv.subscribe_topic),
})
FAN_PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(FAN_SCHEMA.schema)
FAN_SPEEDS = {
'OFF': FAN_SPEED_OFF,
'LOW': FAN_SPEED_LOW,
'MEDIUM': FAN_SPEED_MEDIUM,
'HIGH': FAN_SPEED_HIGH,
}
def setup_fan_core_(fan_var, config):
@coroutine
def setup_fan_core_(var, config):
if CONF_INTERNAL in config:
add(fan_var.set_internal(config[CONF_INTERNAL]))
cg.add(var.set_internal(config[CONF_INTERNAL]))
mqtt_ = fan_var.Pget_mqtt()
if CONF_OSCILLATION_STATE_TOPIC in config:
add(mqtt_.set_custom_oscillation_state_topic(config[CONF_OSCILLATION_STATE_TOPIC]))
if CONF_OSCILLATION_COMMAND_TOPIC in config:
add(mqtt_.set_custom_oscillation_command_topic(config[CONF_OSCILLATION_COMMAND_TOPIC]))
if CONF_SPEED_STATE_TOPIC in config:
add(mqtt_.set_custom_speed_state_topic(config[CONF_SPEED_STATE_TOPIC]))
if CONF_SPEED_COMMAND_TOPIC in config:
add(mqtt_.set_custom_speed_command_topic(config[CONF_SPEED_COMMAND_TOPIC]))
setup_mqtt_component(mqtt_, config)
if CONF_MQTT_ID in config:
mqtt_ = cg.new_Pvariable(config[CONF_MQTT_ID], var)
yield mqtt.register_mqtt_component(mqtt_, config)
if CONF_OSCILLATION_STATE_TOPIC in config:
cg.add(mqtt_.set_custom_oscillation_state_topic(config[CONF_OSCILLATION_STATE_TOPIC]))
if CONF_OSCILLATION_COMMAND_TOPIC in config:
cg.add(mqtt_.set_custom_oscillation_command_topic(
config[CONF_OSCILLATION_COMMAND_TOPIC]))
if CONF_SPEED_STATE_TOPIC in config:
cg.add(mqtt_.set_custom_speed_state_topic(config[CONF_SPEED_STATE_TOPIC]))
if CONF_SPEED_COMMAND_TOPIC in config:
cg.add(mqtt_.set_custom_speed_command_topic(config[CONF_SPEED_COMMAND_TOPIC]))
def setup_fan(fan_obj, config):
fan_var = Pvariable(config[CONF_ID], fan_obj, has_side_effects=False)
CORE.add_job(setup_fan_core_, fan_var, config)
@coroutine
def register_fan(var, config):
if not CORE.has_id(config[CONF_ID]):
var = cg.Pvariable(config[CONF_ID], var)
cg.add(cg.App.register_fan(var))
yield setup_fan_core_(var, config)
BUILD_FLAGS = '-DUSE_FAN'
@coroutine
def create_fan_state(config):
var = cg.new_Pvariable(config[CONF_ID], config[CONF_NAME])
yield register_fan(var, config)
yield var
CONF_FAN_TOGGLE = 'fan.toggle'
FAN_TOGGLE_ACTION_SCHEMA = maybe_simple_id({
vol.Required(CONF_ID): cv.use_variable_id(FanState),
FAN_ACTION_SCHEMA = maybe_simple_id({
cv.Required(CONF_ID): cv.use_variable_id(FanState),
})
@ACTION_REGISTRY.register(CONF_FAN_TOGGLE, FAN_TOGGLE_ACTION_SCHEMA)
@ACTION_REGISTRY.register('fan.toggle', FAN_ACTION_SCHEMA)
def fan_toggle_to_code(config, action_id, template_arg, args):
var = yield get_variable(config[CONF_ID])
rhs = var.make_toggle_action(template_arg)
var = yield cg.get_variable(config[CONF_ID])
type = ToggleAction.template(template_arg)
yield Pvariable(action_id, rhs, type=type)
rhs = type.new(var)
yield cg.Pvariable(action_id, rhs, type=type)
CONF_FAN_TURN_OFF = 'fan.turn_off'
FAN_TURN_OFF_ACTION_SCHEMA = maybe_simple_id({
vol.Required(CONF_ID): cv.use_variable_id(FanState),
})
@ACTION_REGISTRY.register(CONF_FAN_TURN_OFF, FAN_TURN_OFF_ACTION_SCHEMA)
@ACTION_REGISTRY.register('fan.turn_off', FAN_ACTION_SCHEMA)
def fan_turn_off_to_code(config, action_id, template_arg, args):
var = yield get_variable(config[CONF_ID])
rhs = var.make_turn_off_action(template_arg)
var = yield cg.get_variable(config[CONF_ID])
type = TurnOffAction.template(template_arg)
yield Pvariable(action_id, rhs, type=type)
rhs = type.new(var)
yield cg.Pvariable(action_id, rhs, type=type)
CONF_FAN_TURN_ON = 'fan.turn_on'
FAN_TURN_ON_ACTION_SCHEMA = maybe_simple_id({
vol.Required(CONF_ID): cv.use_variable_id(FanState),
vol.Optional(CONF_OSCILLATING): cv.templatable(cv.boolean),
vol.Optional(CONF_SPEED): cv.templatable(cv.one_of(*FAN_SPEEDS, upper=True)),
})
@ACTION_REGISTRY.register(CONF_FAN_TURN_ON, FAN_TURN_ON_ACTION_SCHEMA)
@ACTION_REGISTRY.register('fan.turn_on', maybe_simple_id({
cv.Required(CONF_ID): cv.use_variable_id(FanState),
cv.Optional(CONF_OSCILLATING): cv.templatable(cv.boolean),
cv.Optional(CONF_SPEED): cv.templatable(cv.one_of(*FAN_SPEEDS, upper=True)),
}))
def fan_turn_on_to_code(config, action_id, template_arg, args):
var = yield get_variable(config[CONF_ID])
rhs = var.make_turn_on_action(template_arg)
var = yield cg.get_variable(config[CONF_ID])
type = TurnOnAction.template(template_arg)
action = Pvariable(action_id, rhs, type=type)
rhs = type.new(var)
action = cg.Pvariable(action_id, rhs, type=type)
if CONF_OSCILLATING in config:
template_ = yield templatable(config[CONF_OSCILLATING], args, bool_)
add(action.set_oscillating(template_))
template_ = yield cg.templatable(config[CONF_OSCILLATING], args, bool)
cg.add(action.set_oscillating(template_))
if CONF_SPEED in config:
template_ = yield templatable(config[CONF_SPEED], args, FanSpeed,
to_exp=FAN_SPEEDS)
add(action.set_speed(template_))
template_ = yield cg.templatable(config[CONF_SPEED], args, FanSpeed,
to_exp=FAN_SPEEDS)
cg.add(action.set_speed(template_))
yield action
def to_code(config):
cg.add_define('USE_FAN')
cg.add_global(fan_ns.using)

View File

@@ -0,0 +1,10 @@
#include "automation.h"
#include "esphome/core/log.h"
namespace esphome {
namespace fan {
static const char *TAG = "fan.automation";
} // namespace fan
} // namespace esphome

View File

@@ -0,0 +1,60 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/automation.h"
#include "fan_state.h"
namespace esphome {
namespace fan {
template<typename... Ts> class TurnOnAction : public Action<Ts...> {
public:
explicit TurnOnAction(FanState *state) : state_(state) {}
TEMPLATABLE_VALUE(bool, oscillating)
TEMPLATABLE_VALUE(FanSpeed, speed)
void play(Ts... x) override {
auto call = this->state_->turn_on();
if (this->oscillating_.has_value()) {
call.set_oscillating(this->oscillating_.value(x...));
}
if (this->speed_.has_value()) {
call.set_speed(this->speed_.value(x...));
}
call.perform();
this->play_next(x...);
}
protected:
FanState *state_;
};
template<typename... Ts> class TurnOffAction : public Action<Ts...> {
public:
explicit TurnOffAction(FanState *state) : state_(state) {}
void play(Ts... x) override {
this->state_->turn_off().perform();
this->play_next(x...);
}
protected:
FanState *state_;
};
template<typename... Ts> class ToggleAction : public Action<Ts...> {
public:
explicit ToggleAction(FanState *state) : state_(state) {}
void play(Ts... x) override {
this->state_->toggle().perform();
this->play_next(x...);
}
protected:
FanState *state_;
};
} // namespace fan
} // namespace esphome

View File

@@ -1,28 +0,0 @@
import voluptuous as vol
from esphome.components import fan, output
import esphome.config_validation as cv
from esphome.const import CONF_MAKE_ID, CONF_NAME, CONF_OSCILLATION_OUTPUT, CONF_OUTPUT
from esphome.cpp_generator import add, get_variable, variable
from esphome.cpp_helpers import setup_component
from esphome.cpp_types import App
PLATFORM_SCHEMA = cv.nameable(fan.FAN_PLATFORM_SCHEMA.extend({
cv.GenerateID(CONF_MAKE_ID): cv.declare_variable_id(fan.MakeFan),
vol.Required(CONF_OUTPUT): cv.use_variable_id(output.BinaryOutput),
vol.Optional(CONF_OSCILLATION_OUTPUT): cv.use_variable_id(output.BinaryOutput),
}).extend(cv.COMPONENT_SCHEMA.schema))
def to_code(config):
output_ = yield get_variable(config[CONF_OUTPUT])
rhs = App.make_fan(config[CONF_NAME])
fan_struct = variable(config[CONF_MAKE_ID], rhs)
add(fan_struct.Poutput.set_binary(output_))
if CONF_OSCILLATION_OUTPUT in config:
oscillation_output = yield get_variable(config[CONF_OSCILLATION_OUTPUT])
add(fan_struct.Poutput.set_oscillation(oscillation_output))
fan.setup_fan(fan_struct.Pstate, config)
setup_component(fan_struct.Poutput, config)

View File

@@ -0,0 +1,107 @@
#include "fan_state.h"
#include "esphome/core/log.h"
namespace esphome {
namespace fan {
static const char *TAG = "fan";
const FanTraits &FanState::get_traits() const { return this->traits_; }
void FanState::set_traits(const FanTraits &traits) { this->traits_ = traits; }
void FanState::add_on_state_callback(std::function<void()> &&callback) {
this->state_callback_.add(std::move(callback));
}
FanState::FanState(const std::string &name) : Nameable(name) {}
FanState::StateCall FanState::turn_on() { return this->make_call().set_state(true); }
FanState::StateCall FanState::turn_off() { return this->make_call().set_state(false); }
FanState::StateCall FanState::toggle() { return this->make_call().set_state(!this->state); }
FanState::StateCall FanState::make_call() { return FanState::StateCall(this); }
struct FanStateRTCState {
bool state;
FanSpeed speed;
bool oscillating;
};
void FanState::setup() {
this->rtc_ = global_preferences.make_preference<FanStateRTCState>(this->get_object_id_hash());
FanStateRTCState recovered{};
if (!this->rtc_.load(&recovered))
return;
auto call = this->make_call();
call.set_state(recovered.state);
call.set_speed(recovered.speed);
call.set_oscillating(recovered.oscillating);
call.perform();
}
float FanState::get_setup_priority() const { return setup_priority::HARDWARE - 1.0f; }
uint32_t FanState::hash_base() { return 418001110UL; }
FanState::StateCall::StateCall(FanState *state) : state_(state) {}
FanState::StateCall &FanState::StateCall::set_state(bool state) {
this->binary_state_ = state;
return *this;
}
FanState::StateCall &FanState::StateCall::set_state(optional<bool> state) {
this->binary_state_ = state;
return *this;
}
FanState::StateCall &FanState::StateCall::set_oscillating(bool oscillating) {
this->oscillating_ = oscillating;
return *this;
}
FanState::StateCall &FanState::StateCall::set_oscillating(optional<bool> oscillating) {
this->oscillating_ = oscillating;
return *this;
}
FanState::StateCall &FanState::StateCall::set_speed(FanSpeed speed) {
this->speed_ = speed;
return *this;
}
FanState::StateCall &FanState::StateCall::set_speed(optional<FanSpeed> speed) {
this->speed_ = speed;
return *this;
}
void FanState::StateCall::perform() const {
if (this->binary_state_.has_value()) {
this->state_->state = *this->binary_state_;
}
if (this->oscillating_.has_value()) {
this->state_->oscillating = *this->oscillating_;
}
if (this->speed_.has_value()) {
switch (*this->speed_) {
case FAN_SPEED_LOW:
case FAN_SPEED_MEDIUM:
case FAN_SPEED_HIGH:
this->state_->speed = *this->speed_;
break;
default:
// protect from invalid input
break;
}
}
FanStateRTCState saved{};
saved.state = this->state_->state;
saved.speed = this->state_->speed;
saved.oscillating = this->state_->oscillating;
this->state_->rtc_.save(&saved);
this->state_->state_callback_.call();
}
FanState::StateCall &FanState::StateCall::set_speed(const char *speed) {
if (strcasecmp(speed, "low") == 0) {
this->set_speed(FAN_SPEED_LOW);
} else if (strcasecmp(speed, "medium") == 0) {
this->set_speed(FAN_SPEED_MEDIUM);
} else if (strcasecmp(speed, "high") == 0) {
this->set_speed(FAN_SPEED_HIGH);
}
return *this;
}
} // namespace fan
} // namespace esphome

View File

@@ -0,0 +1,76 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/helpers.h"
#include "esphome/core/preferences.h"
#include "fan_traits.h"
namespace esphome {
namespace fan {
/// Simple enum to represent the speed of a fan.
enum FanSpeed {
FAN_SPEED_LOW = 0, ///< The fan is running on low speed.
FAN_SPEED_MEDIUM = 1, ///< The fan is running on medium speed.
FAN_SPEED_HIGH = 2 ///< The fan is running on high/full speed.
};
class FanState : public Nameable, public Component {
public:
/// Construct the fan state with name.
explicit FanState(const std::string &name);
/// Register a callback that will be called each time the state changes.
void add_on_state_callback(std::function<void()> &&callback);
/// Get the traits of this fan (i.e. what features it supports).
const FanTraits &get_traits() const;
/// Set the traits of this fan (i.e. what features it supports).
void set_traits(const FanTraits &traits);
/// The current ON/OFF state of the fan.
bool state{false};
/// The current oscillation state of the fan.
bool oscillating{false};
/// The current fan speed.
FanSpeed speed{FAN_SPEED_HIGH};
class StateCall {
public:
explicit StateCall(FanState *state);
FanState::StateCall &set_state(bool state);
FanState::StateCall &set_state(optional<bool> state);
FanState::StateCall &set_oscillating(bool oscillating);
FanState::StateCall &set_oscillating(optional<bool> oscillating);
FanState::StateCall &set_speed(FanSpeed speed);
FanState::StateCall &set_speed(optional<FanSpeed> speed);
FanState::StateCall &set_speed(const char *speed);
void perform() const;
protected:
FanState *const state_;
optional<bool> binary_state_;
optional<bool> oscillating_{};
optional<FanSpeed> speed_{};
};
FanState::StateCall turn_on();
FanState::StateCall turn_off();
FanState::StateCall toggle();
FanState::StateCall make_call();
void setup() override;
float get_setup_priority() const override;
protected:
uint32_t hash_base() override;
FanTraits traits_{};
CallbackManager<void()> state_callback_{};
ESPPreferenceObject rtc_;
};
} // namespace fan
} // namespace esphome

View File

@@ -0,0 +1,26 @@
#pragma once
namespace esphome {
namespace fan {
class FanTraits {
public:
FanTraits() = default;
FanTraits(bool oscillation, bool speed) : oscillation_(false), speed_(speed) {}
/// Return if this fan supports oscillation.
bool supports_oscillation() const { return this->oscillation_; }
/// Set whether this fan supports oscillation.
void set_oscillation(bool oscillation) { this->oscillation_ = oscillation; }
/// Return if this fan supports speed modes.
bool supports_speed() const { return this->speed_; }
/// Set whether this fan supports speed modes.
void set_speed(bool speed) { this->speed_ = speed; }
protected:
bool oscillation_{false};
bool speed_{false};
};
} // namespace fan
} // namespace esphome

View File

@@ -1,42 +0,0 @@
import voluptuous as vol
from esphome.components import fan, output
import esphome.config_validation as cv
from esphome.const import CONF_HIGH, CONF_LOW, CONF_MAKE_ID, CONF_MEDIUM, CONF_NAME, \
CONF_OSCILLATION_OUTPUT, CONF_OUTPUT, CONF_SPEED, CONF_SPEED_COMMAND_TOPIC, \
CONF_SPEED_STATE_TOPIC
from esphome.cpp_generator import add, get_variable, variable
from esphome.cpp_types import App
PLATFORM_SCHEMA = cv.nameable(fan.FAN_PLATFORM_SCHEMA.extend({
cv.GenerateID(CONF_MAKE_ID): cv.declare_variable_id(fan.MakeFan),
vol.Required(CONF_OUTPUT): cv.use_variable_id(output.FloatOutput),
vol.Optional(CONF_SPEED_STATE_TOPIC): cv.publish_topic,
vol.Optional(CONF_SPEED_COMMAND_TOPIC): cv.subscribe_topic,
vol.Optional(CONF_OSCILLATION_OUTPUT): cv.use_variable_id(output.BinaryOutput),
vol.Optional(CONF_SPEED): cv.Schema({
vol.Required(CONF_LOW): cv.percentage,
vol.Required(CONF_MEDIUM): cv.percentage,
vol.Required(CONF_HIGH): cv.percentage,
}),
}).extend(cv.COMPONENT_SCHEMA.schema))
def to_code(config):
output_ = yield get_variable(config[CONF_OUTPUT])
rhs = App.make_fan(config[CONF_NAME])
fan_struct = variable(config[CONF_MAKE_ID], rhs)
if CONF_SPEED in config:
speeds = config[CONF_SPEED]
add(fan_struct.Poutput.set_speed(output_,
speeds[CONF_LOW],
speeds[CONF_MEDIUM],
speeds[CONF_HIGH]))
else:
add(fan_struct.Poutput.set_speed(output_))
if CONF_OSCILLATION_OUTPUT in config:
oscillation_output = yield get_variable(config[CONF_OSCILLATION_OUTPUT])
add(fan_struct.Poutput.set_oscillation(oscillation_output))
fan.setup_fan(fan_struct.Pstate, config)