Merge pull request #1333 from esphome/bump-1.15.3

1.15.3
This commit is contained in:
Guillermo Ruffino 2020-10-23 00:16:18 -03:00 committed by GitHub
commit ab48e4a466
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 180 additions and 107 deletions

View file

@ -33,7 +33,7 @@ class AQICalculator : public AbstractAQICalculator {
}
int get_grid_index_(uint16_t value, int array[AMOUNT_OF_LEVELS][2]) {
for (int i = 0; i < AMOUNT_OF_LEVELS - 1; i++) {
for (int i = 0; i < AMOUNT_OF_LEVELS; i++) {
if (value >= array[i][0] && value <= array[i][1]) {
return i;
}

View file

@ -102,17 +102,18 @@ class LightTurnOnTrigger : public Trigger<> {
public:
LightTurnOnTrigger(LightState *a_light) {
a_light->add_new_remote_values_callback([this, a_light]() {
auto is_on = a_light->current_values.is_on();
// using the remote value because of transitions we need to trigger as early as possible
auto is_on = a_light->remote_values.is_on();
// only trigger when going from off to on
auto should_trigger = is_on && !last_on_;
auto should_trigger = is_on && !this->last_on_;
// Set new state immediately so that trigger() doesn't devolve
// into infinite loop
last_on_ = is_on;
this->last_on_ = is_on;
if (should_trigger) {
this->trigger();
}
});
last_on_ = a_light->current_values.is_on();
this->last_on_ = a_light->current_values.is_on();
}
protected:
@ -122,22 +123,14 @@ class LightTurnOnTrigger : public Trigger<> {
class LightTurnOffTrigger : public Trigger<> {
public:
LightTurnOffTrigger(LightState *a_light) {
a_light->add_new_remote_values_callback([this, a_light]() {
a_light->add_new_target_state_reached_callback([this, a_light]() {
auto is_on = a_light->current_values.is_on();
// only trigger when going from on to off
auto should_trigger = !is_on && last_on_;
// Set new state immediately so that trigger() doesn't devolve
// into infinite loop
last_on_ = is_on;
if (should_trigger) {
if (!is_on) {
this->trigger();
}
});
last_on_ = a_light->current_values.is_on();
}
protected:
bool last_on_;
};
template<typename... Ts> class AddressableSet : public Action<Ts...> {

View file

@ -145,6 +145,7 @@ void LightState::loop() {
if (this->transformer_ != nullptr) {
if (this->transformer_->is_finished()) {
this->remote_values = this->current_values = this->transformer_->get_end_values();
this->target_state_reached_callback_.call();
if (this->transformer_->publish_at_end())
this->publish_state();
this->transformer_ = nullptr;
@ -336,6 +337,9 @@ void LightCall::perform() {
this->parent_->set_immediately_(v, this->publish_);
}
if (!this->has_transition_()) {
this->parent_->target_state_reached_callback_.call();
}
if (this->publish_) {
this->parent_->publish_state();
}
@ -395,13 +399,13 @@ LightColorValues LightCall::validate_() {
// sets RGB to 100% if only White specified
if (this->white_.has_value()) {
if (!this->red_.has_value() && !this->green_.has_value() && !this->blue_.has_value()) {
this->red_ = optional<float>(1.0f);
this->green_ = optional<float>(1.0f);
this->blue_ = optional<float>(1.0f);
}
// make white values binary aka 0.0f or 1.0f...this allows brightness to do its job
if (traits.get_supports_color_interlock()) {
if (!this->red_.has_value() && !this->green_.has_value() && !this->blue_.has_value()) {
this->red_ = optional<float>(1.0f);
this->green_ = optional<float>(1.0f);
this->blue_ = optional<float>(1.0f);
}
// make white values binary aka 0.0f or 1.0f...this allows brightness to do its job
if (*this->white_ > 0.0f) {
this->white_ = optional<float>(1.0f);
} else {
@ -411,11 +415,13 @@ LightColorValues LightCall::validate_() {
}
// White to 0% if (exclusively) setting any RGB value that isn't 255,255,255
else if (this->red_.has_value() || this->green_.has_value() || this->blue_.has_value()) {
if (*this->red_ == 1.0f && *this->green_ == 1.0f && *this->blue_ == 1.0f && traits.get_supports_rgb_white_value() &&
traits.get_supports_color_interlock()) {
this->white_ = optional<float>(1.0f);
} else if (!this->white_.has_value() || !traits.get_supports_rgb_white_value()) {
this->white_ = optional<float>(0.0f);
if (traits.get_supports_color_interlock()) {
if (*this->red_ == 1.0f && *this->green_ == 1.0f && *this->blue_ == 1.0f &&
traits.get_supports_rgb_white_value() && traits.get_supports_color_interlock()) {
this->white_ = optional<float>(1.0f);
} else if (!this->white_.has_value() || !traits.get_supports_rgb_white_value()) {
this->white_ = optional<float>(0.0f);
}
}
}
// if changing Kelvin alone, change to white light
@ -752,6 +758,10 @@ void LightState::current_values_as_cwww(float *cold_white, float *warm_white, bo
void LightState::add_new_remote_values_callback(std::function<void()> &&send_callback) {
this->remote_values_callback_.add(std::move(send_callback));
}
void LightState::add_new_target_state_reached_callback(std::function<void()> &&send_callback) {
this->target_state_reached_callback_.add(std::move(send_callback));
}
LightEffect *LightState::get_active_effect_() {
if (this->active_effect_index_ == 0)
return nullptr;

View file

@ -242,6 +242,13 @@ class LightState : public Nameable, public Component {
*/
void add_new_remote_values_callback(std::function<void()> &&send_callback);
/**
* The callback is called once the state of current_values and remote_values are equal
*
* @param send_callback
*/
void add_new_target_state_reached_callback(std::function<void()> &&send_callback);
/// Return whether the light has any effects that meet the trait requirements.
bool supports_effects();
@ -318,6 +325,12 @@ class LightState : public Nameable, public Component {
* starting with the beginning of the transition.
*/
CallbackManager<void()> remote_values_callback_{};
/** Callback to call when the state of current_values and remote_values are equal
* This should be called once the state of current_values changed and equals the state of remote_values
*/
CallbackManager<void()> target_state_reached_callback_{};
LightOutput *output_; ///< Store the output to allow effects to have more access.
/// Whether the light value should be written in the next cycle.
bool next_write_{true};

View file

@ -229,7 +229,7 @@ void MAX7219Component::send64pixels(uint8_t chip, const uint8_t pixels[8]) {
b = pixels[col];
} else if (this->orientation_ == 2) {
for (uint8_t i = 0; i < 8; i++) {
b |= ((pixels[i] >> (7 - col)) << (7 - i));
b |= ((pixels[i] >> (7 - col)) & 1) << i;
}
} else {
b = pixels[7 - col];

View file

@ -12,6 +12,75 @@ namespace xiaomi_ble {
static const char *TAG = "xiaomi_ble";
bool parse_xiaomi_value(uint8_t value_type, const uint8_t *data, uint8_t value_length, XiaomiParseResult &result) {
// motion detection, 1 byte, 8-bit unsigned integer
if ((value_type == 0x03) && (value_length == 1)) {
result.has_motion = (data[0]) ? true : false;
}
// temperature, 2 bytes, 16-bit signed integer (LE), 0.1 °C
else if ((value_type == 0x04) && (value_length == 2)) {
const int16_t temperature = uint16_t(data[0]) | (uint16_t(data[1]) << 8);
result.temperature = temperature / 10.0f;
}
// humidity, 2 bytes, 16-bit signed integer (LE), 0.1 %
else if ((value_type == 0x06) && (value_length == 2)) {
const int16_t humidity = uint16_t(data[0]) | (uint16_t(data[1]) << 8);
result.humidity = humidity / 10.0f;
}
// illuminance (+ motion), 3 bytes, 24-bit unsigned integer (LE), 1 lx
else if (((value_type == 0x07) || (value_type == 0x0F)) && (value_length == 3)) {
const uint32_t illuminance = uint32_t(data[0]) | (uint32_t(data[1]) << 8) | (uint32_t(data[2]) << 16);
result.illuminance = illuminance;
result.is_light = (illuminance == 100) ? true : false;
if (value_type == 0x0F)
result.has_motion = true;
}
// soil moisture, 1 byte, 8-bit unsigned integer, 1 %
else if ((value_type == 0x08) && (value_length == 1)) {
result.moisture = data[0];
}
// conductivity, 2 bytes, 16-bit unsigned integer (LE), 1 µS/cm
else if ((value_type == 0x09) && (value_length == 2)) {
const uint16_t conductivity = uint16_t(data[0]) | (uint16_t(data[1]) << 8);
result.conductivity = conductivity;
}
// battery, 1 byte, 8-bit unsigned integer, 1 %
else if ((value_type == 0x0A) && (value_length == 1)) {
result.battery_level = data[0];
}
// temperature + humidity, 4 bytes, 16-bit signed integer (LE) each, 0.1 °C, 0.1 %
else if ((value_type == 0x0D) && (value_length == 4)) {
const int16_t temperature = uint16_t(data[0]) | (uint16_t(data[1]) << 8);
const int16_t humidity = uint16_t(data[2]) | (uint16_t(data[3]) << 8);
result.temperature = temperature / 10.0f;
result.humidity = humidity / 10.0f;
}
// formaldehyde, 2 bytes, 16-bit unsigned integer (LE), 0.01 mg / m3
else if ((value_type == 0x10) && (value_length == 2)) {
const uint16_t formaldehyde = uint16_t(data[0]) | (uint16_t(data[1]) << 8);
result.formaldehyde = formaldehyde / 100.0f;
}
// on/off state, 1 byte, 8-bit unsigned integer
else if ((value_type == 0x12) && (value_length == 1)) {
result.is_active = (data[0]) ? true : false;
}
// mosquito tablet, 1 byte, 8-bit unsigned integer, 1 %
else if ((value_type == 0x13) && (value_length == 1)) {
result.tablet = data[0];
}
// idle time since last motion, 4 byte, 32-bit unsigned integer, 1 min
else if ((value_type == 0x17) && (value_length == 4)) {
const uint32_t idle_time =
uint32_t(data[0]) | (uint32_t(data[1]) << 8) | (uint32_t(data[2]) << 16) | (uint32_t(data[2]) << 24);
result.idle_time = idle_time / 60.0f;
result.has_motion = (idle_time) ? false : true;
} else {
return false;
}
return true;
}
bool parse_xiaomi_message(const std::vector<uint8_t> &message, XiaomiParseResult &result) {
result.has_encryption = (message[0] & 0x08) ? true : false; // update encryption status
if (result.has_encryption) {
@ -25,81 +94,39 @@ bool parse_xiaomi_message(const std::vector<uint8_t> &message, XiaomiParseResult
// Byte 2: length
// Byte 3..3+len-1: data point value
const uint8_t *raw = message.data() + result.raw_offset;
const uint8_t *data = raw + 3;
const uint8_t data_length = raw[2];
const uint8_t *payload = message.data() + result.raw_offset;
uint8_t payload_length = message.size() - result.raw_offset;
uint8_t payload_offset = 0;
bool success = false;
if ((data_length < 1) || (data_length > 4)) {
ESP_LOGVV(TAG, "parse_xiaomi_message(): payload has wrong size (%d)!", data_length);
if (payload_length < 4) {
ESP_LOGVV(TAG, "parse_xiaomi_message(): payload has wrong size (%d)!", payload_length);
return false;
}
// motion detection, 1 byte, 8-bit unsigned integer
if ((raw[0] == 0x03) && (data_length == 1)) {
result.has_motion = (data[0]) ? true : false;
}
// temperature, 2 bytes, 16-bit signed integer (LE), 0.1 °C
else if ((raw[0] == 0x04) && (data_length == 2)) {
const int16_t temperature = uint16_t(data[0]) | (uint16_t(data[1]) << 8);
result.temperature = temperature / 10.0f;
}
// humidity, 2 bytes, 16-bit signed integer (LE), 0.1 %
else if ((raw[0] == 0x06) && (data_length == 2)) {
const int16_t humidity = uint16_t(data[0]) | (uint16_t(data[1]) << 8);
result.humidity = humidity / 10.0f;
}
// illuminance (+ motion), 3 bytes, 24-bit unsigned integer (LE), 1 lx
else if (((raw[0] == 0x07) || (raw[0] == 0x0F)) && (data_length == 3)) {
const uint32_t illuminance = uint32_t(data[0]) | (uint32_t(data[1]) << 8) | (uint32_t(data[2]) << 16);
result.illuminance = illuminance;
result.is_light = (illuminance == 100) ? true : false;
if (raw[0] == 0x0F)
result.has_motion = true;
}
// soil moisture, 1 byte, 8-bit unsigned integer, 1 %
else if ((raw[0] == 0x08) && (data_length == 1)) {
result.moisture = data[0];
}
// conductivity, 2 bytes, 16-bit unsigned integer (LE), 1 µS/cm
else if ((raw[0] == 0x09) && (data_length == 2)) {
const uint16_t conductivity = uint16_t(data[0]) | (uint16_t(data[1]) << 8);
result.conductivity = conductivity;
}
// battery, 1 byte, 8-bit unsigned integer, 1 %
else if ((raw[0] == 0x0A) && (data_length == 1)) {
result.battery_level = data[0];
}
// temperature + humidity, 4 bytes, 16-bit signed integer (LE) each, 0.1 °C, 0.1 %
else if ((raw[0] == 0x0D) && (data_length == 4)) {
const int16_t temperature = uint16_t(data[0]) | (uint16_t(data[1]) << 8);
const int16_t humidity = uint16_t(data[2]) | (uint16_t(data[3]) << 8);
result.temperature = temperature / 10.0f;
result.humidity = humidity / 10.0f;
}
// formaldehyde, 2 bytes, 16-bit unsigned integer (LE), 0.01 mg / m3
else if ((raw[0] == 0x10) && (data_length == 2)) {
const uint16_t formaldehyde = uint16_t(data[0]) | (uint16_t(data[1]) << 8);
result.formaldehyde = formaldehyde / 100.0f;
}
// on/off state, 1 byte, 8-bit unsigned integer
else if ((raw[0] == 0x12) && (data_length == 1)) {
result.is_active = (data[0]) ? true : false;
}
// mosquito tablet, 1 byte, 8-bit unsigned integer, 1 %
else if ((raw[0] == 0x13) && (data_length == 1)) {
result.tablet = data[0];
}
// idle time since last motion, 4 byte, 32-bit unsigned integer, 1 min
else if ((raw[0] == 0x17) && (data_length == 4)) {
const uint32_t idle_time =
uint32_t(data[0]) | (uint32_t(data[1]) << 8) | (uint32_t(data[2]) << 16) | (uint32_t(data[2]) << 24);
result.idle_time = idle_time / 60.0f;
result.has_motion = (idle_time) ? false : true;
} else {
return false;
while (payload_length > 0) {
if (payload[payload_offset + 1] != 0x10) {
ESP_LOGVV(TAG, "parse_xiaomi_message(): fixed byte not found, stop parsing residual data.");
break;
}
const uint8_t value_length = payload[payload_offset + 2];
if ((value_length < 1) || (value_length > 4) || (payload_length < (3 + value_length))) {
ESP_LOGVV(TAG, "parse_xiaomi_message(): value has wrong size (%d)!", value_length);
break;
}
const uint8_t value_type = payload[payload_offset + 0];
const uint8_t *data = &payload[payload_offset + 3];
if (parse_xiaomi_value(value_type, data, value_length, result))
success = true;
payload_length -= 3 + value_length;
payload_offset += 3 + value_length;
}
return true;
return success;
}
optional<XiaomiParseResult> parse_xiaomi_header(const esp32_ble_tracker::ServiceData &service_data) {

View file

@ -57,6 +57,7 @@ struct XiaomiAESVector {
size_t ivsize;
};
bool parse_xiaomi_value(uint8_t value_type, const uint8_t *data, uint8_t value_length, XiaomiParseResult &result);
bool parse_xiaomi_message(const std::vector<uint8_t> &message, XiaomiParseResult &result);
optional<XiaomiParseResult> parse_xiaomi_header(const esp32_ble_tracker::ServiceData &service_data);
bool decrypt_xiaomi_payload(std::vector<uint8_t> &raw, const uint8_t *bindkey, const uint64_t &address);

View file

@ -11,10 +11,11 @@ from string import ascii_letters, digits
import voluptuous as vol
from esphome import core
from esphome.const import CONF_AVAILABILITY, CONF_COMMAND_TOPIC, CONF_DISCOVERY, CONF_ID, \
CONF_INTERNAL, CONF_NAME, CONF_PAYLOAD_AVAILABLE, CONF_PAYLOAD_NOT_AVAILABLE, \
CONF_RETAIN, CONF_SETUP_PRIORITY, CONF_STATE_TOPIC, CONF_TOPIC, \
CONF_HOUR, CONF_MINUTE, CONF_SECOND, CONF_VALUE, CONF_UPDATE_INTERVAL, CONF_TYPE_ID, CONF_TYPE
from esphome.const import CONF_AVAILABILITY, CONF_COMMAND_TOPIC, \
CONF_DISCOVERY, CONF_ID, CONF_INTERNAL, CONF_NAME, CONF_PAYLOAD_AVAILABLE, \
CONF_PAYLOAD_NOT_AVAILABLE, CONF_RETAIN, CONF_SETUP_PRIORITY, CONF_STATE_TOPIC, CONF_TOPIC, \
CONF_HOUR, CONF_MINUTE, CONF_SECOND, CONF_VALUE, CONF_UPDATE_INTERVAL, CONF_TYPE_ID, \
CONF_TYPE, CONF_PACKAGES
from esphome.core import CORE, HexInt, IPAddress, Lambda, TimePeriod, TimePeriodMicroseconds, \
TimePeriodMilliseconds, TimePeriodSeconds, TimePeriodMinutes
from esphome.helpers import list_starts_with, add_class_to_obj
@ -1167,9 +1168,12 @@ class OnlyWith(Optional):
@property
def default(self):
# pylint: disable=unsupported-membership-test
if self._component not in CORE.raw_config:
return vol.UNDEFINED
return self._default
if (self._component in CORE.raw_config or
(CONF_PACKAGES in CORE.raw_config and
self._component in
{list(x.keys())[0] for x in CORE.raw_config[CONF_PACKAGES].values()})):
return self._default
return vol.UNDEFINED
@default.setter
def default(self, value):

View file

@ -2,7 +2,7 @@
MAJOR_VERSION = 1
MINOR_VERSION = 15
PATCH_VERSION = '2'
PATCH_VERSION = '3'
__short_version__ = f'{MAJOR_VERSION}.{MINOR_VERSION}'
__version__ = f'{__short_version__}.{PATCH_VERSION}'

View file

@ -178,8 +178,8 @@ void delay_microseconds_accurate(uint32_t usec) {
if (usec <= 16383UL) {
delayMicroseconds(usec);
} else {
delay(usec / 16383UL);
delayMicroseconds(usec % 16383UL);
delay(usec / 1000UL);
delayMicroseconds(usec % 1000UL);
}
}

View file

@ -8,6 +8,7 @@ namespace esphome {
static const char *TAG = "scheduler";
static const uint32_t SCHEDULER_DONT_RUN = 4294967295UL;
static const uint32_t MAX_LOGICALLY_DELETED_ITEMS = 10;
// Uncomment to debug scheduler
// #define ESPHOME_DEBUG_SCHEDULER
@ -107,6 +108,26 @@ void ICACHE_RAM_ATTR HOT Scheduler::call() {
}
#endif // ESPHOME_DEBUG_SCHEDULER
auto to_remove_was = to_remove_;
auto items_was = items_.size();
// If we have too many items to remove
if (to_remove_ > MAX_LOGICALLY_DELETED_ITEMS) {
std::vector<std::unique_ptr<SchedulerItem>> valid_items;
while (!this->empty_()) {
auto item = std::move(this->items_[0]);
this->pop_raw_();
valid_items.push_back(std::move(item));
}
this->items_ = std::move(valid_items);
// The following should not happen unless I'm missing something
if (to_remove_ != 0) {
ESP_LOGW(TAG, "to_remove_ was %u now: %u items where %zu now %zu. Please report this", to_remove_was, to_remove_,
items_was, items_.size());
to_remove_ = 0;
}
}
while (!this->empty_()) {
// use scoping to indicate visibility of `item` variable
{
@ -147,6 +168,7 @@ void ICACHE_RAM_ATTR HOT Scheduler::call() {
if (item->remove) {
// We were removed/cancelled in the function call, stop
to_remove_--;
continue;
}
@ -182,6 +204,7 @@ void HOT Scheduler::cleanup_() {
if (!item->remove)
return;
to_remove_--;
this->pop_raw_();
}
}
@ -193,7 +216,8 @@ void HOT Scheduler::push_(std::unique_ptr<Scheduler::SchedulerItem> item) { this
bool HOT Scheduler::cancel_item_(Component *component, const std::string &name, Scheduler::SchedulerItem::Type type) {
bool ret = false;
for (auto &it : this->items_)
if (it->component == component && it->name == name && it->type == type) {
if (it->component == component && it->name == name && it->type == type && !it->remove) {
to_remove_++;
it->remove = true;
ret = true;
}

View file

@ -61,6 +61,7 @@ class Scheduler {
std::vector<std::unique_ptr<SchedulerItem>> to_add_;
uint32_t last_millis_{0};
uint8_t millis_major_{0};
uint32_t to_remove_{0};
};
} // namespace esphome