Updates for 1.13 (#546)

* Update CI matcher

* Check Executable bit

* Quicklint

* Updates

* Allow pm1.0 and pm10.0 for PMS5003ST

Fixes https://github.com/esphome/feature-requests/issues/225

* PowerSupplyRequester

* Lint

* Include debug data in generated main.cpp

* Updates

* Auto-select bit_depth

* Updates
This commit is contained in:
Otto Winter 2019-05-12 23:04:36 +02:00 committed by GitHub
parent 797aadaf26
commit f811b1157c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
41 changed files with 438 additions and 418 deletions

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

@ -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()

View file

@ -10,7 +10,7 @@
# 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,

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

@ -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

@ -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,7 +59,7 @@ 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.int_range(min=1, max=15), cv.Optional(CONF_BIT_DEPTH): cv.int_range(min=1, max=15),
cv.Optional(CONF_CHANNEL): cv.int_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

@ -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

@ -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

@ -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

@ -92,36 +92,27 @@ class SunTrigger : public Trigger<>, public PollingComponent, public Parented<Su
void set_elevation(double elevation) { elevation_ = elevation; } void set_elevation(double elevation) { elevation_ = elevation; }
void update() override { void update() override {
auto now = this->parent_->get_time()->utcnow(); double current = this->parent_->elevation();
if (!now.is_valid()) if (isnan(current))
return; return;
if (!this->last_result_.has_value() || this->last_result_->day_of_year != now.day_of_year) { bool crossed;
this->recalc_(); if (this->sunrise_) {
return; crossed = this->last_elevation_ <= this->elevation_ && this->elevation_ < current;
} else {
crossed = this->last_elevation_ >= this->elevation_ && this->elevation_ > current;
} }
if (this->prev_check_ != -1) { if (crossed) {
auto res = *this->last_result_; this->trigger();
// now >= sunrise > prev_check
if (now.timestamp >= res.timestamp && res.timestamp > this->prev_check_) {
this->trigger();
}
} }
this->prev_check_ = now.timestamp; this->last_elevation_ = current;
} }
protected: protected:
void recalc_() {
if (this->sunrise_)
this->last_result_ = this->parent_->sunrise(this->elevation_);
else
this->last_result_ = this->parent_->sunset(this->elevation_);
}
bool sunrise_; bool sunrise_;
double last_elevation_;
double elevation_; double elevation_;
time_t prev_check_{-1};
optional<time::ESPTime> last_result_{};
}; };
template<typename... Ts> class SunCondition : public Condition<Ts...>, public Parented<Sun> { template<typename... Ts> class SunCondition : public Condition<Ts...>, public Parented<Sun> {

View file

@ -4,7 +4,7 @@
namespace esphome { namespace esphome {
namespace time { namespace time {
static const char *TAG = "something.something"; static const char *TAG = "automation";
void CronTrigger::add_second(uint8_t second) { this->seconds_[second] = true; } void CronTrigger::add_second(uint8_t second) { this->seconds_[second] = true; }
void CronTrigger::add_minute(uint8_t minute) { this->minutes_[minute] = true; } void CronTrigger::add_minute(uint8_t minute) { this->minutes_[minute] = true; }

View file

@ -69,10 +69,6 @@ class ComponentManifest(object):
def auto_load(self): def auto_load(self):
return getattr(self.module, 'AUTO_LOAD', []) return getattr(self.module, 'AUTO_LOAD', [])
@property
def to_code_priority(self):
return getattr(self.module, 'TO_CODE_PRIORITY', [])
def _get_flags_set(self, name, config): def _get_flags_set(self, name, config):
if not hasattr(self.module, name): if not hasattr(self.module, name):
return set() return set()

View file

@ -8,6 +8,7 @@ import re
from contextlib import contextmanager from contextlib import contextmanager
import uuid as uuid_ import uuid as uuid_
from datetime import datetime from datetime import datetime
from string import ascii_letters, digits
import voluptuous as vol import voluptuous as vol
@ -279,8 +280,9 @@ def validate_id_name(value):
raise Invalid("First character in ID cannot be a digit.") raise Invalid("First character in ID cannot be a digit.")
if '-' in value: if '-' in value:
raise Invalid("Dashes are not supported in IDs, please use underscores instead.") raise Invalid("Dashes are not supported in IDs, please use underscores instead.")
valid_chars = ascii_letters + digits + '_'
for char in value: for char in value:
if char != '_' and not char.isalnum(): if char not in valid_chars:
raise Invalid(u"IDs must only consist of upper/lowercase characters, the underscore" raise Invalid(u"IDs must only consist of upper/lowercase characters, the underscore"
u"character and numbers. The character '{}' cannot be used" u"character and numbers. The character '{}' cannot be used"
u"".format(char)) u"".format(char))
@ -611,7 +613,7 @@ if IS_PY2:
path = u' @ data[%s]' % u']['.join(map(repr, self.path)) \ path = u' @ data[%s]' % u']['.join(map(repr, self.path)) \
if self.path else '' if self.path else ''
# pylint: disable=no-member # pylint: disable=no-member
output = Exception.__unicode__(self) output = self.message
if self.error_type: if self.error_type:
output += u' for ' + self.error_type output += u' for ' + self.error_type
return output + path return output + path

View file

@ -11,8 +11,6 @@ ESP_PLATFORM_ESP32 = 'ESP32'
ESP_PLATFORM_ESP8266 = 'ESP8266' ESP_PLATFORM_ESP8266 = 'ESP8266'
ESP_PLATFORMS = [ESP_PLATFORM_ESP32, ESP_PLATFORM_ESP8266] ESP_PLATFORMS = [ESP_PLATFORM_ESP32, ESP_PLATFORM_ESP8266]
APB_CLOCK_FREQ = 80000000
ALLOWED_NAME_CHARS = u'abcdefghijklmnopqrstuvwxyz0123456789_' ALLOWED_NAME_CHARS = u'abcdefghijklmnopqrstuvwxyz0123456789_'
ARDUINO_VERSION_ESP32_DEV = 'https://github.com/platformio/platform-espressif32.git#feature/stage' ARDUINO_VERSION_ESP32_DEV = 'https://github.com/platformio/platform-espressif32.git#feature/stage'
ARDUINO_VERSION_ESP32_1_0_0 = 'espressif32@1.5.0' ARDUINO_VERSION_ESP32_1_0_0 = 'espressif32@1.5.0'

View file

@ -60,7 +60,7 @@ void Application::setup() {
this->dump_config(); this->dump_config();
} }
void Application::dump_config() { void Application::dump_config() {
ESP_LOGI(TAG, "esphome-core version " ESPHOME_VERSION " compiled on %s", this->compilation_time_.c_str()); ESP_LOGI(TAG, "esphome version " ESPHOME_VERSION " compiled on %s", this->compilation_time_.c_str());
for (auto component : this->components_) { for (auto component : this->components_) {
component->dump_config(); component->dump_config();

View file

@ -89,8 +89,7 @@ def default_build_path():
CONFIG_SCHEMA = cv.Schema({ CONFIG_SCHEMA = cv.Schema({
cv.Required(CONF_NAME): cv.valid_name, cv.Required(CONF_NAME): cv.valid_name,
cv.Required(CONF_PLATFORM): cv.one_of('ESP8266', 'ESP32', 'ESPRESSIF32', cv.Required(CONF_PLATFORM): cv.one_of('ESP8266', 'ESP32', upper=True),
upper=True),
cv.Required(CONF_BOARD): validate_board, cv.Required(CONF_BOARD): validate_board,
cv.Optional(CONF_ARDUINO_VERSION, default='recommended'): validate_arduino_version, cv.Optional(CONF_ARDUINO_VERSION, default='recommended'): validate_arduino_version,
cv.Optional(CONF_BUILD_PATH, default=default_build_path): cv.string, cv.Optional(CONF_BUILD_PATH, default=default_build_path): cv.string,
@ -114,6 +113,10 @@ CONFIG_SCHEMA = cv.Schema({
}), }),
cv.Optional(CONF_INCLUDES, default=[]): cv.ensure_list(cv.file_), cv.Optional(CONF_INCLUDES, default=[]): cv.ensure_list(cv.file_),
cv.Optional(CONF_LIBRARIES, default=[]): cv.ensure_list(cv.string_strict), cv.Optional(CONF_LIBRARIES, default=[]): cv.ensure_list(cv.string_strict),
cv.Optional('esphome_core_version'): cv.invalid("The esphome_core_version option has been "
"removed in 1.13 - the esphome core source "
"files are now bundled with ESPHome.")
}) })
PRELOAD_CONFIG_SCHEMA = cv.Schema({ PRELOAD_CONFIG_SCHEMA = cv.Schema({

View file

@ -328,6 +328,17 @@ class ExpressionStatement(Statement):
return u"{};".format(self.expression) return u"{};".format(self.expression)
class LineComment(Statement):
def __init__(self, value): # type: (unicode) -> None
super(LineComment, self).__init__()
self._value = value
def __str__(self):
parts = self._value.split(u'\n')
parts = [u'// {}'.format(x) for x in parts]
return u'\n'.join(parts)
class ProgmemAssignmentExpression(AssignmentExpression): class ProgmemAssignmentExpression(AssignmentExpression):
def __init__(self, type, name, rhs, obj): def __init__(self, type, name, rhs, obj):
super(ProgmemAssignmentExpression, self).__init__( super(ProgmemAssignmentExpression, self).__init__(

View file

@ -729,7 +729,7 @@ def start_web_server(args):
webbrowser.open('localhost:{}'.format(args.port)) webbrowser.open('localhost:{}'.format(args.port))
if not settings.status_use_ping: if settings.status_use_ping:
status_thread = PingStatusThread() status_thread = PingStatusThread()
else: else:
status_thread = MDNSStatusThread() status_thread = MDNSStatusThread()

View file

@ -708,3 +708,11 @@ const startWizard = () => {
}; };
setupWizardStart.addEventListener('click', startWizard); setupWizardStart.addEventListener('click', startWizard);
jQuery.validator.addMethod("nospaces", (value, element) => {
return value.indexOf(' ') < 0;
}, "Name must not contain spaces.");
jQuery.validator.addMethod("lowercase", (value, element) => {
return value === value.toLowerCase();
}, "Name must be lowercase.");

View file

@ -189,7 +189,7 @@
<code class="inlinecode">0-9</code> and <code class="inlinecode">_</code>) <code class="inlinecode">0-9</code> and <code class="inlinecode">_</code>)
</p> </p>
<div class="input-field col s12"> <div class="input-field col s12">
<input id="node_name" class="validate" type="text" name="name" required> <input id="node_name" class="validate" type="text" name="name" data-rule-nospaces="true" data-rule-lowercase="true" required>
<label for="node_name">Name of node</label> <label for="node_name">Name of node</label>
</div> </div>
</div> </div>
@ -207,8 +207,7 @@
Please choose the board you're using below. Please choose the board you're using below.
</p> </p>
<p> <p>
If you're not sure you can also use similar ones or even the <em>If unsure you can also select a similar board or choose the "Generic" option.</em>
"Generic" option. In most cases that will work too.
</p> </p>
<div class="input-field col s12"> <div class="input-field col s12">
<select id="board" name="board" required> <select id="board" name="board" required>

View file

@ -1,93 +1,9 @@
#!/usr/bin/env python #!/usr/bin/env python
import codecs
import json
import os
import re
import sys import sys
import os.path
root_path = os.path.abspath(os.path.normpath(os.path.join(__file__, '..', '..'))) sys.path.append(os.path.dirname(__file__))
basepath = os.path.join(root_path, 'esphome') from helpers import build_all_include, build_compile_commands
temp_header_file = os.path.join(root_path, '.temp-clang-tidy.cpp')
def walk_files(path):
for root, _, files in os.walk(path):
for name in files:
yield os.path.join(root, name)
def shlex_quote(s):
if not s:
return u"''"
if re.search(r'[^\w@%+=:,./-]', s) is None:
return s
return u"'" + s.replace(u"'", u"'\"'\"'") + u"'"
def build_all_include():
# Build a cpp file that includes all header files in this repo.
# Otherwise header-only integrations would not be tested by clang-tidy
headers = []
for path in walk_files(basepath):
filetypes = ('.h',)
ext = os.path.splitext(path)[1]
if ext in filetypes:
path = os.path.relpath(path, root_path)
include_p = path.replace(os.path.sep, '/')
headers.append('#include "{}"'.format(include_p))
headers.sort()
headers.append('')
content = '\n'.join(headers)
with codecs.open(temp_header_file, 'w', encoding='utf-8') as f:
f.write(content)
def build_compile_commands():
gcc_flags_json = os.path.join(root_path, '.gcc-flags.json')
if not os.path.isfile(gcc_flags_json):
print("Could not find {} file which is required for clang-tidy.")
print('Please run "pio init --ide atom" in the root esphome folder to generate that file.')
sys.exit(1)
with codecs.open(gcc_flags_json, 'r', encoding='utf-8') as f:
gcc_flags = json.load(f)
exec_path = gcc_flags['execPath']
include_paths = gcc_flags['gccIncludePaths'].split(',')
includes = ['-I{}'.format(p) for p in include_paths]
cpp_flags = gcc_flags['gccDefaultCppFlags'].split(' ')
defines = [flag for flag in cpp_flags if flag.startswith('-D')]
command = [exec_path]
command.extend(includes)
command.extend(defines)
command.append('-std=gnu++11')
command.append('-Wall')
command.append('-Wno-delete-non-virtual-dtor')
command.append('-Wno-unused-variable')
command.append('-Wunreachable-code')
source_files = []
for path in walk_files(basepath):
filetypes = ('.cpp',)
ext = os.path.splitext(path)[1]
if ext in filetypes:
source_files.append(os.path.abspath(path))
source_files.append(temp_header_file)
source_files.sort()
compile_commands = [{
'directory': root_path,
'command': ' '.join(shlex_quote(x) for x in (command + ['-o', p + '.o', '-c', p])),
'file': p
} for p in source_files]
compile_commands_json = os.path.join(root_path, 'compile_commands.json')
if os.path.isfile(compile_commands_json):
with codecs.open(compile_commands_json, 'r', encoding='utf-8') as f:
try:
if json.load(f) == compile_commands:
return
except:
pass
with codecs.open(compile_commands_json, 'w', encoding='utf-8') as f:
json.dump(compile_commands, f, indent=2)
def main(): def main():

View file

@ -4,7 +4,6 @@ from __future__ import print_function
import codecs import codecs
import collections import collections
import fnmatch import fnmatch
import functools
import os.path import os.path
import subprocess import subprocess
import sys import sys
@ -155,6 +154,28 @@ def lint_pragma_once(content):
return None return None
@lint_content_find_check('ESP_LOG', include=['*.h', '*.tcc'], exclude=[
'esphome/components/binary_sensor/binary_sensor.h',
'esphome/components/cover/cover.h',
'esphome/components/display/display_buffer.h',
'esphome/components/i2c/i2c.h',
'esphome/components/mqtt/mqtt_component.h',
'esphome/components/output/binary_output.h',
'esphome/components/output/float_output.h',
'esphome/components/sensor/sensor.h',
'esphome/components/stepper/stepper.h',
'esphome/components/switch/switch.h',
'esphome/components/text_sensor/text_sensor.h',
'esphome/core/component.h',
'esphome/core/esphal.h',
'esphome/core/log.h',
'tests/custom.h',
])
def lint_log_in_header():
return ('Found reference to ESP_LOG in header file. Using ESP_LOG* in header files '
'is currently not possible - please move the definition to a source file (.cpp)')
errors = collections.defaultdict(list) errors = collections.defaultdict(list)

View file

@ -2,16 +2,19 @@
from __future__ import print_function from __future__ import print_function
import argparse
import multiprocessing import multiprocessing
import os import os
import re import re
import subprocess import subprocess
import sys import sys
import argparse
import click
import threading import threading
import click
sys.path.append(os.path.dirname(__file__))
from helpers import basepath, get_output, walk_files, filter_changed
is_py2 = sys.version[0] == '2' is_py2 = sys.version[0] == '2'
if is_py2: if is_py2:
@ -50,44 +53,6 @@ def progress_bar_show(value):
return value return value
def walk_files(path):
for root, _, files in os.walk(path):
for name in files:
yield os.path.join(root, name)
def get_output(*args):
proc = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
output, err = proc.communicate()
return output.decode('utf-8')
def splitlines_no_ends(string):
return [s.strip() for s in string.splitlines()]
def filter_changed(files):
for remote in ('upstream', 'origin'):
command = ['git', 'merge-base', '{}/dev'.format(remote), 'HEAD']
try:
merge_base = splitlines_no_ends(get_output(*command))[0]
break
except:
pass
else:
return files
command = ['git', 'diff', merge_base, '--name-only']
changed = splitlines_no_ends(get_output(*command))
changed = {os.path.relpath(f, os.getcwd()) for f in changed}
print("Changed Files:")
files = [p for p in files if p in changed]
for p in files:
print(" {}".format(p))
if not files:
print(" No changed files")
return files
def main(): def main():
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
parser.add_argument('-j', '--jobs', type=int, parser.add_argument('-j', '--jobs', type=int,

View file

@ -2,8 +2,6 @@
from __future__ import print_function from __future__ import print_function
import codecs
import json
import multiprocessing import multiprocessing
import os import os
import re import re
@ -18,6 +16,10 @@ import argparse
import click import click
import threading import threading
sys.path.append(os.path.dirname(__file__))
from helpers import basepath, shlex_quote, get_output, build_compile_commands, \
build_all_include, temp_header_file, walk_files, filter_changed
is_py2 = sys.version[0] == '2' is_py2 = sys.version[0] == '2'
if is_py2: if is_py2:
@ -25,11 +27,6 @@ if is_py2:
else: else:
import queue as queue import queue as queue
root_path = os.path.abspath(os.path.normpath(os.path.join(__file__, '..', '..')))
basepath = os.path.join(root_path, 'esphome')
rel_basepath = os.path.relpath(basepath, os.getcwd())
temp_header_file = os.path.join(root_path, '.temp-clang-tidy.cpp')
def run_tidy(args, tmpdir, queue, lock, failed_files): def run_tidy(args, tmpdir, queue, lock, failed_files):
while True: while True:
@ -67,119 +64,6 @@ def run_tidy(args, tmpdir, queue, lock, failed_files):
def progress_bar_show(value): def progress_bar_show(value):
if value is None: if value is None:
return '' return ''
return value
def walk_files(path):
for root, _, files in os.walk(path):
for name in files:
yield os.path.join(root, name)
def get_output(*args):
proc = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
output, err = proc.communicate()
return output.decode('utf-8')
def splitlines_no_ends(string):
return [s.strip() for s in string.splitlines()]
def filter_changed(files):
for remote in ('upstream', 'origin'):
command = ['git', 'merge-base', '{}/dev'.format(remote), 'HEAD']
try:
merge_base = splitlines_no_ends(get_output(*command))[0]
break
except:
pass
else:
return files
command = ['git', 'diff', merge_base, '--name-only']
changed = splitlines_no_ends(get_output(*command))
changed = {os.path.relpath(f, os.getcwd()) for f in changed}
print("Changed Files:")
files = [p for p in files if p in changed]
for p in files:
print(" {}".format(p))
if not files:
print(" No changed files")
return files
def shlex_quote(s):
if not s:
return u"''"
if re.search(r'[^\w@%+=:,./-]', s) is None:
return s
return u"'" + s.replace(u"'", u"'\"'\"'") + u"'"
def build_compile_commands():
gcc_flags_json = os.path.join(root_path, '.gcc-flags.json')
if not os.path.isfile(gcc_flags_json):
print("Could not find {} file which is required for clang-tidy.")
print('Please run "pio init --ide atom" in the root esphome folder to generate that file.')
sys.exit(1)
with codecs.open(gcc_flags_json, 'r', encoding='utf-8') as f:
gcc_flags = json.load(f)
exec_path = gcc_flags['execPath']
include_paths = gcc_flags['gccIncludePaths'].split(',')
includes = ['-I{}'.format(p) for p in include_paths]
cpp_flags = gcc_flags['gccDefaultCppFlags'].split(' ')
defines = [flag for flag in cpp_flags if flag.startswith('-D')]
command = [exec_path]
command.extend(includes)
command.extend(defines)
command.append('-std=gnu++11')
command.append('-Wall')
command.append('-Wno-delete-non-virtual-dtor')
command.append('-Wno-unused-variable')
command.append('-Wunreachable-code')
source_files = []
for path in walk_files(basepath):
filetypes = ('.cpp',)
ext = os.path.splitext(path)[1]
if ext in filetypes:
source_files.append(os.path.abspath(path))
source_files.append(temp_header_file)
source_files.sort()
compile_commands = [{
'directory': root_path,
'command': ' '.join(shlex_quote(x) for x in (command + ['-o', p + '.o', '-c', p])),
'file': p
} for p in source_files]
compile_commands_json = os.path.join(root_path, 'compile_commands.json')
if os.path.isfile(compile_commands_json):
with codecs.open(compile_commands_json, 'r', encoding='utf-8') as f:
try:
if json.load(f) == compile_commands:
return
except:
pass
with codecs.open(compile_commands_json, 'w', encoding='utf-8') as f:
json.dump(compile_commands, f, indent=2)
def build_all_include():
# Build a cpp file that includes all header files in this repo.
# Otherwise header-only integrations would not be tested by clang-tidy
headers = []
for path in walk_files(basepath):
filetypes = ('.h',)
ext = os.path.splitext(path)[1]
if ext in filetypes:
path = os.path.relpath(path, root_path)
include_p = path.replace(os.path.sep, '/')
headers.append('#include "{}"'.format(include_p))
headers.sort()
headers.append('')
content = '\n'.join(headers)
with codecs.open(temp_header_file, 'w', encoding='utf-8') as f:
f.write(content)
def main(): def main():
@ -212,6 +96,7 @@ def main():
""") """)
return 1 return 1
build_all_include()
build_compile_commands() build_compile_commands()
files = [] files = []
@ -231,7 +116,6 @@ def main():
files.sort() files.sort()
if args.all_headers: if args.all_headers:
build_all_include()
files.insert(0, temp_header_file) files.insert(0, temp_header_file)
tmpdir = None tmpdir = None

128
script/helpers.py Normal file
View file

@ -0,0 +1,128 @@
import codecs
import json
import os.path
import re
import subprocess
import sys
root_path = os.path.abspath(os.path.normpath(os.path.join(__file__, '..', '..')))
basepath = os.path.join(root_path, 'esphome')
temp_header_file = os.path.join(root_path, '.temp-clang-tidy.cpp')
def shlex_quote(s):
if not s:
return u"''"
if re.search(r'[^\w@%+=:,./-]', s) is None:
return s
return u"'" + s.replace(u"'", u"'\"'\"'") + u"'"
def build_all_include():
# Build a cpp file that includes all header files in this repo.
# Otherwise header-only integrations would not be tested by clang-tidy
headers = []
for path in walk_files(basepath):
filetypes = ('.h',)
ext = os.path.splitext(path)[1]
if ext in filetypes:
path = os.path.relpath(path, root_path)
include_p = path.replace(os.path.sep, '/')
headers.append('#include "{}"'.format(include_p))
headers.sort()
headers.append('')
content = '\n'.join(headers)
with codecs.open(temp_header_file, 'w', encoding='utf-8') as f:
f.write(content)
def build_compile_commands():
gcc_flags_json = os.path.join(root_path, '.gcc-flags.json')
if not os.path.isfile(gcc_flags_json):
print("Could not find {} file which is required for clang-tidy.")
print('Please run "pio init --ide atom" in the root esphome folder to generate that file.')
sys.exit(1)
with codecs.open(gcc_flags_json, 'r', encoding='utf-8') as f:
gcc_flags = json.load(f)
exec_path = gcc_flags['execPath']
include_paths = gcc_flags['gccIncludePaths'].split(',')
includes = ['-I{}'.format(p) for p in include_paths]
cpp_flags = gcc_flags['gccDefaultCppFlags'].split(' ')
defines = [flag for flag in cpp_flags if flag.startswith('-D')]
command = [exec_path]
command.extend(includes)
command.extend(defines)
command.append('-std=gnu++11')
command.append('-Wall')
command.append('-Wno-delete-non-virtual-dtor')
command.append('-Wno-unused-variable')
command.append('-Wunreachable-code')
source_files = []
for path in walk_files(basepath):
filetypes = ('.cpp',)
ext = os.path.splitext(path)[1]
if ext in filetypes:
source_files.append(os.path.abspath(path))
source_files.append(temp_header_file)
source_files.sort()
compile_commands = [{
'directory': root_path,
'command': ' '.join(shlex_quote(x) for x in (command + ['-o', p + '.o', '-c', p])),
'file': p
} for p in source_files]
compile_commands_json = os.path.join(root_path, 'compile_commands.json')
if os.path.isfile(compile_commands_json):
with codecs.open(compile_commands_json, 'r', encoding='utf-8') as f:
try:
if json.load(f) == compile_commands:
return
except:
pass
with codecs.open(compile_commands_json, 'w', encoding='utf-8') as f:
json.dump(compile_commands, f, indent=2)
def walk_files(path):
for root, _, files in os.walk(path):
for name in files:
yield os.path.join(root, name)
def get_output(*args):
proc = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
output, err = proc.communicate()
return output.decode('utf-8')
def splitlines_no_ends(string):
return [s.strip() for s in string.splitlines()]
def changed_files():
for remote in ('upstream', 'origin'):
command = ['git', 'merge-base', '{}/dev'.format(remote), 'HEAD']
try:
merge_base = splitlines_no_ends(get_output(*command))[0]
break
except:
pass
else:
raise ValueError("Git not configured")
command = ['git', 'diff', merge_base, '--name-only']
changed = splitlines_no_ends(get_output(*command))
changed = [os.path.relpath(f, os.getcwd()) for f in changed]
changed.sort()
return changed
def filter_changed(files):
changed = changed_files()
files = [f for f in files if f in changed]
print("Changed files:")
if not files:
print(" No changed files!")
for c in files:
print(" {}".format(c))
return files

View file

@ -12,5 +12,5 @@ fi
set -x set -x
script/clang-tidy.py -c --fix --all-headers script/clang-tidy -c --fix --all-headers
script/clang-format.py -c -i script/clang-format -c -i

View file

@ -1,10 +1,74 @@
#!/usr/bin/env bash #!/usr/bin/env python
set -e from __future__ import print_function
cd "$(dirname "$0")/.." import argparse
set -x import collections
import os
import re
import sys
script/ci-custom.py sys.path.append(os.path.dirname(__file__))
flake8 esphome from helpers import basepath, get_output, walk_files, filter_changed
pylint esphome
def main():
parser = argparse.ArgumentParser()
parser.add_argument('files', nargs='*', default=[],
help='files to be processed (regex on path)')
parser.add_argument('-c', '--changed', action='store_true',
help='Only run on changed files')
args = parser.parse_args()
files = []
for path in walk_files(basepath):
filetypes = ('.py',)
ext = os.path.splitext(path)[1]
if ext in filetypes:
path = os.path.relpath(path, os.getcwd())
files.append(path)
# Match against re
file_name_re = re.compile('|'.join(args.files))
files = [p for p in files if file_name_re.search(p)]
if args.changed:
files = filter_changed(files)
files.sort()
errors = collections.defaultdict(list)
cmd = ['flake8'] + files
print("Running flake8...")
log = get_output(*cmd)
for line in log.splitlines():
line = line.split(':')
if len(line) < 4:
continue
file_ = line[0]
linno = line[1]
msg = (u':'.join(line[3:])).strip()
errors[file_].append(u'{}:{} - {}'.format(file_, linno, msg))
cmd = ['pylint', '-f', 'parseable', '--persistent=n'] + files
print("Running pylint...")
log = get_output(*cmd)
for line in log.splitlines():
line = line.split(':')
if len(line) < 3:
continue
file_ = line[0]
linno = line[1]
msg = (u':'.join(line[3:])).strip()
errors[file_].append(u'{}:{} - {}'.format(file_, linno, msg))
for f, errs in sorted(errors.items()):
print("\033[0;32m************* File \033[1;32m{}\033[0m".format(f))
for err in errs:
print(err)
print()
sys.exit(len(errors))
if __name__ == '__main__':
main()

11
script/quicklint Executable file
View file

@ -0,0 +1,11 @@
#!/usr/bin/env bash
set -e
cd "$(dirname "$0")/.."
set -x
script/ci-custom.py
script/lint-python -c
script/lint-cpp

View file

@ -15,6 +15,14 @@ esphome:
then: then:
- lambda: >- - lambda: >-
ESP_LOGV("main", "ON LOOP!"); ESP_LOGV("main", "ON LOOP!");
- light.addressable_set:
id: addr1
range_from: 1
range_to: 100
red: 100%
green: !lambda 'return 255;'
blue: 0%
white: 100%
build_path: build/test1 build_path: build/test1
wifi: wifi: