Merge pull request #4 from esphome/dev

update dev branch from source
This commit is contained in:
Michiel van Turnhout 2019-05-17 13:39:09 +02:00 committed by GitHub
commit b4c9b6511c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
150 changed files with 2548 additions and 687 deletions

13
.gitignore vendored
View file

@ -6,6 +6,19 @@ __pycache__/
# C extensions # C extensions
*.so *.so
# Hide sublime text stuff
*.sublime-project
*.sublime-workspace
# Hide some OS X stuff
.DS_Store
.AppleDouble
.LSOverride
Icon
# Thumbnails
._*
# Distribution / packaging # Distribution / packaging
.Python .Python
build/ build/

View file

@ -51,6 +51,6 @@ matrix:
- clang-format-7 -version - clang-format-7 -version
- clang-apply-replacements-7 -version - clang-apply-replacements-7 -version
script: script:
- script/clang-tidy.py --all-headers -j 2 --fix - script/clang-tidy --all-headers -j 2 --fix
- script/clang-format.py -i -j 2 - script/clang-format -i -j 2
- script/ci-suggest-changes - script/ci-suggest-changes

View file

@ -2,12 +2,12 @@ server {
listen %%port%% default_server ssl http2; listen %%port%% default_server ssl http2;
include /etc/nginx/includes/server_params.conf; include /etc/nginx/includes/server_params.conf;
include /etc/nginx/includes/proxy_params.conf; include /etc/nginx/includes/proxy_params.conf;
include /etc/nginx/includes/ssl_params.conf; include /etc/nginx/includes/ssl_params.conf;
# Clear Hass.io Ingress header # Clear Hass.io Ingress header
proxy_set_header X-Hassio-Ingress ""; proxy_set_header X-Hassio-Ingress "";
# Redirect http requests to https on the same port. # Redirect http requests to https on the same port.
# https://rageagainstshell.com/2016/11/redirect-http-to-https-on-the-same-port-in-nginx/ # https://rageagainstshell.com/2016/11/redirect-http-to-https-on-the-same-port-in-nginx/
error_page 497 https://$http_host$request_uri; error_page 497 https://$http_host$request_uri;

View file

@ -7,7 +7,7 @@ server {
proxy_set_header X-Hassio-Ingress "YES"; proxy_set_header X-Hassio-Ingress "YES";
location / { location / {
# Only allow from Hass.io supervisor # Only allow from Hass.io supervisor
allow 172.30.32.2; allow 172.30.32.2;
deny all; deny all;

View file

@ -1,16 +1,18 @@
from __future__ import print_function from __future__ import print_function
import argparse import argparse
import functools
import logging import logging
import os import os
import sys import sys
from datetime import datetime from datetime import datetime
from esphome import const, writer, yaml_util from esphome import const, writer, yaml_util
import esphome.codegen as cg
from esphome.config import iter_components, read_config, strip_default_ids from esphome.config import iter_components, read_config, strip_default_ids
from esphome.const import CONF_BAUD_RATE, CONF_BROKER, CONF_LOGGER, CONF_OTA, \ from esphome.const import CONF_BAUD_RATE, CONF_BROKER, CONF_LOGGER, CONF_OTA, \
CONF_PASSWORD, CONF_PORT CONF_PASSWORD, CONF_PORT
from esphome.core import CORE, EsphomeError from esphome.core import CORE, EsphomeError, coroutine, coroutine_with_priority
from esphome.helpers import color, indent from esphome.helpers import color, indent
from esphome.py_compat import IS_PY2, safe_input from esphome.py_compat import IS_PY2, safe_input
from esphome.util import run_external_command, run_external_process, safe_print from esphome.util import run_external_command, run_external_process, safe_print
@ -117,12 +119,27 @@ def run_miniterm(config, port):
config, line, backtrace_state=backtrace_state) config, line, backtrace_state=backtrace_state)
def wrap_to_code(name, comp):
coro = coroutine(comp.to_code)
@functools.wraps(comp.to_code)
@coroutine_with_priority(coro.priority)
def wrapped(conf):
cg.add(cg.LineComment(u"{}:".format(name)))
if comp.config_schema is not None:
cg.add(cg.LineComment(indent(yaml_util.dump(conf).decode('utf-8'))))
yield coro(conf)
return wrapped
def write_cpp(config): def write_cpp(config):
_LOGGER.info("Generating C++ source...") _LOGGER.info("Generating C++ source...")
for _, component, conf in iter_components(CORE.config): for name, component, conf in iter_components(CORE.config):
if component.to_code is not None: if component.to_code is not None:
CORE.add_job(component.to_code, conf) coro = wrap_to_code(name, component)
CORE.add_job(coro, conf)
CORE.flush_tasks() CORE.flush_tasks()
@ -245,7 +262,7 @@ def command_vscode(args):
from esphome import vscode from esphome import vscode
CORE.config_path = args.configuration CORE.config_path = args.configuration
vscode.read_config() vscode.read_config(args)
def command_compile(args, config): def command_compile(args, config):
@ -423,7 +440,8 @@ def parse_args(argv):
dashboard.add_argument("--socket", dashboard.add_argument("--socket",
help="Make the dashboard serve under a unix socket", type=str) help="Make the dashboard serve under a unix socket", type=str)
subparsers.add_parser('vscode', help=argparse.SUPPRESS) vscode = subparsers.add_parser('vscode', help=argparse.SUPPRESS)
vscode.add_argument('--ace', action='store_true')
return parser.parse_args(argv[1:]) return parser.parse_args(argv[1:])

View file

@ -128,7 +128,7 @@ def or_condition_to_code(config, condition_id, template_arg, args):
yield cg.new_Pvariable(condition_id, template_arg, conditions) yield cg.new_Pvariable(condition_id, template_arg, conditions)
@register_condition('not', NotCondition, validate_condition) @register_condition('not', NotCondition, validate_potentially_and_condition)
def not_condition_to_code(config, condition_id, template_arg, args): def not_condition_to_code(config, condition_id, template_arg, args):
condition = yield build_condition(config, template_arg, args) condition = yield build_condition(config, template_arg, args)
yield cg.new_Pvariable(condition_id, template_arg, condition) yield cg.new_Pvariable(condition_id, template_arg, condition)

View file

@ -10,16 +10,16 @@
# pylint: disable=unused-import # pylint: disable=unused-import
from esphome.cpp_generator import ( # noqa from esphome.cpp_generator import ( # noqa
Expression, RawExpression, RawStatement, TemplateArguments, Expression, RawExpression, RawStatement, TemplateArguments,
StructInitializer, ArrayInitializer, safe_exp, Statement, StructInitializer, ArrayInitializer, safe_exp, Statement, LineComment,
progmem_array, statement, variable, Pvariable, new_Pvariable, progmem_array, statement, variable, Pvariable, new_Pvariable,
add, add_global, add_library, add_build_flag, add_define, add, add_global, add_library, add_build_flag, add_define,
get_variable, get_variable_with_full_id, process_lambda, is_template, templatable, MockObj, get_variable, get_variable_with_full_id, process_lambda, is_template, templatable, MockObj,
MockObjClass) MockObjClass)
from esphome.cpp_helpers import ( # noqa from esphome.cpp_helpers import ( # noqa
gpio_pin_expression, register_component, build_registry_entry, gpio_pin_expression, register_component, build_registry_entry,
build_registry_list, extract_registry_entry_config) build_registry_list, extract_registry_entry_config, register_parented)
from esphome.cpp_types import ( # noqa from esphome.cpp_types import ( # noqa
global_ns, void, nullptr, float_, bool_, std_ns, std_string, global_ns, void, nullptr, float_, double, bool_, std_ns, std_string,
std_vector, uint8, uint16, uint32, int32, const_char_ptr, NAN, std_vector, uint8, uint16, uint32, int32, const_char_ptr, NAN,
esphome_ns, App, Nameable, Component, ComponentPtr, esphome_ns, App, Nameable, Component, ComponentPtr,
PollingComponent, Application, optional, arduino_json_ns, JsonObject, PollingComponent, Application, optional, arduino_json_ns, JsonObject,

View file

@ -1,6 +1,10 @@
#include "esphome/components/adc/adc_sensor.h" #include "esphome/components/adc/adc_sensor.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
#ifdef USE_ADC_SENSOR_VCC
ADC_MODE(ADC_VCC)
#endif
namespace esphome { namespace esphome {
namespace adc { namespace adc {

View file

@ -2,16 +2,13 @@
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/core/esphal.h" #include "esphome/core/esphal.h"
#include "esphome/core/defines.h"
#include "esphome/components/sensor/sensor.h" #include "esphome/components/sensor/sensor.h"
#include "esphome/components/voltage_sampler/voltage_sampler.h" #include "esphome/components/voltage_sampler/voltage_sampler.h"
namespace esphome { namespace esphome {
namespace adc { namespace adc {
#ifdef USE_ADC_SENSOR_VCC
ADC_MODE(ADC_VCC)
#endif
class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage_sampler::VoltageSampler { class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage_sampler::VoltageSampler {
public: public:
#ifdef ARDUINO_ARCH_ESP32 #ifdef ARDUINO_ARCH_ESP32

View file

@ -87,7 +87,7 @@ HOMEASSISTANT_SERVICE_ACTION_SCHEMA = cv.Schema({
cv.string: cv.string, cv.string: cv.string,
}), }),
cv.Optional(CONF_VARIABLES): cv.Schema({ cv.Optional(CONF_VARIABLES): cv.Schema({
cv.string: cv.lambda_, cv.string: cv.returning_lambda,
}), }),
}) })

View file

@ -7,7 +7,7 @@ from esphome.const import CONF_AWAY_CONFIG, CONF_COOL_ACTION, \
CONF_ID, CONF_IDLE_ACTION, CONF_SENSOR CONF_ID, CONF_IDLE_ACTION, CONF_SENSOR
bang_bang_ns = cg.esphome_ns.namespace('bang_bang') bang_bang_ns = cg.esphome_ns.namespace('bang_bang')
BangBangClimate = bang_bang_ns.class_('BangBangClimate', climate.ClimateDevice) BangBangClimate = bang_bang_ns.class_('BangBangClimate', climate.Climate)
BangBangClimateTargetTempConfig = bang_bang_ns.struct('BangBangClimateTargetTempConfig') BangBangClimateTargetTempConfig = bang_bang_ns.struct('BangBangClimateTargetTempConfig')
CONFIG_SCHEMA = cv.All(climate.CLIMATE_SCHEMA.extend({ CONFIG_SCHEMA = cv.All(climate.CLIMATE_SCHEMA.extend({

View file

@ -70,7 +70,7 @@ def delayed_off_filter_to_code(config, filter_id):
yield var yield var
@FILTER_REGISTRY.register('lambda', LambdaFilter, cv.lambda_) @FILTER_REGISTRY.register('lambda', LambdaFilter, cv.returning_lambda)
def lambda_filter_to_code(config, filter_id): def lambda_filter_to_code(config, filter_id):
lambda_ = yield cg.process_lambda(config, [(bool, 'x')], return_type=cg.optional.template(bool)) lambda_ = yield cg.process_lambda(config, [(bool, 'x')], return_type=cg.optional.template(bool))
yield cg.new_Pvariable(filter_id, lambda_) yield cg.new_Pvariable(filter_id, lambda_)

View file

@ -5,7 +5,7 @@ namespace esphome {
namespace binary_sensor { namespace binary_sensor {
static const char *TAG = "something.Filter"; static const char *TAG = "sensor.filter";
void Filter::output(bool value, bool is_initial) { void Filter::output(bool value, bool is_initial) {
if (!this->dedup_.next(value)) if (!this->dedup_.next(value))

View file

@ -6,7 +6,7 @@
namespace esphome { namespace esphome {
namespace ble_presence { namespace ble_presence {
static const char *TAG = "something.something"; static const char *TAG = "ble_presence";
void BLEPresenceDevice::dump_config() { LOG_BINARY_SENSOR("", "BLE Presence", this); } void BLEPresenceDevice::dump_config() { LOG_BINARY_SENSOR("", "BLE Presence", this); }

View file

@ -232,7 +232,7 @@ float BME680Component::get_setup_priority() const { return setup_priority::DATA;
void BME680Component::update() { void BME680Component::update() {
uint8_t meas_control = 0; // No need to fetch, we're setting all fields uint8_t meas_control = 0; // No need to fetch, we're setting all fields
meas_control |= (this->temperature_oversampling_ & 0b111) << 5; meas_control |= (this->temperature_oversampling_ & 0b111) << 5;
meas_control |= (this->pressure_oversampling_ & 0b111) << 5; meas_control |= (this->pressure_oversampling_ & 0b111) << 2;
meas_control |= 0b01; // forced mode meas_control |= 0b01; // forced mode
if (!this->write_byte(BME680_REGISTER_CONTROL_MEAS, meas_control)) { if (!this->write_byte(BME680_REGISTER_CONTROL_MEAS, meas_control)) {
this->status_set_warning(); this->status_set_warning();

View file

View file

@ -0,0 +1,147 @@
#include "ccs811.h"
#include "esphome/core/log.h"
namespace esphome {
namespace ccs811 {
static const char *TAG = "ccs811";
// based on
// - https://cdn.sparkfun.com/datasheets/BreakoutBoards/CCS811_Programming_Guide.pdf
#define CHECK_TRUE(f, error_code) \
if (!(f)) { \
this->mark_failed(); \
this->error_code_ = (error_code); \
return; \
}
#define CHECKED_IO(f) CHECK_TRUE(f, COMMUNICAITON_FAILED)
void CCS811Component::setup() {
// page 9 programming guide - hwid is always 0x81
uint8_t hw_id;
CHECKED_IO(this->read_byte(0x20, &hw_id))
CHECK_TRUE(hw_id == 0x81, INVALID_ID)
// software reset, page 3 - allowed to fail
this->write_bytes(0xFF, {0x11, 0xE5, 0x72, 0x8A});
delay(5);
// page 10, APP_START
CHECK_TRUE(!this->status_has_error_(), SENSOR_REPORTED_ERROR)
CHECK_TRUE(this->status_app_is_valid_(), APP_INVALID)
CHECK_TRUE(this->write_bytes(0xF4, {}), APP_START_FAILED)
// App setup, wait for it to load
delay(1);
// set MEAS_MODE (page 5)
uint8_t meas_mode = 0;
uint32_t interval = this->get_update_interval();
if (interval <= 1000)
meas_mode = 1 << 4;
else if (interval <= 10000)
meas_mode = 2 << 4;
else
meas_mode = 3 << 4;
CHECKED_IO(this->write_byte(0x01, meas_mode))
if (this->baseline_.has_value()) {
// baseline available, write to sensor
this->write_bytes(0x11, decode_uint16(*this->baseline_));
}
}
void CCS811Component::update() {
if (!this->status_has_data_())
this->status_set_warning();
// page 12 - alg result data
auto alg_data = this->read_bytes<4>(0x02);
if (!alg_data.has_value()) {
ESP_LOGW(TAG, "Reading CCS811 data failed!");
this->status_set_warning();
return;
}
auto res = *alg_data;
uint16_t co2 = encode_uint16(res[0], res[1]);
uint16_t tvoc = encode_uint16(res[2], res[3]);
// also print baseline
auto baseline_data = this->read_bytes<2>(0x11);
uint16_t baseline = 0;
if (baseline_data.has_value()) {
baseline = encode_uint16((*baseline_data)[0], (*baseline_data)[1]);
}
ESP_LOGD(TAG, "Got co2=%u ppm, tvoc=%u ppb, baseline=0x%04X", co2, tvoc, baseline);
if (this->co2_ != nullptr)
this->co2_->publish_state(co2);
if (this->tvoc_ != nullptr)
this->tvoc_->publish_state(tvoc);
this->status_clear_warning();
this->send_env_data_();
}
void CCS811Component::send_env_data_() {
if (this->humidity_ == nullptr && this->temperature_ == nullptr)
return;
float humidity = NAN;
if (this->humidity_ != nullptr)
humidity = this->humidity_->state;
if (isnan(humidity) || humidity < 0 || humidity > 100)
humidity = 50;
float temperature = NAN;
if (this->temperature_ != nullptr)
temperature = this->temperature_->state;
if (isnan(temperature) || temperature < -25 || temperature > 50)
temperature = 25;
// temperature has a 25° offset to allow negative temperatures
temperature += 25;
// only 0.5 fractions are supported (application note)
auto hum_value = static_cast<uint8_t>(roundf(humidity * 2));
auto temp_value = static_cast<uint8_t>(roundf(temperature * 2));
this->write_bytes(0x05, {hum_value, 0x00, temp_value, 0x00});
}
void CCS811Component::dump_config() {
ESP_LOGCONFIG(TAG, "CCS811");
LOG_I2C_DEVICE(this)
LOG_UPDATE_INTERVAL(this)
LOG_SENSOR(" ", "CO2 Sensor", this->co2_)
LOG_SENSOR(" ", "TVOC Sensor", this->tvoc_)
if (this->baseline_) {
ESP_LOGCONFIG(TAG, " Baseline: %04X", *this->baseline_);
} else {
ESP_LOGCONFIG(TAG, " Baseline: NOT SET");
}
if (this->is_failed()) {
switch (this->error_code_) {
case COMMUNICAITON_FAILED:
ESP_LOGW(TAG, "Communication failed! Is the sensor connected?");
break;
case INVALID_ID:
ESP_LOGW(TAG, "Sensor reported an invalid ID. Is this a CCS811?");
break;
case SENSOR_REPORTED_ERROR:
ESP_LOGW(TAG, "Sensor reported internal error");
break;
case APP_INVALID:
ESP_LOGW(TAG, "Sensor reported invalid APP installed.");
break;
case APP_START_FAILED:
ESP_LOGW(TAG, "Sensor reported APP start failed.");
break;
case UNKNOWN:
default:
ESP_LOGW(TAG, "Unknown setup error!");
break;
}
}
}
} // namespace ccs811
} // namespace esphome

View file

@ -0,0 +1,54 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/preferences.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/i2c/i2c.h"
namespace esphome {
namespace ccs811 {
class CCS811Component : public PollingComponent, public i2c::I2CDevice {
public:
void set_co2(sensor::Sensor *co2) { co2_ = co2; }
void set_tvoc(sensor::Sensor *tvoc) { tvoc_ = tvoc; }
void set_baseline(uint16_t baseline) { baseline_ = baseline; }
void set_humidity(sensor::Sensor *humidity) { humidity_ = humidity; }
void set_temperature(sensor::Sensor *temperature) { temperature_ = temperature; }
/// Setup the sensor and test for a connection.
void setup() override;
/// Schedule temperature+pressure readings.
void update() override;
void dump_config() override;
float get_setup_priority() const override { return setup_priority::DATA; }
protected:
optional<uint8_t> read_status_() { return this->read_byte(0x00); }
bool status_has_error_() { return this->read_status_().value_or(1) & 1; }
bool status_app_is_valid_() { return this->read_status_().value_or(0) & (1 << 4); }
bool status_has_data_() { return this->read_status_().value_or(0) & (1 << 3); }
void send_env_data_();
enum ErrorCode {
UNKNOWN,
COMMUNICAITON_FAILED,
INVALID_ID,
SENSOR_REPORTED_ERROR,
APP_INVALID,
APP_START_FAILED,
} error_code_{UNKNOWN};
sensor::Sensor *co2_{nullptr};
sensor::Sensor *tvoc_{nullptr};
optional<uint16_t> baseline_{};
/// Input sensor for humidity reading.
sensor::Sensor *humidity_{nullptr};
/// Input sensor for temperature reading.
sensor::Sensor *temperature_{nullptr};
};
} // namespace ccs811
} // namespace esphome

View file

@ -0,0 +1,46 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import i2c, sensor
from esphome.const import CONF_ID, ICON_RADIATOR, UNIT_PARTS_PER_MILLION, \
UNIT_PARTS_PER_BILLION, CONF_TEMPERATURE, CONF_HUMIDITY, ICON_PERIODIC_TABLE_CO2
DEPENDENCIES = ['i2c']
ccs811_ns = cg.esphome_ns.namespace('ccs811')
CCS811Component = ccs811_ns.class_('CCS811Component', cg.PollingComponent, i2c.I2CDevice)
CONF_ECO2 = 'eco2'
CONF_TVOC = 'tvoc'
CONF_BASELINE = 'baseline'
CONFIG_SCHEMA = cv.Schema({
cv.GenerateID(): cv.declare_id(CCS811Component),
cv.Required(CONF_ECO2): sensor.sensor_schema(UNIT_PARTS_PER_MILLION, ICON_PERIODIC_TABLE_CO2,
0),
cv.Required(CONF_TVOC): sensor.sensor_schema(UNIT_PARTS_PER_BILLION, ICON_RADIATOR, 0),
cv.Optional(CONF_BASELINE): cv.hex_uint16_t,
cv.Optional(CONF_TEMPERATURE): cv.use_id(sensor.Sensor),
cv.Optional(CONF_HUMIDITY): cv.use_id(sensor.Sensor),
}).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x5A))
def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
yield cg.register_component(var, config)
yield i2c.register_i2c_device(var, config)
sens = yield sensor.new_sensor(config[CONF_ECO2])
cg.add(var.set_co2(sens))
sens = yield sensor.new_sensor(config[CONF_TVOC])
cg.add(var.set_tvoc(sens))
if CONF_BASELINE in config:
cg.add(var.set_baseline(config[CONF_BASELINE]))
if CONF_TEMPERATURE in config:
sens = yield cg.get_variable(config[CONF_TEMPERATURE])
cg.add(var.set_temperature(sens))
if CONF_HUMIDITY in config:
sens = yield cg.get_variable(config[CONF_HUMIDITY])
cg.add(var.set_humidity(sens))

View file

@ -12,7 +12,7 @@ IS_PLATFORM_COMPONENT = True
climate_ns = cg.esphome_ns.namespace('climate') climate_ns = cg.esphome_ns.namespace('climate')
ClimateDevice = climate_ns.class_('Climate', cg.Nameable) Climate = climate_ns.class_('Climate', cg.Nameable)
ClimateCall = climate_ns.class_('ClimateCall') ClimateCall = climate_ns.class_('ClimateCall')
ClimateTraits = climate_ns.class_('ClimateTraits') ClimateTraits = climate_ns.class_('ClimateTraits')
@ -30,7 +30,7 @@ validate_climate_mode = cv.enum(CLIMATE_MODES, upper=True)
ControlAction = climate_ns.class_('ControlAction', automation.Action) ControlAction = climate_ns.class_('ControlAction', automation.Action)
CLIMATE_SCHEMA = cv.MQTT_COMMAND_COMPONENT_SCHEMA.extend({ CLIMATE_SCHEMA = cv.MQTT_COMMAND_COMPONENT_SCHEMA.extend({
cv.GenerateID(): cv.declare_id(ClimateDevice), cv.GenerateID(): cv.declare_id(Climate),
cv.OnlyWith(CONF_MQTT_ID, 'mqtt'): cv.declare_id(mqtt.MQTTClimateComponent), cv.OnlyWith(CONF_MQTT_ID, 'mqtt'): cv.declare_id(mqtt.MQTTClimateComponent),
cv.Optional(CONF_VISUAL, default={}): cv.Schema({ cv.Optional(CONF_VISUAL, default={}): cv.Schema({
cv.Optional(CONF_MIN_TEMPERATURE): cv.temperature, cv.Optional(CONF_MIN_TEMPERATURE): cv.temperature,
@ -68,7 +68,7 @@ def register_climate(var, config):
CLIMATE_CONTROL_ACTION_SCHEMA = cv.Schema({ CLIMATE_CONTROL_ACTION_SCHEMA = cv.Schema({
cv.Required(CONF_ID): cv.use_id(ClimateDevice), cv.Required(CONF_ID): cv.use_id(Climate),
cv.Optional(CONF_MODE): cv.templatable(validate_climate_mode), cv.Optional(CONF_MODE): cv.templatable(validate_climate_mode),
cv.Optional(CONF_TARGET_TEMPERATURE): cv.templatable(cv.temperature), cv.Optional(CONF_TARGET_TEMPERATURE): cv.templatable(cv.temperature),
cv.Optional(CONF_TARGET_TEMPERATURE_LOW): cv.templatable(cv.temperature), cv.Optional(CONF_TARGET_TEMPERATURE_LOW): cv.templatable(cv.temperature),

View file

@ -207,11 +207,6 @@ ClimateTraits Climate::get_traits() {
return traits; return traits;
} }
#ifdef USE_MQTT_CLIMATE
MQTTClimateComponent *Climate::get_mqtt() const { return this->mqtt_; }
void Climate::set_mqtt(MQTTClimateComponent *mqtt) { this->mqtt_ = mqtt; }
#endif
void Climate::set_visual_min_temperature_override(float visual_min_temperature_override) { void Climate::set_visual_min_temperature_override(float visual_min_temperature_override) {
this->visual_min_temperature_override_ = visual_min_temperature_override; this->visual_min_temperature_override_ = visual_min_temperature_override;
} }

View file

@ -169,11 +169,6 @@ class Climate : public Nameable {
*/ */
ClimateTraits get_traits(); ClimateTraits get_traits();
#ifdef USE_MQTT_COVER
MQTTClimateComponent *get_mqtt() const;
void set_mqtt(MQTTClimateComponent *mqtt);
#endif
void set_visual_min_temperature_override(float visual_min_temperature_override); void set_visual_min_temperature_override(float visual_min_temperature_override);
void set_visual_max_temperature_override(float visual_max_temperature_override); void set_visual_max_temperature_override(float visual_max_temperature_override);
void set_visual_temperature_step_override(float visual_temperature_step_override); void set_visual_temperature_step_override(float visual_temperature_step_override);

View file

@ -6,13 +6,13 @@ namespace climate {
const char *climate_mode_to_string(ClimateMode mode) { const char *climate_mode_to_string(ClimateMode mode) {
switch (mode) { switch (mode) {
case CLIMATE_MODE_OFF: case CLIMATE_MODE_OFF:
return "OFF"; return "off";
case CLIMATE_MODE_AUTO: case CLIMATE_MODE_AUTO:
return "AUTO"; return "auto";
case CLIMATE_MODE_COOL: case CLIMATE_MODE_COOL:
return "COOL"; return "cool";
case CLIMATE_MODE_HEAT: case CLIMATE_MODE_HEAT:
return "HEAT"; return "heat";
default: default:
return "UNKNOWN"; return "UNKNOWN";
} }

View file

@ -8,7 +8,7 @@ CustomBinarySensorConstructor = custom_ns.class_('CustomBinarySensorConstructor'
CONFIG_SCHEMA = cv.Schema({ CONFIG_SCHEMA = cv.Schema({
cv.GenerateID(): cv.declare_id(CustomBinarySensorConstructor), cv.GenerateID(): cv.declare_id(CustomBinarySensorConstructor),
cv.Required(CONF_LAMBDA): cv.lambda_, cv.Required(CONF_LAMBDA): cv.returning_lambda,
cv.Required(CONF_BINARY_SENSORS): cv.ensure_list(binary_sensor.BINARY_SENSOR_SCHEMA), cv.Required(CONF_BINARY_SENSORS): cv.ensure_list(binary_sensor.BINARY_SENSOR_SCHEMA),
}) })

View file

@ -0,0 +1,26 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import climate
from esphome.const import CONF_ID, CONF_LAMBDA
from .. import custom_ns
CustomClimateConstructor = custom_ns.class_('CustomClimateConstructor')
CONF_CLIMATES = 'climates'
CONFIG_SCHEMA = cv.Schema({
cv.GenerateID(): cv.declare_id(CustomClimateConstructor),
cv.Required(CONF_LAMBDA): cv.returning_lambda,
cv.Required(CONF_CLIMATES): cv.ensure_list(climate.CLIMATE_SCHEMA),
})
def to_code(config):
template_ = yield cg.process_lambda(
config[CONF_LAMBDA], [],
return_type=cg.std_vector.template(climate.Climate.operator('ptr')))
rhs = CustomClimateConstructor(template_)
custom = cg.variable(config[CONF_ID], rhs)
for i, conf in enumerate(config[CONF_CLIMATES]):
rhs = custom.Pget_climate(i)
yield climate.register_climate(rhs, conf)

View file

@ -0,0 +1,20 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/climate/climate.h"
namespace esphome {
namespace custom {
class CustomClimateConstructor {
public:
CustomClimateConstructor(const std::function<std::vector<climate::Climate *>()> &init) { this->climates_ = init(); }
climate::Climate *get_climate(int i) { return this->climates_[i]; }
protected:
std::vector<climate::Climate *> climates_;
};
} // namespace custom
} // namespace esphome

View file

@ -0,0 +1,26 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import cover
from esphome.const import CONF_ID, CONF_LAMBDA
from .. import custom_ns
CustomCoverConstructor = custom_ns.class_('CustomCoverConstructor')
CONF_COVERS = 'covers'
CONFIG_SCHEMA = cv.Schema({
cv.GenerateID(): cv.declare_id(CustomCoverConstructor),
cv.Required(CONF_LAMBDA): cv.returning_lambda,
cv.Required(CONF_COVERS): cv.ensure_list(cover.COVER_SCHEMA),
})
def to_code(config):
template_ = yield cg.process_lambda(
config[CONF_LAMBDA], [],
return_type=cg.std_vector.template(cover.Cover.operator('ptr')))
rhs = CustomCoverConstructor(template_)
custom = cg.variable(config[CONF_ID], rhs)
for i, conf in enumerate(config[CONF_COVERS]):
rhs = custom.Pget_cover(i)
yield cover.register_cover(rhs, conf)

View file

@ -0,0 +1,19 @@
#pragma once
#include "esphome/components/cover/cover.h"
namespace esphome {
namespace custom {
class CustomCoverConstructor {
public:
CustomCoverConstructor(const std::function<std::vector<cover::Cover *>()> &init) { this->covers_ = init(); }
cover::Cover *get_cover(int i) { return this->covers_[i]; }
protected:
std::vector<cover::Cover *> covers_;
};
} // namespace custom
} // namespace esphome

View file

@ -0,0 +1,26 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import light
from esphome.const import CONF_ID, CONF_LAMBDA
from .. import custom_ns
CustomLightOutputConstructor = custom_ns.class_('CustomLightOutputConstructor')
CONF_LIGHTS = 'lights'
CONFIG_SCHEMA = cv.Schema({
cv.GenerateID(): cv.declare_id(CustomLightOutputConstructor),
cv.Required(CONF_LAMBDA): cv.returning_lambda,
cv.Required(CONF_LIGHTS): cv.ensure_list(light.RGB_LIGHT_SCHEMA),
})
def to_code(config):
template_ = yield cg.process_lambda(
config[CONF_LAMBDA], [],
return_type=cg.std_vector.template(light.LightOutput.operator('ptr')))
rhs = CustomLightOutputConstructor(template_)
custom = cg.variable(config[CONF_ID], rhs)
for i, conf in enumerate(config[CONF_LIGHTS]):
rhs = custom.Pget_light(i)
yield light.register_light(rhs, conf)

View file

@ -0,0 +1,22 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/light/light_output.h"
namespace esphome {
namespace custom {
class CustomLightOutputConstructor {
public:
CustomLightOutputConstructor(const std::function<std::vector<light::LightOutput *>()> &init) {
this->outputs_ = init();
}
light::LightOutput *get_light(int i) { return this->outputs_[i]; }
protected:
std::vector<light::LightOutput *> outputs_;
};
} // namespace custom
} // namespace esphome

View file

@ -7,42 +7,27 @@ from .. import custom_ns
CustomBinaryOutputConstructor = custom_ns.class_('CustomBinaryOutputConstructor') CustomBinaryOutputConstructor = custom_ns.class_('CustomBinaryOutputConstructor')
CustomFloatOutputConstructor = custom_ns.class_('CustomFloatOutputConstructor') CustomFloatOutputConstructor = custom_ns.class_('CustomFloatOutputConstructor')
BINARY_SCHEMA = cv.Schema({ CONF_BINARY = 'binary'
cv.GenerateID(): cv.declare_id(CustomBinaryOutputConstructor), CONF_FLOAT = 'float'
cv.Required(CONF_LAMBDA): cv.lambda_,
cv.Required(CONF_TYPE): 'binary',
cv.Required(CONF_OUTPUTS):
cv.ensure_list(output.BINARY_OUTPUT_SCHEMA.extend({
cv.GenerateID(): cv.declare_id(output.BinaryOutput),
})),
})
FLOAT_SCHEMA = cv.Schema({ CONFIG_SCHEMA = cv.typed_schema({
cv.GenerateID(): cv.declare_id(CustomFloatOutputConstructor), CONF_BINARY: cv.Schema({
cv.Required(CONF_LAMBDA): cv.lambda_, cv.GenerateID(): cv.declare_id(CustomBinaryOutputConstructor),
cv.Required(CONF_TYPE): 'float', cv.Required(CONF_LAMBDA): cv.returning_lambda,
cv.Required(CONF_OUTPUTS): cv.Required(CONF_OUTPUTS):
cv.ensure_list(output.FLOAT_OUTPUT_SCHEMA.extend({ cv.ensure_list(output.BINARY_OUTPUT_SCHEMA.extend({
cv.GenerateID(): cv.declare_id(output.FloatOutput), cv.GenerateID(): cv.declare_id(output.BinaryOutput),
})), })),
}) }),
CONF_FLOAT: cv.Schema({
cv.GenerateID(): cv.declare_id(CustomFloatOutputConstructor),
def validate_custom_output(value): cv.Required(CONF_LAMBDA): cv.returning_lambda,
if not isinstance(value, dict): cv.Required(CONF_OUTPUTS):
raise cv.Invalid("Value must be dict") cv.ensure_list(output.FLOAT_OUTPUT_SCHEMA.extend({
if CONF_TYPE not in value: cv.GenerateID(): cv.declare_id(output.FloatOutput),
raise cv.Invalid("type not specified!") })),
type = cv.string_strict(value[CONF_TYPE]).lower() })
value[CONF_TYPE] = type }, lower=True)
if type == 'binary':
return BINARY_SCHEMA(value)
if type == 'float':
return FLOAT_SCHEMA(value)
raise cv.Invalid("type must either be binary or float, not {}!".format(type))
CONFIG_SCHEMA = validate_custom_output
def to_code(config): def to_code(config):

View file

@ -8,7 +8,7 @@ CustomSensorConstructor = custom_ns.class_('CustomSensorConstructor')
CONFIG_SCHEMA = cv.Schema({ CONFIG_SCHEMA = cv.Schema({
cv.GenerateID(): cv.declare_id(CustomSensorConstructor), cv.GenerateID(): cv.declare_id(CustomSensorConstructor),
cv.Required(CONF_LAMBDA): cv.lambda_, cv.Required(CONF_LAMBDA): cv.returning_lambda,
cv.Required(CONF_SENSORS): cv.ensure_list(sensor.SENSOR_SCHEMA), cv.Required(CONF_SENSORS): cv.ensure_list(sensor.SENSOR_SCHEMA),
}) })

View file

@ -8,7 +8,7 @@ CustomSwitchConstructor = custom_ns.class_('CustomSwitchConstructor')
CONFIG_SCHEMA = cv.Schema({ CONFIG_SCHEMA = cv.Schema({
cv.GenerateID(): cv.declare_id(CustomSwitchConstructor), cv.GenerateID(): cv.declare_id(CustomSwitchConstructor),
cv.Required(CONF_LAMBDA): cv.lambda_, cv.Required(CONF_LAMBDA): cv.returning_lambda,
cv.Required(CONF_SWITCHES): cv.Required(CONF_SWITCHES):
cv.ensure_list(switch.SWITCH_SCHEMA.extend({ cv.ensure_list(switch.SWITCH_SCHEMA.extend({
cv.GenerateID(): cv.declare_id(switch.Switch), cv.GenerateID(): cv.declare_id(switch.Switch),

View file

@ -8,7 +8,7 @@ CustomTextSensorConstructor = custom_ns.class_('CustomTextSensorConstructor')
CONFIG_SCHEMA = cv.Schema({ CONFIG_SCHEMA = cv.Schema({
cv.GenerateID(): cv.declare_id(CustomTextSensorConstructor), cv.GenerateID(): cv.declare_id(CustomTextSensorConstructor),
cv.Required(CONF_LAMBDA): cv.lambda_, cv.Required(CONF_LAMBDA): cv.returning_lambda,
cv.Required(CONF_TEXT_SENSORS): cv.Required(CONF_TEXT_SENSORS):
cv.ensure_list(text_sensor.TEXT_SENSOR_SCHEMA.extend({ cv.ensure_list(text_sensor.TEXT_SENSOR_SCHEMA.extend({
cv.GenerateID(): cv.declare_id(text_sensor.TextSensor), cv.GenerateID(): cv.declare_id(text_sensor.TextSensor),

View file

@ -8,7 +8,7 @@ CustomComponentConstructor = custom_component_ns.class_('CustomComponentConstruc
MULTI_CONF = True MULTI_CONF = True
CONFIG_SCHEMA = cv.Schema({ CONFIG_SCHEMA = cv.Schema({
cv.GenerateID(): cv.declare_id(CustomComponentConstructor), cv.GenerateID(): cv.declare_id(CustomComponentConstructor),
cv.Required(CONF_LAMBDA): cv.lambda_, cv.Required(CONF_LAMBDA): cv.returning_lambda,
cv.Optional(CONF_COMPONENTS): cv.ensure_list(cv.Schema({ cv.Optional(CONF_COMPONENTS): cv.ensure_list(cv.Schema({
cv.GenerateID(): cv.declare_id(cg.Component) cv.GenerateID(): cv.declare_id(cg.Component)
}).extend(cv.COMPONENT_SCHEMA)), }).extend(cv.COMPONENT_SCHEMA)),

View file

@ -13,7 +13,7 @@ CONFIG_SCHEMA = cv.All(sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 1).e
cv.Optional(CONF_ADDRESS): cv.hex_int, cv.Optional(CONF_ADDRESS): cv.hex_int,
cv.Optional(CONF_INDEX): cv.positive_int, cv.Optional(CONF_INDEX): cv.positive_int,
cv.Optional(CONF_RESOLUTION, default=12): cv.All(cv.int_, cv.Range(min=9, max=12)), cv.Optional(CONF_RESOLUTION, default=12): cv.int_range(min=9, max=12),
}), cv.has_exactly_one_key(CONF_ADDRESS, CONF_INDEX)) }), cv.has_exactly_one_key(CONF_ADDRESS, CONF_INDEX))

View file

@ -55,7 +55,7 @@ CONF_BRIGHTNESS = 'brightness'
CONF_SATURATION = 'saturation' CONF_SATURATION = 'saturation'
CONF_TEST_PATTERN = 'test_pattern' CONF_TEST_PATTERN = 'test_pattern'
camera_range_param = cv.All(cv.int_, cv.Range(min=-2, max=2)) camera_range_param = cv.int_range(min=-2, max=2)
CONFIG_SCHEMA = cv.Schema({ CONFIG_SCHEMA = cv.Schema({
cv.GenerateID(): cv.declare_id(ESP32Camera), cv.GenerateID(): cv.declare_id(ESP32Camera),
@ -81,7 +81,7 @@ CONFIG_SCHEMA = cv.Schema({
cv.Optional(CONF_IDLE_FRAMERATE, default='0.1 fps'): cv.All(cv.framerate, cv.Optional(CONF_IDLE_FRAMERATE, default='0.1 fps'): cv.All(cv.framerate,
cv.Range(min=0, max=1)), cv.Range(min=0, max=1)),
cv.Optional(CONF_RESOLUTION, default='640X480'): cv.enum(FRAME_SIZES, upper=True), cv.Optional(CONF_RESOLUTION, default='640X480'): cv.enum(FRAME_SIZES, upper=True),
cv.Optional(CONF_JPEG_QUALITY, default=10): cv.All(cv.int_, cv.Range(min=10, max=63)), cv.Optional(CONF_JPEG_QUALITY, default=10): cv.int_range(min=10, max=63),
cv.Optional(CONF_CONTRAST, default=0): camera_range_param, cv.Optional(CONF_CONTRAST, default=0): camera_range_param,
cv.Optional(CONF_BRIGHTNESS, default=0): camera_range_param, cv.Optional(CONF_BRIGHTNESS, default=0): camera_range_param,
cv.Optional(CONF_SATURATION, default=0): camera_range_param, cv.Optional(CONF_SATURATION, default=0): camera_range_param,

View file

@ -11,7 +11,7 @@
namespace esphome { namespace esphome {
namespace esp8266_pwm { namespace esp8266_pwm {
static const char *TAG = "something.something"; static const char *TAG = "esp8266_pwm";
void ESP8266PWM::setup() { void ESP8266PWM::setup() {
ESP_LOGCONFIG(TAG, "Setting up ESP8266 PWM Output..."); ESP_LOGCONFIG(TAG, "Setting up ESP8266 PWM Output...");

View file

@ -1,8 +1,7 @@
import esphome.codegen as cg import esphome.codegen as cg
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.components import light, power_supply from esphome.components import light
from esphome.const import CONF_OUTPUT_ID, CONF_NUM_LEDS, CONF_RGB_ORDER, CONF_MAX_REFRESH_RATE, \ from esphome.const import CONF_OUTPUT_ID, CONF_NUM_LEDS, CONF_RGB_ORDER, CONF_MAX_REFRESH_RATE
CONF_POWER_SUPPLY
from esphome.core import coroutine from esphome.core import coroutine
fastled_base_ns = cg.esphome_ns.namespace('fastled_base') fastled_base_ns = cg.esphome_ns.namespace('fastled_base')
@ -24,8 +23,6 @@ BASE_SCHEMA = light.ADDRESSABLE_LIGHT_SCHEMA.extend({
cv.Required(CONF_NUM_LEDS): cv.positive_not_null_int, cv.Required(CONF_NUM_LEDS): cv.positive_not_null_int,
cv.Optional(CONF_RGB_ORDER): cv.one_of(*RGB_ORDERS, upper=True), cv.Optional(CONF_RGB_ORDER): cv.one_of(*RGB_ORDERS, upper=True),
cv.Optional(CONF_MAX_REFRESH_RATE): cv.positive_time_period_microseconds, cv.Optional(CONF_MAX_REFRESH_RATE): cv.positive_time_period_microseconds,
cv.Optional(CONF_POWER_SUPPLY): cv.use_id(power_supply.PowerSupply),
}).extend(cv.COMPONENT_SCHEMA) }).extend(cv.COMPONENT_SCHEMA)
@ -37,10 +34,6 @@ def new_fastled_light(config):
if CONF_MAX_REFRESH_RATE in config: if CONF_MAX_REFRESH_RATE in config:
cg.add(var.set_max_refresh_rate(config[CONF_MAX_REFRESH_RATE])) cg.add(var.set_max_refresh_rate(config[CONF_MAX_REFRESH_RATE]))
if CONF_POWER_SUPPLY in config:
var_ = yield cg.get_variable(config[CONF_POWER_SUPPLY])
cg.add(var.set_power_supply(var_))
yield light.register_light(var, config) yield light.register_light(var, config)
cg.add_library('FastLED', '3.2.0') cg.add_library('FastLED', '3.2.0')
yield var yield var

View file

@ -33,27 +33,6 @@ void FastLEDLightOutput::loop() {
this->mark_shown_(); this->mark_shown_();
ESP_LOGVV(TAG, "Writing RGB values to bus..."); ESP_LOGVV(TAG, "Writing RGB values to bus...");
#ifdef USE_POWER_SUPPLY
if (this->power_supply_ != nullptr) {
bool is_on = false;
for (int i = 0; i < this->num_leds_; i++) {
if (bool(this->leds_[i])) {
is_on = true;
break;
}
}
if (is_on && !this->has_requested_high_power_) {
this->power_supply_->request_high_power();
this->has_requested_high_power_ = true;
}
if (!is_on && this->has_requested_high_power_) {
this->power_supply_->unrequest_high_power();
this->has_requested_high_power_ = false;
}
}
#endif
this->controller_->showLeds(); this->controller_->showLeds();
} }

View file

@ -4,10 +4,6 @@
#include "esphome/core/helpers.h" #include "esphome/core/helpers.h"
#include "esphome/components/light/addressable_light.h" #include "esphome/components/light/addressable_light.h"
#ifdef USE_POWER_SUPPLY
#include "esphome/components/power_supply/power_supply.h"
#endif
#define FASTLED_ESP8266_RAW_PIN_ORDER #define FASTLED_ESP8266_RAW_PIN_ORDER
#define FASTLED_ESP32_RAW_PIN_ORDER #define FASTLED_ESP32_RAW_PIN_ORDER
#define FASTLED_RMT_BUILTIN_DRIVER true #define FASTLED_RMT_BUILTIN_DRIVER true
@ -30,10 +26,6 @@ class FastLEDLightOutput : public Component, public light::AddressableLight {
/// Set a maximum refresh rate in µs as some lights do not like being updated too often. /// Set a maximum refresh rate in µs as some lights do not like being updated too often.
void set_max_refresh_rate(uint32_t interval_us) { this->max_refresh_rate_ = interval_us; } void set_max_refresh_rate(uint32_t interval_us) { this->max_refresh_rate_ = interval_us; }
#ifdef USE_POWER_SUPPLY
void set_power_supply(power_supply::PowerSupply *power_supply) { this->power_supply_ = power_supply; }
#endif
/// Add some LEDS, can only be called once. /// Add some LEDS, can only be called once.
CLEDController &add_leds(CLEDController *controller, int num_leds) { CLEDController &add_leds(CLEDController *controller, int num_leds) {
this->controller_ = controller; this->controller_ = controller;
@ -242,10 +234,6 @@ class FastLEDLightOutput : public Component, public light::AddressableLight {
int num_leds_{0}; int num_leds_{0};
uint32_t last_refresh_{0}; uint32_t last_refresh_{0};
optional<uint32_t> max_refresh_rate_{}; optional<uint32_t> max_refresh_rate_{};
#ifdef USE_POWER_SUPPLY
power_supply::PowerSupply *power_supply_{nullptr};
bool has_requested_high_power_{false};
#endif
}; };
} // namespace fastled_base } // namespace fastled_base

View file

@ -70,7 +70,7 @@ FONT_SCHEMA = cv.Schema({
cv.Required(CONF_ID): cv.declare_id(Font), cv.Required(CONF_ID): cv.declare_id(Font),
cv.Required(CONF_FILE): validate_truetype_file, cv.Required(CONF_FILE): validate_truetype_file,
cv.Optional(CONF_GLYPHS, default=DEFAULT_GLYPHS): validate_glyphs, cv.Optional(CONF_GLYPHS, default=DEFAULT_GLYPHS): validate_glyphs,
cv.Optional(CONF_SIZE, default=20): cv.All(cv.int_, cv.Range(min=1)), cv.Optional(CONF_SIZE, default=20): cv.int_range(min=1),
cv.GenerateID(CONF_RAW_DATA_ID): cv.declare_id(cg.uint8), cv.GenerateID(CONF_RAW_DATA_ID): cv.declare_id(cg.uint8),
}) })

View file

@ -0,0 +1,23 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import uart
from esphome.const import CONF_ID
DEPENDENCIES = ['uart']
gps_ns = cg.esphome_ns.namespace('gps')
GPS = gps_ns.class_('GPS', cg.Component, uart.UARTDevice)
GPSListener = gps_ns.class_('GPSListener')
CONF_GPS_ID = 'gps_id'
MULTI_CONF = True
CONFIG_SCHEMA = cv.Schema({
cv.GenerateID(): cv.declare_id(GPS),
}).extend(cv.COMPONENT_SCHEMA).extend(uart.UART_DEVICE_SCHEMA)
def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
yield cg.register_component(var, config)
yield uart.register_uart_device(var, config)
cg.add_library('TinyGPSPlus', '1.0.2')

View file

@ -0,0 +1,12 @@
#include "gps.h"
#include "esphome/core/log.h"
namespace esphome {
namespace gps {
static const char *TAG = "gps";
TinyGPSPlus &GPSListener::get_tiny_gps() { return this->parent_->get_tiny_gps(); }
} // namespace gps
} // namespace esphome

View file

@ -0,0 +1,47 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/uart/uart.h"
#include <TinyGPS++.h>
namespace esphome {
namespace gps {
class GPS;
class GPSListener {
public:
virtual void on_update(TinyGPSPlus &tiny_gps) = 0;
TinyGPSPlus &get_tiny_gps();
protected:
friend GPS;
GPS *parent_;
};
class GPS : public Component, public uart::UARTDevice {
public:
void register_listener(GPSListener *listener) {
listener->parent_ = this;
this->listeners_.push_back(listener);
}
float get_setup_priority() const override { return setup_priority::HARDWARE; }
void loop() override {
while (this->available() && !this->has_time_) {
if (this->tiny_gps_.encode(this->read())) {
for (auto *listener : this->listeners_)
listener->on_update(this->tiny_gps_);
}
}
}
TinyGPSPlus &get_tiny_gps() { return this->tiny_gps_; }
protected:
bool has_time_{false};
TinyGPSPlus tiny_gps_;
std::vector<GPSListener *> listeners_{};
};
} // namespace gps
} // namespace esphome

View file

@ -0,0 +1,23 @@
from esphome.components import time as time_
import esphome.config_validation as cv
import esphome.codegen as cg
from esphome.const import CONF_ID
from .. import gps_ns, GPSListener, CONF_GPS_ID, GPS
DEPENDENCIES = ['gps']
GPSTime = gps_ns.class_('GPSTime', time_.RealTimeClock, GPSListener)
CONFIG_SCHEMA = time_.TIME_SCHEMA.extend({
cv.GenerateID(): cv.declare_id(GPSTime),
cv.GenerateID(CONF_GPS_ID): cv.use_id(GPS),
}).extend(cv.COMPONENT_SCHEMA)
def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
yield time_.register_time(var, config)
yield cg.register_component(var, config)
paren = yield cg.get_variable(config[CONF_GPS_ID])
cg.add(paren.register_listener(var))

View file

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

View file

@ -0,0 +1,39 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/time/real_time_clock.h"
#include "esphome/components/gps/gps.h"
namespace esphome {
namespace gps {
class GPSTime : public time::RealTimeClock, public GPSListener {
public:
void on_update(TinyGPSPlus &tiny_gps) override {
if (!this->has_time_)
this->from_tiny_gps_(tiny_gps);
}
void setup() override {
this->set_interval(5 * 60 * 1000, [this]() { this->from_tiny_gps_(this->get_tiny_gps()); });
}
protected:
void from_tiny_gps_(TinyGPSPlus &tiny_gps) {
if (!tiny_gps.time.isValid() || !tiny_gps.date.isValid())
return;
time::ESPTime val{};
val.year = tiny_gps.date.year();
val.month = tiny_gps.date.month();
val.day_of_month = tiny_gps.date.day();
val.hour = tiny_gps.time.hour();
val.minute = tiny_gps.time.minute();
val.second = tiny_gps.time.second();
val.recalc_timestamp_utc(false);
this->synchronize_epoch_(val.timestamp);
this->has_time_ = true;
}
bool has_time_{false};
};
} // namespace gps
} // namespace esphome

View file

@ -13,8 +13,8 @@ void HLW8012Component::setup() {
ESP_LOGCONFIG(TAG, "Setting up HLW8012..."); ESP_LOGCONFIG(TAG, "Setting up HLW8012...");
this->sel_pin_->setup(); this->sel_pin_->setup();
this->sel_pin_->digital_write(this->current_mode_); this->sel_pin_->digital_write(this->current_mode_);
this->cf_store_.setup(this->cf_pin_); this->cf_store_.pulse_counter_setup(this->cf_pin_);
this->cf1_store_.setup(this->cf1_pin_); this->cf1_store_.pulse_counter_setup(this->cf1_pin_);
} }
void HLW8012Component::dump_config() { void HLW8012Component::dump_config() {
ESP_LOGCONFIG(TAG, "HLW8012:"); ESP_LOGCONFIG(TAG, "HLW8012:");
@ -32,18 +32,18 @@ void HLW8012Component::dump_config() {
float HLW8012Component::get_setup_priority() const { return setup_priority::DATA; } float HLW8012Component::get_setup_priority() const { return setup_priority::DATA; }
void HLW8012Component::update() { void HLW8012Component::update() {
// HLW8012 has 50% duty cycle // HLW8012 has 50% duty cycle
const uint32_t last_rise_cf = this->cf_store_.get_last_rise(); pulse_counter::pulse_counter_t raw_cf = this->cf_store_.read_raw_value();
const uint32_t last_rise_cf1 = this->cf1_store_.get_last_rise(); pulse_counter::pulse_counter_t raw_cf1 = this->cf1_store_.read_raw_value();
const uint32_t now = micros(); float cf_hz = raw_cf / (this->get_update_interval() / 1000.0f);
float full_cycle_cf = this->cf_store_.get_pulse_width_s() * 2; if (raw_cf <= 1) {
float full_cycle_cf1 = this->cf1_store_.get_pulse_width_s() * 2; // don't count single pulse as power
float cf_hz = 0.0f, cf1_hz = 0.0f; cf_hz = 0.0f;
auto update_interval_micros = static_cast<uint32_t>(this->update_interval_ * 1e3f); }
float cf1_hz = raw_cf1 / (this->get_update_interval() / 1000.0f);
if (full_cycle_cf != 0.0f && now - last_rise_cf < update_interval_micros * 3) if (raw_cf1 <= 1) {
cf_hz = 1.0f / full_cycle_cf; // don't count single pulse as anything
if (full_cycle_cf1 != 0.0f && now - last_rise_cf1 < update_interval_micros * 3) cf1_hz = 0.0f;
cf1_hz = 1.0f / full_cycle_cf1; }
if (this->nth_value_++ < 2) { if (this->nth_value_++ < 2) {
return; return;

View file

@ -3,7 +3,7 @@
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/core/esphal.h" #include "esphome/core/esphal.h"
#include "esphome/components/sensor/sensor.h" #include "esphome/components/sensor/sensor.h"
#include "esphome/components/pulse_width/pulse_width.h" #include "esphome/components/pulse_counter/pulse_counter_sensor.h"
namespace esphome { namespace esphome {
namespace hlw8012 { namespace hlw8012 {
@ -34,9 +34,9 @@ class HLW8012Component : public PollingComponent {
float voltage_divider_{2351}; float voltage_divider_{2351};
GPIOPin *sel_pin_; GPIOPin *sel_pin_;
GPIOPin *cf_pin_; GPIOPin *cf_pin_;
pulse_width::PulseWidthSensorStore cf_store_; pulse_counter::PulseCounterStorage cf_store_;
GPIOPin *cf1_pin_; GPIOPin *cf1_pin_;
pulse_width::PulseWidthSensorStore cf1_store_; pulse_counter::PulseCounterStorage cf1_store_;
sensor::Sensor *voltage_sensor_{nullptr}; sensor::Sensor *voltage_sensor_{nullptr};
sensor::Sensor *current_sensor_{nullptr}; sensor::Sensor *current_sensor_{nullptr};
sensor::Sensor *power_sensor_{nullptr}; sensor::Sensor *power_sensor_{nullptr};

View file

@ -6,7 +6,7 @@ from esphome.const import CONF_CHANGE_MODE_EVERY, CONF_CURRENT, \
CONF_CURRENT_RESISTOR, CONF_ID, CONF_POWER, CONF_SEL_PIN, CONF_VOLTAGE, CONF_VOLTAGE_DIVIDER, \ CONF_CURRENT_RESISTOR, CONF_ID, CONF_POWER, CONF_SEL_PIN, CONF_VOLTAGE, CONF_VOLTAGE_DIVIDER, \
ICON_FLASH, UNIT_VOLT, UNIT_AMPERE, UNIT_WATT ICON_FLASH, UNIT_VOLT, UNIT_AMPERE, UNIT_WATT
AUTO_LOAD = ['pulse_width'] AUTO_LOAD = ['pulse_counter']
hlw8012_ns = cg.esphome_ns.namespace('hlw8012') hlw8012_ns = cg.esphome_ns.namespace('hlw8012')
HLW8012Component = hlw8012_ns.class_('HLW8012Component', cg.PollingComponent) HLW8012Component = hlw8012_ns.class_('HLW8012Component', cg.PollingComponent)

View file

@ -163,6 +163,14 @@ class I2CDevice {
*/ */
bool read_bytes(uint8_t a_register, uint8_t *data, uint8_t len, uint32_t conversion = 0); // NOLINT bool read_bytes(uint8_t a_register, uint8_t *data, uint8_t len, uint32_t conversion = 0); // NOLINT
template<size_t N> optional<std::array<uint8_t, N>> read_bytes(uint8_t a_register) { // NOLINT
std::array<uint8_t, N> res;
if (!this->read_bytes(a_register, res.data(), N)) {
return {};
}
return res;
}
/** Read len amount of 16-bit words (MSB first) from a register into data. /** Read len amount of 16-bit words (MSB first) from a register into data.
* *
* @param a_register The register number to write to the bus before reading. * @param a_register The register number to write to the bus before reading.
@ -176,6 +184,13 @@ class I2CDevice {
/// Read a single byte from a register into the data variable. Return true if successful. /// Read a single byte from a register into the data variable. Return true if successful.
bool read_byte(uint8_t a_register, uint8_t *data, uint32_t conversion = 0); // NOLINT bool read_byte(uint8_t a_register, uint8_t *data, uint32_t conversion = 0); // NOLINT
optional<uint8_t> read_byte(uint8_t a_register) { // NOLINT
uint8_t data;
if (!this->read_byte(a_register, &data))
return {};
return data;
}
/// Read a single 16-bit words (MSB first) from a register into the data variable. Return true if successful. /// Read a single 16-bit words (MSB first) from a register into the data variable. Return true if successful.
bool read_byte_16(uint8_t a_register, uint16_t *data, uint32_t conversion = 0); // NOLINT bool read_byte_16(uint8_t a_register, uint16_t *data, uint32_t conversion = 0); // NOLINT
@ -188,6 +203,20 @@ class I2CDevice {
*/ */
bool write_bytes(uint8_t a_register, const uint8_t *data, uint8_t len); // NOLINT bool write_bytes(uint8_t a_register, const uint8_t *data, uint8_t len); // NOLINT
/** Write a vector of data to a register.
*
* @param a_register The register to write to.
* @param data The data to write.
* @return If the operation was successful.
*/
bool write_bytes(uint8_t a_register, const std::vector<uint8_t> &data) { // NOLINT
return this->write_bytes(a_register, data.data(), data.size());
}
template<size_t N> bool write_bytes(uint8_t a_register, const std::array<uint8_t, N> &data) { // NOLINT
return this->write_bytes(a_register, data.data(), data.size());
}
/** Write len amount of 16-bit words (MSB first) to the specified register. /** Write len amount of 16-bit words (MSB first) to the specified register.
* *
* @param a_register The register to write the values to. * @param a_register The register to write the values to.

View file

@ -33,7 +33,7 @@ CONFIG_SCHEMA = sensor.SENSOR_SCHEMA.extend({
cv.Required(CONF_TIME_UNIT): cv.enum(INTEGRATION_TIMES, lower=True), cv.Required(CONF_TIME_UNIT): cv.enum(INTEGRATION_TIMES, lower=True),
cv.Optional(CONF_INTEGRATION_METHOD, default='trapezoid'): cv.Optional(CONF_INTEGRATION_METHOD, default='trapezoid'):
cv.enum(INTEGRATION_METHODS, lower=True), cv.enum(INTEGRATION_METHODS, lower=True),
cv.Optional(CONF_RESTORE, default=True): cv.boolean, cv.Optional(CONF_RESTORE, default=False): cv.boolean,
}).extend(cv.COMPONENT_SCHEMA) }).extend(cv.COMPONENT_SCHEMA)

View file

@ -1,20 +1,54 @@
import math
from esphome import pins from esphome import pins
from esphome.components import output from esphome.components import output
import esphome.config_validation as cv import esphome.config_validation as cv
import esphome.codegen as cg import esphome.codegen as cg
from esphome.const import APB_CLOCK_FREQ, CONF_BIT_DEPTH, CONF_CHANNEL, CONF_FREQUENCY, \ from esphome.const import CONF_BIT_DEPTH, CONF_CHANNEL, CONF_FREQUENCY, \
CONF_ID, CONF_PIN, ESP_PLATFORM_ESP32 CONF_ID, CONF_PIN, ESP_PLATFORM_ESP32
ESP_PLATFORMS = [ESP_PLATFORM_ESP32] ESP_PLATFORMS = [ESP_PLATFORM_ESP32]
def calc_max_frequency(bit_depth):
return 80e6 / (2**bit_depth)
def calc_min_frequency(bit_depth):
# LEDC_DIV_NUM_HSTIMER is 15-bit unsigned integer
# lower 8 bits represent fractional part
max_div_num = ((1 << 16) - 1) / 256.0
return 80e6 / (max_div_num * (2**bit_depth))
def validate_frequency_bit_depth(obj): def validate_frequency_bit_depth(obj):
frequency = obj[CONF_FREQUENCY] frequency = obj[CONF_FREQUENCY]
if CONF_BIT_DEPTH not in obj:
obj = obj.copy()
for bit_depth in range(15, 0, -1):
if calc_min_frequency(bit_depth) <= frequency <= calc_max_frequency(bit_depth):
obj[CONF_BIT_DEPTH] = bit_depth
break
else:
min_freq = min(calc_min_frequency(x) for x in range(1, 16))
max_freq = max(calc_max_frequency(x) for x in range(1, 16))
if frequency < min_freq:
raise cv.Invalid("This frequency setting is not possible, please choose a higher "
"frequency (at least {}Hz)".format(int(min_freq)))
if frequency > max_freq:
raise cv.Invalid("This frequency setting is not possible, please choose a lower "
"frequency (at most {}Hz)".format(int(max_freq)))
raise cv.Invalid("Invalid frequency!")
bit_depth = obj[CONF_BIT_DEPTH] bit_depth = obj[CONF_BIT_DEPTH]
max_freq = APB_CLOCK_FREQ / (2**bit_depth) min_freq = calc_min_frequency(bit_depth)
max_freq = calc_max_frequency(bit_depth)
if frequency > max_freq: if frequency > max_freq:
raise cv.Invalid('Maximum frequency for bit depth {} is {}Hz'.format(bit_depth, max_freq)) raise cv.Invalid('Maximum frequency for bit depth {} is {}Hz. Please decrease the '
'bit_depth.'.format(bit_depth, int(math.floor(max_freq))))
if frequency < calc_min_frequency(bit_depth):
raise cv.Invalid('Minimum frequency for bit depth {} is {}Hz. Please increase the '
'bit_depth.'.format(bit_depth, int(math.ceil(min_freq))))
return obj return obj
@ -25,8 +59,8 @@ CONFIG_SCHEMA = cv.All(output.FLOAT_OUTPUT_SCHEMA.extend({
cv.Required(CONF_ID): cv.declare_id(LEDCOutput), cv.Required(CONF_ID): cv.declare_id(LEDCOutput),
cv.Required(CONF_PIN): pins.internal_gpio_output_pin_schema, cv.Required(CONF_PIN): pins.internal_gpio_output_pin_schema,
cv.Optional(CONF_FREQUENCY, default='1kHz'): cv.frequency, cv.Optional(CONF_FREQUENCY, default='1kHz'): cv.frequency,
cv.Optional(CONF_BIT_DEPTH, default=12): cv.All(cv.int_, cv.Range(min=1, max=15)), cv.Optional(CONF_BIT_DEPTH): cv.int_range(min=1, max=15),
cv.Optional(CONF_CHANNEL): cv.All(cv.int_, cv.Range(min=0, max=15)) cv.Optional(CONF_CHANNEL): cv.int_range(min=0, max=15),
}).extend(cv.COMPONENT_SCHEMA), validate_frequency_bit_depth) }).extend(cv.COMPONENT_SCHEMA), validate_frequency_bit_depth)

View file

@ -1,9 +1,9 @@
import esphome.codegen as cg import esphome.codegen as cg
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.components import mqtt from esphome.components import mqtt, power_supply
from esphome.const import CONF_COLOR_CORRECT, \ from esphome.const import CONF_COLOR_CORRECT, \
CONF_DEFAULT_TRANSITION_LENGTH, CONF_EFFECTS, CONF_GAMMA_CORRECT, CONF_ID, \ CONF_DEFAULT_TRANSITION_LENGTH, CONF_EFFECTS, CONF_GAMMA_CORRECT, CONF_ID, \
CONF_INTERNAL, CONF_NAME, CONF_MQTT_ID CONF_INTERNAL, CONF_NAME, CONF_MQTT_ID, CONF_POWER_SUPPLY
from esphome.core import coroutine, coroutine_with_priority from esphome.core import coroutine, coroutine_with_priority
from .automation import light_control_to_code # noqa from .automation import light_control_to_code # noqa
from .effects import validate_effects, BINARY_EFFECTS, \ from .effects import validate_effects, BINARY_EFFECTS, \
@ -35,6 +35,7 @@ ADDRESSABLE_LIGHT_SCHEMA = RGB_LIGHT_SCHEMA.extend({
cv.GenerateID(): cv.declare_id(AddressableLightState), cv.GenerateID(): cv.declare_id(AddressableLightState),
cv.Optional(CONF_EFFECTS): validate_effects(ADDRESSABLE_EFFECTS), cv.Optional(CONF_EFFECTS): validate_effects(ADDRESSABLE_EFFECTS),
cv.Optional(CONF_COLOR_CORRECT): cv.All([cv.percentage], cv.Length(min=3, max=4)), cv.Optional(CONF_COLOR_CORRECT): cv.All([cv.percentage], cv.Length(min=3, max=4)),
cv.Optional(CONF_POWER_SUPPLY): cv.use_id(power_supply.PowerSupply),
}) })
@ -52,6 +53,10 @@ def setup_light_core_(light_var, output_var, config):
if CONF_COLOR_CORRECT in config: if CONF_COLOR_CORRECT in config:
cg.add(output_var.set_correction(*config[CONF_COLOR_CORRECT])) cg.add(output_var.set_correction(*config[CONF_COLOR_CORRECT]))
if CONF_POWER_SUPPLY in config:
var_ = yield cg.get_variable(config[CONF_POWER_SUPPLY])
cg.add(output_var.set_power_supply(var_))
if CONF_MQTT_ID in config: if CONF_MQTT_ID in config:
mqtt_ = cg.new_Pvariable(config[CONF_MQTT_ID], light_var) mqtt_ = cg.new_Pvariable(config[CONF_MQTT_ID], light_var)
yield mqtt.register_mqtt_component(mqtt_, config) yield mqtt.register_mqtt_component(mqtt_, config)

View file

@ -1,9 +1,14 @@
#pragma once #pragma once
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/core/defines.h"
#include "light_output.h" #include "light_output.h"
#include "light_state.h" #include "light_state.h"
#ifdef USE_POWER_SUPPLY
#include "esphome/components/power_supply/power_supply.h"
#endif
namespace esphome { namespace esphome {
namespace light { namespace light {
@ -30,6 +35,7 @@ struct ESPColor {
}; };
}; };
uint8_t raw[4]; uint8_t raw[4];
uint32_t raw_32;
}; };
inline ESPColor() ALWAYS_INLINE : r(0), g(0), b(0), w(0) {} // NOLINT inline ESPColor() ALWAYS_INLINE : r(0), g(0), b(0), w(0) {} // NOLINT
inline ESPColor(uint8_t red, uint8_t green, uint8_t blue, uint8_t white) ALWAYS_INLINE : r(red), inline ESPColor(uint8_t red, uint8_t green, uint8_t blue, uint8_t white) ALWAYS_INLINE : r(red),
@ -47,7 +53,7 @@ struct ESPColor {
this->b = rhs.b; this->b = rhs.b;
this->w = rhs.w; this->w = rhs.w;
} }
inline bool is_on() ALWAYS_INLINE { return this->r != 0 || this->g != 0 || this->b != 0 || this->w != 0; } inline bool is_on() ALWAYS_INLINE { return this->raw_32 != 0; }
inline ESPColor &operator=(const ESPColor &rhs) ALWAYS_INLINE { inline ESPColor &operator=(const ESPColor &rhs) ALWAYS_INLINE {
this->r = rhs.r; this->r = rhs.r;
this->g = rhs.g; this->g = rhs.g;
@ -527,14 +533,32 @@ class AddressableLight : public LightOutput {
void setup_state(LightState *state) override { this->correction_.calculate_gamma_table(state->get_gamma_correct()); } void setup_state(LightState *state) override { this->correction_.calculate_gamma_table(state->get_gamma_correct()); }
void schedule_show() { this->next_show_ = true; } void schedule_show() { this->next_show_ = true; }
#ifdef USE_POWER_SUPPLY
void set_power_supply(power_supply::PowerSupply *power_supply) { this->power_.set_parent(power_supply); }
#endif
protected: protected:
bool should_show_() const { return this->effect_active_ || this->next_show_; } bool should_show_() const { return this->effect_active_ || this->next_show_; }
void mark_shown_() { this->next_show_ = false; } void mark_shown_() {
this->next_show_ = false;
#ifdef USE_POWER_SUPPLY
for (auto c : *this) {
if (c.get().is_on()) {
this->power_.request();
return;
}
}
this->power_.unrequest();
#endif
}
virtual ESPColorView get_view_internal(int32_t index) const = 0; virtual ESPColorView get_view_internal(int32_t index) const = 0;
bool effect_active_{false}; bool effect_active_{false};
bool next_show_{true}; bool next_show_{true};
ESPColorCorrection correction_{}; ESPColorCorrection correction_{};
#ifdef USE_POWER_SUPPLY
power_supply::PowerSupplyRequester power_;
#endif
}; };
} // namespace light } // namespace light

View file

@ -112,7 +112,7 @@ def to_code(config):
if CORE.is_esp8266 and has_serial_logging and is_at_least_verbose: if CORE.is_esp8266 and has_serial_logging and is_at_least_verbose:
debug_serial_port = HARDWARE_UART_TO_SERIAL[config.get(CONF_HARDWARE_UART)] debug_serial_port = HARDWARE_UART_TO_SERIAL[config.get(CONF_HARDWARE_UART)]
cg.add_build_flag("-DDEBUG_ESP_PORT{}".format(debug_serial_port)) cg.add_build_flag("-DDEBUG_ESP_PORT={}".format(debug_serial_port))
cg.add_build_flag("-DLWIP_DEBUG") cg.add_build_flag("-DLWIP_DEBUG")
DEBUG_COMPONENTS = { DEBUG_COMPONENTS = {
'HTTP_CLIENT', 'HTTP_CLIENT',

View file

@ -12,8 +12,8 @@ MAX7219ComponentRef = MAX7219Component.operator('ref')
CONFIG_SCHEMA = display.BASIC_DISPLAY_SCHEMA.extend({ CONFIG_SCHEMA = display.BASIC_DISPLAY_SCHEMA.extend({
cv.GenerateID(): cv.declare_id(MAX7219Component), cv.GenerateID(): cv.declare_id(MAX7219Component),
cv.Optional(CONF_NUM_CHIPS, default=1): cv.All(cv.uint8_t, cv.Range(min=1)), cv.Optional(CONF_NUM_CHIPS, default=1): cv.int_range(min=1, max=255),
cv.Optional(CONF_INTENSITY, default=15): cv.All(cv.uint8_t, cv.Range(min=0, max=15)), cv.Optional(CONF_INTENSITY, default=15): cv.int_range(min=0, max=15),
}).extend(cv.polling_component_schema('1s')).extend(spi.SPI_DEVICE_SCHEMA) }).extend(cv.polling_component_schema('1s')).extend(spi.SPI_DEVICE_SCHEMA)

View file

@ -110,7 +110,7 @@ void MPU6050Component::update() {
float accel_y = data[1] * MPU6050_RANGE_PER_DIGIT_2G * GRAVITY_EARTH; float accel_y = data[1] * MPU6050_RANGE_PER_DIGIT_2G * GRAVITY_EARTH;
float accel_z = data[2] * MPU6050_RANGE_PER_DIGIT_2G * GRAVITY_EARTH; float accel_z = data[2] * MPU6050_RANGE_PER_DIGIT_2G * GRAVITY_EARTH;
float temperature = raw_data[3] / 340.0f + 36.53f; float temperature = data[3] / 340.0f + 36.53f;
float gyro_x = data[4] * MPU6050_SCALE_DPS_PER_DIGIT_2000; float gyro_x = data[4] * MPU6050_SCALE_DPS_PER_DIGIT_2000;
float gyro_y = data[5] * MPU6050_SCALE_DPS_PER_DIGIT_2000; float gyro_y = data[5] * MPU6050_SCALE_DPS_PER_DIGIT_2000;

View file

@ -24,12 +24,12 @@ void MQTTClimateComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryC
JsonArray &modes = root.createNestedArray("modes"); JsonArray &modes = root.createNestedArray("modes");
// sort array for nice UI in HA // sort array for nice UI in HA
if (traits.supports_mode(CLIMATE_MODE_AUTO)) if (traits.supports_mode(CLIMATE_MODE_AUTO))
modes.add("auto"); modes.add(climate_mode_to_string(CLIMATE_MODE_AUTO));
modes.add("off"); modes.add(climate_mode_to_string(CLIMATE_MODE_OFF));
if (traits.supports_mode(CLIMATE_MODE_COOL)) if (traits.supports_mode(CLIMATE_MODE_COOL))
modes.add("cool"); modes.add(climate_mode_to_string(CLIMATE_MODE_COOL));
if (traits.supports_mode(CLIMATE_MODE_HEAT)) if (traits.supports_mode(CLIMATE_MODE_HEAT))
modes.add("heat"); modes.add(climate_mode_to_string(CLIMATE_MODE_HEAT));
if (traits.get_supports_two_point_target_temperature()) { if (traits.get_supports_two_point_target_temperature()) {
// temperature_low_command_topic // temperature_low_command_topic
@ -60,6 +60,8 @@ void MQTTClimateComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryC
// away_mode_state_topic // away_mode_state_topic
root["away_mode_state_topic"] = this->get_away_state_topic(); root["away_mode_state_topic"] = this->get_away_state_topic();
} }
config.state_topic = false;
config.command_topic = false;
} }
void MQTTClimateComponent::setup() { void MQTTClimateComponent::setup() {
auto traits = this->device_->get_traits(); auto traits = this->device_->get_traits();
@ -144,7 +146,7 @@ bool MQTTClimateComponent::publish_state_() {
if (!this->publish(this->get_mode_state_topic(), mode_s)) if (!this->publish(this->get_mode_state_topic(), mode_s))
success = false; success = false;
int8_t accuracy = traits.get_temperature_accuracy_decimals(); int8_t accuracy = traits.get_temperature_accuracy_decimals();
if (traits.get_supports_current_temperature()) { if (traits.get_supports_current_temperature() && !isnan(this->device_->current_temperature)) {
std::string payload = value_accuracy_to_string(this->device_->current_temperature, accuracy); std::string payload = value_accuracy_to_string(this->device_->current_temperature, accuracy);
if (!this->publish(this->get_current_temperature_state_topic(), payload)) if (!this->publish(this->get_current_temperature_state_topic(), payload))
success = false; success = false;

View file

@ -13,8 +13,8 @@ CONFIG_SCHEMA = cv.Schema({
cv.GenerateID(): cv.declare_id(MY9231OutputComponent), cv.GenerateID(): cv.declare_id(MY9231OutputComponent),
cv.Required(CONF_DATA_PIN): pins.gpio_output_pin_schema, cv.Required(CONF_DATA_PIN): pins.gpio_output_pin_schema,
cv.Required(CONF_CLOCK_PIN): pins.gpio_output_pin_schema, cv.Required(CONF_CLOCK_PIN): pins.gpio_output_pin_schema,
cv.Optional(CONF_NUM_CHANNELS, default=6): cv.All(cv.int_, cv.Range(min=3, max=1020)), cv.Optional(CONF_NUM_CHANNELS, default=6): cv.int_range(min=3, max=1020),
cv.Optional(CONF_NUM_CHIPS, default=2): cv.All(cv.int_, cv.Range(min=1, max=255)), cv.Optional(CONF_NUM_CHIPS, default=2): cv.int_range(min=1, max=255),
cv.Optional(CONF_BIT_DEPTH, default=16): cv.one_of(8, 12, 14, 16, int=True), cv.Optional(CONF_BIT_DEPTH, default=16): cv.one_of(8, 12, 14, 16, int=True),
}).extend(cv.COMPONENT_SCHEMA) }).extend(cv.COMPONENT_SCHEMA)

View file

@ -1,6 +1,7 @@
#pragma once #pragma once
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/core/esphal.h"
#include "esphome/components/output/float_output.h" #include "esphome/components/output/float_output.h"
namespace esphome { namespace esphome {

View file

@ -13,7 +13,7 @@ CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend({
cv.GenerateID(CONF_MY9231_ID): cv.use_id(MY9231OutputComponent), cv.GenerateID(CONF_MY9231_ID): cv.use_id(MY9231OutputComponent),
cv.Required(CONF_ID): cv.declare_id(Channel), cv.Required(CONF_ID): cv.declare_id(Channel),
cv.Required(CONF_CHANNEL): cv.All(cv.int_, cv.Range(min=0, max=65535)), cv.Required(CONF_CHANNEL): cv.uint16_t,
}).extend(cv.COMPONENT_SCHEMA) }).extend(cv.COMPONENT_SCHEMA)

View file

@ -1,9 +1,9 @@
import esphome.codegen as cg import esphome.codegen as cg
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome import pins from esphome import pins
from esphome.components import light, power_supply from esphome.components import light
from esphome.const import CONF_CLOCK_PIN, CONF_DATA_PIN, CONF_METHOD, CONF_NUM_LEDS, CONF_PIN, \ from esphome.const import CONF_CLOCK_PIN, CONF_DATA_PIN, CONF_METHOD, CONF_NUM_LEDS, CONF_PIN, \
CONF_POWER_SUPPLY, CONF_TYPE, CONF_VARIANT, CONF_OUTPUT_ID CONF_TYPE, CONF_VARIANT, CONF_OUTPUT_ID
from esphome.core import CORE from esphome.core import CORE
neopixelbus_ns = cg.esphome_ns.namespace('neopixelbus') neopixelbus_ns = cg.esphome_ns.namespace('neopixelbus')
@ -138,8 +138,6 @@ CONFIG_SCHEMA = cv.All(light.ADDRESSABLE_LIGHT_SCHEMA.extend({
cv.Optional(CONF_DATA_PIN): pins.output_pin, cv.Optional(CONF_DATA_PIN): pins.output_pin,
cv.Required(CONF_NUM_LEDS): cv.positive_not_null_int, cv.Required(CONF_NUM_LEDS): cv.positive_not_null_int,
cv.Optional(CONF_POWER_SUPPLY): cv.use_id(power_supply.PowerSupply),
}).extend(cv.COMPONENT_SCHEMA), validate, validate_method_pin) }).extend(cv.COMPONENT_SCHEMA), validate, validate_method_pin)
@ -162,8 +160,4 @@ def to_code(config):
cg.add(var.set_pixel_order(getattr(ESPNeoPixelOrder, config[CONF_TYPE]))) cg.add(var.set_pixel_order(getattr(ESPNeoPixelOrder, config[CONF_TYPE])))
if CONF_POWER_SUPPLY in config:
var_ = yield cg.get_variable(config[CONF_POWER_SUPPLY])
cg.add(var.set_power_supply(var_))
cg.add_library('NeoPixelBus', '2.4.1') cg.add_library('NeoPixelBus', '2.4.1')

View file

@ -9,10 +9,6 @@
#error The NeoPixelBus library requires at least arduino_core_version 2.4.x #error The NeoPixelBus library requires at least arduino_core_version 2.4.x
#endif #endif
#ifdef USE_POWER_SUPPLY
#include "esphome/components/power_supply/power_supply.h"
#endif
#include "NeoPixelBus.h" #include "NeoPixelBus.h"
namespace esphome { namespace esphome {
@ -54,10 +50,6 @@ enum class ESPNeoPixelOrder {
template<typename T_METHOD, typename T_COLOR_FEATURE> template<typename T_METHOD, typename T_COLOR_FEATURE>
class NeoPixelBusLightOutputBase : public Component, public light::AddressableLight { class NeoPixelBusLightOutputBase : public Component, public light::AddressableLight {
public: public:
#ifdef USE_POWER_SUPPLY
void set_power_supply(power_supply::PowerSupply *power_supply) { this->power_supply_ = power_supply; }
#endif
NeoPixelBus<T_COLOR_FEATURE, T_METHOD> *get_controller() const { return this->controller_; } NeoPixelBus<T_COLOR_FEATURE, T_METHOD> *get_controller() const { return this->controller_; }
void clear_effect_data() override { void clear_effect_data() override {
@ -95,27 +87,6 @@ class NeoPixelBusLightOutputBase : public Component, public light::AddressableLi
this->mark_shown_(); this->mark_shown_();
this->controller_->Dirty(); this->controller_->Dirty();
#ifdef USE_POWER_SUPPLY
if (this->power_supply_ != nullptr) {
bool is_light_on = false;
for (int i = 0; i < this->size(); i++) {
if ((*this)[i].get().is_on()) {
is_light_on = true;
break;
}
}
if (is_light_on && !this->has_requested_high_power_) {
this->power_supply_->request_high_power();
this->has_requested_high_power_ = true;
}
if (!is_light_on && this->has_requested_high_power_) {
this->power_supply_->unrequest_high_power();
this->has_requested_high_power_ = false;
}
}
#endif
this->controller_->Show(); this->controller_->Show();
} }
@ -135,10 +106,6 @@ class NeoPixelBusLightOutputBase : public Component, public light::AddressableLi
NeoPixelBus<T_COLOR_FEATURE, T_METHOD> *controller_{nullptr}; NeoPixelBus<T_COLOR_FEATURE, T_METHOD> *controller_{nullptr};
uint8_t *effect_data_{nullptr}; uint8_t *effect_data_{nullptr};
uint8_t rgb_offsets_[4]{0, 1, 2, 3}; uint8_t rgb_offsets_[4]{0, 1, 2, 3};
#ifdef USE_POWER_SUPPLY
power_supply::PowerSupply *power_supply_{nullptr};
bool has_requested_high_power_{false};
#endif
}; };
template<typename T_METHOD, typename T_COLOR_FEATURE = NeoRgbFeature> template<typename T_METHOD, typename T_COLOR_FEATURE = NeoRgbFeature>

View file

@ -4,7 +4,6 @@
#include "esphome/core/helpers.h" #include "esphome/core/helpers.h"
#include "esphome/core/application.h" #include "esphome/core/application.h"
#include "esphome/core/util.h" #include "esphome/core/util.h"
//#include "esphome/components/status_led.h"
#include <cstdio> #include <cstdio>
#include <MD5Builder.h> #include <MD5Builder.h>

View file

@ -27,16 +27,13 @@ class BinaryOutput {
* *
* @param power_supply The PowerSupplyComponent, set this to nullptr to disable the power supply. * @param power_supply The PowerSupplyComponent, set this to nullptr to disable the power supply.
*/ */
void set_power_supply(power_supply::PowerSupply *power_supply) { this->power_supply_ = power_supply; } void set_power_supply(power_supply::PowerSupply *power_supply) { this->power_.set_parent(power_supply); }
#endif #endif
/// Enable this binary output. /// Enable this binary output.
virtual void turn_on() { virtual void turn_on() {
#ifdef USE_POWER_SUPPLY #ifdef USE_POWER_SUPPLY
if (this->power_supply_ != nullptr && !this->has_requested_high_power_) { this->power_.request();
this->power_supply_->request_high_power();
this->has_requested_high_power_ = true;
}
#endif #endif
this->write_state(!this->inverted_); this->write_state(!this->inverted_);
} }
@ -44,10 +41,7 @@ class BinaryOutput {
/// Disable this binary output. /// Disable this binary output.
virtual void turn_off() { virtual void turn_off() {
#ifdef USE_POWER_SUPPLY #ifdef USE_POWER_SUPPLY
if (this->power_supply_ != nullptr && this->has_requested_high_power_) { this->power_.unrequest();
this->power_supply_->unrequest_high_power();
this->has_requested_high_power_ = false;
}
#endif #endif
this->write_state(this->inverted_); this->write_state(this->inverted_);
} }
@ -62,8 +56,7 @@ class BinaryOutput {
bool inverted_{false}; bool inverted_{false};
#ifdef USE_POWER_SUPPLY #ifdef USE_POWER_SUPPLY
power_supply::PowerSupply *power_supply_{nullptr}; power_supply::PowerSupplyRequester power_{};
bool has_requested_high_power_{false};
#endif #endif
}; };

View file

@ -24,15 +24,9 @@ void FloatOutput::set_level(float state) {
#ifdef USE_POWER_SUPPLY #ifdef USE_POWER_SUPPLY
if (state > 0.0f) { // ON if (state > 0.0f) { // ON
if (this->power_supply_ != nullptr && !this->has_requested_high_power_) { this->power_.request();
this->power_supply_->request_high_power();
this->has_requested_high_power_ = true;
}
} else { // OFF } else { // OFF
if (this->power_supply_ != nullptr && this->has_requested_high_power_) { this->power_.unrequest();
this->power_supply_->unrequest_high_power();
this->has_requested_high_power_ = false;
}
} }
#endif #endif

View file

@ -13,7 +13,7 @@ CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend({
cv.Required(CONF_ID): cv.declare_id(PCA9685Channel), cv.Required(CONF_ID): cv.declare_id(PCA9685Channel),
cv.GenerateID(CONF_PCA9685_ID): cv.use_id(PCA9685Output), cv.GenerateID(CONF_PCA9685_ID): cv.use_id(PCA9685Output),
cv.Required(CONF_CHANNEL): cv.All(cv.Coerce(int), cv.Range(min=0, max=15)), cv.Required(CONF_CHANNEL): cv.int_range(min=0, max=15),
}) })

View file

@ -132,14 +132,20 @@ void PMSX003Component::parse_data_() {
break; break;
} }
case PMSX003_TYPE_5003ST: { case PMSX003_TYPE_5003ST: {
uint16_t pm_1_0_concentration = this->get_16_bit_uint_(10);
uint16_t pm_2_5_concentration = this->get_16_bit_uint_(12); uint16_t pm_2_5_concentration = this->get_16_bit_uint_(12);
uint16_t pm_10_0_concentration = this->get_16_bit_uint_(14);
uint16_t formaldehyde = this->get_16_bit_uint_(28); uint16_t formaldehyde = this->get_16_bit_uint_(28);
float temperature = this->get_16_bit_uint_(30) / 10.0f; float temperature = this->get_16_bit_uint_(30) / 10.0f;
float humidity = this->get_16_bit_uint_(32) / 10.0f; float humidity = this->get_16_bit_uint_(32) / 10.0f;
ESP_LOGD(TAG, "Got PM2.5 Concentration: %u µg/m^3, Temperature: %.1f°C, Humidity: %.1f%% Formaldehyde: %u µg/m^3", ESP_LOGD(TAG, "Got PM2.5 Concentration: %u µg/m^3, Temperature: %.1f°C, Humidity: %.1f%% Formaldehyde: %u µg/m^3",
pm_2_5_concentration, temperature, humidity, formaldehyde); pm_2_5_concentration, temperature, humidity, formaldehyde);
if (this->pm_1_0_sensor_ != nullptr)
this->pm_1_0_sensor_->publish_state(pm_1_0_concentration);
if (this->pm_2_5_sensor_ != nullptr) if (this->pm_2_5_sensor_ != nullptr)
this->pm_2_5_sensor_->publish_state(pm_2_5_concentration); this->pm_2_5_sensor_->publish_state(pm_2_5_concentration);
if (this->pm_10_0_sensor_ != nullptr)
this->pm_10_0_sensor_->publish_state(pm_10_0_concentration);
if (this->temperature_sensor_ != nullptr) if (this->temperature_sensor_ != nullptr)
this->temperature_sensor_->publish_state(temperature); this->temperature_sensor_->publish_state(temperature);
if (this->humidity_sensor_ != nullptr) if (this->humidity_sensor_ != nullptr)

View file

@ -24,9 +24,9 @@ PMSX003_TYPES = {
} }
SENSORS_TO_TYPE = { SENSORS_TO_TYPE = {
CONF_PM_1_0: [CONF_PMSX003], CONF_PM_1_0: [CONF_PMSX003, CONF_PMS5003ST],
CONF_PM_2_5: [CONF_PMSX003, CONF_PMS5003T, CONF_PMS5003ST], CONF_PM_2_5: [CONF_PMSX003, CONF_PMS5003T, CONF_PMS5003ST],
CONF_PM_10_0: [CONF_PMSX003], CONF_PM_10_0: [CONF_PMSX003, CONF_PMS5003ST],
CONF_TEMPERATURE: [CONF_PMS5003T, CONF_PMS5003ST], CONF_TEMPERATURE: [CONF_PMS5003T, CONF_PMS5003ST],
CONF_HUMIDITY: [CONF_PMS5003T, CONF_PMS5003ST], CONF_HUMIDITY: [CONF_PMS5003T, CONF_PMS5003ST],
CONF_FORMALDEHYDE: [CONF_PMS5003ST], CONF_FORMALDEHYDE: [CONF_PMS5003ST],

View file

@ -42,7 +42,7 @@ void PowerSupply::request_high_power() {
void PowerSupply::unrequest_high_power() { void PowerSupply::unrequest_high_power() {
this->active_requests_--; this->active_requests_--;
if (this->active_requests_ < 0) { if (this->active_requests_ < 0) {
// if asserts are disabled we're just going to use 0 as our now counter. // we're just going to use 0 as our now counter.
this->active_requests_ = 0; this->active_requests_ = 0;
} }

View file

@ -39,5 +39,26 @@ class PowerSupply : public Component {
int16_t active_requests_{0}; // use signed integer to make catching negative requests easier. int16_t active_requests_{0}; // use signed integer to make catching negative requests easier.
}; };
class PowerSupplyRequester {
public:
void set_parent(PowerSupply *parent) { parent_ = parent; }
void request() {
if (!this->requested_ && this->parent_ != nullptr) {
this->parent_->request_high_power();
this->requested_ = true;
}
}
void unrequest() {
if (this->requested_ && this->parent_ != nullptr) {
this->parent_->unrequest_high_power();
this->requested_ = false;
}
}
protected:
PowerSupply *parent_{nullptr};
bool requested_{false};
};
} // namespace power_supply } // namespace power_supply
} // namespace esphome } // namespace esphome

View file

@ -28,7 +28,9 @@ void ICACHE_RAM_ATTR PulseCounterStorage::gpio_intr(PulseCounterStorage *arg) {
break; break;
} }
} }
bool PulseCounterStorage::pulse_counter_setup() { bool PulseCounterStorage::pulse_counter_setup(GPIOPin *pin) {
this->pin = pin;
this->pin->setup();
this->isr_pin = this->pin->to_isr(); this->isr_pin = this->pin->to_isr();
this->pin->attach_interrupt(PulseCounterStorage::gpio_intr, this, CHANGE); this->pin->attach_interrupt(PulseCounterStorage::gpio_intr, this, CHANGE);
return true; return true;
@ -42,7 +44,9 @@ pulse_counter_t PulseCounterStorage::read_raw_value() {
#endif #endif
#ifdef ARDUINO_ARCH_ESP32 #ifdef ARDUINO_ARCH_ESP32
bool PulseCounterStorage::pulse_counter_setup() { bool PulseCounterStorage::pulse_counter_setup(GPIOPin *pin) {
this->pin = pin;
this->pin->setup();
this->pcnt_unit = next_pcnt_unit; this->pcnt_unit = next_pcnt_unit;
next_pcnt_unit = pcnt_unit_t(int(next_pcnt_unit) + 1); // NOLINT next_pcnt_unit = pcnt_unit_t(int(next_pcnt_unit) + 1); // NOLINT
@ -133,9 +137,7 @@ pulse_counter_t PulseCounterStorage::read_raw_value() {
void PulseCounterSensor::setup() { void PulseCounterSensor::setup() {
ESP_LOGCONFIG(TAG, "Setting up pulse counter '%s'...", this->name_.c_str()); ESP_LOGCONFIG(TAG, "Setting up pulse counter '%s'...", this->name_.c_str());
this->pin_->setup(); if (!this->storage_.pulse_counter_setup(this->pin_)) {
this->storage_.pin = this->pin_;
if (!this->storage_.pulse_counter_setup()) {
this->mark_failed(); this->mark_failed();
return; return;
} }

View file

@ -25,7 +25,7 @@ using pulse_counter_t = int32_t;
#endif #endif
struct PulseCounterStorage { struct PulseCounterStorage {
bool pulse_counter_setup(); bool pulse_counter_setup(GPIOPin *pin);
pulse_counter_t read_raw_value(); pulse_counter_t read_raw_value();
static void gpio_intr(PulseCounterStorage *arg); static void gpio_intr(PulseCounterStorage *arg);
@ -42,9 +42,9 @@ struct PulseCounterStorage {
#ifdef ARDUINO_ARCH_ESP8266 #ifdef ARDUINO_ARCH_ESP8266
ISRInternalGPIOPin *isr_pin; ISRInternalGPIOPin *isr_pin;
#endif #endif
PulseCounterCountMode rising_edge_mode{}; PulseCounterCountMode rising_edge_mode{PULSE_COUNTER_INCREMENT};
PulseCounterCountMode falling_edge_mode{}; PulseCounterCountMode falling_edge_mode{PULSE_COUNTER_DISABLE};
uint32_t filter_us{}; uint32_t filter_us{0};
pulse_counter_t last_value{0}; pulse_counter_t last_value{0};
}; };

View file

@ -5,7 +5,7 @@ from esphome.components import binary_sensor
from esphome.const import CONF_DATA, CONF_ID, CONF_TRIGGER_ID, CONF_NBITS, CONF_ADDRESS, \ from esphome.const import CONF_DATA, CONF_ID, CONF_TRIGGER_ID, CONF_NBITS, CONF_ADDRESS, \
CONF_COMMAND, CONF_CODE, CONF_PULSE_LENGTH, CONF_SYNC, CONF_ZERO, CONF_ONE, CONF_INVERTED, \ CONF_COMMAND, CONF_CODE, CONF_PULSE_LENGTH, CONF_SYNC, CONF_ZERO, CONF_ONE, CONF_INVERTED, \
CONF_PROTOCOL, CONF_GROUP, CONF_DEVICE, CONF_STATE, CONF_CHANNEL, CONF_FAMILY, CONF_REPEAT, \ CONF_PROTOCOL, CONF_GROUP, CONF_DEVICE, CONF_STATE, CONF_CHANNEL, CONF_FAMILY, CONF_REPEAT, \
CONF_WAIT_TIME, CONF_TIMES, CONF_TYPE_ID CONF_WAIT_TIME, CONF_TIMES, CONF_TYPE_ID, CONF_CARRIER_FREQUENCY
from esphome.core import coroutine from esphome.core import coroutine
from esphome.py_compat import string_types, text_type from esphome.py_compat import string_types, text_type
from esphome.util import Registry, SimpleRegistry from esphome.util import Registry, SimpleRegistry
@ -359,7 +359,9 @@ def raw_dumper(var, config):
pass pass
@register_action('raw', RawAction, RAW_SCHEMA) @register_action('raw', RawAction, RAW_SCHEMA.extend({
cv.Optional(CONF_CARRIER_FREQUENCY, default='0Hz'): cv.All(cv.frequency, cv.int_),
}))
def raw_action(var, config, args): def raw_action(var, config, args):
code_ = config[CONF_CODE] code_ = config[CONF_CODE]
if cg.is_template(code_): if cg.is_template(code_):
@ -369,6 +371,8 @@ def raw_action(var, config, args):
code_ = config[CONF_CODE] code_ = config[CONF_CODE]
arr = cg.progmem_array(config[CONF_CODE_STORAGE_ID], code_) arr = cg.progmem_array(config[CONF_CODE_STORAGE_ID], code_)
cg.add(var.set_code_static(arr, len(code_))) cg.add(var.set_code_static(arr, len(code_)))
templ = yield cg.templatable(config[CONF_CARRIER_FREQUENCY], args, cg.uint32)
cg.add(var.set_carrier_frequency(templ))
# RC5 # RC5
@ -410,7 +414,7 @@ def rc5_action(var, config, args):
RC_SWITCH_TIMING_SCHEMA = cv.All([cv.uint8_t], cv.Length(min=2, max=2)) RC_SWITCH_TIMING_SCHEMA = cv.All([cv.uint8_t], cv.Length(min=2, max=2))
RC_SWITCH_PROTOCOL_SCHEMA = cv.Any( RC_SWITCH_PROTOCOL_SCHEMA = cv.Any(
cv.All(cv.Coerce(int), cv.Range(min=1, max=7)), cv.int_range(min=1, max=7),
cv.Schema({ cv.Schema({
cv.Required(CONF_PULSE_LENGTH): cv.uint32_t, cv.Required(CONF_PULSE_LENGTH): cv.uint32_t,
cv.Optional(CONF_SYNC, default=[1, 31]): RC_SWITCH_TIMING_SCHEMA, cv.Optional(CONF_SYNC, default=[1, 31]): RC_SWITCH_TIMING_SCHEMA,
@ -457,25 +461,32 @@ RC_SWITCH_TYPE_A_SCHEMA = cv.Schema({
cv.Optional(CONF_PROTOCOL, default=1): RC_SWITCH_PROTOCOL_SCHEMA, cv.Optional(CONF_PROTOCOL, default=1): RC_SWITCH_PROTOCOL_SCHEMA,
}) })
RC_SWITCH_TYPE_B_SCHEMA = cv.Schema({ RC_SWITCH_TYPE_B_SCHEMA = cv.Schema({
cv.Required(CONF_ADDRESS): cv.All(cv.uint8_t, cv.Range(min=1, max=4)), cv.Required(CONF_ADDRESS): cv.int_range(min=1, max=4),
cv.Required(CONF_CHANNEL): cv.All(cv.uint8_t, cv.Range(min=1, max=4)), cv.Required(CONF_CHANNEL): cv.int_range(min=1, max=4),
cv.Required(CONF_STATE): cv.boolean, cv.Required(CONF_STATE): cv.boolean,
cv.Optional(CONF_PROTOCOL, default=1): RC_SWITCH_PROTOCOL_SCHEMA, cv.Optional(CONF_PROTOCOL, default=1): RC_SWITCH_PROTOCOL_SCHEMA,
}) })
RC_SWITCH_TYPE_C_SCHEMA = cv.Schema({ RC_SWITCH_TYPE_C_SCHEMA = cv.Schema({
cv.Required(CONF_FAMILY): cv.one_of('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', cv.Required(CONF_FAMILY): cv.one_of('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k',
'l', 'm', 'n', 'o', 'p', lower=True), 'l', 'm', 'n', 'o', 'p', lower=True),
cv.Required(CONF_GROUP): cv.All(cv.uint8_t, cv.Range(min=1, max=4)), cv.Required(CONF_GROUP): cv.int_range(min=1, max=4),
cv.Required(CONF_DEVICE): cv.All(cv.uint8_t, cv.Range(min=1, max=4)), cv.Required(CONF_DEVICE): cv.int_range(min=1, max=4),
cv.Required(CONF_STATE): cv.boolean, cv.Required(CONF_STATE): cv.boolean,
cv.Optional(CONF_PROTOCOL, default=1): RC_SWITCH_PROTOCOL_SCHEMA, cv.Optional(CONF_PROTOCOL, default=1): RC_SWITCH_PROTOCOL_SCHEMA,
}) })
RC_SWITCH_TYPE_D_SCHEMA = cv.Schema({ RC_SWITCH_TYPE_D_SCHEMA = cv.Schema({
cv.Required(CONF_GROUP): cv.one_of('a', 'b', 'c', 'd', lower=True), cv.Required(CONF_GROUP): cv.one_of('a', 'b', 'c', 'd', lower=True),
cv.Required(CONF_DEVICE): cv.All(cv.uint8_t, cv.Range(min=1, max=3)), cv.Required(CONF_DEVICE): cv.int_range(min=1, max=3),
cv.Required(CONF_STATE): cv.boolean, cv.Required(CONF_STATE): cv.boolean,
cv.Optional(CONF_PROTOCOL, default=1): RC_SWITCH_PROTOCOL_SCHEMA, cv.Optional(CONF_PROTOCOL, default=1): RC_SWITCH_PROTOCOL_SCHEMA,
}) })
RC_SWITCH_TRANSMITTER = cv.Schema({
cv.Optional(CONF_REPEAT, default={CONF_TIMES: 5}): cv.Schema({
cv.Required(CONF_TIMES): cv.templatable(cv.positive_int),
cv.Optional(CONF_WAIT_TIME, default='10ms'):
cv.templatable(cv.positive_time_period_milliseconds),
}),
})
rc_switch_protocols = ns.rc_switch_protocols rc_switch_protocols = ns.rc_switch_protocols
RCSwitchBase = ns.class_('RCSwitchBase') RCSwitchBase = ns.class_('RCSwitchBase')
@ -494,7 +505,8 @@ def rc_switch_raw_binary_sensor(var, config):
cg.add(var.set_code(config[CONF_CODE])) cg.add(var.set_code(config[CONF_CODE]))
@register_action('rc_switch_raw', RCSwitchRawAction, RC_SWITCH_RAW_SCHEMA) @register_action('rc_switch_raw', RCSwitchRawAction,
RC_SWITCH_RAW_SCHEMA.extend(RC_SWITCH_TRANSMITTER))
def rc_switch_raw_action(var, config, args): def rc_switch_raw_action(var, config, args):
proto = yield cg.templatable(config[CONF_PROTOCOL], args, RCSwitchBase, proto = yield cg.templatable(config[CONF_PROTOCOL], args, RCSwitchBase,
to_exp=build_rc_switch_protocol) to_exp=build_rc_switch_protocol)
@ -508,7 +520,8 @@ def rc_switch_type_a_binary_sensor(var, config):
cg.add(var.set_type_a(config[CONF_GROUP], config[CONF_DEVICE], config[CONF_STATE])) cg.add(var.set_type_a(config[CONF_GROUP], config[CONF_DEVICE], config[CONF_STATE]))
@register_action('rc_switch_type_a', RCSwitchTypeAAction, RC_SWITCH_TYPE_A_SCHEMA) @register_action('rc_switch_type_a', RCSwitchTypeAAction,
RC_SWITCH_TYPE_A_SCHEMA.extend(RC_SWITCH_TRANSMITTER))
def rc_switch_type_a_action(var, config, args): def rc_switch_type_a_action(var, config, args):
proto = yield cg.templatable(config[CONF_PROTOCOL], args, RCSwitchBase, proto = yield cg.templatable(config[CONF_PROTOCOL], args, RCSwitchBase,
to_exp=build_rc_switch_protocol) to_exp=build_rc_switch_protocol)
@ -524,7 +537,8 @@ def rc_switch_type_b_binary_sensor(var, config):
cg.add(var.set_type_b(config[CONF_ADDRESS], config[CONF_CHANNEL], config[CONF_STATE])) cg.add(var.set_type_b(config[CONF_ADDRESS], config[CONF_CHANNEL], config[CONF_STATE]))
@register_action('rc_switch_type_b', RCSwitchTypeBAction, RC_SWITCH_TYPE_B_SCHEMA) @register_action('rc_switch_type_b', RCSwitchTypeBAction,
RC_SWITCH_TYPE_B_SCHEMA.extend(RC_SWITCH_TRANSMITTER))
def rc_switch_type_b_action(var, config, args): def rc_switch_type_b_action(var, config, args):
proto = yield cg.templatable(config[CONF_PROTOCOL], args, RCSwitchBase, proto = yield cg.templatable(config[CONF_PROTOCOL], args, RCSwitchBase,
to_exp=build_rc_switch_protocol) to_exp=build_rc_switch_protocol)
@ -541,7 +555,8 @@ def rc_switch_type_c_binary_sensor(var, config):
config[CONF_STATE])) config[CONF_STATE]))
@register_action('rc_switch_type_c', RCSwitchTypeCAction, RC_SWITCH_TYPE_C_SCHEMA) @register_action('rc_switch_type_c', RCSwitchTypeCAction,
RC_SWITCH_TYPE_C_SCHEMA.extend(RC_SWITCH_TRANSMITTER))
def rc_switch_type_c_action(var, config, args): def rc_switch_type_c_action(var, config, args):
proto = yield cg.templatable(config[CONF_PROTOCOL], args, RCSwitchBase, proto = yield cg.templatable(config[CONF_PROTOCOL], args, RCSwitchBase,
to_exp=build_rc_switch_protocol) to_exp=build_rc_switch_protocol)
@ -552,13 +567,15 @@ def rc_switch_type_c_action(var, config, args):
cg.add(var.set_state((yield cg.templatable(config[CONF_STATE], args, bool)))) cg.add(var.set_state((yield cg.templatable(config[CONF_STATE], args, bool))))
@register_binary_sensor('rc_switch_type_d', RCSwitchRawReceiver, RC_SWITCH_TYPE_D_SCHEMA) @register_binary_sensor('rc_switch_type_d', RCSwitchRawReceiver,
RC_SWITCH_TYPE_D_SCHEMA.extend(RC_SWITCH_TRANSMITTER))
def rc_switch_type_d_binary_sensor(var, config): def rc_switch_type_d_binary_sensor(var, config):
cg.add(var.set_protocol(build_rc_switch_protocol(config[CONF_PROTOCOL]))) cg.add(var.set_protocol(build_rc_switch_protocol(config[CONF_PROTOCOL])))
cg.add(var.set_type_d(config[CONF_GROUP], config[CONF_DEVICE], config[CONF_STATE])) cg.add(var.set_type_d(config[CONF_GROUP], config[CONF_DEVICE], config[CONF_STATE]))
@register_action('rc_switch_type_d', RCSwitchTypeDAction, RC_SWITCH_TYPE_D_SCHEMA) @register_action('rc_switch_type_d', RCSwitchTypeDAction,
RC_SWITCH_TYPE_D_SCHEMA.extend(RC_SWITCH_TRANSMITTER))
def rc_switch_type_d_action(var, config, args): def rc_switch_type_d_action(var, config, args):
proto = yield cg.templatable(config[CONF_PROTOCOL], args, RCSwitchBase, proto = yield cg.templatable(config[CONF_PROTOCOL], args, RCSwitchBase,
to_exp=build_rc_switch_protocol) to_exp=build_rc_switch_protocol)

View file

@ -6,7 +6,7 @@ namespace remote_base {
static const char *TAG = "remote.raw"; static const char *TAG = "remote.raw";
void RawDumper::dump(RemoteReceiveData src) { bool RawDumper::dump(RemoteReceiveData src) {
char buffer[256]; char buffer[256];
uint32_t buffer_offset = 0; uint32_t buffer_offset = 0;
buffer_offset += sprintf(buffer, "Received Raw: "); buffer_offset += sprintf(buffer, "Received Raw: ");
@ -16,7 +16,7 @@ void RawDumper::dump(RemoteReceiveData src) {
const uint32_t remaining_length = sizeof(buffer) - buffer_offset; const uint32_t remaining_length = sizeof(buffer) - buffer_offset;
int written; int written;
if (i + 1 < src.size()) { if (i + 1 < src.size() - 1) {
written = snprintf(buffer + buffer_offset, remaining_length, "%d, ", value); written = snprintf(buffer + buffer_offset, remaining_length, "%d, ", value);
} else { } else {
written = snprintf(buffer + buffer_offset, remaining_length, "%d", value); written = snprintf(buffer + buffer_offset, remaining_length, "%d", value);
@ -40,6 +40,7 @@ void RawDumper::dump(RemoteReceiveData src) {
if (buffer_offset != 0) { if (buffer_offset != 0) {
ESP_LOGD(TAG, "%s", buffer); ESP_LOGD(TAG, "%s", buffer);
} }
return true;
} }
} // namespace remote_base } // namespace remote_base

View file

@ -44,9 +44,9 @@ template<typename... Ts> class RawAction : public RemoteTransmitterActionBase<Ts
this->code_static_ = code; this->code_static_ = code;
this->code_static_len_ = len; this->code_static_len_ = len;
} }
TEMPLATABLE_VALUE(uint32_t, carrier_frequency);
void encode(RemoteTransmitData *dst, Ts... x) override { void encode(RemoteTransmitData *dst, Ts... x) override {
// dst->set_data(data);
if (this->code_static_ != nullptr) { if (this->code_static_ != nullptr) {
for (size_t i = 0; i < this->code_static_len_; i++) { for (size_t i = 0; i < this->code_static_len_; i++) {
auto val = this->code_static_[i]; auto val = this->code_static_[i];
@ -58,6 +58,7 @@ template<typename... Ts> class RawAction : public RemoteTransmitterActionBase<Ts
} else { } else {
dst->set_data(this->code_func_(x...)); dst->set_data(this->code_func_(x...));
} }
dst->set_carrier_frequency(this->carrier_frequency_.value(x...));
} }
protected: protected:
@ -68,7 +69,8 @@ template<typename... Ts> class RawAction : public RemoteTransmitterActionBase<Ts
class RawDumper : public RemoteReceiverDumperBase { class RawDumper : public RemoteReceiverDumperBase {
public: public:
void dump(RemoteReceiveData src) override; bool dump(RemoteReceiveData src) override;
bool is_secondary() override { return true; }
}; };
} // namespace remote_base } // namespace remote_base

View file

@ -53,6 +53,7 @@ void RCSwitchBase::sync(RemoteTransmitData *dst) const {
} }
} }
void RCSwitchBase::transmit(RemoteTransmitData *dst, uint32_t code, uint8_t len) const { void RCSwitchBase::transmit(RemoteTransmitData *dst, uint32_t code, uint8_t len) const {
dst->set_carrier_frequency(0);
for (int16_t i = len - 1; i >= 0; i--) { for (int16_t i = len - 1; i >= 0; i--) {
if (code & (1 << i)) if (code & (1 << i))
this->one(dst); this->one(dst);
@ -224,21 +225,25 @@ bool RCSwitchRawReceiver::matches(RemoteReceiveData src) {
return decoded_nbits == this->nbits_ && decoded_code == this->code_; return decoded_nbits == this->nbits_ && decoded_code == this->code_;
} }
void RCSwitchDumper::dump(RemoteReceiveData src) { bool RCSwitchDumper::dump(RemoteReceiveData src) {
for (uint8_t i = 1; i <= 7; i++) { for (uint8_t i = 1; i <= 7; i++) {
src.reset(); src.reset();
uint32_t out_data; uint32_t out_data;
uint8_t out_nbits; uint8_t out_nbits;
RCSwitchBase *protocol = &rc_switch_protocols[i]; RCSwitchBase *protocol = &rc_switch_protocols[i];
if (protocol->decode(src, &out_data, &out_nbits)) { if (protocol->decode(src, &out_data, &out_nbits) && out_nbits >= 3) {
char buffer[32]; char buffer[32];
for (uint8_t j = 0; j < out_nbits; j++) for (uint8_t j = 0; j < out_nbits; j++)
buffer[j] = (out_data & (1 << (out_nbits - j - 1))) ? '1' : '0'; buffer[j] = (out_data & (1 << (out_nbits - j - 1))) ? '1' : '0';
buffer[out_nbits] = '\0'; buffer[out_nbits] = '\0';
ESP_LOGD(TAG, "Received RCSwitch Raw: protocol=%u data='%s'", i, buffer); ESP_LOGD(TAG, "Received RCSwitch Raw: protocol=%u data='%s'", i, buffer);
// only send first decoded protocol
return true;
} }
} }
return false;
} }
} // namespace remote_base } // namespace remote_base

View file

@ -197,7 +197,7 @@ class RCSwitchRawReceiver : public RemoteReceiverBinarySensorBase {
class RCSwitchDumper : public RemoteReceiverDumperBase { class RCSwitchDumper : public RemoteReceiverDumperBase {
public: public:
void dump(RemoteReceiveData src) override; bool dump(RemoteReceiveData src) override;
}; };
} // namespace remote_base } // namespace remote_base

View file

@ -212,14 +212,21 @@ class RemoteReceiverListener {
class RemoteReceiverDumperBase { class RemoteReceiverDumperBase {
public: public:
virtual void dump(RemoteReceiveData src) = 0; virtual bool dump(RemoteReceiveData src) = 0;
virtual bool is_secondary() { return false; }
}; };
class RemoteReceiverBase : public RemoteComponentBase { class RemoteReceiverBase : public RemoteComponentBase {
public: public:
RemoteReceiverBase(GPIOPin *pin) : RemoteComponentBase(pin) {} RemoteReceiverBase(GPIOPin *pin) : RemoteComponentBase(pin) {}
void register_listener(RemoteReceiverListener *listener) { this->listeners_.push_back(listener); } void register_listener(RemoteReceiverListener *listener) { this->listeners_.push_back(listener); }
void register_dumper(RemoteReceiverDumperBase *dumper) { this->dumpers_.push_back(dumper); } void register_dumper(RemoteReceiverDumperBase *dumper) {
if (dumper->is_secondary()) {
this->secondary_dumpers_.push_back(dumper);
} else {
this->dumpers_.push_back(dumper);
}
}
void set_tolerance(uint8_t tolerance) { tolerance_ = tolerance; } void set_tolerance(uint8_t tolerance) { tolerance_ = tolerance; }
protected: protected:
@ -233,9 +240,17 @@ class RemoteReceiverBase : public RemoteComponentBase {
return success; return success;
} }
void call_dumpers_() { void call_dumpers_() {
bool success = false;
for (auto *dumper : this->dumpers_) { for (auto *dumper : this->dumpers_) {
auto data = RemoteReceiveData(&this->temp_, this->tolerance_); auto data = RemoteReceiveData(&this->temp_, this->tolerance_);
dumper->dump(data); if (dumper->dump(data))
success = true;
}
if (!success) {
for (auto *dumper : this->secondary_dumpers_) {
auto data = RemoteReceiveData(&this->temp_, this->tolerance_);
dumper->dump(data);
}
} }
} }
void call_listeners_dumpers_() { void call_listeners_dumpers_() {
@ -247,6 +262,7 @@ class RemoteReceiverBase : public RemoteComponentBase {
std::vector<RemoteReceiverListener *> listeners_; std::vector<RemoteReceiverListener *> listeners_;
std::vector<RemoteReceiverDumperBase *> dumpers_; std::vector<RemoteReceiverDumperBase *> dumpers_;
std::vector<RemoteReceiverDumperBase *> secondary_dumpers_;
std::vector<int32_t> temp_; std::vector<int32_t> temp_;
uint8_t tolerance_{25}; uint8_t tolerance_{25};
}; };
@ -323,12 +339,13 @@ template<typename... Ts> class RemoteTransmitterActionBase : public Action<Ts...
template<typename T, typename D> class RemoteReceiverDumper : public RemoteReceiverDumperBase { template<typename T, typename D> class RemoteReceiverDumper : public RemoteReceiverDumperBase {
public: public:
void dump(RemoteReceiveData src) override { bool dump(RemoteReceiveData src) override {
auto proto = T(); auto proto = T();
auto decoded = proto.decode(src); auto decoded = proto.decode(src);
if (!decoded.has_value()) if (!decoded.has_value())
return; return false;
proto.dump(*decoded); proto.dump(*decoded);
return true;
} }
}; };

View file

@ -53,6 +53,10 @@ void RemoteReceiverComponent::setup() {
void RemoteReceiverComponent::dump_config() { void RemoteReceiverComponent::dump_config() {
ESP_LOGCONFIG(TAG, "Remote Receiver:"); ESP_LOGCONFIG(TAG, "Remote Receiver:");
LOG_PIN(" Pin: ", this->pin_); LOG_PIN(" Pin: ", this->pin_);
if (this->pin_->digital_read()) {
ESP_LOGW(TAG, "Remote Receiver Signal starts with a HIGH value. Usually this means you have to "
"invert the signal using 'inverted: True' in the pin schema!");
}
ESP_LOGCONFIG(TAG, " Channel: %d", this->channel_); ESP_LOGCONFIG(TAG, " Channel: %d", this->channel_);
ESP_LOGCONFIG(TAG, " Clock divider: %u", this->clock_divider_); ESP_LOGCONFIG(TAG, " Clock divider: %u", this->clock_divider_);
ESP_LOGCONFIG(TAG, " Tolerance: %u%%", this->tolerance_); ESP_LOGCONFIG(TAG, " Tolerance: %u%%", this->tolerance_);

View file

@ -33,7 +33,7 @@ CONFIG_SCHEMA = cv.All(cv.Schema({
sensor.sensor_schema(UNIT_MICROGRAMS_PER_CUBIC_METER, ICON_CHEMICAL_WEAPON, 1), sensor.sensor_schema(UNIT_MICROGRAMS_PER_CUBIC_METER, ICON_CHEMICAL_WEAPON, 1),
cv.Optional(CONF_RX_ONLY, default=False): cv.boolean, cv.Optional(CONF_RX_ONLY, default=False): cv.boolean,
cv.Optional(CONF_UPDATE_INTERVAL, default='0min'): cv.positive_time_period_minutes, cv.Optional(CONF_UPDATE_INTERVAL): cv.positive_time_period_minutes,
}).extend(cv.COMPONENT_SCHEMA).extend(uart.UART_DEVICE_SCHEMA), validate_sds011_rx_mode) }).extend(cv.COMPONENT_SCHEMA).extend(uart.UART_DEVICE_SCHEMA), validate_sds011_rx_mode)
@ -42,7 +42,8 @@ def to_code(config):
yield cg.register_component(var, config) yield cg.register_component(var, config)
yield uart.register_uart_device(var, config) yield uart.register_uart_device(var, config)
cg.add(var.set_update_interval_min(config[CONF_UPDATE_INTERVAL])) if CONF_UPDATE_INTERVAL in config:
cg.add(var.set_update_interval_min(config[CONF_UPDATE_INTERVAL]))
cg.add(var.set_rx_mode_only(config[CONF_RX_ONLY])) cg.add(var.set_rx_mode_only(config[CONF_RX_ONLY]))
if CONF_PM_2_5 in config: if CONF_PM_2_5 in config:

View file

@ -148,7 +148,7 @@ def exponential_moving_average_filter_to_code(config, filter_id):
yield cg.new_Pvariable(filter_id, config[CONF_ALPHA], config[CONF_SEND_EVERY]) yield cg.new_Pvariable(filter_id, config[CONF_ALPHA], config[CONF_SEND_EVERY])
@FILTER_REGISTRY.register('lambda', LambdaFilter, cv.lambda_) @FILTER_REGISTRY.register('lambda', LambdaFilter, cv.returning_lambda)
def lambda_filter_to_code(config, filter_id): def lambda_filter_to_code(config, filter_id):
lambda_ = yield cg.process_lambda(config, [(float, 'x')], lambda_ = yield cg.process_lambda(config, [(float, 'x')],
return_type=cg.optional.template(float)) return_type=cg.optional.template(float))

View file

@ -88,10 +88,8 @@ std::string Sensor::unique_id() { return ""; }
void Sensor::internal_send_state_to_frontend(float state) { void Sensor::internal_send_state_to_frontend(float state) {
this->has_state_ = true; this->has_state_ = true;
this->state = state; this->state = state;
if (this->filter_list_ != nullptr) { ESP_LOGD(TAG, "'%s': Sending state %.5f %s with %d decimals of accuracy", this->get_name().c_str(), state,
ESP_LOGD(TAG, "'%s': Sending state %.5f %s with %d decimals of accuracy", this->get_name().c_str(), state, this->get_unit_of_measurement().c_str(), this->get_accuracy_decimals());
this->get_unit_of_measurement().c_str(), this->get_accuracy_decimals());
}
this->callback_.call(state); this->callback_.call(state);
} }
bool Sensor::has_state() const { return this->has_state_; } bool Sensor::has_state() const { return this->has_state_; }

View file

@ -0,0 +1,103 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import automation
from esphome.components import time
from esphome.const import CONF_TIME_ID, CONF_ID, CONF_TRIGGER_ID
sun_ns = cg.esphome_ns.namespace('sun')
Sun = sun_ns.class_('Sun')
SunTrigger = sun_ns.class_('SunTrigger', cg.PollingComponent, automation.Trigger.template())
SunCondition = sun_ns.class_('SunCondition', automation.Condition)
CONF_SUN_ID = 'sun_id'
CONF_LATITUDE = 'latitude'
CONF_LONGITUDE = 'longitude'
CONF_ELEVATION = 'elevation'
CONF_ON_SUNRISE = 'on_sunrise'
CONF_ON_SUNSET = 'on_sunset'
ELEVATION_MAP = {
'sunrise': 0.0,
'sunset': 0.0,
'civil': -6.0,
'nautical': -12.0,
'astronomical': -18.0,
}
def elevation(value):
if isinstance(value, str):
try:
value = ELEVATION_MAP[cv.one_of(*ELEVATION_MAP, lower=True, space='_')]
except cv.Invalid:
pass
value = cv.angle(value)
return cv.float_range(min=-180, max=180)(value)
CONFIG_SCHEMA = cv.Schema({
cv.GenerateID(): cv.declare_id(Sun),
cv.GenerateID(CONF_TIME_ID): cv.use_id(time.RealTimeClock),
cv.Required(CONF_LATITUDE): cv.float_range(min=-90, max=90),
cv.Required(CONF_LONGITUDE): cv.float_range(min=-180, max=180),
cv.Optional(CONF_ON_SUNRISE): automation.validate_automation({
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(SunTrigger),
cv.Optional(CONF_ELEVATION, default=0.0): elevation,
}),
cv.Optional(CONF_ON_SUNSET): automation.validate_automation({
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(SunTrigger),
cv.Optional(CONF_ELEVATION, default=0.0): elevation,
}),
})
def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
time_ = yield cg.get_variable(config[CONF_TIME_ID])
cg.add(var.set_time(time_))
cg.add(var.set_latitude(config[CONF_LATITUDE]))
cg.add(var.set_longitude(config[CONF_LONGITUDE]))
for conf in config.get(CONF_ON_SUNRISE, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID])
yield cg.register_component(trigger, conf)
yield cg.register_parented(trigger, var)
cg.add(trigger.set_sunrise(True))
cg.add(trigger.set_elevation(conf[CONF_ELEVATION]))
yield automation.build_automation(trigger, [], conf)
for conf in config.get(CONF_ON_SUNSET, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID])
yield cg.register_component(trigger, conf)
yield cg.register_parented(trigger, var)
cg.add(trigger.set_sunrise(False))
cg.add(trigger.set_elevation(conf[CONF_ELEVATION]))
yield automation.build_automation(trigger, [], conf)
@automation.register_condition('sun.is_above_horizon', SunCondition, cv.Schema({
cv.GenerateID(): cv.use_id(Sun),
cv.Optional(CONF_ELEVATION, default=0): cv.templatable(elevation),
}))
def sun_above_horizon_to_code(config, condition_id, template_arg, args):
var = cg.new_Pvariable(condition_id, template_arg)
yield cg.register_parented(var, config[CONF_ID])
templ = yield cg.templatable(config[CONF_ELEVATION], args, cg.double)
cg.add(var.set_elevation(templ))
cg.add(var.set_above(True))
yield var
@automation.register_condition('sun.is_below_horizon', SunCondition, cv.Schema({
cv.GenerateID(): cv.use_id(Sun),
cv.Optional(CONF_ELEVATION, default=0): cv.templatable(elevation),
}))
def sun_below_horizon_to_code(config, condition_id, template_arg, args):
var = cg.new_Pvariable(condition_id, template_arg)
yield cg.register_parented(var, config[CONF_ID])
templ = yield cg.templatable(config[CONF_ELEVATION], args, cg.double)
cg.add(var.set_elevation(templ))
cg.add(var.set_above(False))
yield var

View file

@ -0,0 +1,30 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import sensor
from esphome.const import UNIT_DEGREES, ICON_WEATHER_SUNSET, CONF_ID, CONF_TYPE
from .. import sun_ns, CONF_SUN_ID, Sun
DEPENDENCIES = ['sun']
SunSensor = sun_ns.class_('SunSensor', sensor.Sensor, cg.PollingComponent)
SensorType = sun_ns.enum('SensorType')
TYPES = {
'elevation': SensorType.SUN_SENSOR_ELEVATION,
'azimuth': SensorType.SUN_SENSOR_AZIMUTH,
}
CONFIG_SCHEMA = sensor.sensor_schema(UNIT_DEGREES, ICON_WEATHER_SUNSET, 1).extend({
cv.GenerateID(): cv.declare_id(SunSensor),
cv.GenerateID(CONF_SUN_ID): cv.use_id(Sun),
cv.Required(CONF_TYPE): cv.enum(TYPES, lower=True),
}).extend(cv.polling_component_schema('60s'))
def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
yield cg.register_component(var, config)
yield sensor.register_sensor(var, config)
cg.add(var.set_type(config[CONF_TYPE]))
paren = yield cg.get_variable(config[CONF_SUN_ID])
cg.add(var.set_parent(paren))

View file

@ -0,0 +1,12 @@
#include "sun_sensor.h"
#include "esphome/core/log.h"
namespace esphome {
namespace sun {
static const char *TAG = "sun.sensor";
void SunSensor::dump_config() { LOG_SENSOR("", "Sun Sensor", this); }
} // namespace sun
} // namespace esphome

View file

@ -0,0 +1,41 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/sun/sun.h"
#include "esphome/components/sensor/sensor.h"
namespace esphome {
namespace sun {
enum SensorType {
SUN_SENSOR_ELEVATION,
SUN_SENSOR_AZIMUTH,
};
class SunSensor : public sensor::Sensor, public PollingComponent {
public:
void set_parent(Sun *parent) { parent_ = parent; }
void set_type(SensorType type) { type_ = type; }
void dump_config() override;
void update() override {
double val;
switch (this->type_) {
case SUN_SENSOR_ELEVATION:
val = this->parent_->elevation();
break;
case SUN_SENSOR_AZIMUTH:
val = this->parent_->azimuth();
break;
default:
return;
}
this->publish_state(val);
}
protected:
sun::Sun *parent_;
SensorType type_;
};
} // namespace sun
} // namespace esphome

View file

@ -0,0 +1,168 @@
#include "sun.h"
#include "esphome/core/log.h"
namespace esphome {
namespace sun {
static const char *TAG = "sun";
#undef PI
/* Usually, ESPHome uses single-precision floating point values
* because those tend to be accurate enough and are more efficient.
*
* However, some of the data in this class has to be quite accurate, so double is
* used everywhere.
*/
static const double PI = 3.141592653589793;
static const double TAU = 6.283185307179586;
static const double TO_RADIANS = PI / 180.0;
static const double TO_DEGREES = 180.0 / PI;
static const double EARTH_TILT = 23.44 * TO_RADIANS;
optional<time::ESPTime> Sun::sunrise(double elevation) {
auto time = this->time_->now();
if (!time.is_valid())
return {};
double sun_time = this->sun_time_for_elevation_(time.day_of_year, elevation, true);
if (isnan(sun_time))
return {};
uint32_t epoch = this->calc_epoch_(time, sun_time);
return time::ESPTime::from_epoch_local(epoch);
}
optional<time::ESPTime> Sun::sunset(double elevation) {
auto time = this->time_->now();
if (!time.is_valid())
return {};
double sun_time = this->sun_time_for_elevation_(time.day_of_year, elevation, false);
if (isnan(sun_time))
return {};
uint32_t epoch = this->calc_epoch_(time, sun_time);
return time::ESPTime::from_epoch_local(epoch);
}
double Sun::elevation() {
auto time = this->current_sun_time_();
if (isnan(time))
return NAN;
return this->elevation_(time);
}
double Sun::azimuth() {
auto time = this->current_sun_time_();
if (isnan(time))
return NAN;
return this->azimuth_(time);
}
double Sun::sun_declination_(double sun_time) {
double n = sun_time - 1.0;
// maximum declination
const double tot = -sin(EARTH_TILT);
// eccentricity of the earth's orbit (ellipse)
double eccentricity = 0.0167;
// days since perihelion (January 3rd)
double days_since_perihelion = n - 2;
// days since december solstice (december 22)
double days_since_december_solstice = n + 10;
const double c = TAU / 365.24;
double v = cos(c * days_since_december_solstice + 2 * eccentricity * sin(c * days_since_perihelion));
// Make sure value is in range (double error may lead to results slightly larger than 1)
double x = clamp(tot * v, 0, 1);
return asin(x);
}
double Sun::elevation_ratio_(double sun_time) {
double decl = this->sun_declination_(sun_time);
double hangle = this->hour_angle_(sun_time);
double a = sin(this->latitude_rad_()) * sin(decl);
double b = cos(this->latitude_rad_()) * cos(decl) * cos(hangle);
double val = clamp(a + b, -1.0, 1.0);
return val;
}
double Sun::latitude_rad_() { return this->latitude_ * TO_RADIANS; }
double Sun::hour_angle_(double sun_time) {
double time_of_day = fmod(sun_time, 1.0) * 24.0;
return -PI * (time_of_day - 12) / 12;
}
double Sun::elevation_(double sun_time) { return this->elevation_rad_(sun_time) * TO_DEGREES; }
double Sun::elevation_rad_(double sun_time) { return asin(this->elevation_ratio_(sun_time)); }
double Sun::zenith_rad_(double sun_time) { return acos(this->elevation_ratio_(sun_time)); }
double Sun::azimuth_rad_(double sun_time) {
double hangle = -this->hour_angle_(sun_time);
double decl = this->sun_declination_(sun_time);
double zen = this->zenith_rad_(sun_time);
double nom = cos(zen) * sin(this->latitude_rad_()) - sin(decl);
double denom = sin(zen) * cos(this->latitude_rad_());
double v = clamp(nom / denom, -1.0, 1.0);
double az = PI - acos(v);
if (hangle > 0)
az = -az;
if (az < 0)
az += TAU;
return az;
}
double Sun::azimuth_(double sun_time) { return this->azimuth_rad_(sun_time) * TO_DEGREES; }
double Sun::calc_sun_time_(const time::ESPTime &time) {
// Time as seen at 0° longitude
if (!time.is_valid())
return NAN;
double base = (time.day_of_year + time.hour / 24.0 + time.minute / 24.0 / 60.0 + time.second / 24.0 / 60.0 / 60.0);
// Add longitude correction
double add = this->longitude_ / 360.0;
return base + add;
}
uint32_t Sun::calc_epoch_(time::ESPTime base, double sun_time) {
sun_time -= this->longitude_ / 360.0;
base.day_of_year = uint32_t(floor(sun_time));
sun_time = (sun_time - base.day_of_year) * 24.0;
base.hour = uint32_t(floor(sun_time));
sun_time = (sun_time - base.hour) * 60.0;
base.minute = uint32_t(floor(sun_time));
sun_time = (sun_time - base.minute) * 60.0;
base.second = uint32_t(floor(sun_time));
base.recalc_timestamp_utc(true);
return base.timestamp;
}
double Sun::sun_time_for_elevation_(int32_t day_of_year, double elevation, bool rising) {
// Use binary search, newton's method would be better but binary search already
// converges quite well (19 cycles) and much simpler. Function is guaranteed to be
// monotonous.
double lo, hi;
if (rising) {
lo = day_of_year + 0.0;
hi = day_of_year + 0.5;
} else {
lo = day_of_year + 1.0;
hi = day_of_year + 0.5;
}
double min_elevation = this->elevation_(lo);
double max_elevation = this->elevation_(hi);
if (elevation < min_elevation || elevation > max_elevation)
return NAN;
// Accuracy: 0.1s
const double accuracy = 1.0 / (24.0 * 60.0 * 60.0 * 10.0);
while (fabs(hi - lo) > accuracy) {
double mid = (lo + hi) / 2.0;
double value = this->elevation_(mid) - elevation;
if (value < 0) {
lo = mid;
} else if (value > 0) {
hi = mid;
} else {
lo = hi = mid;
break;
}
}
return (lo + hi) / 2.0;
}
} // namespace sun
} // namespace esphome

View file

@ -0,0 +1,137 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/helpers.h"
#include "esphome/core/automation.h"
#include "esphome/components/time/real_time_clock.h"
namespace esphome {
namespace sun {
class Sun {
public:
void set_time(time::RealTimeClock *time) { time_ = time; }
time::RealTimeClock *get_time() const { return time_; }
void set_latitude(double latitude) { latitude_ = latitude; }
void set_longitude(double longitude) { longitude_ = longitude; }
optional<time::ESPTime> sunrise(double elevation = 0.0);
optional<time::ESPTime> sunset(double elevation = 0.0);
double elevation();
double azimuth();
protected:
double current_sun_time_() { return this->calc_sun_time_(this->time_->utcnow()); }
/** Calculate the declination of the sun in rad.
*
* See https://en.wikipedia.org/wiki/Position_of_the_Sun#Declination_of_the_Sun_as_seen_from_Earth
*
* Accuracy: ±0.2°
*
* @param sun_time The day of the year, 1 means January 1st. See calc_sun_time_.
* @return Sun declination in degrees
*/
double sun_declination_(double sun_time);
double elevation_ratio_(double sun_time);
/** Calculate the hour angle based on the sun time of day in hours.
*
* Positive in morning, 0 at noon, negative in afternoon.
*
* @param sun_time Sun time, see calc_sun_time_.
* @return Hour angle in rad.
*/
double hour_angle_(double sun_time);
double elevation_(double sun_time);
double elevation_rad_(double sun_time);
double zenith_rad_(double sun_time);
double azimuth_rad_(double sun_time);
double azimuth_(double sun_time);
/** Return the sun time given by the time_ object.
*
* Sun time is defined as doubleing point day of year.
* Integer part encodes the day of the year (1=January 1st)
* Decimal part encodes time of day (1/24 = 1 hour)
*/
double calc_sun_time_(const time::ESPTime &time);
uint32_t calc_epoch_(time::ESPTime base, double sun_time);
/** Calculate the sun time of day
*
* @param day_of_year
* @param elevation
* @param rising
* @return
*/
double sun_time_for_elevation_(int32_t day_of_year, double elevation, bool rising);
double latitude_rad_();
time::RealTimeClock *time_;
/// Latitude in degrees, range: -90 to 90.
double latitude_;
/// Longitude in degrees, range: -180 to 180.
double longitude_;
};
class SunTrigger : public Trigger<>, public PollingComponent, public Parented<Sun> {
public:
SunTrigger() : PollingComponent(1000) {}
void set_sunrise(bool sunrise) { sunrise_ = sunrise; }
void set_elevation(double elevation) { elevation_ = elevation; }
void update() override {
double current = this->parent_->elevation();
if (isnan(current))
return;
bool crossed;
if (this->sunrise_) {
crossed = this->last_elevation_ <= this->elevation_ && this->elevation_ < current;
} else {
crossed = this->last_elevation_ >= this->elevation_ && this->elevation_ > current;
}
if (crossed) {
this->trigger();
}
this->last_elevation_ = current;
}
protected:
bool sunrise_;
double last_elevation_;
double elevation_;
};
template<typename... Ts> class SunCondition : public Condition<Ts...>, public Parented<Sun> {
public:
TEMPLATABLE_VALUE(double, elevation);
void set_above(bool above) { above_ = above; }
bool check(Ts... x) override {
double elevation = this->elevation_.value(x...);
double current = this->parent_->elevation();
if (this->above_)
return current > elevation;
else
return current < elevation;
}
protected:
bool above_;
};
} // namespace sun
} // namespace esphome

View file

@ -0,0 +1,45 @@
from esphome.components import text_sensor
import esphome.config_validation as cv
import esphome.codegen as cg
from esphome.const import CONF_ICON, ICON_WEATHER_SUNSET_DOWN, ICON_WEATHER_SUNSET_UP, CONF_TYPE, \
CONF_ID, CONF_FORMAT
from .. import sun_ns, CONF_SUN_ID, Sun, CONF_ELEVATION, elevation
DEPENDENCIES = ['sun']
SunTextSensor = sun_ns.class_('SunTextSensor', text_sensor.TextSensor, cg.PollingComponent)
SUN_TYPES = {
'sunset': False,
'sunrise': True,
}
def validate_optional_icon(config):
if CONF_ICON not in config:
config = config.copy()
config[CONF_ICON] = {
'sunset': ICON_WEATHER_SUNSET_DOWN,
'sunrise': ICON_WEATHER_SUNSET_UP,
}[config[CONF_TYPE]]
return config
CONFIG_SCHEMA = cv.All(text_sensor.TEXT_SENSOR_SCHEMA.extend({
cv.GenerateID(): cv.declare_id(SunTextSensor),
cv.GenerateID(CONF_SUN_ID): cv.use_id(Sun),
cv.Required(CONF_TYPE): cv.one_of(*SUN_TYPES, lower=True),
cv.Optional(CONF_ELEVATION, default=0): elevation,
cv.Optional(CONF_FORMAT, default='%X'): cv.string_strict,
}).extend(cv.polling_component_schema('60s')), validate_optional_icon)
def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
yield cg.register_component(var, config)
yield text_sensor.register_text_sensor(var, config)
paren = yield cg.get_variable(config[CONF_SUN_ID])
cg.add(var.set_parent(paren))
cg.add(var.set_sunrise(SUN_TYPES[config[CONF_TYPE]]))
cg.add(var.set_elevation(config[CONF_ELEVATION]))
cg.add(var.set_format(config[CONF_FORMAT]))

View file

@ -0,0 +1,12 @@
#include "sun_text_sensor.h"
#include "esphome/core/log.h"
namespace esphome {
namespace sun {
static const char *TAG = "sun.text_sensor";
void SunTextSensor::dump_config() { LOG_TEXT_SENSOR("", "Sun Text Sensor", this); }
} // namespace sun
} // namespace esphome

View file

@ -0,0 +1,41 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/sun/sun.h"
#include "esphome/components/text_sensor/text_sensor.h"
namespace esphome {
namespace sun {
class SunTextSensor : public text_sensor::TextSensor, public PollingComponent {
public:
void set_parent(Sun *parent) { parent_ = parent; }
void set_elevation(double elevation) { elevation_ = elevation; }
void set_sunrise(bool sunrise) { sunrise_ = sunrise; }
void set_format(const std::string &format) { format_ = format; }
void update() override {
optional<time::ESPTime> res;
if (this->sunrise_)
res = this->parent_->sunrise(this->elevation_);
else
res = this->parent_->sunset(this->elevation_);
if (!res) {
this->publish_state("");
return;
}
this->publish_state(res->strftime(this->format_));
}
void dump_config() override;
protected:
std::string format_{};
Sun *parent_;
double elevation_;
bool sunrise_;
};
} // namespace sun
} // namespace esphome

View file

@ -10,7 +10,7 @@ TemplateBinarySensor = template_ns.class_('TemplateBinarySensor', binary_sensor.
CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend({ CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend({
cv.GenerateID(): cv.declare_id(TemplateBinarySensor), cv.GenerateID(): cv.declare_id(TemplateBinarySensor),
cv.Optional(CONF_LAMBDA): cv.lambda_, cv.Optional(CONF_LAMBDA): cv.returning_lambda,
}).extend(cv.COMPONENT_SCHEMA) }).extend(cv.COMPONENT_SCHEMA)

View file

@ -18,7 +18,7 @@ RESTORE_MODES = {
CONFIG_SCHEMA = cover.COVER_SCHEMA.extend({ CONFIG_SCHEMA = cover.COVER_SCHEMA.extend({
cv.GenerateID(): cv.declare_id(TemplateCover), cv.GenerateID(): cv.declare_id(TemplateCover),
cv.Optional(CONF_LAMBDA): cv.lambda_, cv.Optional(CONF_LAMBDA): cv.returning_lambda,
cv.Optional(CONF_OPTIMISTIC, default=False): cv.boolean, cv.Optional(CONF_OPTIMISTIC, default=False): cv.boolean,
cv.Optional(CONF_ASSUMED_STATE, default=False): cv.boolean, cv.Optional(CONF_ASSUMED_STATE, default=False): cv.boolean,
cv.Optional(CONF_OPEN_ACTION): automation.validate_automation(single=True), cv.Optional(CONF_OPEN_ACTION): automation.validate_automation(single=True),

View file

@ -0,0 +1,35 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import automation
from esphome.components import output
from esphome.const import CONF_ID, CONF_TYPE
from .. import template_ns
TemplateBinaryOutput = template_ns.class_('TemplateBinaryOutput', output.BinaryOutput)
TemplateFloatOutput = template_ns.class_('TemplateFloatOutput', output.FloatOutput)
CONF_BINARY = 'binary'
CONF_FLOAT = 'float'
CONF_WRITE_ACTION = 'write_action'
CONFIG_SCHEMA = cv.typed_schema({
CONF_BINARY: output.BINARY_OUTPUT_SCHEMA.extend({
cv.GenerateID(): cv.declare_id(TemplateBinaryOutput),
cv.Required(CONF_WRITE_ACTION): automation.validate_automation(single=True),
}),
CONF_FLOAT: output.FLOAT_OUTPUT_SCHEMA.extend({
cv.GenerateID(): cv.declare_id(TemplateFloatOutput),
cv.Required(CONF_WRITE_ACTION): automation.validate_automation(single=True),
}),
}, lower=True)
def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
if config[CONF_TYPE] == CONF_BINARY:
yield automation.build_automation(var.get_trigger(), [(bool, 'state')],
config[CONF_WRITE_ACTION])
else:
yield automation.build_automation(var.get_trigger(), [(float, 'state')],
config[CONF_WRITE_ACTION])
yield output.register_output(var, config)

View file

@ -0,0 +1,31 @@
#pragma once
#include "esphome/core/automation.h"
#include "esphome/components/output/binary_output.h"
#include "esphome/components/output/float_output.h"
namespace esphome {
namespace template_ {
class TemplateBinaryOutput : public output::BinaryOutput {
public:
Trigger<bool> *get_trigger() const { return trigger_; }
protected:
void write_state(bool state) override { this->trigger_->trigger(state); }
Trigger<bool> *trigger_ = new Trigger<bool>();
};
class TemplateFloatOutput : public output::FloatOutput {
public:
Trigger<float> *get_trigger() const { return trigger_; }
protected:
void write_state(float state) override { this->trigger_->trigger(state); }
Trigger<float> *trigger_ = new Trigger<float>();
};
} // namespace template_
} // namespace esphome

View file

@ -9,7 +9,7 @@ TemplateSensor = template_ns.class_('TemplateSensor', sensor.Sensor, cg.PollingC
CONFIG_SCHEMA = sensor.sensor_schema(UNIT_EMPTY, ICON_EMPTY, 1).extend({ CONFIG_SCHEMA = sensor.sensor_schema(UNIT_EMPTY, ICON_EMPTY, 1).extend({
cv.GenerateID(): cv.declare_id(TemplateSensor), cv.GenerateID(): cv.declare_id(TemplateSensor),
cv.Optional(CONF_LAMBDA): cv.lambda_, cv.Optional(CONF_LAMBDA): cv.returning_lambda,
}).extend(cv.polling_component_schema('60s')) }).extend(cv.polling_component_schema('60s'))

View file

@ -10,7 +10,7 @@ TemplateSwitch = template_ns.class_('TemplateSwitch', switch.Switch, cg.Componen
CONFIG_SCHEMA = switch.SWITCH_SCHEMA.extend({ CONFIG_SCHEMA = switch.SWITCH_SCHEMA.extend({
cv.GenerateID(): cv.declare_id(TemplateSwitch), cv.GenerateID(): cv.declare_id(TemplateSwitch),
cv.Optional(CONF_LAMBDA): cv.lambda_, cv.Optional(CONF_LAMBDA): cv.returning_lambda,
cv.Optional(CONF_OPTIMISTIC, default=False): cv.boolean, cv.Optional(CONF_OPTIMISTIC, default=False): cv.boolean,
cv.Optional(CONF_ASSUMED_STATE, default=False): cv.boolean, cv.Optional(CONF_ASSUMED_STATE, default=False): cv.boolean,
cv.Optional(CONF_TURN_OFF_ACTION): automation.validate_automation(single=True), cv.Optional(CONF_TURN_OFF_ACTION): automation.validate_automation(single=True),

Some files were not shown because too many files have changed in this diff Show more