diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d22c2b7e03..affdf944a7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,6 +23,7 @@ jobs: runs-on: ubuntu-latest strategy: fail-fast: false + max-parallel: 5 matrix: include: - id: ci-custom diff --git a/esphome/components/binary_sensor_map/binary_sensor_map.cpp b/esphome/components/binary_sensor_map/binary_sensor_map.cpp index 3934e0a99c..0bf6202893 100644 --- a/esphome/components/binary_sensor_map/binary_sensor_map.cpp +++ b/esphome/components/binary_sensor_map/binary_sensor_map.cpp @@ -16,6 +16,9 @@ void BinarySensorMap::loop() { case BINARY_SENSOR_MAP_TYPE_SUM: this->process_sum_(); break; + case BINARY_SENSOR_MAP_TYPE_BAYESIAN: + this->process_bayesian_(); + break; } } @@ -23,46 +26,51 @@ void BinarySensorMap::process_group_() { float total_current_value = 0.0; uint8_t num_active_sensors = 0; uint64_t mask = 0x00; - // check all binary_sensors for its state. when active add its value to total_current_value. - // create a bitmask for the binary_sensor status on all channels + + // - check all binary_sensors for its state + // - if active, add its value to total_current_value. + // - creates a bitmask for the binary_sensor states on all channels for (size_t i = 0; i < this->channels_.size(); i++) { auto bs = this->channels_[i]; if (bs.binary_sensor->state) { num_active_sensors++; - total_current_value += bs.sensor_value; + total_current_value += bs.parameters.sensor_value; mask |= 1ULL << i; } } - // check if the sensor map was touched + + // potentially update state only if a binary_sensor is active if (mask != 0ULL) { - // did the bit_mask change or is it a new sensor touch + // publish the average if the bitmask has changed if (this->last_mask_ != mask) { float publish_value = total_current_value / num_active_sensors; this->publish_state(publish_value); } } else if (this->last_mask_ != 0ULL) { - // is this a new sensor release + // no buttons are pressed and the states have changed since last run, so publish NAN ESP_LOGV(TAG, "'%s' - No binary sensor active, publishing NAN", this->name_.c_str()); this->publish_state(NAN); } + this->last_mask_ = mask; } void BinarySensorMap::process_sum_() { float total_current_value = 0.0; uint64_t mask = 0x00; + // - check all binary_sensor states // - if active, add its value to total_current_value - // - creates a bitmask for the binary_sensor status on all channels + // - creates a bitmask for the binary_sensor states on all channels for (size_t i = 0; i < this->channels_.size(); i++) { auto bs = this->channels_[i]; if (bs.binary_sensor->state) { - total_current_value += bs.sensor_value; + total_current_value += bs.parameters.sensor_value; mask |= 1ULL << i; } } - // update state only if the binary sensor states have changed or if no state has ever been sent on boot + // update state only if any binary_sensor states have changed or if no state has ever been sent on boot if ((this->last_mask_ != mask) || (!this->has_state())) { this->publish_state(total_current_value); } @@ -70,15 +78,65 @@ void BinarySensorMap::process_sum_() { this->last_mask_ = mask; } +void BinarySensorMap::process_bayesian_() { + float posterior_probability = this->bayesian_prior_; + uint64_t mask = 0x00; + + // - compute the posterior probability by taking the product of the predicate probablities for each observation + // - create a bitmask for the binary_sensor states on all channels/observations + for (size_t i = 0; i < this->channels_.size(); i++) { + auto bs = this->channels_[i]; + + posterior_probability *= + this->bayesian_predicate_(bs.binary_sensor->state, posterior_probability, + bs.parameters.probabilities.given_true, bs.parameters.probabilities.given_false); + + mask |= ((uint64_t) (bs.binary_sensor->state)) << i; + } + + // update state only if any binary_sensor states have changed or if no state has ever been sent on boot + if ((this->last_mask_ != mask) || (!this->has_state())) { + this->publish_state(posterior_probability); + } + + this->last_mask_ = mask; +} + +float BinarySensorMap::bayesian_predicate_(bool sensor_state, float prior, float prob_given_true, + float prob_given_false) { + float prob_state_source_true = prob_given_true; + float prob_state_source_false = prob_given_false; + + // if sensor is off, then we use the probabilities for the observation's complement + if (!sensor_state) { + prob_state_source_true = 1 - prob_given_true; + prob_state_source_false = 1 - prob_given_false; + } + + return prob_state_source_true / (prior * prob_state_source_true + (1.0 - prior) * prob_state_source_false); +} + void BinarySensorMap::add_channel(binary_sensor::BinarySensor *sensor, float value) { BinarySensorMapChannel sensor_channel{ .binary_sensor = sensor, - .sensor_value = value, + .parameters{ + .sensor_value = value, + }, }; this->channels_.push_back(sensor_channel); } -void BinarySensorMap::set_sensor_type(BinarySensorMapType sensor_type) { this->sensor_type_ = sensor_type; } - +void BinarySensorMap::add_channel(binary_sensor::BinarySensor *sensor, float prob_given_true, float prob_given_false) { + BinarySensorMapChannel sensor_channel{ + .binary_sensor = sensor, + .parameters{ + .probabilities{ + .given_true = prob_given_true, + .given_false = prob_given_false, + }, + }, + }; + this->channels_.push_back(sensor_channel); +} } // namespace binary_sensor_map } // namespace esphome diff --git a/esphome/components/binary_sensor_map/binary_sensor_map.h b/esphome/components/binary_sensor_map/binary_sensor_map.h index a1d6f95009..a07154c0e8 100644 --- a/esphome/components/binary_sensor_map/binary_sensor_map.h +++ b/esphome/components/binary_sensor_map/binary_sensor_map.h @@ -12,51 +12,88 @@ namespace binary_sensor_map { enum BinarySensorMapType { BINARY_SENSOR_MAP_TYPE_GROUP, BINARY_SENSOR_MAP_TYPE_SUM, + BINARY_SENSOR_MAP_TYPE_BAYESIAN, }; struct BinarySensorMapChannel { binary_sensor::BinarySensor *binary_sensor; - float sensor_value; + union { + float sensor_value; + struct { + float given_true; + float given_false; + } probabilities; + } parameters; }; -/** Class to group binary_sensors to one Sensor. +/** Class to map one or more binary_sensors to one Sensor. * - * Each binary sensor represents a float value in the group. + * Each binary sensor has configured parameters that each mapping type uses to compute the single numerical result */ class BinarySensorMap : public sensor::Sensor, public Component { public: void dump_config() override; + /** - * The loop checks all binary_sensor states - * When the binary_sensor reports a true value for its state, then the float value it represents is added to the - * total_current_value + * The loop calls the configured type processing method * - * Only when the total_current_value changed and at least one sensor reports an active state we publish the sensors - * average value. When the value changed and no sensors ar active we publish NAN. - * */ + * The processing method loops through all sensors and calculates the numerical result + * The result is only published if a binary sensor state has changed or, for some types, on initial boot + */ void loop() override; - float get_setup_priority() const override { return setup_priority::DATA; } - /** Add binary_sensors to the group. - * Each binary_sensor represents a float value when its state is true + + /** + * Add binary_sensors to the group when only one parameter is needed for the configured mapping type. * * @param *sensor The binary sensor. * @param value The value this binary_sensor represents */ void add_channel(binary_sensor::BinarySensor *sensor, float value); - void set_sensor_type(BinarySensorMapType sensor_type); + + /** + * Add binary_sensors to the group when two parameters are needed for the Bayesian mapping type. + * + * @param *sensor The binary sensor. + * @param prob_given_true Probability this observation is on when the Bayesian event is true + * @param prob_given_false Probability this observation is on when the Bayesian event is false + */ + void add_channel(binary_sensor::BinarySensor *sensor, float prob_given_true, float prob_given_false); + + void set_sensor_type(BinarySensorMapType sensor_type) { this->sensor_type_ = sensor_type; } + + void set_bayesian_prior(float prior) { this->bayesian_prior_ = prior; }; protected: std::vector channels_{}; BinarySensorMapType sensor_type_{BINARY_SENSOR_MAP_TYPE_GROUP}; - // this gives max 64 channels per binary_sensor_map + + // this allows a max of 64 channels/observations in order to keep track of binary_sensor states uint64_t last_mask_{0x00}; + + // Bayesian event prior probability before taking into account any observations + float bayesian_prior_{}; + /** - * methods to process the types of binary_sensor_maps - * GROUP: process_group_() just map to a value + * Methods to process the binary_sensor_maps types + * + * GROUP: process_group_() averages all the values * ADD: process_add_() adds all the values + * BAYESIAN: process_bayesian_() computes the predicate probability * */ void process_group_(); void process_sum_(); + void process_bayesian_(); + + /** + * Computes the Bayesian predicate for a specific observation + * If the sensor state is false, then we use the parameters' probabilities for the observatiosn complement + * + * @param sensor_state State of observation + * @param prior Prior probability before accounting for this observation + * @param prob_given_true Probability this observation is on when the Bayesian event is true + * @param prob_given_false Probability this observation is on when the Bayesian event is false + * */ + float bayesian_predicate_(bool sensor_state, float prior, float prob_given_true, float prob_given_false); }; } // namespace binary_sensor_map diff --git a/esphome/components/binary_sensor_map/sensor.py b/esphome/components/binary_sensor_map/sensor.py index 573cce9223..1181905f30 100644 --- a/esphome/components/binary_sensor_map/sensor.py +++ b/esphome/components/binary_sensor_map/sensor.py @@ -20,16 +20,29 @@ BinarySensorMap = binary_sensor_map_ns.class_( ) SensorMapType = binary_sensor_map_ns.enum("SensorMapType") +CONF_BAYESIAN = "bayesian" +CONF_PRIOR = "prior" +CONF_PROB_GIVEN_TRUE = "prob_given_true" +CONF_PROB_GIVEN_FALSE = "prob_given_false" +CONF_OBSERVATIONS = "observations" + SENSOR_MAP_TYPES = { CONF_GROUP: SensorMapType.BINARY_SENSOR_MAP_TYPE_GROUP, CONF_SUM: SensorMapType.BINARY_SENSOR_MAP_TYPE_SUM, + CONF_BAYESIAN: SensorMapType.BINARY_SENSOR_MAP_TYPE_BAYESIAN, } -entry = { +entry_one_parameter = { cv.Required(CONF_BINARY_SENSOR): cv.use_id(binary_sensor.BinarySensor), cv.Required(CONF_VALUE): cv.float_, } +entry_bayesian_parameters = { + cv.Required(CONF_BINARY_SENSOR): cv.use_id(binary_sensor.BinarySensor), + cv.Required(CONF_PROB_GIVEN_TRUE): cv.float_range(min=0, max=1), + cv.Required(CONF_PROB_GIVEN_FALSE): cv.float_range(min=0, max=1), +} + CONFIG_SCHEMA = cv.typed_schema( { CONF_GROUP: sensor.sensor_schema( @@ -39,7 +52,7 @@ CONFIG_SCHEMA = cv.typed_schema( ).extend( { cv.Required(CONF_CHANNELS): cv.All( - cv.ensure_list(entry), cv.Length(min=1, max=64) + cv.ensure_list(entry_one_parameter), cv.Length(min=1, max=64) ), } ), @@ -50,7 +63,18 @@ CONFIG_SCHEMA = cv.typed_schema( ).extend( { cv.Required(CONF_CHANNELS): cv.All( - cv.ensure_list(entry), cv.Length(min=1, max=64) + cv.ensure_list(entry_one_parameter), cv.Length(min=1, max=64) + ), + } + ), + CONF_BAYESIAN: sensor.sensor_schema( + BinarySensorMap, + accuracy_decimals=2, + ).extend( + { + cv.Required(CONF_PRIOR): cv.float_range(min=0, max=1), + cv.Required(CONF_OBSERVATIONS): cv.All( + cv.ensure_list(entry_bayesian_parameters), cv.Length(min=1, max=64) ), } ), @@ -66,6 +90,17 @@ async def to_code(config): constant = SENSOR_MAP_TYPES[config[CONF_TYPE]] cg.add(var.set_sensor_type(constant)) - for ch in config[CONF_CHANNELS]: - input_var = await cg.get_variable(ch[CONF_BINARY_SENSOR]) - cg.add(var.add_channel(input_var, ch[CONF_VALUE])) + if config[CONF_TYPE] == CONF_BAYESIAN: + cg.add(var.set_bayesian_prior(config[CONF_PRIOR])) + + for obs in config[CONF_OBSERVATIONS]: + input_var = await cg.get_variable(obs[CONF_BINARY_SENSOR]) + cg.add( + var.add_channel( + input_var, obs[CONF_PROB_GIVEN_TRUE], obs[CONF_PROB_GIVEN_FALSE] + ) + ) + else: + for ch in config[CONF_CHANNELS]: + input_var = await cg.get_variable(ch[CONF_BINARY_SENSOR]) + cg.add(var.add_channel(input_var, ch[CONF_VALUE])) diff --git a/esphome/components/ethernet/__init__.py b/esphome/components/ethernet/__init__.py index f6ca376681..bedc0a4c30 100644 --- a/esphome/components/ethernet/__init__.py +++ b/esphome/components/ethernet/__init__.py @@ -34,6 +34,7 @@ ETHERNET_TYPES = { "DP83848": EthernetType.ETHERNET_TYPE_DP83848, "IP101": EthernetType.ETHERNET_TYPE_IP101, "JL1101": EthernetType.ETHERNET_TYPE_JL1101, + "KSZ8081": EthernetType.ETHERNET_TYPE_KSZ8081, } emac_rmii_clock_mode_t = cg.global_ns.enum("emac_rmii_clock_mode_t") diff --git a/esphome/components/ethernet/ethernet_component.cpp b/esphome/components/ethernet/ethernet_component.cpp index 4792728a71..8eb4718f49 100644 --- a/esphome/components/ethernet/ethernet_component.cpp +++ b/esphome/components/ethernet/ethernet_component.cpp @@ -74,6 +74,10 @@ void EthernetComponent::setup() { phy = esp_eth_phy_new_jl1101(&phy_config); break; } + case ETHERNET_TYPE_KSZ8081: { + phy = esp_eth_phy_new_ksz8081(&phy_config); + break; + } default: { this->mark_failed(); return; @@ -140,7 +144,7 @@ void EthernetComponent::loop() { } void EthernetComponent::dump_config() { - std::string eth_type; + const char *eth_type; switch (this->type_) { case ETHERNET_TYPE_LAN8720: eth_type = "LAN8720"; @@ -158,6 +162,14 @@ void EthernetComponent::dump_config() { eth_type = "IP101"; break; + case ETHERNET_TYPE_JL1101: + eth_type = "JL1101"; + break; + + case ETHERNET_TYPE_KSZ8081: + eth_type = "KSZ8081"; + break; + default: eth_type = "Unknown"; break; @@ -170,7 +182,8 @@ void EthernetComponent::dump_config() { } ESP_LOGCONFIG(TAG, " MDC Pin: %u", this->mdc_pin_); ESP_LOGCONFIG(TAG, " MDIO Pin: %u", this->mdio_pin_); - ESP_LOGCONFIG(TAG, " Type: %s", eth_type.c_str()); + ESP_LOGCONFIG(TAG, " Type: %s", eth_type); + ESP_LOGCONFIG(TAG, " PHY addr: %u", this->phy_addr_); } float EthernetComponent::get_setup_priority() const { return setup_priority::WIFI; } diff --git a/esphome/components/ethernet/ethernet_component.h b/esphome/components/ethernet/ethernet_component.h index 872ed17044..0d9ebf29a8 100644 --- a/esphome/components/ethernet/ethernet_component.h +++ b/esphome/components/ethernet/ethernet_component.h @@ -14,11 +14,13 @@ namespace esphome { namespace ethernet { enum EthernetType { - ETHERNET_TYPE_LAN8720 = 0, + ETHERNET_TYPE_UNKNOWN = 0, + ETHERNET_TYPE_LAN8720, ETHERNET_TYPE_RTL8201, ETHERNET_TYPE_DP83848, ETHERNET_TYPE_IP101, ETHERNET_TYPE_JL1101, + ETHERNET_TYPE_KSZ8081, }; struct ManualIP { @@ -69,7 +71,7 @@ class EthernetComponent : public Component { int power_pin_{-1}; uint8_t mdc_pin_{23}; uint8_t mdio_pin_{18}; - EthernetType type_{ETHERNET_TYPE_LAN8720}; + EthernetType type_{ETHERNET_TYPE_UNKNOWN}; emac_rmii_clock_mode_t clk_mode_{EMAC_CLK_EXT_IN}; emac_rmii_clock_gpio_t clk_gpio_{EMAC_CLK_IN_GPIO}; optional manual_ip_{}; diff --git a/esphome/components/fan/__init__.py b/esphome/components/fan/__init__.py index eb67bbcbd7..9a05bff3a0 100644 --- a/esphome/components/fan/__init__.py +++ b/esphome/components/fan/__init__.py @@ -63,7 +63,7 @@ FanIsOffCondition = fan_ns.class_("FanIsOffCondition", automation.Condition.temp FAN_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend( { cv.GenerateID(): cv.declare_id(Fan), - cv.Optional(CONF_RESTORE_MODE, default="RESTORE_DEFAULT_OFF"): cv.enum( + cv.Optional(CONF_RESTORE_MODE, default="ALWAYS_OFF"): cv.enum( RESTORE_MODES, upper=True, space="_" ), cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTFanComponent), diff --git a/esphome/components/graph/graph.cpp b/esphome/components/graph/graph.cpp index daff89e0a6..c229f17dd8 100644 --- a/esphome/components/graph/graph.cpp +++ b/esphome/components/graph/graph.cpp @@ -122,11 +122,18 @@ void Graph::draw(DisplayBuffer *buff, uint16_t x_offset, uint16_t y_offset, Colo } // Adjust limits to nice y_per_div boundaries - int yn = int(ymin / y_per_div); - int ym = int(ymax / y_per_div) + int(1 * (fmodf(ymax, y_per_div) != 0)); - ymin = yn * y_per_div; - ymax = ym * y_per_div; - yrange = ymax - ymin; + int yn = 0; + int ym = 1; + if (!std::isnan(ymin) && !std::isnan(ymax)) { + yn = (int) floorf(ymin / y_per_div); + ym = (int) ceilf(ymax / y_per_div); + if (yn == ym) { + ym++; + } + ymin = yn * y_per_div; + ymax = ym * y_per_div; + yrange = ymax - ymin; + } /// Draw grid if (!std::isnan(this->gridspacing_y_)) { diff --git a/esphome/components/light/__init__.py b/esphome/components/light/__init__.py index c397910ec4..ba3a26ebe5 100644 --- a/esphome/components/light/__init__.py +++ b/esphome/components/light/__init__.py @@ -60,7 +60,7 @@ LIGHT_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).ex { cv.GenerateID(): cv.declare_id(LightState), cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTJSONLightComponent), - cv.Optional(CONF_RESTORE_MODE, default="restore_default_off"): cv.enum( + cv.Optional(CONF_RESTORE_MODE, default="ALWAYS_OFF"): cv.enum( RESTORE_MODES, upper=True, space="_" ), cv.Optional(CONF_ON_TURN_ON): auto.validate_automation( diff --git a/esphome/components/switch/__init__.py b/esphome/components/switch/__init__.py index c9bf81982a..21cbe3dfe4 100644 --- a/esphome/components/switch/__init__.py +++ b/esphome/components/switch/__init__.py @@ -92,7 +92,7 @@ def switch_schema( device_class: str = _UNDEF, icon: str = _UNDEF, block_inverted: bool = False, - default_restore_mode: str = "RESTORE_DEFAULT_OFF", + default_restore_mode: str = "ALWAYS_OFF", ): schema = _SWITCH_SCHEMA.extend( { diff --git a/esphome/components/time_based/cover.py b/esphome/components/time_based/cover.py index 9625781c96..a14a08ccad 100644 --- a/esphome/components/time_based/cover.py +++ b/esphome/components/time_based/cover.py @@ -16,6 +16,7 @@ time_based_ns = cg.esphome_ns.namespace("time_based") TimeBasedCover = time_based_ns.class_("TimeBasedCover", cover.Cover, cg.Component) CONF_HAS_BUILT_IN_ENDSTOP = "has_built_in_endstop" +CONF_MANUAL_CONTROL = "manual_control" CONFIG_SCHEMA = cover.COVER_SCHEMA.extend( { @@ -26,6 +27,7 @@ CONFIG_SCHEMA = cover.COVER_SCHEMA.extend( cv.Required(CONF_CLOSE_ACTION): automation.validate_automation(single=True), cv.Required(CONF_CLOSE_DURATION): cv.positive_time_period_milliseconds, cv.Optional(CONF_HAS_BUILT_IN_ENDSTOP, default=False): cv.boolean, + cv.Optional(CONF_MANUAL_CONTROL, default=False): cv.boolean, cv.Optional(CONF_ASSUMED_STATE, default=True): cv.boolean, } ).extend(cv.COMPONENT_SCHEMA) @@ -51,4 +53,5 @@ async def to_code(config): ) cg.add(var.set_has_built_in_endstop(config[CONF_HAS_BUILT_IN_ENDSTOP])) + cg.add(var.set_manual_control(config[CONF_MANUAL_CONTROL])) cg.add(var.set_assumed_state(config[CONF_ASSUMED_STATE])) diff --git a/esphome/components/time_based/time_based_cover.cpp b/esphome/components/time_based/time_based_cover.cpp index 522252e907..a7ba6d0595 100644 --- a/esphome/components/time_based/time_based_cover.cpp +++ b/esphome/components/time_based/time_based_cover.cpp @@ -79,6 +79,14 @@ void TimeBasedCover::control(const CoverCall &call) { auto pos = *call.get_position(); if (pos == this->position) { // already at target + if (this->manual_control_ && (pos == COVER_OPEN || pos == COVER_CLOSED)) { + // for covers with manual control switch, we can't rely on the computed position, so if + // the command triggered again, we'll assume it's in the opposite direction anyway. + auto op = pos == COVER_CLOSED ? COVER_OPERATION_CLOSING : COVER_OPERATION_OPENING; + this->position = pos == COVER_CLOSED ? COVER_OPEN : COVER_CLOSED; + this->target_position_ = pos; + this->start_direction_(op); + } // for covers with built in end stop, we should send the command again if (this->has_built_in_endstop_ && (pos == COVER_OPEN || pos == COVER_CLOSED)) { auto op = pos == COVER_CLOSED ? COVER_OPERATION_CLOSING : COVER_OPERATION_OPENING; diff --git a/esphome/components/time_based/time_based_cover.h b/esphome/components/time_based/time_based_cover.h index 517ab77cb3..b7a826d237 100644 --- a/esphome/components/time_based/time_based_cover.h +++ b/esphome/components/time_based/time_based_cover.h @@ -21,6 +21,7 @@ class TimeBasedCover : public cover::Cover, public Component { void set_close_duration(uint32_t close_duration) { this->close_duration_ = close_duration; } cover::CoverTraits get_traits() override; void set_has_built_in_endstop(bool value) { this->has_built_in_endstop_ = value; } + void set_manual_control(bool value) { this->manual_control_ = value; } void set_assumed_state(bool value) { this->assumed_state_ = value; } protected: @@ -44,6 +45,7 @@ class TimeBasedCover : public cover::Cover, public Component { uint32_t last_publish_time_{0}; float target_position_{0}; bool has_built_in_endstop_{false}; + bool manual_control_{false}; bool assumed_state_{false}; cover::CoverOperation last_operation_{cover::COVER_OPERATION_OPENING}; }; diff --git a/esphome/components/waveshare_epaper/waveshare_epaper.cpp b/esphome/components/waveshare_epaper/waveshare_epaper.cpp index 8fd5c2e1f3..8c4b137514 100644 --- a/esphome/components/waveshare_epaper/waveshare_epaper.cpp +++ b/esphome/components/waveshare_epaper/waveshare_epaper.cpp @@ -137,7 +137,7 @@ void HOT WaveshareEPaper::draw_absolute_pixel_internal(int x, int y, Color color if (x >= this->get_width_internal() || y >= this->get_height_internal() || x < 0 || y < 0) return; - const uint32_t pos = (x + y * this->get_width_internal()) / 8u; + const uint32_t pos = (x + y * this->get_width_controller()) / 8u; const uint8_t subpos = x & 0x07; // flip logic if (!color.is_on()) { @@ -146,7 +146,9 @@ void HOT WaveshareEPaper::draw_absolute_pixel_internal(int x, int y, Color color this->buffer_[pos] &= ~(0x80 >> subpos); } } -uint32_t WaveshareEPaper::get_buffer_length_() { return this->get_width_internal() * this->get_height_internal() / 8u; } +uint32_t WaveshareEPaper::get_buffer_length_() { + return this->get_width_controller() * this->get_height_internal() / 8u; +} void WaveshareEPaper::start_command_() { this->dc_pin_->digital_write(false); this->enable(); @@ -291,7 +293,7 @@ void HOT WaveshareEPaperTypeA::display() { // COMMAND SET RAM X ADDRESS START END POSITION this->command(0x44); this->data(0x00); - this->data((this->get_width_internal() - 1) >> 3); + this->data((this->get_width_controller() - 1) >> 3); // COMMAND SET RAM Y ADDRESS START END POSITION this->command(0x45); this->data(this->get_height_internal() - 1); @@ -392,12 +394,26 @@ int WaveshareEPaperTypeA::get_width_internal() { case TTGO_EPAPER_2_13_IN_B73: case TTGO_EPAPER_2_13_IN_B74: case TTGO_EPAPER_2_13_IN_B1: + return 122; case WAVESHARE_EPAPER_2_9_IN: case WAVESHARE_EPAPER_2_9_IN_V2: return 128; } return 0; } +// The controller of the 2.13" displays has a buffer larger than screen size +int WaveshareEPaperTypeA::get_width_controller() { + switch (this->model_) { + case WAVESHARE_EPAPER_2_13_IN: + case TTGO_EPAPER_2_13_IN: + case TTGO_EPAPER_2_13_IN_B73: + case TTGO_EPAPER_2_13_IN_B74: + case TTGO_EPAPER_2_13_IN_B1: + return 128; + default: + return this->get_width_internal(); + } +} int WaveshareEPaperTypeA::get_height_internal() { switch (this->model_) { case WAVESHARE_EPAPER_1_54_IN: diff --git a/esphome/components/waveshare_epaper/waveshare_epaper.h b/esphome/components/waveshare_epaper/waveshare_epaper.h index 848b293c45..a674d3af0c 100644 --- a/esphome/components/waveshare_epaper/waveshare_epaper.h +++ b/esphome/components/waveshare_epaper/waveshare_epaper.h @@ -54,6 +54,8 @@ class WaveshareEPaper : public PollingComponent, } } + virtual int get_width_controller() { return this->get_width_internal(); }; + uint32_t get_buffer_length_(); uint32_t reset_duration_{200}; @@ -111,6 +113,8 @@ class WaveshareEPaperTypeA : public WaveshareEPaper { int get_height_internal() override; + int get_width_controller() override; + uint32_t full_update_every_{30}; uint32_t at_update_{0}; WaveshareEPaperTypeAModel model_; diff --git a/esphome/const.py b/esphome/const.py index 534a8ade01..2f66b47b8e 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2023.4.0-dev" +__version__ = "2023.5.0-dev" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" diff --git a/requirements.txt b/requirements.txt index 5a57342189..5f73cf3a06 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,7 +10,7 @@ platformio==6.1.6 # When updating platformio, also update Dockerfile esptool==4.5.1 click==8.1.3 esphome-dashboard==20230214.0 -aioesphomeapi==13.5.1 +aioesphomeapi==13.7.0 zeroconf==0.56.0 # esp-idf requires this, but doesn't bundle it by default diff --git a/tests/test3.yaml b/tests/test3.yaml index ceb9047d17..c4847725e8 100644 --- a/tests/test3.yaml +++ b/tests/test3.yaml @@ -368,6 +368,32 @@ sensor: - binary_sensor: bin3 value: 100.0 + - platform: binary_sensor_map + name: Binary Sensor Map + type: sum + channels: + - binary_sensor: bin1 + value: 10.0 + - binary_sensor: bin2 + value: 15.0 + - binary_sensor: bin3 + value: 100.0 + + - platform: binary_sensor_map + name: Binary Sensor Map + type: bayesian + prior: 0.4 + observations: + - binary_sensor: bin1 + prob_given_true: 0.9 + prob_given_false: 0.4 + - binary_sensor: bin2 + prob_given_true: 0.7 + prob_given_false: 0.05 + - binary_sensor: bin3 + prob_given_true: 0.8 + prob_given_false: 0.2 + - platform: bl0939 uart_id: uart_8 voltage: