From 04a139fe3d32f4c89fe856ed0f71feae9cdbf3f0 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 13 Apr 2023 10:13:53 +1200 Subject: [PATCH 01/71] Bump version to 2023.5.0-dev --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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-_" From cc1eb648f9734402018baa8f6b9acdda6463cdd1 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 13 Apr 2023 10:46:19 +1200 Subject: [PATCH 02/71] Only allow 5 jobs from each CI run to be in parallel (#4682) --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) 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 From afc848bf22f93a650ea1641179de205b0ac5f3cb Mon Sep 17 00:00:00 2001 From: kahrendt Date: Wed, 12 Apr 2023 21:48:29 -0400 Subject: [PATCH 03/71] Add Bayesian type for binary_sensor_map component (#4640) * initial support for Bayesian type * Cast bool state of binary_sensor to uint64_t * Rename channels to observations with Bayesian * Improve/standardize comments for all types * Use black to correct sensor.py formatting * Add SUM and BAYESIAN binary sensor map tests * Remove unused variable * Update esphome/components/binary_sensor_map/binary_sensor_map.cpp Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --------- Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- .../binary_sensor_map/binary_sensor_map.cpp | 82 ++++++++++++++++--- .../binary_sensor_map/binary_sensor_map.h | 69 ++++++++++++---- .../components/binary_sensor_map/sensor.py | 47 +++++++++-- tests/test3.yaml | 26 ++++++ 4 files changed, 190 insertions(+), 34 deletions(-) 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/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: From 0643b719086f70689eb5f6de572f9d1ba6f3c54a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 13 Apr 2023 17:31:47 +1200 Subject: [PATCH 04/71] Bump aioesphomeapi from 13.5.1 to 13.7.0 (#4676) Bumps [aioesphomeapi](https://github.com/esphome/aioesphomeapi) from 13.5.1 to 13.7.0. - [Release notes](https://github.com/esphome/aioesphomeapi/releases) - [Commits](https://github.com/esphome/aioesphomeapi/compare/v13.5.1...v13.7.0) --- updated-dependencies: - dependency-name: aioesphomeapi dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From 47555d314a8077ec3f31ef4cef374246a1d30c26 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 13 Apr 2023 20:12:24 +1200 Subject: [PATCH 05/71] Bump peter-evans/create-pull-request from 4 to 5 (#4661) Bumps [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request) from 4 to 5. - [Release notes](https://github.com/peter-evans/create-pull-request/releases) - [Commits](https://github.com/peter-evans/create-pull-request/compare/v4...v5) --- updated-dependencies: - dependency-name: peter-evans/create-pull-request dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/sync-device-classes.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/sync-device-classes.yml b/.github/workflows/sync-device-classes.yml index 671fe1f21a..396dd64165 100644 --- a/.github/workflows/sync-device-classes.yml +++ b/.github/workflows/sync-device-classes.yml @@ -48,7 +48,7 @@ jobs: echo "$delimiter" >> $GITHUB_OUTPUT - name: Commit changes - uses: peter-evans/create-pull-request@v4 + uses: peter-evans/create-pull-request@v5 with: commit-message: "Synchronise Device Classes from Home Assistant" committer: esphomebot From 7b0fca6824a7f3312451d080ae68472c0eeab1b1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 13 Apr 2023 08:13:13 +0000 Subject: [PATCH 06/71] Bump docker/build-push-action from 3 to 4 (#4367) Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 3 to 4. - [Release notes](https://github.com/docker/build-push-action/releases) - [Commits](https://github.com/docker/build-push-action/compare/v3...v4) --- updated-dependencies: - dependency-name: docker/build-push-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3904834dc9..7ebd04e793 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -117,7 +117,7 @@ jobs: --suffix "${{ matrix.image.suffix }}" - name: Build and push - uses: docker/build-push-action@v3 + uses: docker/build-push-action@v4 with: context: . file: ./docker/Dockerfile From 6b67acbeb5fc6fae713ed81f7ee549f53a3d42ff Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Fri, 14 Apr 2023 14:29:28 +1200 Subject: [PATCH 07/71] debug component, allow without debug logging (#4685) --- esphome/components/debug/__init__.py | 16 ---------------- esphome/components/debug/debug_component.cpp | 4 ++++ 2 files changed, 4 insertions(+), 16 deletions(-) diff --git a/esphome/components/debug/__init__.py b/esphome/components/debug/__init__.py index 223c3c8df1..c18baa1cca 100644 --- a/esphome/components/debug/__init__.py +++ b/esphome/components/debug/__init__.py @@ -1,15 +1,11 @@ import esphome.codegen as cg import esphome.config_validation as cv -import esphome.final_validate as fv -from esphome.components import logger from esphome.const import ( CONF_BLOCK, CONF_DEVICE, CONF_FRAGMENTATION, CONF_FREE, CONF_ID, - CONF_LEVEL, - CONF_LOGGER, CONF_LOOP_TIME, ) @@ -43,18 +39,6 @@ CONFIG_SCHEMA = cv.Schema( ).extend(cv.polling_component_schema("60s")) -def _final_validate(_): - logger_conf = fv.full_config.get()[CONF_LOGGER] - severity = logger.LOG_LEVEL_SEVERITY.index(logger_conf[CONF_LEVEL]) - if severity < logger.LOG_LEVEL_SEVERITY.index("DEBUG"): - raise cv.Invalid( - "The debug component requires the logger to be at least at DEBUG level" - ) - - -FINAL_VALIDATE_SCHEMA = _final_validate - - async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) diff --git a/esphome/components/debug/debug_component.cpp b/esphome/components/debug/debug_component.cpp index c1ede684e6..9843fa1c99 100644 --- a/esphome/components/debug/debug_component.cpp +++ b/esphome/components/debug/debug_component.cpp @@ -37,6 +37,10 @@ static uint32_t get_free_heap() { } void DebugComponent::dump_config() { +#ifndef ESPHOME_LOG_HAS_DEBUG + return; // Can't log below if debug logging is disabled +#endif + std::string device_info; std::string reset_reason; device_info.reserve(256); From 382dcddf12003b3779066a34f611c203f744fc54 Mon Sep 17 00:00:00 2001 From: Jimmy Hedman Date: Sun, 16 Apr 2023 22:10:07 +0200 Subject: [PATCH 08/71] Fixed dns2 for ethernet (#4698) --- esphome/components/ethernet/ethernet_component.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/ethernet/ethernet_component.cpp b/esphome/components/ethernet/ethernet_component.cpp index 8eb4718f49..447c5b8075 100644 --- a/esphome/components/ethernet/ethernet_component.cpp +++ b/esphome/components/ethernet/ethernet_component.cpp @@ -276,7 +276,7 @@ void EthernetComponent::start_connect_() { #endif dns_setserver(0, &d); } - if (uint32_t(this->manual_ip_->dns1) != 0) { + if (uint32_t(this->manual_ip_->dns2) != 0) { ip_addr_t d; #if LWIP_IPV6 d.type = IPADDR_TYPE_V4; From 8a60919e1ff145a433d090d740495b2f7064ed21 Mon Sep 17 00:00:00 2001 From: Szewcson Date: Sun, 16 Apr 2023 22:12:13 +0200 Subject: [PATCH 09/71] Add timeout to i2c write error logs (#4697) --- esphome/components/i2c/i2c_bus_arduino.cpp | 29 ++++++++++++++-------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/esphome/components/i2c/i2c_bus_arduino.cpp b/esphome/components/i2c/i2c_bus_arduino.cpp index 16d89c3450..e08622a3ae 100644 --- a/esphome/components/i2c/i2c_bus_arduino.cpp +++ b/esphome/components/i2c/i2c_bus_arduino.cpp @@ -154,18 +154,25 @@ ErrorCode ArduinoI2CBus::writev(uint8_t address, WriteBuffer *buffers, size_t cn } } uint8_t status = wire_->endTransmission(stop); - if (status == 0) { - return ERROR_OK; - } else if (status == 1) { - // transmit buffer not large enough - ESP_LOGVV(TAG, "TX failed: buffer not large enough"); - return ERROR_UNKNOWN; - } else if (status == 2 || status == 3) { - ESP_LOGVV(TAG, "TX failed: not acknowledged"); - return ERROR_NOT_ACKNOWLEDGED; + switch (status) { + case 0: + return ERROR_OK; + case 1: + // transmit buffer not large enough + ESP_LOGVV(TAG, "TX failed: buffer not large enough"); + return ERROR_UNKNOWN; + case 2: + case 3: + ESP_LOGVV(TAG, "TX failed: not acknowledged"); + return ERROR_NOT_ACKNOWLEDGED; + case 5: + ESP_LOGVV(TAG, "TX failed: timeout"); + return ERROR_UNKNOWN; + case 4: + default: + ESP_LOGVV(TAG, "TX failed: unknown error %u", status); + return ERROR_UNKNOWN; } - ESP_LOGVV(TAG, "TX failed: unknown error %u", status); - return ERROR_UNKNOWN; } /// Perform I2C bus recovery, see: From 3a587ea0d4f4d68f71227ff7bcf161c3070bd736 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 17 Apr 2023 14:57:28 +1200 Subject: [PATCH 10/71] Add event triggers to voice_assistant (#4699) * Add event triggers to voice_assistant * Add triggers to test --- .../components/voice_assistant/__init__.py | 48 +++++++++++++++++++ .../voice_assistant/voice_assistant.cpp | 17 ++++--- .../voice_assistant/voice_assistant.h | 14 ++++++ tests/test4.yaml | 20 ++++++++ 4 files changed, 93 insertions(+), 6 deletions(-) diff --git a/esphome/components/voice_assistant/__init__.py b/esphome/components/voice_assistant/__init__.py index c90fd554ae..20698a1b82 100644 --- a/esphome/components/voice_assistant/__init__.py +++ b/esphome/components/voice_assistant/__init__.py @@ -11,6 +11,14 @@ DEPENDENCIES = ["api", "microphone"] CODEOWNERS = ["@jesserockz"] +CONF_ON_START = "on_start" +CONF_ON_STT_END = "on_stt_end" +CONF_ON_TTS_START = "on_tts_start" +CONF_ON_TTS_END = "on_tts_end" +CONF_ON_END = "on_end" +CONF_ON_ERROR = "on_error" + + voice_assistant_ns = cg.esphome_ns.namespace("voice_assistant") VoiceAssistant = voice_assistant_ns.class_("VoiceAssistant", cg.Component) @@ -26,6 +34,12 @@ CONFIG_SCHEMA = cv.Schema( { cv.GenerateID(): cv.declare_id(VoiceAssistant), cv.GenerateID(CONF_MICROPHONE): cv.use_id(microphone.Microphone), + cv.Optional(CONF_ON_START): automation.validate_automation(single=True), + cv.Optional(CONF_ON_STT_END): automation.validate_automation(single=True), + cv.Optional(CONF_ON_TTS_START): automation.validate_automation(single=True), + cv.Optional(CONF_ON_TTS_END): automation.validate_automation(single=True), + cv.Optional(CONF_ON_END): automation.validate_automation(single=True), + cv.Optional(CONF_ON_ERROR): automation.validate_automation(single=True), } ).extend(cv.COMPONENT_SCHEMA) @@ -37,6 +51,40 @@ async def to_code(config): mic = await cg.get_variable(config[CONF_MICROPHONE]) cg.add(var.set_microphone(mic)) + if CONF_ON_START in config: + await automation.build_automation( + var.get_start_trigger(), [], config[CONF_ON_START] + ) + + if CONF_ON_STT_END in config: + await automation.build_automation( + var.get_stt_end_trigger(), [(cg.std_string, "x")], config[CONF_ON_STT_END] + ) + + if CONF_ON_TTS_START in config: + await automation.build_automation( + var.get_tts_start_trigger(), + [(cg.std_string, "x")], + config[CONF_ON_TTS_START], + ) + + if CONF_ON_TTS_END in config: + await automation.build_automation( + var.get_tts_end_trigger(), [(cg.std_string, "x")], config[CONF_ON_TTS_END] + ) + + if CONF_ON_END in config: + await automation.build_automation( + var.get_end_trigger(), [], config[CONF_ON_END] + ) + + if CONF_ON_ERROR in config: + await automation.build_automation( + var.get_error_trigger(), + [(cg.std_string, "code"), (cg.std_string, "message")], + config[CONF_ON_ERROR], + ) + cg.add_define("USE_VOICE_ASSISTANT") diff --git a/esphome/components/voice_assistant/voice_assistant.cpp b/esphome/components/voice_assistant/voice_assistant.cpp index 7f4bbf9934..777bef4edb 100644 --- a/esphome/components/voice_assistant/voice_assistant.cpp +++ b/esphome/components/voice_assistant/voice_assistant.cpp @@ -76,8 +76,9 @@ void VoiceAssistant::signal_stop() { void VoiceAssistant::on_event(const api::VoiceAssistantEventResponse &msg) { switch (msg.event_type) { - case api::enums::VOICE_ASSISTANT_RUN_END: - ESP_LOGD(TAG, "Voice Assistant ended."); + case api::enums::VOICE_ASSISTANT_RUN_START: + ESP_LOGD(TAG, "Assist Pipeline running"); + this->start_trigger_->trigger(); break; case api::enums::VOICE_ASSISTANT_STT_END: { std::string text; @@ -91,7 +92,7 @@ void VoiceAssistant::on_event(const api::VoiceAssistantEventResponse &msg) { return; } ESP_LOGD(TAG, "Speech recognised as: \"%s\"", text.c_str()); - // TODO `on_stt_end` trigger + this->stt_end_trigger_->trigger(text); break; } case api::enums::VOICE_ASSISTANT_TTS_START: { @@ -106,7 +107,7 @@ void VoiceAssistant::on_event(const api::VoiceAssistantEventResponse &msg) { return; } ESP_LOGD(TAG, "Response: \"%s\"", text.c_str()); - // TODO `on_tts_start` trigger + this->tts_start_trigger_->trigger(text); break; } case api::enums::VOICE_ASSISTANT_TTS_END: { @@ -121,9 +122,13 @@ void VoiceAssistant::on_event(const api::VoiceAssistantEventResponse &msg) { return; } ESP_LOGD(TAG, "Response URL: \"%s\"", url.c_str()); - // TODO `on_tts_end` trigger + this->tts_end_trigger_->trigger(url); break; } + case api::enums::VOICE_ASSISTANT_RUN_END: + ESP_LOGD(TAG, "Assist Pipeline ended"); + this->end_trigger_->trigger(); + break; case api::enums::VOICE_ASSISTANT_ERROR: { std::string code = ""; std::string message = ""; @@ -135,7 +140,7 @@ void VoiceAssistant::on_event(const api::VoiceAssistantEventResponse &msg) { } } ESP_LOGE(TAG, "Error: %s - %s", code.c_str(), message.c_str()); - // TODO `on_error` trigger + this->error_trigger_->trigger(code, message); } default: break; diff --git a/esphome/components/voice_assistant/voice_assistant.h b/esphome/components/voice_assistant/voice_assistant.h index 8cc20c31ad..813c006e98 100644 --- a/esphome/components/voice_assistant/voice_assistant.h +++ b/esphome/components/voice_assistant/voice_assistant.h @@ -25,10 +25,24 @@ class VoiceAssistant : public Component { void on_event(const api::VoiceAssistantEventResponse &msg); + Trigger<> *get_start_trigger() const { return this->start_trigger_; } + Trigger *get_stt_end_trigger() const { return this->stt_end_trigger_; } + Trigger *get_tts_start_trigger() const { return this->tts_start_trigger_; } + Trigger *get_tts_end_trigger() const { return this->tts_end_trigger_; } + Trigger<> *get_end_trigger() const { return this->end_trigger_; } + Trigger *get_error_trigger() const { return this->error_trigger_; } + protected: std::unique_ptr socket_ = nullptr; struct sockaddr_storage dest_addr_; + Trigger<> *start_trigger_ = new Trigger<>(); + Trigger *stt_end_trigger_ = new Trigger(); + Trigger *tts_start_trigger_ = new Trigger(); + Trigger *tts_end_trigger_ = new Trigger(); + Trigger<> *end_trigger_ = new Trigger<>(); + Trigger *error_trigger_ = new Trigger(); + microphone::Microphone *mic_{nullptr}; bool running_{false}; diff --git a/tests/test4.yaml b/tests/test4.yaml index c21e71be00..7b8f139a43 100644 --- a/tests/test4.yaml +++ b/tests/test4.yaml @@ -696,3 +696,23 @@ microphone: voice_assistant: microphone: mic_id + on_start: + - logger.log: "Voice assistant started" + on_stt_end: + - logger.log: + format: "Voice assistant STT ended with result %s" + args: [x.c_str()] + on_tts_start: + - logger.log: + format: "Voice assistant TTS started with text %s" + args: [x.c_str()] + on_tts_end: + - logger.log: + format: "Voice assistant TTS ended with url %s" + args: [x.c_str()] + on_end: + - logger.log: "Voice assistant ended" + on_error: + - logger.log: + format: "Voice assistant error - code %s, message: %s" + args: [code.c_str(), message.c_str()] From 4cea74ef3b52bc794abbcf7c216b5e5f4abc67fd Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 20 Apr 2023 08:56:37 +1200 Subject: [PATCH 11/71] Call on_error if no api client connected that handles voice (#4709) --- esphome/components/api/api_server.cpp | 6 ++++-- esphome/components/api/api_server.h | 2 +- esphome/components/voice_assistant/voice_assistant.cpp | 5 ++++- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/esphome/components/api/api_server.cpp b/esphome/components/api/api_server.cpp index fbef4b253f..97a7d6fbf6 100644 --- a/esphome/components/api/api_server.cpp +++ b/esphome/components/api/api_server.cpp @@ -428,10 +428,12 @@ void APIServer::on_shutdown() { } #ifdef USE_VOICE_ASSISTANT -void APIServer::start_voice_assistant() { +bool APIServer::start_voice_assistant() { + bool result = false; for (auto &c : this->clients_) { - c->request_voice_assistant(true); + result |= c->request_voice_assistant(true); } + return result; } void APIServer::stop_voice_assistant() { for (auto &c : this->clients_) { diff --git a/esphome/components/api/api_server.h b/esphome/components/api/api_server.h index 30103b2e3f..a1bec2802f 100644 --- a/esphome/components/api/api_server.h +++ b/esphome/components/api/api_server.h @@ -96,7 +96,7 @@ class APIServer : public Component, public Controller { #endif #ifdef USE_VOICE_ASSISTANT - void start_voice_assistant(); + bool start_voice_assistant(); void stop_voice_assistant(); #endif diff --git a/esphome/components/voice_assistant/voice_assistant.cpp b/esphome/components/voice_assistant/voice_assistant.cpp index 777bef4edb..e2d5bea90a 100644 --- a/esphome/components/voice_assistant/voice_assistant.cpp +++ b/esphome/components/voice_assistant/voice_assistant.cpp @@ -63,7 +63,10 @@ void VoiceAssistant::start(struct sockaddr_storage *addr, uint16_t port) { void VoiceAssistant::request_start() { ESP_LOGD(TAG, "Requesting start..."); - api::global_api_server->start_voice_assistant(); + if (!api::global_api_server->start_voice_assistant()) { + ESP_LOGW(TAG, "Could not request start."); + this->error_trigger_->trigger("not-connected", "Could not request start."); + } } void VoiceAssistant::signal_stop() { From 2be703b32997774cd92545e042fad53c8192dc63 Mon Sep 17 00:00:00 2001 From: tracestep <16390082+tracestep@users.noreply.github.com> Date: Wed, 19 Apr 2023 19:10:01 -0300 Subject: [PATCH 12/71] Add ethernet powerdown (fixes esphome/issues#4420) (#4706) * Add ethernet powerdown * Add on_shutdown (fixes esphome/issues#4420 * Sync dev and clang-tidy fix * fix typo and trainling space * Initialize phy_ member Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> * Use `this` pointer Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> * Member initialized at declaration Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> * Use `this` pointer Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> * Use `this` pointer --------- Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- .../ethernet/ethernet_component.cpp | 36 +++++++++++++------ .../components/ethernet/ethernet_component.h | 3 ++ 2 files changed, 29 insertions(+), 10 deletions(-) diff --git a/esphome/components/ethernet/ethernet_component.cpp b/esphome/components/ethernet/ethernet_component.cpp index 447c5b8075..0487ea5498 100644 --- a/esphome/components/ethernet/ethernet_component.cpp +++ b/esphome/components/ethernet/ethernet_component.cpp @@ -26,8 +26,10 @@ EthernetComponent::EthernetComponent() { global_eth_component = this; } void EthernetComponent::setup() { ESP_LOGCONFIG(TAG, "Setting up Ethernet..."); - // Delay here to allow power to stabilise before Ethernet is initialised. - delay(300); // NOLINT + if (esp_reset_reason() != ESP_RST_DEEPSLEEP) { + // Delay here to allow power to stabilise before Ethernet is initialized. + delay(300); // NOLINT + } esp_err_t err; err = esp_netif_init(); @@ -52,30 +54,29 @@ void EthernetComponent::setup() { esp_eth_mac_t *mac = esp_eth_mac_new_esp32(&mac_config); - esp_eth_phy_t *phy; switch (this->type_) { case ETHERNET_TYPE_LAN8720: { - phy = esp_eth_phy_new_lan87xx(&phy_config); + this->phy_ = esp_eth_phy_new_lan87xx(&phy_config); break; } case ETHERNET_TYPE_RTL8201: { - phy = esp_eth_phy_new_rtl8201(&phy_config); + this->phy_ = esp_eth_phy_new_rtl8201(&phy_config); break; } case ETHERNET_TYPE_DP83848: { - phy = esp_eth_phy_new_dp83848(&phy_config); + this->phy_ = esp_eth_phy_new_dp83848(&phy_config); break; } case ETHERNET_TYPE_IP101: { - phy = esp_eth_phy_new_ip101(&phy_config); + this->phy_ = esp_eth_phy_new_ip101(&phy_config); break; } case ETHERNET_TYPE_JL1101: { - phy = esp_eth_phy_new_jl1101(&phy_config); + this->phy_ = esp_eth_phy_new_jl1101(&phy_config); break; } case ETHERNET_TYPE_KSZ8081: { - phy = esp_eth_phy_new_ksz8081(&phy_config); + this->phy_ = esp_eth_phy_new_ksz8081(&phy_config); break; } default: { @@ -84,7 +85,7 @@ void EthernetComponent::setup() { } } - esp_eth_config_t eth_config = ETH_DEFAULT_CONFIG(mac, phy); + esp_eth_config_t eth_config = ETH_DEFAULT_CONFIG(mac, this->phy_); this->eth_handle_ = nullptr; err = esp_eth_driver_install(ð_config, &this->eth_handle_); ESPHL_ERROR_CHECK(err, "ETH driver install error"); @@ -356,6 +357,21 @@ std::string EthernetComponent::get_use_address() const { void EthernetComponent::set_use_address(const std::string &use_address) { this->use_address_ = use_address; } +bool EthernetComponent::powerdown() { + ESP_LOGI(TAG, "Powering down ethernet PHY"); + if (this->phy_ == nullptr) { + ESP_LOGE(TAG, "Ethernet PHY not assigned"); + return false; + } + this->connected_ = false; + this->started_ = false; + if (this->phy_->pwrctl(this->phy_, false) != ESP_OK) { + ESP_LOGE(TAG, "Error powering down ethernet PHY"); + return false; + } + return true; +} + } // namespace ethernet } // namespace esphome diff --git a/esphome/components/ethernet/ethernet_component.h b/esphome/components/ethernet/ethernet_component.h index 0d9ebf29a8..918e47212f 100644 --- a/esphome/components/ethernet/ethernet_component.h +++ b/esphome/components/ethernet/ethernet_component.h @@ -45,6 +45,7 @@ class EthernetComponent : public Component { void dump_config() override; float get_setup_priority() const override; bool can_proceed() override; + void on_shutdown() override { powerdown(); } bool is_connected(); void set_phy_addr(uint8_t phy_addr); @@ -58,6 +59,7 @@ class EthernetComponent : public Component { network::IPAddress get_ip_address(); std::string get_use_address() const; void set_use_address(const std::string &use_address); + bool powerdown(); protected: static void eth_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data); @@ -82,6 +84,7 @@ class EthernetComponent : public Component { uint32_t connect_begin_; esp_netif_t *eth_netif_{nullptr}; esp_eth_handle_t eth_handle_; + esp_eth_phy_t *phy_{nullptr}; }; // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) From 0f7e34e7ec4f8d38b6897f516c26de606c2ca5c0 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 20 Apr 2023 12:44:49 +1200 Subject: [PATCH 13/71] Bump arduino platform version to 5.3.0 (#4713) * Bump arduino platform version to 5.3.0 * Update root platformio.ini --- esphome/components/esp32/__init__.py | 2 +- platformio.ini | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index 62021afea9..d0f74b7226 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -163,7 +163,7 @@ RECOMMENDED_ARDUINO_FRAMEWORK_VERSION = cv.Version(2, 0, 5) # The platformio/espressif32 version to use for arduino frameworks # - https://github.com/platformio/platform-espressif32/releases # - https://api.registry.platformio.org/v3/packages/platformio/platform/espressif32 -ARDUINO_PLATFORM_VERSION = cv.Version(5, 2, 0) +ARDUINO_PLATFORM_VERSION = cv.Version(5, 3, 0) # The default/recommended esp-idf framework version # - https://github.com/espressif/esp-idf/releases diff --git a/platformio.ini b/platformio.ini index 3df6446ff1..c8db90bacb 100644 --- a/platformio.ini +++ b/platformio.ini @@ -103,7 +103,7 @@ extra_scripts = post:esphome/components/esp8266/post_build.py.script ; This are common settings for the ESP32 (all variants) using Arduino. [common:esp32-arduino] extends = common:arduino -platform = platformio/espressif32 @ 5.2.0 +platform = platformio/espressif32 @ 5.3.0 platform_packages = platformio/framework-arduinoespressif32 @ ~3.20005.0 From afc2b3b74f9bc2a7cfeebb2573676331259f0b8c Mon Sep 17 00:00:00 2001 From: Fabian Date: Thu, 20 Apr 2023 05:53:35 +0200 Subject: [PATCH 14/71] Keep Device Class in Flash. (#4639) * Keep Device Class in Flash. * Remove blank line --------- Co-authored-by: Your Name --- esphome/components/binary_sensor/binary_sensor.cpp | 7 +------ esphome/components/binary_sensor/binary_sensor.h | 9 +-------- esphome/components/button/button.cpp | 3 --- esphome/components/button/button.h | 9 +-------- esphome/components/cover/cover.cpp | 8 ++------ esphome/components/cover/cover.h | 5 +---- esphome/components/number/number_traits.cpp | 8 -------- esphome/components/number/number_traits.h | 8 ++------ esphome/components/sensor/sensor.cpp | 7 ------- esphome/components/sensor/sensor.h | 8 +------- esphome/components/switch/switch.cpp | 7 ------- esphome/components/switch/switch.h | 7 +------ esphome/core/entity_base.cpp | 10 ++++++++++ esphome/core/entity_base.h | 11 +++++++++++ 14 files changed, 31 insertions(+), 76 deletions(-) diff --git a/esphome/components/binary_sensor/binary_sensor.cpp b/esphome/components/binary_sensor/binary_sensor.cpp index bd33b2af2d..20604a0b7e 100644 --- a/esphome/components/binary_sensor/binary_sensor.cpp +++ b/esphome/components/binary_sensor/binary_sensor.cpp @@ -43,12 +43,7 @@ void BinarySensor::send_state_internal(bool state, bool is_initial) { } BinarySensor::BinarySensor() : state(false) {} -void BinarySensor::set_device_class(const std::string &device_class) { this->device_class_ = device_class; } -std::string BinarySensor::get_device_class() { - if (this->device_class_.has_value()) - return *this->device_class_; - return ""; -} + void BinarySensor::add_filter(Filter *filter) { filter->parent_ = this; if (this->filter_list_ == nullptr) { diff --git a/esphome/components/binary_sensor/binary_sensor.h b/esphome/components/binary_sensor/binary_sensor.h index 0bf8cf2cdc..301a472810 100644 --- a/esphome/components/binary_sensor/binary_sensor.h +++ b/esphome/components/binary_sensor/binary_sensor.h @@ -34,7 +34,7 @@ namespace binary_sensor { * The sub classes should notify the front-end of new states via the publish_state() method which * handles inverted inputs for you. */ -class BinarySensor : public EntityBase { +class BinarySensor : public EntityBase, public EntityBase_DeviceClass { public: explicit BinarySensor(); @@ -60,12 +60,6 @@ class BinarySensor : public EntityBase { /// The current reported state of the binary sensor. bool state; - /// Manually set the Home Assistant device class (see binary_sensor::device_class) - void set_device_class(const std::string &device_class); - - /// Get the device class for this binary sensor, using the manual override if specified. - std::string get_device_class(); - void add_filter(Filter *filter); void add_filters(const std::vector &filters); @@ -82,7 +76,6 @@ class BinarySensor : public EntityBase { protected: CallbackManager state_callback_{}; - optional device_class_{}; ///< Stores the override of the device class Filter *filter_list_{nullptr}; bool has_state_{false}; bool publish_initial_state_{false}; diff --git a/esphome/components/button/button.cpp b/esphome/components/button/button.cpp index dfa417de7b..4c4cb7740c 100644 --- a/esphome/components/button/button.cpp +++ b/esphome/components/button/button.cpp @@ -13,8 +13,5 @@ void Button::press() { } void Button::add_on_press_callback(std::function &&callback) { this->press_callback_.add(std::move(callback)); } -void Button::set_device_class(const std::string &device_class) { this->device_class_ = device_class; } -std::string Button::get_device_class() { return this->device_class_; } - } // namespace button } // namespace esphome diff --git a/esphome/components/button/button.h b/esphome/components/button/button.h index a4902810b2..9488eca221 100644 --- a/esphome/components/button/button.h +++ b/esphome/components/button/button.h @@ -26,7 +26,7 @@ namespace button { * * A button is just a momentary switch that does not have a state, only a trigger. */ -class Button : public EntityBase { +class Button : public EntityBase, public EntityBase_DeviceClass { public: /** Press this button. This is called by the front-end. * @@ -40,19 +40,12 @@ class Button : public EntityBase { */ void add_on_press_callback(std::function &&callback); - /// Set the Home Assistant device class (see button::device_class). - void set_device_class(const std::string &device_class); - - /// Get the device class for this button. - std::string get_device_class(); - protected: /** You should implement this virtual method if you want to create your own button. */ virtual void press_action() = 0; CallbackManager press_callback_{}; - std::string device_class_{}; }; } // namespace button diff --git a/esphome/components/cover/cover.cpp b/esphome/components/cover/cover.cpp index 24dd88b698..d139bab8ee 100644 --- a/esphome/components/cover/cover.cpp +++ b/esphome/components/cover/cover.cpp @@ -145,7 +145,7 @@ CoverCall &CoverCall::set_stop(bool stop) { return *this; } bool CoverCall::get_stop() const { return this->stop_; } -void Cover::set_device_class(const std::string &device_class) { this->device_class_override_ = device_class; } + CoverCall Cover::make_call() { return {this}; } void Cover::open() { auto call = this->make_call(); @@ -204,11 +204,7 @@ optional Cover::restore_state_() { return {}; return recovered; } -std::string Cover::get_device_class() { - if (this->device_class_override_.has_value()) - return *this->device_class_override_; - return ""; -} + bool Cover::is_fully_open() const { return this->position == COVER_OPEN; } bool Cover::is_fully_closed() const { return this->position == COVER_CLOSED; } diff --git a/esphome/components/cover/cover.h b/esphome/components/cover/cover.h index c6a420fa97..d21fbe02be 100644 --- a/esphome/components/cover/cover.h +++ b/esphome/components/cover/cover.h @@ -108,7 +108,7 @@ const char *cover_operation_to_str(CoverOperation op); * to control all values of the cover. Also implement get_traits() to return what operations * the cover supports. */ -class Cover : public EntityBase { +class Cover : public EntityBase, public EntityBase_DeviceClass { public: explicit Cover(); @@ -156,8 +156,6 @@ class Cover : public EntityBase { void publish_state(bool save = true); virtual CoverTraits get_traits() = 0; - void set_device_class(const std::string &device_class); - std::string get_device_class(); /// Helper method to check if the cover is fully open. Equivalent to comparing .position against 1.0 bool is_fully_open() const; @@ -172,7 +170,6 @@ class Cover : public EntityBase { optional restore_state_(); CallbackManager state_callback_{}; - optional device_class_override_{}; ESPPreferenceObject rtc_; }; diff --git a/esphome/components/number/number_traits.cpp b/esphome/components/number/number_traits.cpp index 1554f8d9c9..dcd05daa2a 100644 --- a/esphome/components/number/number_traits.cpp +++ b/esphome/components/number/number_traits.cpp @@ -16,13 +16,5 @@ std::string NumberTraits::get_unit_of_measurement() { return ""; } -void NumberTraits::set_device_class(const std::string &device_class) { this->device_class_ = device_class; } - -std::string NumberTraits::get_device_class() { - if (this->device_class_.has_value()) - return *this->device_class_; - return ""; -} - } // namespace number } // namespace esphome diff --git a/esphome/components/number/number_traits.h b/esphome/components/number/number_traits.h index ee10b0010c..5b14b77718 100644 --- a/esphome/components/number/number_traits.h +++ b/esphome/components/number/number_traits.h @@ -1,5 +1,6 @@ #pragma once +#include "esphome/core/entity_base.h" #include "esphome/core/helpers.h" namespace esphome { @@ -11,7 +12,7 @@ enum NumberMode : uint8_t { NUMBER_MODE_SLIDER = 2, }; -class NumberTraits { +class NumberTraits : public EntityBase_DeviceClass { public: // Set/get the number value boundaries. void set_min_value(float min_value) { min_value_ = min_value; } @@ -32,17 +33,12 @@ class NumberTraits { void set_mode(NumberMode mode) { this->mode_ = mode; } NumberMode get_mode() const { return this->mode_; } - // Set/get the device class. - void set_device_class(const std::string &device_class); - std::string get_device_class(); - protected: float min_value_ = NAN; float max_value_ = NAN; float step_ = NAN; optional unit_of_measurement_; ///< Unit of measurement override NumberMode mode_{NUMBER_MODE_AUTO}; - optional device_class_; }; } // namespace number diff --git a/esphome/components/sensor/sensor.cpp b/esphome/components/sensor/sensor.cpp index fc66e03d6b..6ce1e193f5 100644 --- a/esphome/components/sensor/sensor.cpp +++ b/esphome/components/sensor/sensor.cpp @@ -38,13 +38,6 @@ int8_t Sensor::get_accuracy_decimals() { } void Sensor::set_accuracy_decimals(int8_t accuracy_decimals) { this->accuracy_decimals_ = accuracy_decimals; } -std::string Sensor::get_device_class() { - if (this->device_class_.has_value()) - return *this->device_class_; - return ""; -} -void Sensor::set_device_class(const std::string &device_class) { this->device_class_ = device_class; } - void Sensor::set_state_class(StateClass state_class) { this->state_class_ = state_class; } StateClass Sensor::get_state_class() { if (this->state_class_.has_value()) diff --git a/esphome/components/sensor/sensor.h b/esphome/components/sensor/sensor.h index efcada1411..165d013b2a 100644 --- a/esphome/components/sensor/sensor.h +++ b/esphome/components/sensor/sensor.h @@ -54,7 +54,7 @@ std::string state_class_to_string(StateClass state_class); * * A sensor has unit of measurement and can use publish_state to send out a new value with the specified accuracy. */ -class Sensor : public EntityBase { +class Sensor : public EntityBase, public EntityBase_DeviceClass { public: explicit Sensor(); @@ -68,11 +68,6 @@ class Sensor : public EntityBase { /// Manually set the accuracy in decimals. void set_accuracy_decimals(int8_t accuracy_decimals); - /// Get the device class, using the manual override if set. - std::string get_device_class(); - /// Manually set the device class. - void set_device_class(const std::string &device_class); - /// Get the state class, using the manual override if set. StateClass get_state_class(); /// Manually set the state class. @@ -165,7 +160,6 @@ class Sensor : public EntityBase { optional unit_of_measurement_; ///< Unit of measurement override optional accuracy_decimals_; ///< Accuracy in decimals override - optional device_class_; ///< Device class override optional state_class_{STATE_CLASS_NONE}; ///< State class override bool force_update_{false}; ///< Force update mode bool has_state_{false}; diff --git a/esphome/components/switch/switch.cpp b/esphome/components/switch/switch.cpp index 72e7add158..96611b0b87 100644 --- a/esphome/components/switch/switch.cpp +++ b/esphome/components/switch/switch.cpp @@ -63,13 +63,6 @@ void Switch::add_on_state_callback(std::function &&callback) { void Switch::set_inverted(bool inverted) { this->inverted_ = inverted; } bool Switch::is_inverted() const { return this->inverted_; } -std::string Switch::get_device_class() { - if (this->device_class_.has_value()) - return *this->device_class_; - return ""; -} -void Switch::set_device_class(const std::string &device_class) { this->device_class_ = device_class; } - void log_switch(const char *tag, const char *prefix, const char *type, Switch *obj) { if (obj != nullptr) { ESP_LOGCONFIG(tag, "%s%s '%s'", prefix, type, obj->get_name().c_str()); diff --git a/esphome/components/switch/switch.h b/esphome/components/switch/switch.h index 8bea3b36db..9daac4ee23 100644 --- a/esphome/components/switch/switch.h +++ b/esphome/components/switch/switch.h @@ -29,7 +29,7 @@ enum SwitchRestoreMode { * A switch is basically just a combination of a binary sensor (for reporting switch values) * and a write_state method that writes a state to the hardware. */ -class Switch : public EntityBase { +class Switch : public EntityBase, public EntityBase_DeviceClass { public: explicit Switch(); @@ -103,10 +103,6 @@ class Switch : public EntityBase { bool is_inverted() const; - /// Get the device class for this switch. - std::string get_device_class(); - /// Set the Home Assistant device class for this switch. - void set_device_class(const std::string &device_class); void set_restore_mode(SwitchRestoreMode restore_mode) { this->restore_mode = restore_mode; } protected: @@ -124,7 +120,6 @@ class Switch : public EntityBase { bool inverted_{false}; Deduplicator publish_dedup_; ESPPreferenceObject rtc_; - optional device_class_; }; #define LOG_SWITCH(prefix, type, obj) log_switch((TAG), (prefix), LOG_STR_LITERAL(type), (obj)) diff --git a/esphome/core/entity_base.cpp b/esphome/core/entity_base.cpp index 3d61e36fd1..1e2ccc35b5 100644 --- a/esphome/core/entity_base.cpp +++ b/esphome/core/entity_base.cpp @@ -72,6 +72,16 @@ void EntityBase::calc_object_id_() { this->object_id_hash_ = fnv1_hash(this->object_id_c_str_); } } + uint32_t EntityBase::get_object_id_hash() { return this->object_id_hash_; } +std::string EntityBase_DeviceClass::get_device_class() { + if (this->device_class_ == nullptr) { + return ""; + } + return this->device_class_; +} + +void EntityBase_DeviceClass::set_device_class(const char *device_class) { this->device_class_ = device_class; } + } // namespace esphome diff --git a/esphome/core/entity_base.h b/esphome/core/entity_base.h index e40a7013bf..d717674450 100644 --- a/esphome/core/entity_base.h +++ b/esphome/core/entity_base.h @@ -63,4 +63,15 @@ class EntityBase { EntityCategory entity_category_{ENTITY_CATEGORY_NONE}; }; +class EntityBase_DeviceClass { + public: + /// Get the device class, using the manual override if set. + std::string get_device_class(); + /// Manually set the device class. + void set_device_class(const char *device_class); + + protected: + const char *device_class_{nullptr}; ///< Device class override +}; + } // namespace esphome From 4c396314286bc459ceb780891698488fb7dbfda2 Mon Sep 17 00:00:00 2001 From: Bella Coola <31868524+BellaCoola@users.noreply.github.com> Date: Thu, 20 Apr 2023 03:53:42 +0000 Subject: [PATCH 15/71] Add support for passive WiFi scanning (#4666) * Add support for passive WiFi scanning. * Apply suggestions from code review Made changes suggested by @jesserockz Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --------- Co-authored-by: BellaCoola Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/wifi/__init__.py | 3 +++ esphome/components/wifi/wifi_component.cpp | 4 +++- esphome/components/wifi/wifi_component.h | 5 ++++- .../wifi/wifi_component_esp32_arduino.cpp | 4 ++-- .../wifi/wifi_component_esp8266.cpp | 20 +++++++++++++------ .../wifi/wifi_component_esp_idf.cpp | 12 +++++++---- .../components/wifi/wifi_component_pico_w.cpp | 3 ++- 7 files changed, 36 insertions(+), 15 deletions(-) diff --git a/esphome/components/wifi/__init__.py b/esphome/components/wifi/__init__.py index f5684f06f7..c9da07795c 100644 --- a/esphome/components/wifi/__init__.py +++ b/esphome/components/wifi/__init__.py @@ -252,6 +252,7 @@ def _validate(config): CONF_OUTPUT_POWER = "output_power" +CONF_PASSIVE_SCAN = "passive_scan" CONFIG_SCHEMA = cv.All( cv.Schema( { @@ -280,6 +281,7 @@ CONFIG_SCHEMA = cv.All( cv.SplitDefault(CONF_ENABLE_RRM, esp32_idf=False): cv.All( cv.boolean, cv.only_with_esp_idf ), + cv.Optional(CONF_PASSIVE_SCAN, default=False): cv.boolean, cv.Optional("enable_mdns"): cv.invalid( "This option has been removed. Please use the [disabled] option under the " "new mdns component instead." @@ -379,6 +381,7 @@ async def to_code(config): cg.add(var.set_reboot_timeout(config[CONF_REBOOT_TIMEOUT])) cg.add(var.set_power_save_mode(config[CONF_POWER_SAVE_MODE])) cg.add(var.set_fast_connect(config[CONF_FAST_CONNECT])) + cg.add(var.set_passive_scan(config[CONF_PASSIVE_SCAN])) if CONF_OUTPUT_POWER in config: cg.add(var.set_output_power(config[CONF_OUTPUT_POWER])) diff --git a/esphome/components/wifi/wifi_component.cpp b/esphome/components/wifi/wifi_component.cpp index efb1af171d..9f047dd5ed 100644 --- a/esphome/components/wifi/wifi_component.cpp +++ b/esphome/components/wifi/wifi_component.cpp @@ -385,7 +385,7 @@ void WiFiComponent::print_connect_params_() { void WiFiComponent::start_scanning() { this->action_started_ = millis(); ESP_LOGD(TAG, "Starting scan..."); - this->wifi_scan_start_(); + this->wifi_scan_start_(this->passive_scan_); this->state_ = WIFI_COMPONENT_STATE_STA_SCANNING; } @@ -615,6 +615,8 @@ bool WiFiComponent::is_connected() { } void WiFiComponent::set_power_save_mode(WiFiPowerSaveMode power_save) { this->power_save_ = power_save; } +void WiFiComponent::set_passive_scan(bool passive) { this->passive_scan_ = passive; } + std::string WiFiComponent::format_mac_addr(const uint8_t *mac) { char buf[20]; sprintf(buf, "%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); diff --git a/esphome/components/wifi/wifi_component.h b/esphome/components/wifi/wifi_component.h index 544cb3dc61..3f81b94cce 100644 --- a/esphome/components/wifi/wifi_component.h +++ b/esphome/components/wifi/wifi_component.h @@ -217,6 +217,8 @@ class WiFiComponent : public Component { void set_power_save_mode(WiFiPowerSaveMode power_save); void set_output_power(float output_power) { output_power_ = output_power; } + void set_passive_scan(bool passive); + void save_wifi_sta(const std::string &ssid, const std::string &password); // ========== INTERNAL METHODS ========== // (In most use cases you won't need these) @@ -294,7 +296,7 @@ class WiFiComponent : public Component { bool wifi_sta_connect_(const WiFiAP &ap); void wifi_pre_setup_(); WiFiSTAConnectStatus wifi_sta_connect_status_(); - bool wifi_scan_start_(); + bool wifi_scan_start_(bool passive); bool wifi_ap_ip_config_(optional manual_ip); bool wifi_start_ap_(const WiFiAP &ap); bool wifi_disconnect_(); @@ -349,6 +351,7 @@ class WiFiComponent : public Component { bool scan_done_{false}; bool ap_setup_{false}; optional output_power_; + bool passive_scan_{false}; ESPPreferenceObject pref_; bool has_saved_wifi_settings_{false}; #ifdef USE_WIFI_11KV_SUPPORT diff --git a/esphome/components/wifi/wifi_component_esp32_arduino.cpp b/esphome/components/wifi/wifi_component_esp32_arduino.cpp index ab04224161..f35f5dfc43 100644 --- a/esphome/components/wifi/wifi_component_esp32_arduino.cpp +++ b/esphome/components/wifi/wifi_component_esp32_arduino.cpp @@ -618,13 +618,13 @@ WiFiSTAConnectStatus WiFiComponent::wifi_sta_connect_status_() { } return WiFiSTAConnectStatus::IDLE; } -bool WiFiComponent::wifi_scan_start_() { +bool WiFiComponent::wifi_scan_start_(bool passive) { // enable STA if (!this->wifi_mode_(true, {})) return false; // need to use WiFi because of WiFiScanClass allocations :( - int16_t err = WiFi.scanNetworks(true, true, false, 200); + int16_t err = WiFi.scanNetworks(true, true, passive, 200); if (err != WIFI_SCAN_RUNNING) { ESP_LOGV(TAG, "WiFi.scanNetworks failed! %d", err); return false; diff --git a/esphome/components/wifi/wifi_component_esp8266.cpp b/esphome/components/wifi/wifi_component_esp8266.cpp index de4253fe41..8b38297b17 100644 --- a/esphome/components/wifi/wifi_component_esp8266.cpp +++ b/esphome/components/wifi/wifi_component_esp8266.cpp @@ -601,7 +601,7 @@ WiFiSTAConnectStatus WiFiComponent::wifi_sta_connect_status_() { return WiFiSTAConnectStatus::IDLE; } } -bool WiFiComponent::wifi_scan_start_() { +bool WiFiComponent::wifi_scan_start_(bool passive) { static bool first_scan = false; // enable STA @@ -615,13 +615,21 @@ bool WiFiComponent::wifi_scan_start_() { config.channel = 0; config.show_hidden = 1; #if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 4, 0) - config.scan_type = WIFI_SCAN_TYPE_ACTIVE; + config.scan_type = passive ? WIFI_SCAN_TYPE_PASSIVE : WIFI_SCAN_TYPE_ACTIVE; if (first_scan) { - config.scan_time.active.min = 100; - config.scan_time.active.max = 200; + if (passive) { + config.scan_time.passive = 200; + } else { + config.scan_time.active.min = 100; + config.scan_time.active.max = 200; + } } else { - config.scan_time.active.min = 400; - config.scan_time.active.max = 500; + if (passive) { + config.scan_time.passive = 500; + } else { + config.scan_time.active.min = 400; + config.scan_time.active.max = 500; + } } #endif first_scan = false; diff --git a/esphome/components/wifi/wifi_component_esp_idf.cpp b/esphome/components/wifi/wifi_component_esp_idf.cpp index 1edde74743..9b2fdaf500 100644 --- a/esphome/components/wifi/wifi_component_esp_idf.cpp +++ b/esphome/components/wifi/wifi_component_esp_idf.cpp @@ -736,7 +736,7 @@ WiFiSTAConnectStatus WiFiComponent::wifi_sta_connect_status_() { } return WiFiSTAConnectStatus::IDLE; } -bool WiFiComponent::wifi_scan_start_() { +bool WiFiComponent::wifi_scan_start_(bool passive) { // enable STA if (!this->wifi_mode_(true, {})) return false; @@ -746,9 +746,13 @@ bool WiFiComponent::wifi_scan_start_() { config.bssid = nullptr; config.channel = 0; config.show_hidden = true; - config.scan_type = WIFI_SCAN_TYPE_ACTIVE; - config.scan_time.active.min = 100; - config.scan_time.active.max = 300; + config.scan_type = passive ? WIFI_SCAN_TYPE_PASSIVE : WIFI_SCAN_TYPE_ACTIVE; + if (passive) { + config.scan_time.passive = 300; + } else { + config.scan_time.active.min = 100; + config.scan_time.active.max = 300; + } esp_err_t err = esp_wifi_scan_start(&config, false); if (err != ESP_OK) { diff --git a/esphome/components/wifi/wifi_component_pico_w.cpp b/esphome/components/wifi/wifi_component_pico_w.cpp index 8e64878f8e..489ebc3699 100644 --- a/esphome/components/wifi/wifi_component_pico_w.cpp +++ b/esphome/components/wifi/wifi_component_pico_w.cpp @@ -125,10 +125,11 @@ void WiFiComponent::wifi_scan_result(void *env, const cyw43_ev_scan_result_t *re } } -bool WiFiComponent::wifi_scan_start_() { +bool WiFiComponent::wifi_scan_start_(bool passive) { this->scan_result_.clear(); this->scan_done_ = false; cyw43_wifi_scan_options_t scan_options = {0}; + scan_options.scan_type = passive ? 1 : 0; int err = cyw43_wifi_scan(&cyw43_state, &scan_options, nullptr, &s_wifi_scan_result); if (err) { ESP_LOGV(TAG, "cyw43_wifi_scan failed!"); From c0ad5d1d16461acf0fe6de59e38bd9f224aff16d Mon Sep 17 00:00:00 2001 From: Keith Burzinski Date: Wed, 19 Apr 2023 22:54:06 -0500 Subject: [PATCH 16/71] Initial attempt at supporting ESP-IDF 5.0.0 (#4364) * requirements: add pyparsing >= 3.0 ESP-IDF >= 5.0 requires pyparsing's rest_of_file, which was introduced in version 3.0. Signed-off-by: Stijn Tintel * esp32: fix build with ESP-IDF >= 5 We need to include esp_timer.h to be able to use esp_timer_get_time(). This header existed in ESP-IDF < 5 so we don't need if guards. Signed-off-by: Stijn Tintel * ota: fix build with ESP-IDF >= 5 As of version 5, esp_task_wdt_init() takes a struct as argument. We also need to include spi_flash_mmap.h. [split unrelated change into separate commits, maintain ESP-IDF < 5 compat, use esp_task_wdt_reconfigure, add commit message] Signed-off-by: Stijn Tintel * core: fix build with ESP-IDF >= 5 These header files already existed in ESP-IDF < 5 so skip if guards. [add commit message] Signed-off-by: Stijn Tintel * wifi: fix build with ESP-IDF >= 5 ESP-IDF 4.1 introduced the esp-netif API as successor to the tcp_adapter API. The tcp_adapter API was removed in ESP-IDF 5.0.0. Part of the wifi component was already migrated to the new API. Migrate the leftover uses of the old API to the new API to fix build on ESP-IDF >= 5. The version of ESP-IDF currently in use (4.4.4) supports the new API, so we don't need any if guards to maintain backwards compatibility. Also replace xQueueHandle, which is a pre FreeRTOS v8.0.0 data type, with QueueHandle_t, so we don't need to enable backward compatibility (CONFIG_FREERTOS_ENABLE_BACKWARD_COMPATIBILITY). This reverts part of commit d42f35de5d54 to wifi_component_esp_idf.cpp, as the esp-netif API handles that internally. [replace pre FreeRTOS v8.0.0 data type, add commit message] Signed-off-by: Stijn Tintel * mdns: fix build with ESP-IDF >= 5 In ESP-IDF 5.0.0, the mdns component was removed and moved to another repository. Since the mdns component in esphome is always built, we need to add the mdns component from the esp-protocols repository. This component depends on ESP-IDF >= 5.0, so we need to add a version guard. Signed-off-by: Stijn Tintel * docker: install python3-venv As of version 6.0.1, platform-espressif32 requires python3-venv. Switching between esp-idf 4.4.4 and 5.0 causes problems with esp-idf python dependencies installed by PlatformIO. They've solved this by using venv. Install python3-venv so that platform-espressif32 6.0.1 and later can be used, and we don't need to wipe the dependencies manually when switching esp-idf versions. Signed-off-by: Stijn Tintel --------- Signed-off-by: Stijn Tintel Co-authored-by: Stijn Tintel --- docker/Dockerfile | 1 + esphome/components/esp32/core.cpp | 1 + esphome/components/mdns/__init__.py | 13 +++ .../components/ota/ota_backend_esp_idf.cpp | 27 +++++- .../wifi/wifi_component_esp_idf.cpp | 88 ++++++++----------- esphome/core/helpers.cpp | 2 + requirements.txt | 3 + 7 files changed, 82 insertions(+), 53 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 383c73565d..13fba50288 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -24,6 +24,7 @@ RUN \ python3-setuptools=52.0.0-4 \ python3-pil=8.1.2+dfsg-0.3+deb11u1 \ python3-cryptography=3.3.2-1 \ + python3-venv=3.9.2-3 \ iputils-ping=3:20210202-1 \ git=1:2.30.2-1 \ curl=7.74.0-1.3+deb11u7 \ diff --git a/esphome/components/esp32/core.cpp b/esphome/components/esp32/core.cpp index b47392bc6b..512a8857b6 100644 --- a/esphome/components/esp32/core.cpp +++ b/esphome/components/esp32/core.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #if ESP_IDF_VERSION_MAJOR >= 4 diff --git a/esphome/components/mdns/__init__.py b/esphome/components/mdns/__init__.py index e27786a98b..66c84da8d8 100644 --- a/esphome/components/mdns/__init__.py +++ b/esphome/components/mdns/__init__.py @@ -4,10 +4,13 @@ from esphome.const import ( CONF_PROTOCOL, CONF_SERVICES, CONF_SERVICE, + KEY_CORE, + KEY_FRAMEWORK_VERSION, ) import esphome.codegen as cg import esphome.config_validation as cv from esphome.core import CORE, coroutine_with_priority +from esphome.components.esp32 import add_idf_component CODEOWNERS = ["@esphome/core"] DEPENDENCIES = ["network"] @@ -79,6 +82,16 @@ async def to_code(config): elif CORE.is_rp2040: cg.add_library("LEAmDNS", None) + if CORE.using_esp_idf and CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] >= cv.Version( + 5, 0, 0 + ): + add_idf_component( + "mdns", + "https://github.com/espressif/esp-protocols.git", + "mdns-v1.0.9", + "components/mdns", + ) + if config[CONF_DISABLED]: return diff --git a/esphome/components/ota/ota_backend_esp_idf.cpp b/esphome/components/ota/ota_backend_esp_idf.cpp index 2fdc00c54d..7688629e39 100644 --- a/esphome/components/ota/ota_backend_esp_idf.cpp +++ b/esphome/components/ota/ota_backend_esp_idf.cpp @@ -8,6 +8,10 @@ #include #include "esphome/components/md5/md5.h" +#if ESP_IDF_VERSION_MAJOR >= 5 +#include +#endif + namespace esphome { namespace ota { @@ -16,9 +20,28 @@ OTAResponseTypes IDFOTABackend::begin(size_t image_size) { if (this->partition_ == nullptr) { return OTA_RESPONSE_ERROR_NO_UPDATE_PARTITION; } - esp_task_wdt_init(15, false); // The following function takes longer than the 5 seconds timeout of WDT + + // The following function takes longer than the 5 seconds timeout of WDT +#if ESP_IDF_VERSION_MAJOR >= 5 + esp_task_wdt_config_t wdtc; + wdtc.timeout_ms = 15000; + wdtc.idle_core_mask = 0; + wdtc.trigger_panic = false; + esp_task_wdt_reconfigure(&wdtc); +#else + esp_task_wdt_init(15, false); +#endif + esp_err_t err = esp_ota_begin(this->partition_, image_size, &this->update_handle_); - esp_task_wdt_init(CONFIG_ESP_TASK_WDT_TIMEOUT_S, false); // Set the WDT back to the configured timeout + + // Set the WDT back to the configured timeout +#if ESP_IDF_VERSION_MAJOR >= 5 + wdtc.timeout_ms = CONFIG_ESP_TASK_WDT_TIMEOUT_S; + esp_task_wdt_reconfigure(&wdtc); +#else + esp_task_wdt_init(CONFIG_ESP_TASK_WDT_TIMEOUT_S, false); +#endif + if (err != ESP_OK) { esp_ota_abort(this->update_handle_); this->update_handle_ = 0; diff --git a/esphome/components/wifi/wifi_component_esp_idf.cpp b/esphome/components/wifi/wifi_component_esp_idf.cpp index 9b2fdaf500..1c70f33040 100644 --- a/esphome/components/wifi/wifi_component_esp_idf.cpp +++ b/esphome/components/wifi/wifi_component_esp_idf.cpp @@ -17,6 +17,7 @@ #ifdef USE_WIFI_WPA2_EAP #include #endif +#include "dhcpserver/dhcpserver.h" #include "lwip/err.h" #include "lwip/dns.h" @@ -32,7 +33,7 @@ namespace wifi { static const char *const TAG = "wifi_esp32"; static EventGroupHandle_t s_wifi_event_group; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) -static xQueueHandle s_event_queue; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +static QueueHandle_t s_event_queue; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) static esp_netif_t *s_sta_netif = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) static esp_netif_t *s_ap_netif = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) static bool s_sta_started = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) @@ -414,17 +415,17 @@ bool WiFiComponent::wifi_sta_ip_config_(optional manual_ip) { if (!this->wifi_mode_(true, {})) return false; - tcpip_adapter_dhcp_status_t dhcp_status; - esp_err_t err = tcpip_adapter_dhcpc_get_status(TCPIP_ADAPTER_IF_STA, &dhcp_status); + esp_netif_dhcp_status_t dhcp_status; + esp_err_t err = esp_netif_dhcpc_get_status(s_sta_netif, &dhcp_status); if (err != ESP_OK) { - ESP_LOGV(TAG, "tcpip_adapter_dhcpc_get_status failed: %s", esp_err_to_name(err)); + ESP_LOGV(TAG, "esp_netif_dhcpc_get_status failed: %s", esp_err_to_name(err)); return false; } if (!manual_ip.has_value()) { - // Use DHCP client - if (dhcp_status != TCPIP_ADAPTER_DHCP_STARTED) { - err = tcpip_adapter_dhcpc_start(TCPIP_ADAPTER_IF_STA); + // No manual IP is set; use DHCP client + if (dhcp_status != ESP_NETIF_DHCP_STARTED) { + err = esp_netif_dhcpc_start(s_sta_netif); if (err != ESP_OK) { ESP_LOGV(TAG, "Starting DHCP client failed! %d", err); } @@ -433,43 +434,29 @@ bool WiFiComponent::wifi_sta_ip_config_(optional manual_ip) { return true; } - tcpip_adapter_ip_info_t info; - memset(&info, 0, sizeof(info)); + esp_netif_ip_info_t info; // struct of ip4_addr_t with ip, netmask, gw info.ip.addr = static_cast(manual_ip->static_ip); info.gw.addr = static_cast(manual_ip->gateway); info.netmask.addr = static_cast(manual_ip->subnet); - - err = tcpip_adapter_dhcpc_stop(TCPIP_ADAPTER_IF_STA); + err = esp_netif_dhcpc_stop(s_sta_netif); if (err != ESP_OK && err != ESP_ERR_ESP_NETIF_DHCP_ALREADY_STOPPED) { - ESP_LOGV(TAG, "tcpip_adapter_dhcpc_stop failed: %s", esp_err_to_name(err)); + ESP_LOGV(TAG, "esp_netif_dhcpc_stop failed: %s", esp_err_to_name(err)); return false; } - - err = tcpip_adapter_set_ip_info(TCPIP_ADAPTER_IF_STA, &info); + err = esp_netif_set_ip_info(s_sta_netif, &info); if (err != ESP_OK) { - ESP_LOGV(TAG, "tcpip_adapter_set_ip_info failed: %s", esp_err_to_name(err)); + ESP_LOGV(TAG, "esp_netif_set_ip_info failed: %s", esp_err_to_name(err)); return false; } - ip_addr_t dns; -#if LWIP_IPV6 - dns.type = IPADDR_TYPE_V4; -#endif + esp_netif_dns_info_t dns; if (uint32_t(manual_ip->dns1) != 0) { -#if LWIP_IPV6 - dns.u_addr.ip4.addr = static_cast(manual_ip->dns1); -#else - dns.addr = static_cast(manual_ip->dns1); -#endif - dns_setserver(0, &dns); + dns.ip.u_addr.ip4.addr = static_cast(manual_ip->dns1); + esp_netif_set_dns_info(s_sta_netif, ESP_NETIF_DNS_MAIN, &dns); } if (uint32_t(manual_ip->dns2) != 0) { -#if LWIP_IPV6 - dns.u_addr.ip4.addr = static_cast(manual_ip->dns2); -#else - dns.addr = static_cast(manual_ip->dns2); -#endif - dns_setserver(1, &dns); + dns.ip.u_addr.ip4.addr = static_cast(manual_ip->dns2); + esp_netif_set_dns_info(s_sta_netif, ESP_NETIF_DNS_BACKUP, &dns); } return true; @@ -478,10 +465,10 @@ bool WiFiComponent::wifi_sta_ip_config_(optional manual_ip) { network::IPAddress WiFiComponent::wifi_sta_ip() { if (!this->has_sta()) return {}; - tcpip_adapter_ip_info_t ip; - esp_err_t err = tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_STA, &ip); + esp_netif_ip_info_t ip; + esp_err_t err = esp_netif_get_ip_info(s_sta_netif, &ip); if (err != ESP_OK) { - ESP_LOGV(TAG, "tcpip_adapter_get_ip_info failed: %s", esp_err_to_name(err)); + ESP_LOGV(TAG, "esp_netif_get_ip_info failed: %s", esp_err_to_name(err)); return false; } return {ip.ip.addr}; @@ -601,9 +588,9 @@ void WiFiComponent::wifi_process_event_(IDFWiFiEvent *data) { if (data->event_base == WIFI_EVENT && data->event_id == WIFI_EVENT_STA_START) { ESP_LOGV(TAG, "Event: WiFi STA start"); // apply hostname - err = tcpip_adapter_set_hostname(TCPIP_ADAPTER_IF_STA, App.get_name().c_str()); + err = esp_netif_set_hostname(s_sta_netif, App.get_name().c_str()); if (err != ERR_OK) { - ESP_LOGW(TAG, "tcpip_adapter_set_hostname failed: %s", esp_err_to_name(err)); + ESP_LOGW(TAG, "esp_netif_set_hostname failed: %s", esp_err_to_name(err)); } s_sta_started = true; @@ -651,7 +638,7 @@ void WiFiComponent::wifi_process_event_(IDFWiFiEvent *data) { } else if (data->event_base == IP_EVENT && data->event_id == IP_EVENT_STA_GOT_IP) { const auto &it = data->data.ip_got_ip; #if LWIP_IPV6_AUTOCONFIG - tcpip_adapter_create_ip6_linklocal(TCPIP_ADAPTER_IF_STA); + esp_netif_create_ip6_linklocal(s_sta_netif); #endif ESP_LOGV(TAG, "Event: Got IP static_ip=%s gateway=%s", format_ip4_addr(it.ip_info.ip).c_str(), format_ip4_addr(it.ip_info.gw).c_str()); @@ -770,8 +757,7 @@ bool WiFiComponent::wifi_ap_ip_config_(optional manual_ip) { if (!this->wifi_mode_({}, true)) return false; - tcpip_adapter_ip_info_t info; - memset(&info, 0, sizeof(info)); + esp_netif_ip_info_t info; if (manual_ip.has_value()) { info.ip.addr = static_cast(manual_ip->static_ip); info.gw.addr = static_cast(manual_ip->gateway); @@ -781,17 +767,17 @@ bool WiFiComponent::wifi_ap_ip_config_(optional manual_ip) { info.gw.addr = static_cast(network::IPAddress(192, 168, 4, 1)); info.netmask.addr = static_cast(network::IPAddress(255, 255, 255, 0)); } - tcpip_adapter_dhcp_status_t dhcp_status; - tcpip_adapter_dhcps_get_status(TCPIP_ADAPTER_IF_AP, &dhcp_status); - err = tcpip_adapter_dhcps_stop(TCPIP_ADAPTER_IF_AP); + esp_netif_dhcp_status_t dhcp_status; + esp_netif_dhcps_get_status(s_sta_netif, &dhcp_status); + err = esp_netif_dhcps_stop(s_sta_netif); if (err != ESP_OK) { - ESP_LOGV(TAG, "tcpip_adapter_dhcps_stop failed! %d", err); + ESP_LOGV(TAG, "esp_netif_dhcps_stop failed! %d", err); return false; } - err = tcpip_adapter_set_ip_info(TCPIP_ADAPTER_IF_AP, &info); + err = esp_netif_set_ip_info(s_sta_netif, &info); if (err != ESP_OK) { - ESP_LOGV(TAG, "tcpip_adapter_set_ip_info failed! %d", err); + ESP_LOGV(TAG, "esp_netif_set_ip_info failed! %d", err); return false; } @@ -804,17 +790,17 @@ bool WiFiComponent::wifi_ap_ip_config_(optional manual_ip) { start_address[3] += 100; lease.end_ip.addr = static_cast(start_address); ESP_LOGV(TAG, "DHCP server IP lease end: %s", start_address.str().c_str()); - err = tcpip_adapter_dhcps_option(TCPIP_ADAPTER_OP_SET, TCPIP_ADAPTER_REQUESTED_IP_ADDRESS, &lease, sizeof(lease)); + err = esp_netif_dhcps_option(s_sta_netif, ESP_NETIF_OP_SET, ESP_NETIF_REQUESTED_IP_ADDRESS, &lease, sizeof(lease)); if (err != ESP_OK) { - ESP_LOGV(TAG, "tcpip_adapter_dhcps_option failed! %d", err); + ESP_LOGV(TAG, "esp_netif_dhcps_option failed! %d", err); return false; } - err = tcpip_adapter_dhcps_start(TCPIP_ADAPTER_IF_AP); + err = esp_netif_dhcps_start(s_sta_netif); if (err != ESP_OK) { - ESP_LOGV(TAG, "tcpip_adapter_dhcps_start failed! %d", err); + ESP_LOGV(TAG, "esp_netif_dhcps_start failed! %d", err); return false; } @@ -860,8 +846,8 @@ bool WiFiComponent::wifi_start_ap_(const WiFiAP &ap) { return true; } network::IPAddress WiFiComponent::wifi_soft_ap_ip() { - tcpip_adapter_ip_info_t ip; - tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_AP, &ip); + esp_netif_ip_info_t ip; + esp_netif_get_ip_info(s_sta_netif, &ip); return {ip.ip.addr}; } bool WiFiComponent::wifi_disconnect_() { return esp_wifi_disconnect(); } diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index 7f5c3ad333..7e8ba41987 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -18,6 +18,8 @@ #elif defined(USE_ESP32_FRAMEWORK_ARDUINO) #include #elif defined(USE_ESP_IDF) +#include "esp_mac.h" +#include "esp_random.h" #include "esp_system.h" #include #include diff --git a/requirements.txt b/requirements.txt index 5f73cf3a06..bb2c736709 100644 --- a/requirements.txt +++ b/requirements.txt @@ -16,3 +16,6 @@ zeroconf==0.56.0 # esp-idf requires this, but doesn't bundle it by default # https://github.com/espressif/esp-idf/blob/220590d599e134d7a5e7f1e683cc4550349ffbf8/requirements.txt#L24 kconfiglib==13.7.1 + +# esp-idf >= 5.0 requires this +pyparsing >= 3.0 From bb05ba3d00dce21e5ce22dd3dba7701143ebd9d7 Mon Sep 17 00:00:00 2001 From: Samuel Sieb Date: Sat, 22 Apr 2023 00:41:12 -0700 Subject: [PATCH 17/71] fix flip_x (#4727) --- esphome/components/max7219digit/display.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/max7219digit/display.py b/esphome/components/max7219digit/display.py index faa8a08f4a..8db9123a39 100644 --- a/esphome/components/max7219digit/display.py +++ b/esphome/components/max7219digit/display.py @@ -93,7 +93,7 @@ async def to_code(config): cg.add(var.set_scroll(config[CONF_SCROLL_ENABLE])) cg.add(var.set_scroll_mode(config[CONF_SCROLL_MODE])) cg.add(var.set_reverse(config[CONF_REVERSE_ENABLE])) - cg.add(var.set_flip_x([CONF_FLIP_X])) + cg.add(var.set_flip_x(config[CONF_FLIP_X])) if CONF_LAMBDA in config: lambda_ = await cg.process_lambda( From 327cd662b4dd95a92ddf88c595bc4014923aa084 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 24 Apr 2023 08:42:46 +1200 Subject: [PATCH 18/71] Use proper schema for delta filter (#4723) --- esphome/components/sensor/__init__.py | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/esphome/components/sensor/__init__.py b/esphome/components/sensor/__init__.py index ac25884697..f0a58d908c 100644 --- a/esphome/components/sensor/__init__.py +++ b/esphome/components/sensor/__init__.py @@ -25,10 +25,12 @@ from esphome.const import ( CONF_STATE_CLASS, CONF_TO, CONF_TRIGGER_ID, + CONF_TYPE, CONF_UNIT_OF_MEASUREMENT, CONF_WINDOW_SIZE, CONF_MQTT_ID, CONF_FORCE_UPDATE, + CONF_VALUE, DEVICE_CLASS_APPARENT_POWER, DEVICE_CLASS_AQI, DEVICE_CLASS_ATMOSPHERIC_PRESSURE, @@ -476,21 +478,38 @@ async def lambda_filter_to_code(config, filter_id): return cg.new_Pvariable(filter_id, lambda_) +DELTA_SCHEMA = cv.Schema( + { + cv.Required(CONF_VALUE): cv.positive_float, + cv.Optional(CONF_TYPE, default="absolute"): cv.one_of( + "absolute", "percentage", lower=True + ), + } +) + + def validate_delta(config): try: - return (cv.positive_float(config), False) + value = cv.positive_float(config) + return DELTA_SCHEMA({CONF_VALUE: value, CONF_TYPE: "absolute"}) except cv.Invalid: pass try: - return (cv.percentage(config), True) + value = cv.percentage(config) + return DELTA_SCHEMA({CONF_VALUE: value, CONF_TYPE: "percentage"}) except cv.Invalid: pass raise cv.Invalid("Delta filter requires a positive number or percentage value.") -@FILTER_REGISTRY.register("delta", DeltaFilter, validate_delta) +@FILTER_REGISTRY.register("delta", DeltaFilter, cv.Any(DELTA_SCHEMA, validate_delta)) async def delta_filter_to_code(config, filter_id): - return cg.new_Pvariable(filter_id, *config) + percentage = config[CONF_TYPE] == "percentage" + return cg.new_Pvariable( + filter_id, + config[CONF_VALUE], + percentage, + ) @FILTER_REGISTRY.register("or", OrFilter, validate_filters) From 0a95f116fcf9c8cbaa98c3d85796c746004ee60d Mon Sep 17 00:00:00 2001 From: Rebbe Pod <66928914+RebbePod@users.noreply.github.com> Date: Sun, 23 Apr 2023 16:44:35 -0400 Subject: [PATCH 19/71] Get Sunrise & Sunset for a Specific Date (#4712) * Update real_time_clock.cpp * Update real_time_clock.h * Update sun.h * Update sun.h * Update sun.h * Enable the sunAtLocation to be used externally * Enable the sunAtLocation to be used externally * Update sun.h * Update sun.h * update * update * update to only use one function * Update sun.h * Update sun.cpp --- esphome/components/sun/sun.cpp | 19 ++++++++++++++----- esphome/components/sun/sun.h | 3 +++ 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/esphome/components/sun/sun.cpp b/esphome/components/sun/sun.cpp index 54aaf0942b..5f9179682a 100644 --- a/esphome/components/sun/sun.cpp +++ b/esphome/components/sun/sun.cpp @@ -287,18 +287,17 @@ HorizontalCoordinate Sun::calc_coords_() { */ return sun.true_coordinate(m); } -optional Sun::calc_event_(bool rising, double zenith) { +optional Sun::calc_event_(time::ESPTime date, bool rising, double zenith) { SunAtLocation sun{location_}; - auto now = this->time_->utcnow(); - if (!now.is_valid()) + if (!date.is_valid()) return {}; // Calculate UT1 timestamp at 0h - auto today = now; + auto today = date; today.hour = today.minute = today.second = 0; today.recalc_timestamp_utc(); auto it = sun.event(rising, today, zenith); - if (it.has_value() && it->timestamp < now.timestamp) { + if (it.has_value() && it->timestamp < date.timestamp) { // We're calculating *next* sunrise/sunset, but calculated event // is today, so try again tomorrow time_t new_timestamp = today.timestamp + 24 * 60 * 60; @@ -307,9 +306,19 @@ optional Sun::calc_event_(bool rising, double zenith) { } return it; } +optional Sun::calc_event_(bool rising, double zenith) { + auto it = Sun::calc_event_(this->time_->utcnow(), rising, zenith); + return it; +} optional Sun::sunrise(double elevation) { return this->calc_event_(true, 90 - elevation); } optional Sun::sunset(double elevation) { return this->calc_event_(false, 90 - elevation); } +optional Sun::sunrise(time::ESPTime date, double elevation) { + return this->calc_event_(date, true, 90 - elevation); +} +optional Sun::sunset(time::ESPTime date, double elevation) { + return this->calc_event_(date, false, 90 - elevation); +} double Sun::elevation() { return this->calc_coords_().elevation; } double Sun::azimuth() { return this->calc_coords_().azimuth; } diff --git a/esphome/components/sun/sun.h b/esphome/components/sun/sun.h index efc6a1ab0a..9547b2f280 100644 --- a/esphome/components/sun/sun.h +++ b/esphome/components/sun/sun.h @@ -59,6 +59,8 @@ class Sun { optional sunrise(double elevation); optional sunset(double elevation); + optional sunrise(time::ESPTime date, double elevation); + optional sunset(time::ESPTime date, double elevation); double elevation(); double azimuth(); @@ -66,6 +68,7 @@ class Sun { protected: internal::HorizontalCoordinate calc_coords_(); optional calc_event_(bool rising, double zenith); + optional calc_event_(time::ESPTime date, bool rising, double zenith); time::RealTimeClock *time_; internal::GeoLocation location_; From bef5b38d497af8319605ee9f9900d74a353eeb51 Mon Sep 17 00:00:00 2001 From: Alexander Momchilov Date: Sun, 23 Apr 2023 16:51:32 -0400 Subject: [PATCH 20/71] Add `supports_stop` trait to Cover (#3897) * Add "stop" trait to Cover * Add `supports_stop` to Cover protobuf msg * Run `script/api_protobuf/api_protobuf.py` ... followed by `script/clang-format -i` * Add `has_stop` field to template Cover * Set `has_stop` during Cover codegen * Set `supports_stop` trait on all other Cover types * Bump APIVersion to 1.8 --------- Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/am43/cover/am43_cover.cpp | 1 + esphome/components/api/api.proto | 1 + esphome/components/api/api_connection.cpp | 2 +- esphome/components/api/api_pb2.cpp | 9 +++++++++ esphome/components/api/api_pb2.h | 1 + esphome/components/copy/cover/copy_cover.cpp | 1 + esphome/components/cover/cover_traits.h | 3 +++ esphome/components/current_based/current_based_cover.cpp | 1 + esphome/components/demo/demo_cover.h | 1 + esphome/components/endstop/endstop_cover.cpp | 1 + esphome/components/feedback/feedback_cover.cpp | 1 + esphome/components/template/cover/__init__.py | 1 + esphome/components/template/cover/template_cover.cpp | 2 ++ esphome/components/template/cover/template_cover.h | 2 ++ esphome/components/time_based/time_based_cover.cpp | 1 + esphome/components/tuya/cover/tuya_cover.cpp | 1 + 16 files changed, 28 insertions(+), 1 deletion(-) diff --git a/esphome/components/am43/cover/am43_cover.cpp b/esphome/components/am43/cover/am43_cover.cpp index d0ef4a2fbb..93c77ea364 100644 --- a/esphome/components/am43/cover/am43_cover.cpp +++ b/esphome/components/am43/cover/am43_cover.cpp @@ -40,6 +40,7 @@ void Am43Component::loop() { CoverTraits Am43Component::get_traits() { auto traits = CoverTraits(); + traits.set_supports_stop(true); traits.set_supports_position(true); traits.set_supports_tilt(false); traits.set_is_assumed_state(false); diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index f31ef3ffc0..1fafc56e59 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -288,6 +288,7 @@ message ListEntitiesCoverResponse { bool disabled_by_default = 9; string icon = 10; EntityCategory entity_category = 11; + bool supports_stop = 12; } enum LegacyCoverState { diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 96fb3ea9fa..ff8f97b34b 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -944,7 +944,7 @@ HelloResponse APIConnection::hello(const HelloRequest &msg) { HelloResponse resp; resp.api_version_major = 1; - resp.api_version_minor = 7; + resp.api_version_minor = 8; resp.server_info = App.get_name() + " (esphome v" ESPHOME_VERSION ")"; resp.name = App.get_name(); diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index 334cde16b3..82301456f7 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -941,6 +941,10 @@ bool ListEntitiesCoverResponse::decode_varint(uint32_t field_id, ProtoVarInt val this->entity_category = value.as_enum(); return true; } + case 12: { + this->supports_stop = value.as_bool(); + return true; + } default: return false; } @@ -993,6 +997,7 @@ void ListEntitiesCoverResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(9, this->disabled_by_default); buffer.encode_string(10, this->icon); buffer.encode_enum(11, this->entity_category); + buffer.encode_bool(12, this->supports_stop); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesCoverResponse::dump_to(std::string &out) const { @@ -1042,6 +1047,10 @@ void ListEntitiesCoverResponse::dump_to(std::string &out) const { out.append(" entity_category: "); out.append(proto_enum_to_string(this->entity_category)); out.append("\n"); + + out.append(" supports_stop: "); + out.append(YESNO(this->supports_stop)); + out.append("\n"); out.append("}"); } #endif diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index 9f71c07913..b386cfb4fd 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -375,6 +375,7 @@ class ListEntitiesCoverResponse : public ProtoMessage { bool disabled_by_default{false}; std::string icon{}; enums::EntityCategory entity_category{}; + bool supports_stop{false}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; diff --git a/esphome/components/copy/cover/copy_cover.cpp b/esphome/components/copy/cover/copy_cover.cpp index cf50473018..28f8c9877c 100644 --- a/esphome/components/copy/cover/copy_cover.cpp +++ b/esphome/components/copy/cover/copy_cover.cpp @@ -28,6 +28,7 @@ cover::CoverTraits CopyCover::get_traits() { // copy traits manually so it doesn't break when new options are added // but the control() method hasn't implemented them yet. traits.set_is_assumed_state(base.get_is_assumed_state()); + traits.set_supports_stop(base.get_supports_stop()); traits.set_supports_position(base.get_supports_position()); traits.set_supports_tilt(base.get_supports_tilt()); traits.set_supports_toggle(base.get_supports_toggle()); diff --git a/esphome/components/cover/cover_traits.h b/esphome/components/cover/cover_traits.h index fb30883f77..79001c3b03 100644 --- a/esphome/components/cover/cover_traits.h +++ b/esphome/components/cover/cover_traits.h @@ -15,12 +15,15 @@ class CoverTraits { void set_supports_tilt(bool supports_tilt) { this->supports_tilt_ = supports_tilt; } bool get_supports_toggle() const { return this->supports_toggle_; } void set_supports_toggle(bool supports_toggle) { this->supports_toggle_ = supports_toggle; } + bool get_supports_stop() const { return this->supports_stop_; } + void set_supports_stop(bool supports_stop) { this->supports_stop_ = supports_stop; } protected: bool is_assumed_state_{false}; bool supports_position_{false}; bool supports_tilt_{false}; bool supports_toggle_{false}; + bool supports_stop_{false}; }; } // namespace cover diff --git a/esphome/components/current_based/current_based_cover.cpp b/esphome/components/current_based/current_based_cover.cpp index 7edbdf5a72..ff5ad43997 100644 --- a/esphome/components/current_based/current_based_cover.cpp +++ b/esphome/components/current_based/current_based_cover.cpp @@ -12,6 +12,7 @@ using namespace esphome::cover; CoverTraits CurrentBasedCover::get_traits() { auto traits = CoverTraits(); + traits.set_supports_stop(true); traits.set_supports_position(true); traits.set_supports_toggle(true); traits.set_is_assumed_state(false); diff --git a/esphome/components/demo/demo_cover.h b/esphome/components/demo/demo_cover.h index ab039736fb..ec266d46ab 100644 --- a/esphome/components/demo/demo_cover.h +++ b/esphome/components/demo/demo_cover.h @@ -72,6 +72,7 @@ class DemoCover : public cover::Cover, public Component { traits.set_supports_tilt(true); break; case DemoCoverType::TYPE_4: + traits.set_supports_stop(true); traits.set_is_assumed_state(true); traits.set_supports_tilt(true); break; diff --git a/esphome/components/endstop/endstop_cover.cpp b/esphome/components/endstop/endstop_cover.cpp index f468d13492..1190acc46b 100644 --- a/esphome/components/endstop/endstop_cover.cpp +++ b/esphome/components/endstop/endstop_cover.cpp @@ -11,6 +11,7 @@ using namespace esphome::cover; CoverTraits EndstopCover::get_traits() { auto traits = CoverTraits(); + traits.set_supports_stop(true); traits.set_supports_position(true); traits.set_supports_toggle(true); traits.set_is_assumed_state(false); diff --git a/esphome/components/feedback/feedback_cover.cpp b/esphome/components/feedback/feedback_cover.cpp index 213ce7ff8f..117c626f58 100644 --- a/esphome/components/feedback/feedback_cover.cpp +++ b/esphome/components/feedback/feedback_cover.cpp @@ -41,6 +41,7 @@ void FeedbackCover::setup() { CoverTraits FeedbackCover::get_traits() { auto traits = CoverTraits(); + traits.set_supports_stop(true); traits.set_supports_position(true); traits.set_supports_toggle(true); traits.set_is_assumed_state(this->assumed_state_); diff --git a/esphome/components/template/cover/__init__.py b/esphome/components/template/cover/__init__.py index a628da70d2..8844ddd6ab 100644 --- a/esphome/components/template/cover/__init__.py +++ b/esphome/components/template/cover/__init__.py @@ -73,6 +73,7 @@ async def to_code(config): await automation.build_automation( var.get_stop_trigger(), [], config[CONF_STOP_ACTION] ) + cg.add(var.set_has_stop(True)) if CONF_TILT_ACTION in config: await automation.build_automation( var.get_tilt_trigger(), [(float, "tilt")], config[CONF_TILT_ACTION] diff --git a/esphome/components/template/cover/template_cover.cpp b/esphome/components/template/cover/template_cover.cpp index 47c651e643..b16e439943 100644 --- a/esphome/components/template/cover/template_cover.cpp +++ b/esphome/components/template/cover/template_cover.cpp @@ -109,6 +109,7 @@ void TemplateCover::control(const CoverCall &call) { CoverTraits TemplateCover::get_traits() { auto traits = CoverTraits(); traits.set_is_assumed_state(this->assumed_state_); + traits.set_supports_stop(this->has_stop_); traits.set_supports_position(this->has_position_); traits.set_supports_tilt(this->has_tilt_); return traits; @@ -116,6 +117,7 @@ CoverTraits TemplateCover::get_traits() { Trigger *TemplateCover::get_position_trigger() const { return this->position_trigger_; } Trigger *TemplateCover::get_tilt_trigger() const { return this->tilt_trigger_; } void TemplateCover::set_tilt_lambda(std::function()> &&tilt_f) { this->tilt_f_ = tilt_f; } +void TemplateCover::set_has_stop(bool has_stop) { this->has_stop_ = has_stop; } void TemplateCover::set_has_position(bool has_position) { this->has_position_ = has_position; } void TemplateCover::set_has_tilt(bool has_tilt) { this->has_tilt_ = has_tilt; } void TemplateCover::stop_prev_trigger_() { diff --git a/esphome/components/template/cover/template_cover.h b/esphome/components/template/cover/template_cover.h index 3b9dcea50b..4ff5caf1db 100644 --- a/esphome/components/template/cover/template_cover.h +++ b/esphome/components/template/cover/template_cover.h @@ -26,6 +26,7 @@ class TemplateCover : public cover::Cover, public Component { void set_optimistic(bool optimistic); void set_assumed_state(bool assumed_state); void set_tilt_lambda(std::function()> &&tilt_f); + void set_has_stop(bool has_stop); void set_has_position(bool has_position); void set_has_tilt(bool has_tilt); void set_restore_mode(TemplateCoverRestoreMode restore_mode) { restore_mode_ = restore_mode; } @@ -48,6 +49,7 @@ class TemplateCover : public cover::Cover, public Component { bool optimistic_{false}; Trigger<> *open_trigger_; Trigger<> *close_trigger_; + bool has_stop_{false}; Trigger<> *stop_trigger_; Trigger<> *prev_command_trigger_{nullptr}; Trigger *position_trigger_; diff --git a/esphome/components/time_based/time_based_cover.cpp b/esphome/components/time_based/time_based_cover.cpp index a7ba6d0595..50376224a9 100644 --- a/esphome/components/time_based/time_based_cover.cpp +++ b/esphome/components/time_based/time_based_cover.cpp @@ -51,6 +51,7 @@ void TimeBasedCover::loop() { float TimeBasedCover::get_setup_priority() const { return setup_priority::DATA; } CoverTraits TimeBasedCover::get_traits() { auto traits = CoverTraits(); + traits.set_supports_stop(true); traits.set_supports_position(true); traits.set_supports_toggle(true); traits.set_is_assumed_state(this->assumed_state_); diff --git a/esphome/components/tuya/cover/tuya_cover.cpp b/esphome/components/tuya/cover/tuya_cover.cpp index 11a458449f..fcb961f45e 100644 --- a/esphome/components/tuya/cover/tuya_cover.cpp +++ b/esphome/components/tuya/cover/tuya_cover.cpp @@ -128,6 +128,7 @@ void TuyaCover::dump_config() { cover::CoverTraits TuyaCover::get_traits() { auto traits = cover::CoverTraits(); + traits.set_supports_stop(true); traits.set_supports_position(true); return traits; } From 4a177e39316b43523939d49b7be7092c3410a466 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Apr 2023 08:53:50 +1200 Subject: [PATCH 21/71] Bump aioesphomeapi from 13.7.0 to 13.7.1 (#4725) Bumps [aioesphomeapi](https://github.com/esphome/aioesphomeapi) from 13.7.0 to 13.7.1. - [Release notes](https://github.com/esphome/aioesphomeapi/releases) - [Commits](https://github.com/esphome/aioesphomeapi/compare/v13.7.0...v13.7.1) --- updated-dependencies: - dependency-name: aioesphomeapi dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index bb2c736709..4ab30e414e 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.7.0 +aioesphomeapi==13.7.1 zeroconf==0.56.0 # esp-idf requires this, but doesn't bundle it by default From 7abdb5d04689b344b7c69dc51ea1156e5be0427f Mon Sep 17 00:00:00 2001 From: gcopeland Date: Wed, 26 Apr 2023 15:00:37 -0500 Subject: [PATCH 22/71] I2c scan recovery reset fix (#4724) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/i2c/i2c_bus_arduino.cpp | 11 ++++++++--- esphome/components/i2c/i2c_bus_esp_idf.cpp | 11 ++++++++--- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/esphome/components/i2c/i2c_bus_arduino.cpp b/esphome/components/i2c/i2c_bus_arduino.cpp index e08622a3ae..d80ab1fd1d 100644 --- a/esphome/components/i2c/i2c_bus_arduino.cpp +++ b/esphome/components/i2c/i2c_bus_arduino.cpp @@ -3,6 +3,7 @@ #include "i2c_bus_arduino.h" #include "esphome/core/log.h" #include "esphome/core/helpers.h" +#include "esphome/core/application.h" #include #include @@ -227,10 +228,14 @@ void ArduinoI2CBus::recover_() { // When SCL is kept LOW at this point, we might be looking at a device // that applies clock stretching. Wait for the release of the SCL line, // but not forever. There is no specification for the maximum allowed - // time. We'll stick to 500ms here. - auto wait = 20; + // time. We yield and reset the WDT, so as to avoid triggering reset. + // No point in trying to recover the bus by forcing a uC reset. Bus + // should recover in a few ms or less else not likely to recovery at + // all. + auto wait = 250; while (wait-- && digitalRead(scl_pin_) == LOW) { // NOLINT - delay(25); + App.feed_wdt(); + delayMicroseconds(half_period_usec * 2); } if (digitalRead(scl_pin_) == LOW) { // NOLINT ESP_LOGE(TAG, "Recovery failed: SCL is held LOW during clock pulse cycle"); diff --git a/esphome/components/i2c/i2c_bus_esp_idf.cpp b/esphome/components/i2c/i2c_bus_esp_idf.cpp index 5178f6d4f2..51688322f6 100644 --- a/esphome/components/i2c/i2c_bus_esp_idf.cpp +++ b/esphome/components/i2c/i2c_bus_esp_idf.cpp @@ -4,6 +4,7 @@ #include "esphome/core/hal.h" #include "esphome/core/log.h" #include "esphome/core/helpers.h" +#include "esphome/core/application.h" #include #include @@ -273,10 +274,14 @@ void IDFI2CBus::recover_() { // When SCL is kept LOW at this point, we might be looking at a device // that applies clock stretching. Wait for the release of the SCL line, // but not forever. There is no specification for the maximum allowed - // time. We'll stick to 500ms here. - auto wait = 20; + // time. We yield and reset the WDT, so as to avoid triggering reset. + // No point in trying to recover the bus by forcing a uC reset. Bus + // should recover in a few ms or less else not likely to recovery at + // all. + auto wait = 250; while (wait-- && gpio_get_level(scl_pin) == 0) { - delay(25); + App.feed_wdt(); + delayMicroseconds(half_period_usec * 2); } if (gpio_get_level(scl_pin) == 0) { ESP_LOGE(TAG, "Recovery failed: SCL is held LOW during clock pulse cycle"); From 986dd2ddd2e00c817fae671cfd9169c3c44bc39b Mon Sep 17 00:00:00 2001 From: Jimmy Hedman Date: Wed, 26 Apr 2023 22:03:30 +0200 Subject: [PATCH 23/71] Debug component doesn't work on RP2040 (#4728) --- esphome/components/debug/__init__.py | 43 +++++++++++++++------------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/esphome/components/debug/__init__.py b/esphome/components/debug/__init__.py index c18baa1cca..9742b3b19e 100644 --- a/esphome/components/debug/__init__.py +++ b/esphome/components/debug/__init__.py @@ -17,26 +17,29 @@ debug_ns = cg.esphome_ns.namespace("debug") DebugComponent = debug_ns.class_("DebugComponent", cg.PollingComponent) -CONFIG_SCHEMA = cv.Schema( - { - cv.GenerateID(): cv.declare_id(DebugComponent), - cv.Optional(CONF_DEVICE): cv.invalid( - "The 'device' option has been moved to the 'debug' text_sensor component" - ), - cv.Optional(CONF_FREE): cv.invalid( - "The 'free' option has been moved to the 'debug' sensor component" - ), - cv.Optional(CONF_BLOCK): cv.invalid( - "The 'block' option has been moved to the 'debug' sensor component" - ), - cv.Optional(CONF_FRAGMENTATION): cv.invalid( - "The 'fragmentation' option has been moved to the 'debug' sensor component" - ), - cv.Optional(CONF_LOOP_TIME): cv.invalid( - "The 'loop_time' option has been moved to the 'debug' sensor component" - ), - } -).extend(cv.polling_component_schema("60s")) +CONFIG_SCHEMA = cv.All( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(DebugComponent), + cv.Optional(CONF_DEVICE): cv.invalid( + "The 'device' option has been moved to the 'debug' text_sensor component" + ), + cv.Optional(CONF_FREE): cv.invalid( + "The 'free' option has been moved to the 'debug' sensor component" + ), + cv.Optional(CONF_BLOCK): cv.invalid( + "The 'block' option has been moved to the 'debug' sensor component" + ), + cv.Optional(CONF_FRAGMENTATION): cv.invalid( + "The 'fragmentation' option has been moved to the 'debug' sensor component" + ), + cv.Optional(CONF_LOOP_TIME): cv.invalid( + "The 'loop_time' option has been moved to the 'debug' sensor component" + ), + } + ).extend(cv.polling_component_schema("60s")), + cv.only_on(["esp32", "esp8266"]), +) async def to_code(config): From f639f7c280c7c489e6c6ac197690d8c67532bdeb Mon Sep 17 00:00:00 2001 From: Keith Burzinski Date: Wed, 26 Apr 2023 17:47:45 -0500 Subject: [PATCH 24/71] Add on_tag_removed trigger for RC522 (#4742) --- esphome/components/rc522/__init__.py | 19 +++++++++++++++++-- esphome/components/rc522/rc522.cpp | 7 ++++++- esphome/components/rc522/rc522.h | 6 ++++-- 3 files changed, 27 insertions(+), 5 deletions(-) diff --git a/esphome/components/rc522/__init__.py b/esphome/components/rc522/__init__.py index d64cf3c085..1a1e641623 100644 --- a/esphome/components/rc522/__init__.py +++ b/esphome/components/rc522/__init__.py @@ -2,7 +2,12 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome import automation, pins from esphome.components import i2c -from esphome.const import CONF_ON_TAG, CONF_TRIGGER_ID, CONF_RESET_PIN +from esphome.const import ( + CONF_ON_TAG, + CONF_ON_TAG_REMOVED, + CONF_TRIGGER_ID, + CONF_RESET_PIN, +) CODEOWNERS = ["@glmnet"] AUTO_LOAD = ["binary_sensor"] @@ -24,6 +29,11 @@ RC522_SCHEMA = cv.Schema( cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(RC522Trigger), } ), + cv.Optional(CONF_ON_TAG_REMOVED): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(RC522Trigger), + } + ), } ).extend(cv.polling_component_schema("1s")) @@ -37,5 +47,10 @@ async def setup_rc522(var, config): for conf in config.get(CONF_ON_TAG, []): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID]) - cg.add(var.register_trigger(trigger)) + cg.add(var.register_ontag_trigger(trigger)) + await automation.build_automation(trigger, [(cg.std_string, "x")], conf) + + for conf in config.get(CONF_ON_TAG_REMOVED, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID]) + cg.add(var.register_ontagremoved_trigger(trigger)) await automation.build_automation(trigger, [(cg.std_string, "x")], conf) diff --git a/esphome/components/rc522/rc522.cpp b/esphome/components/rc522/rc522.cpp index 5bfeb40156..4e74020e4c 100644 --- a/esphome/components/rc522/rc522.cpp +++ b/esphome/components/rc522/rc522.cpp @@ -256,7 +256,7 @@ void RC522::loop() { this->current_uid_ = rfid_uid; - for (auto *trigger : this->triggers_) + for (auto *trigger : this->triggers_ontag_) trigger->process(rfid_uid); if (report) { @@ -265,6 +265,11 @@ void RC522::loop() { break; } case STATE_DONE: { + if (!this->current_uid_.empty()) { + ESP_LOGV(TAG, "Tag '%s' removed", format_uid(this->current_uid_).c_str()); + for (auto *trigger : this->triggers_ontagremoved_) + trigger->process(this->current_uid_); + } this->current_uid_ = {}; state_ = STATE_INIT; break; diff --git a/esphome/components/rc522/rc522.h b/esphome/components/rc522/rc522.h index 5eea3c665e..c6c5e119f0 100644 --- a/esphome/components/rc522/rc522.h +++ b/esphome/components/rc522/rc522.h @@ -24,7 +24,8 @@ class RC522 : public PollingComponent { void loop() override; void register_tag(RC522BinarySensor *tag) { this->binary_sensors_.push_back(tag); } - void register_trigger(RC522Trigger *trig) { this->triggers_.push_back(trig); } + void register_ontag_trigger(RC522Trigger *trig) { this->triggers_ontag_.push_back(trig); } + void register_ontagremoved_trigger(RC522Trigger *trig) { this->triggers_ontagremoved_.push_back(trig); } void set_reset_pin(GPIOPin *reset) { this->reset_pin_ = reset; } @@ -242,7 +243,8 @@ class RC522 : public PollingComponent { uint8_t reset_count_{0}; uint32_t reset_timeout_{0}; std::vector binary_sensors_; - std::vector triggers_; + std::vector triggers_ontag_; + std::vector triggers_ontagremoved_; std::vector current_uid_; enum RC522Error { From 64afb07e91a81bd1fe21e7c4130483cb5eed50c8 Mon Sep 17 00:00:00 2001 From: RoboMagus <68224306+RoboMagus@users.noreply.github.com> Date: Thu, 27 Apr 2023 00:48:53 +0200 Subject: [PATCH 25/71] Fix 'blutooth' typo in esp32_ble component (#4738) --- esphome/components/esp32_ble/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/esp32_ble/__init__.py b/esphome/components/esp32_ble/__init__.py index 3dc4af1058..7db6fff6b9 100644 --- a/esphome/components/esp32_ble/__init__.py +++ b/esphome/components/esp32_ble/__init__.py @@ -10,7 +10,7 @@ CONFLICTS_WITH = ["esp32_ble_beacon"] CONF_BLE_ID = "ble_id" -NO_BLUTOOTH_VARIANTS = [const.VARIANT_ESP32S2] +NO_BLUETOOTH_VARIANTS = [const.VARIANT_ESP32S2] esp32_ble_ns = cg.esphome_ns.namespace("esp32_ble") ESP32BLE = esp32_ble_ns.class_("ESP32BLE", cg.Component) @@ -29,7 +29,7 @@ CONFIG_SCHEMA = cv.Schema( def validate_variant(_): variant = get_esp32_variant() - if variant in NO_BLUTOOTH_VARIANTS: + if variant in NO_BLUETOOTH_VARIANTS: raise cv.Invalid(f"{variant} does not support Bluetooth") From e3d89cc6b61f4413dd12d215979dad5a5e37c497 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 27 Apr 2023 10:49:40 +1200 Subject: [PATCH 26/71] Bump pylint from 2.17.2 to 2.17.3 (#4740) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements_test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index b063dd2797..0bf5619f9b 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,4 +1,4 @@ -pylint==2.17.2 +pylint==2.17.3 flake8==6.0.0 # also change in .pre-commit-config.yaml when updating black==23.3.0 # also change in .pre-commit-config.yaml when updating pyupgrade==3.3.1 # also change in .pre-commit-config.yaml when updating From 4a08a5413dd9b99ec0990074bd2e923b94317e60 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 27 Apr 2023 11:00:34 +1200 Subject: [PATCH 27/71] Bump tornado from 6.2 to 6.3.1 (#4741) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 4ab30e414e..41fedc88cf 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,7 @@ voluptuous==0.13.1 PyYAML==6.0 paho-mqtt==1.6.1 colorama==0.4.6 -tornado==6.2 +tornado==6.3.1 tzlocal==4.2 # from time tzdata>=2021.1 # from time pyserial==3.5 From 77f71acbc8ae9f0eab6817c5e1d41e22a8972b8a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 27 Apr 2023 11:45:23 +1200 Subject: [PATCH 28/71] Bump pytest from 7.3.0 to 7.3.1 (#4686) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements_test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index 0bf5619f9b..b18aabe7b4 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -5,7 +5,7 @@ pyupgrade==3.3.1 # also change in .pre-commit-config.yaml when updating pre-commit # Unit tests -pytest==7.3.0 +pytest==7.3.1 pytest-cov==4.0.0 pytest-mock==3.10.0 pytest-asyncio==0.21.0 From 6476357596c9c71de02f22d86a896e337d0122d9 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 27 Apr 2023 12:26:06 +1200 Subject: [PATCH 29/71] Expand the platformio dep installer to also install platforms and tools (#4716) --- .github/workflows/ci-docker.yml | 2 ++ docker/Dockerfile | 2 +- docker/platformio_install_deps.py | 30 ---------------- esphome/components/esp32/__init__.py | 8 ++--- esphome/components/esp8266/__init__.py | 4 +-- esphome/components/rp2040/__init__.py | 4 +-- platformio.ini | 16 ++++----- script/platformio_install_deps.py | 48 ++++++++++++++++++++++++++ script/setup | 2 ++ 9 files changed, 69 insertions(+), 47 deletions(-) delete mode 100755 docker/platformio_install_deps.py create mode 100755 script/platformio_install_deps.py diff --git a/.github/workflows/ci-docker.yml b/.github/workflows/ci-docker.yml index 1d2a1b5323..eb3a5a945c 100644 --- a/.github/workflows/ci-docker.yml +++ b/.github/workflows/ci-docker.yml @@ -11,6 +11,7 @@ on: - ".github/workflows/**" - "requirements*.txt" - "platformio.ini" + - "script/platformio_install_deps.py" pull_request: paths: @@ -18,6 +19,7 @@ on: - ".github/workflows/**" - "requirements*.txt" - "platformio.ini" + - "script/platformio_install_deps.py" permissions: contents: read diff --git a/docker/Dockerfile b/docker/Dockerfile index 13fba50288..a59a470394 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -60,7 +60,7 @@ RUN \ # First install requirements to leverage caching when requirements don't change -COPY requirements.txt requirements_optional.txt docker/platformio_install_deps.py platformio.ini / +COPY requirements.txt requirements_optional.txt script/platformio_install_deps.py platformio.ini / RUN \ pip3 install --no-cache-dir -r /requirements.txt -r /requirements_optional.txt \ && /platformio_install_deps.py /platformio.ini diff --git a/docker/platformio_install_deps.py b/docker/platformio_install_deps.py deleted file mode 100755 index c7b11cf321..0000000000 --- a/docker/platformio_install_deps.py +++ /dev/null @@ -1,30 +0,0 @@ -#!/usr/bin/env python3 -# This script is used in the docker containers to preinstall -# all platformio libraries in the global storage - -import configparser -import subprocess -import sys - -config = configparser.ConfigParser(inline_comment_prefixes=(';', )) -config.read(sys.argv[1]) - -libs = [] -# Extract from every lib_deps key in all sections -for section in config.sections(): - conf = config[section] - if "lib_deps" not in conf: - continue - for lib_dep in conf["lib_deps"].splitlines(): - if not lib_dep: - # Empty line or comment - continue - if lib_dep.startswith("${"): - # Extending from another section - continue - if "@" not in lib_dep: - # No version pinned, this is an internal lib - continue - libs.append(lib_dep) - -subprocess.check_call(['platformio', 'lib', '-g', 'install', *libs]) diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index d0f74b7226..3ca140f0d4 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -252,7 +252,7 @@ def _parse_platform_version(value): try: # if platform version is a valid version constraint, prefix the default package cv.platformio_version_constraint(value) - return f"platformio/espressif32 @ {value}" + return f"platformio/espressif32@{value}" except cv.Invalid: return value @@ -367,12 +367,12 @@ async def to_code(config): cg.add_build_flag("-Wno-nonnull-compare") cg.add_platformio_option( "platform_packages", - [f"platformio/framework-espidf @ {conf[CONF_SOURCE]}"], + [f"platformio/framework-espidf@{conf[CONF_SOURCE]}"], ) # platformio/toolchain-esp32ulp does not support linux_aarch64 yet and has not been updated for over 2 years # This is espressif's own published version which is more up to date. cg.add_platformio_option( - "platform_packages", ["espressif/toolchain-esp32ulp @ 2.35.0-20220830"] + "platform_packages", ["espressif/toolchain-esp32ulp@2.35.0-20220830"] ) add_idf_sdkconfig_option("CONFIG_PARTITION_TABLE_SINGLE_APP", False) add_idf_sdkconfig_option("CONFIG_PARTITION_TABLE_CUSTOM", True) @@ -433,7 +433,7 @@ async def to_code(config): cg.add_build_flag("-DUSE_ESP32_FRAMEWORK_ARDUINO") cg.add_platformio_option( "platform_packages", - [f"platformio/framework-arduinoespressif32 @ {conf[CONF_SOURCE]}"], + [f"platformio/framework-arduinoespressif32@{conf[CONF_SOURCE]}"], ) cg.add_platformio_option("board_build.partitions", "partitions.csv") diff --git a/esphome/components/esp8266/__init__.py b/esphome/components/esp8266/__init__.py index 59a1f2cd85..674f433d52 100644 --- a/esphome/components/esp8266/__init__.py +++ b/esphome/components/esp8266/__init__.py @@ -125,7 +125,7 @@ def _parse_platform_version(value): try: # if platform version is a valid version constraint, prefix the default package cv.platformio_version_constraint(value) - return f"platformio/espressif8266 @ {value}" + return f"platformio/espressif8266@{value}" except cv.Invalid: return value @@ -181,7 +181,7 @@ async def to_code(config): cg.add_platformio_option("platform", conf[CONF_PLATFORM_VERSION]) cg.add_platformio_option( "platform_packages", - [f"platformio/framework-arduinoespressif8266 @ {conf[CONF_SOURCE]}"], + [f"platformio/framework-arduinoespressif8266@{conf[CONF_SOURCE]}"], ) # Default for platformio is LWIP2_LOW_MEMORY with: diff --git a/esphome/components/rp2040/__init__.py b/esphome/components/rp2040/__init__.py index c6fbcf8deb..3d0d6ec060 100644 --- a/esphome/components/rp2040/__init__.py +++ b/esphome/components/rp2040/__init__.py @@ -102,7 +102,7 @@ def _parse_platform_version(value): try: # if platform version is a valid version constraint, prefix the default package cv.platformio_version_constraint(value) - return f"platformio/raspberrypi @ {value}" + return f"platformio/raspberrypi@{value}" except cv.Invalid: return value @@ -148,7 +148,7 @@ async def to_code(config): cg.add_platformio_option("platform", conf[CONF_PLATFORM_VERSION]) cg.add_platformio_option( "platform_packages", - [f"earlephilhower/framework-arduinopico @ {conf[CONF_SOURCE]}"], + [f"earlephilhower/framework-arduinopico@{conf[CONF_SOURCE]}"], ) cg.add_platformio_option("board_build.core", "earlephilhower") diff --git a/platformio.ini b/platformio.ini index c8db90bacb..7f301e560c 100644 --- a/platformio.ini +++ b/platformio.ini @@ -79,9 +79,9 @@ build_flags = ; This are common settings for the ESP8266 using Arduino. [common:esp8266-arduino] extends = common:arduino -platform = platformio/espressif8266 @ 3.2.0 +platform = platformio/espressif8266@3.2.0 platform_packages = - platformio/framework-arduinoespressif8266 @ ~3.30002.0 + platformio/framework-arduinoespressif8266@~3.30002.0 framework = arduino lib_deps = @@ -103,9 +103,9 @@ extra_scripts = post:esphome/components/esp8266/post_build.py.script ; This are common settings for the ESP32 (all variants) using Arduino. [common:esp32-arduino] extends = common:arduino -platform = platformio/espressif32 @ 5.3.0 +platform = platformio/espressif32@5.3.0 platform_packages = - platformio/framework-arduinoespressif32 @ ~3.20005.0 + platformio/framework-arduinoespressif32@~3.20005.0 framework = arduino board = nodemcu-32s @@ -133,9 +133,9 @@ extra_scripts = post:esphome/components/esp32/post_build.py.script ; This are common settings for the ESP32 (all variants) using IDF. [common:esp32-idf] extends = common:idf -platform = platformio/espressif32 @ 5.3.0 +platform = platformio/espressif32@5.3.0 platform_packages = - platformio/framework-espidf @ ~3.40404.0 + platformio/framework-espidf@~3.40404.0 framework = espidf lib_deps = @@ -156,8 +156,8 @@ board_build.filesystem_size = 0.5m platform = https://github.com/maxgerhardt/platform-raspberrypi.git platform_packages = - ; earlephilhower/framework-arduinopico @ ~1.20602.0 ; Cannot use the platformio package until old releases stop getting deleted - earlephilhower/framework-arduinopico @ https://github.com/earlephilhower/arduino-pico/releases/download/2.6.2/rp2040-2.6.2.zip + ; earlephilhower/framework-arduinopico@~1.20602.0 ; Cannot use the platformio package until old releases stop getting deleted + earlephilhower/framework-arduinopico@https://github.com/earlephilhower/arduino-pico/releases/download/2.6.2/rp2040-2.6.2.zip framework = arduino lib_deps = diff --git a/script/platformio_install_deps.py b/script/platformio_install_deps.py new file mode 100755 index 0000000000..2340410161 --- /dev/null +++ b/script/platformio_install_deps.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python3 +# This script is used to preinstall +# all platformio libraries in the global storage + +import configparser +import subprocess +import sys + +config = configparser.ConfigParser(inline_comment_prefixes=(";",)) +config.read(sys.argv[1]) + +libs = [] +tools = [] +platforms = [] +# Extract from every lib_deps key in all sections +for section in config.sections(): + conf = config[section] + if "lib_deps" in conf: + for lib_dep in conf["lib_deps"].splitlines(): + if not lib_dep: + # Empty line or comment + continue + if lib_dep.startswith("${"): + # Extending from another section + continue + if "@" not in lib_dep: + # No version pinned, this is an internal lib + continue + libs.append("-l") + libs.append(lib_dep) + if "platform" in conf: + platforms.append("-p") + platforms.append(conf["platform"]) + if "platform_packages" in conf: + for tool in conf["platform_packages"].splitlines(): + if not tool: + # Empty line or comment + continue + if tool.startswith("${"): + # Extending from another section + continue + if tool.find("https://github.com") != -1: + split = tool.find("@") + tool = tool[split + 1 :] + tools.append("-t") + tools.append(tool) + +subprocess.check_call(["platformio", "pkg", "install", "-g", *libs, *platforms, *tools]) diff --git a/script/setup b/script/setup index c650960f05..5acd1a9f13 100755 --- a/script/setup +++ b/script/setup @@ -14,3 +14,5 @@ pip3 install -r requirements.txt -r requirements_optional.txt -r requirements_te pip3 install --no-use-pep517 -e . pre-commit install + +script/platformio_install_deps.py platformio.ini From c5efaa1c000284829a6d9d84e03a4e3db3c4e3cc Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 27 Apr 2023 13:11:32 +1200 Subject: [PATCH 30/71] Remove climate legacy away flags (#4744) --- esphome/components/api/api.proto | 8 ++--- esphome/components/api/api_connection.cpp | 3 -- esphome/components/api/api_pb2.cpp | 24 +++++++------- esphome/components/api/api_pb2.h | 6 ++-- esphome/components/climate/__init__.py | 5 +-- esphome/components/climate/climate.cpp | 14 --------- esphome/components/climate/climate.h | 14 --------- esphome/components/climate/climate_traits.h | 9 ------ esphome/components/mqtt/mqtt_climate.cpp | 35 +-------------------- 9 files changed, 20 insertions(+), 98 deletions(-) diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index 1fafc56e59..4cc98c91d9 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -862,8 +862,7 @@ message ClimateStateResponse { float target_temperature = 4; float target_temperature_low = 5; float target_temperature_high = 6; - // For older peers, equal to preset == CLIMATE_PRESET_AWAY - bool legacy_away = 7; + bool unused_legacy_away = 7; ClimateAction action = 8; ClimateFanMode fan_mode = 9; ClimateSwingMode swing_mode = 10; @@ -886,9 +885,8 @@ message ClimateCommandRequest { float target_temperature_low = 7; bool has_target_temperature_high = 8; float target_temperature_high = 9; - // legacy, for older peers, newer ones should use CLIMATE_PRESET_AWAY in preset - bool has_legacy_away = 10; - bool legacy_away = 11; + bool unused_has_legacy_away = 10; + bool unused_legacy_away = 11; bool has_fan_mode = 12; ClimateFanMode fan_mode = 13; bool has_swing_mode = 14; diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index ff8f97b34b..a79444a7e9 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -530,7 +530,6 @@ bool APIConnection::send_climate_state(climate::Climate *climate) { resp.custom_fan_mode = climate->custom_fan_mode.value(); if (traits.get_supports_presets() && climate->preset.has_value()) { resp.preset = static_cast(climate->preset.value()); - resp.legacy_away = resp.preset == enums::CLIMATE_PRESET_AWAY; } if (!traits.get_supported_custom_presets().empty() && climate->custom_preset.has_value()) resp.custom_preset = climate->custom_preset.value(); @@ -591,8 +590,6 @@ void APIConnection::climate_command(const ClimateCommandRequest &msg) { call.set_target_temperature_low(msg.target_temperature_low); if (msg.has_target_temperature_high) call.set_target_temperature_high(msg.target_temperature_high); - if (msg.has_legacy_away) - call.set_preset(msg.legacy_away ? climate::CLIMATE_PRESET_AWAY : climate::CLIMATE_PRESET_HOME); if (msg.has_fan_mode) call.set_fan_mode(static_cast(msg.fan_mode)); if (msg.has_custom_fan_mode) diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index 82301456f7..1dd8c82e00 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -3658,7 +3658,7 @@ bool ClimateStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { return true; } case 7: { - this->legacy_away = value.as_bool(); + this->unused_legacy_away = value.as_bool(); return true; } case 8: { @@ -3728,7 +3728,7 @@ void ClimateStateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_float(4, this->target_temperature); buffer.encode_float(5, this->target_temperature_low); buffer.encode_float(6, this->target_temperature_high); - buffer.encode_bool(7, this->legacy_away); + buffer.encode_bool(7, this->unused_legacy_away); buffer.encode_enum(8, this->action); buffer.encode_enum(9, this->fan_mode); buffer.encode_enum(10, this->swing_mode); @@ -3769,8 +3769,8 @@ void ClimateStateResponse::dump_to(std::string &out) const { out.append(buffer); out.append("\n"); - out.append(" legacy_away: "); - out.append(YESNO(this->legacy_away)); + out.append(" unused_legacy_away: "); + out.append(YESNO(this->unused_legacy_away)); out.append("\n"); out.append(" action: "); @@ -3822,11 +3822,11 @@ bool ClimateCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) return true; } case 10: { - this->has_legacy_away = value.as_bool(); + this->unused_has_legacy_away = value.as_bool(); return true; } case 11: { - this->legacy_away = value.as_bool(); + this->unused_legacy_away = value.as_bool(); return true; } case 12: { @@ -3911,8 +3911,8 @@ void ClimateCommandRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_float(7, this->target_temperature_low); buffer.encode_bool(8, this->has_target_temperature_high); buffer.encode_float(9, this->target_temperature_high); - buffer.encode_bool(10, this->has_legacy_away); - buffer.encode_bool(11, this->legacy_away); + buffer.encode_bool(10, this->unused_has_legacy_away); + buffer.encode_bool(11, this->unused_legacy_away); buffer.encode_bool(12, this->has_fan_mode); buffer.encode_enum(13, this->fan_mode); buffer.encode_bool(14, this->has_swing_mode); @@ -3968,12 +3968,12 @@ void ClimateCommandRequest::dump_to(std::string &out) const { out.append(buffer); out.append("\n"); - out.append(" has_legacy_away: "); - out.append(YESNO(this->has_legacy_away)); + out.append(" unused_has_legacy_away: "); + out.append(YESNO(this->unused_has_legacy_away)); out.append("\n"); - out.append(" legacy_away: "); - out.append(YESNO(this->legacy_away)); + out.append(" unused_legacy_away: "); + out.append(YESNO(this->unused_legacy_away)); out.append("\n"); out.append(" has_fan_mode: "); diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index b386cfb4fd..0f4b79de19 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -959,7 +959,7 @@ class ClimateStateResponse : public ProtoMessage { float target_temperature{0.0f}; float target_temperature_low{0.0f}; float target_temperature_high{0.0f}; - bool legacy_away{false}; + bool unused_legacy_away{false}; enums::ClimateAction action{}; enums::ClimateFanMode fan_mode{}; enums::ClimateSwingMode swing_mode{}; @@ -987,8 +987,8 @@ class ClimateCommandRequest : public ProtoMessage { float target_temperature_low{0.0f}; bool has_target_temperature_high{false}; float target_temperature_high{0.0f}; - bool has_legacy_away{false}; - bool legacy_away{false}; + bool unused_has_legacy_away{false}; + bool unused_legacy_away{false}; bool has_fan_mode{false}; enums::ClimateFanMode fan_mode{}; bool has_swing_mode{false}; diff --git a/esphome/components/climate/__init__.py b/esphome/components/climate/__init__.py index 6734917bf3..bf167fe837 100644 --- a/esphome/components/climate/__init__.py +++ b/esphome/components/climate/__init__.py @@ -343,7 +343,7 @@ CLIMATE_CONTROL_ACTION_SCHEMA = cv.Schema( cv.Optional(CONF_TARGET_TEMPERATURE): cv.templatable(cv.temperature), cv.Optional(CONF_TARGET_TEMPERATURE_LOW): cv.templatable(cv.temperature), cv.Optional(CONF_TARGET_TEMPERATURE_HIGH): cv.templatable(cv.temperature), - cv.Optional(CONF_AWAY): cv.templatable(cv.boolean), + cv.Optional(CONF_AWAY): cv.invalid("Use preset instead"), cv.Exclusive(CONF_FAN_MODE, "fan_mode"): cv.templatable( validate_climate_fan_mode ), @@ -379,9 +379,6 @@ async def climate_control_to_code(config, action_id, template_arg, args): config[CONF_TARGET_TEMPERATURE_HIGH], args, float ) cg.add(var.set_target_temperature_high(template_)) - if CONF_AWAY in config: - template_ = await cg.templatable(config[CONF_AWAY], args, bool) - cg.add(var.set_away(template_)) if CONF_FAN_MODE in config: template_ = await cg.templatable(config[CONF_FAN_MODE], args, ClimateFanMode) cg.add(var.set_fan_mode(template_)) diff --git a/esphome/components/climate/climate.cpp b/esphome/components/climate/climate.cpp index b4d5ee9685..a032596eb3 100644 --- a/esphome/components/climate/climate.cpp +++ b/esphome/components/climate/climate.cpp @@ -264,25 +264,11 @@ const optional &ClimateCall::get_mode() const { return this->mode_; const optional &ClimateCall::get_target_temperature() const { return this->target_temperature_; } const optional &ClimateCall::get_target_temperature_low() const { return this->target_temperature_low_; } const optional &ClimateCall::get_target_temperature_high() const { return this->target_temperature_high_; } -optional ClimateCall::get_away() const { - if (!this->preset_.has_value()) - return {}; - return *this->preset_ == ClimatePreset::CLIMATE_PRESET_AWAY; -} const optional &ClimateCall::get_fan_mode() const { return this->fan_mode_; } const optional &ClimateCall::get_custom_fan_mode() const { return this->custom_fan_mode_; } const optional &ClimateCall::get_preset() const { return this->preset_; } const optional &ClimateCall::get_custom_preset() const { return this->custom_preset_; } const optional &ClimateCall::get_swing_mode() const { return this->swing_mode_; } -ClimateCall &ClimateCall::set_away(bool away) { - this->preset_ = away ? CLIMATE_PRESET_AWAY : CLIMATE_PRESET_HOME; - return *this; -} -ClimateCall &ClimateCall::set_away(optional away) { - if (away.has_value()) - this->preset_ = *away ? CLIMATE_PRESET_AWAY : CLIMATE_PRESET_HOME; - return *this; -} ClimateCall &ClimateCall::set_target_temperature_high(optional target_temperature_high) { this->target_temperature_high_ = target_temperature_high; return *this; diff --git a/esphome/components/climate/climate.h b/esphome/components/climate/climate.h index 43bd71657d..656e1c4852 100644 --- a/esphome/components/climate/climate.h +++ b/esphome/components/climate/climate.h @@ -64,10 +64,6 @@ class ClimateCall { * For climate devices with two point target temperature control */ ClimateCall &set_target_temperature_high(optional target_temperature_high); - ESPDEPRECATED("set_away() is deprecated, please use .set_preset(CLIMATE_PRESET_AWAY) instead", "v1.20") - ClimateCall &set_away(bool away); - ESPDEPRECATED("set_away() is deprecated, please use .set_preset(CLIMATE_PRESET_AWAY) instead", "v1.20") - ClimateCall &set_away(optional away); /// Set the fan mode of the climate device. ClimateCall &set_fan_mode(ClimateFanMode fan_mode); /// Set the fan mode of the climate device. @@ -97,8 +93,6 @@ class ClimateCall { const optional &get_target_temperature() const; const optional &get_target_temperature_low() const; const optional &get_target_temperature_high() const; - ESPDEPRECATED("get_away() is deprecated, please use .get_preset() instead", "v1.20") - optional get_away() const; const optional &get_fan_mode() const; const optional &get_swing_mode() const; const optional &get_custom_fan_mode() const; @@ -184,14 +178,6 @@ class Climate : public EntityBase { }; }; - /** Whether the climate device is in away mode. - * - * Away allows climate devices to have two different target temperature configs: - * one for normal mode and one for away mode. - */ - ESPDEPRECATED("away is deprecated, use preset instead", "v1.20") - bool away{false}; - /// The active fan mode of the climate device. optional fan_mode; diff --git a/esphome/components/climate/climate_traits.h b/esphome/components/climate/climate_traits.h index ffbd8c5ae0..e8c2db6c06 100644 --- a/esphome/components/climate/climate_traits.h +++ b/esphome/components/climate/climate_traits.h @@ -117,15 +117,6 @@ class ClimateTraits { bool supports_custom_preset(const std::string &custom_preset) const { return supported_custom_presets_.count(custom_preset); } - ESPDEPRECATED("This method is deprecated, use set_supported_presets() instead", "v1.20") - void set_supports_away(bool supports) { - if (supports) { - supported_presets_.insert(CLIMATE_PRESET_AWAY); - supported_presets_.insert(CLIMATE_PRESET_HOME); - } - } - ESPDEPRECATED("This method is deprecated, use supports_preset() instead", "v1.20") - bool get_supports_away() const { return supports_preset(CLIMATE_PRESET_AWAY); } void set_supported_swing_modes(std::set modes) { supported_swing_modes_ = std::move(modes); } void add_supported_swing_mode(ClimateSwingMode mode) { supported_swing_modes_.insert(mode); } diff --git a/esphome/components/mqtt/mqtt_climate.cpp b/esphome/components/mqtt/mqtt_climate.cpp index e88ffcc37c..d63885fa04 100644 --- a/esphome/components/mqtt/mqtt_climate.cpp +++ b/esphome/components/mqtt/mqtt_climate.cpp @@ -75,13 +75,8 @@ void MQTTClimateComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryCo JsonArray presets = root.createNestedArray("preset_modes"); if (traits.supports_preset(CLIMATE_PRESET_HOME)) presets.add("home"); - if (traits.supports_preset(CLIMATE_PRESET_AWAY)) { - // away_mode_command_topic - root[MQTT_AWAY_MODE_COMMAND_TOPIC] = this->get_away_command_topic(); - // away_mode_state_topic - root[MQTT_AWAY_MODE_STATE_TOPIC] = this->get_away_state_topic(); + if (traits.supports_preset(CLIMATE_PRESET_AWAY)) presets.add("away"); - } if (traits.supports_preset(CLIMATE_PRESET_BOOST)) presets.add("boost"); if (traits.supports_preset(CLIMATE_PRESET_COMFORT)) @@ -197,29 +192,6 @@ void MQTTClimateComponent::setup() { }); } - if (traits.supports_preset(CLIMATE_PRESET_AWAY)) { - this->subscribe(this->get_away_command_topic(), [this](const std::string &topic, const std::string &payload) { - auto onoff = parse_on_off(payload.c_str()); - auto call = this->device_->make_call(); - switch (onoff) { - case PARSE_ON: - call.set_preset(CLIMATE_PRESET_AWAY); - break; - case PARSE_OFF: - call.set_preset(CLIMATE_PRESET_HOME); - break; - case PARSE_TOGGLE: - call.set_preset(this->device_->preset == CLIMATE_PRESET_AWAY ? CLIMATE_PRESET_HOME : CLIMATE_PRESET_AWAY); - break; - case PARSE_NONE: - default: - ESP_LOGW(TAG, "Unknown payload '%s'", payload.c_str()); - return; - } - call.perform(); - }); - } - if (traits.get_supports_presets() || !traits.get_supported_custom_presets().empty()) { this->subscribe(this->get_preset_command_topic(), [this](const std::string &topic, const std::string &payload) { auto call = this->device_->make_call(); @@ -301,11 +273,6 @@ bool MQTTClimateComponent::publish_state_() { success = false; } - if (traits.supports_preset(CLIMATE_PRESET_AWAY)) { - std::string payload = ONOFF(this->device_->preset == CLIMATE_PRESET_AWAY); - if (!this->publish(this->get_away_state_topic(), payload)) - success = false; - } if (traits.get_supports_presets() || !traits.get_supported_custom_presets().empty()) { std::string payload; if (this->device_->preset.has_value()) { From ee21a9131355c99cd7b3b7002e3714e48adf4ea6 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 27 Apr 2023 13:17:09 +1200 Subject: [PATCH 31/71] Add mlx90614 sensors (#3749) Co-authored-by: Greg Arnold Co-authored-by: notsonominal <130870838+notsonominal@users.noreply.github.com> --- CODEOWNERS | 1 + esphome/components/mlx90614/__init__.py | 0 esphome/components/mlx90614/mlx90614.cpp | 122 +++++++++++++++++++++++ esphome/components/mlx90614/mlx90614.h | 34 +++++++ esphome/components/mlx90614/sensor.py | 63 ++++++++++++ tests/test1.yaml | 7 ++ 6 files changed, 227 insertions(+) create mode 100644 esphome/components/mlx90614/__init__.py create mode 100644 esphome/components/mlx90614/mlx90614.cpp create mode 100644 esphome/components/mlx90614/mlx90614.h create mode 100644 esphome/components/mlx90614/sensor.py diff --git a/CODEOWNERS b/CODEOWNERS index 8e606d253a..32bf2c2d2c 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -162,6 +162,7 @@ esphome/components/midea/* @dudanov esphome/components/midea_ir/* @dudanov esphome/components/mitsubishi/* @RubyBailey esphome/components/mlx90393/* @functionpointer +esphome/components/mlx90614/* @jesserockz esphome/components/mmc5603/* @benhoff esphome/components/modbus_controller/* @martgras esphome/components/modbus_controller/binary_sensor/* @martgras diff --git a/esphome/components/mlx90614/__init__.py b/esphome/components/mlx90614/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esphome/components/mlx90614/mlx90614.cpp b/esphome/components/mlx90614/mlx90614.cpp new file mode 100644 index 0000000000..f681f3cc7e --- /dev/null +++ b/esphome/components/mlx90614/mlx90614.cpp @@ -0,0 +1,122 @@ +#include "mlx90614.h" + +#include "esphome/core/hal.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace mlx90614 { + +static const uint8_t MLX90614_RAW_IR_1 = 0x04; +static const uint8_t MLX90614_RAW_IR_2 = 0x05; +static const uint8_t MLX90614_TEMPERATURE_AMBIENT = 0x06; +static const uint8_t MLX90614_TEMPERATURE_OBJECT_1 = 0x07; +static const uint8_t MLX90614_TEMPERATURE_OBJECT_2 = 0x08; + +static const uint8_t MLX90614_TOMAX = 0x20; +static const uint8_t MLX90614_TOMIN = 0x21; +static const uint8_t MLX90614_PWMCTRL = 0x22; +static const uint8_t MLX90614_TARANGE = 0x23; +static const uint8_t MLX90614_EMISSIVITY = 0x24; +static const uint8_t MLX90614_CONFIG = 0x25; +static const uint8_t MLX90614_ADDR = 0x2E; +static const uint8_t MLX90614_ID1 = 0x3C; +static const uint8_t MLX90614_ID2 = 0x3D; +static const uint8_t MLX90614_ID3 = 0x3E; +static const uint8_t MLX90614_ID4 = 0x3F; + +static const char *const TAG = "mlx90614"; + +void MLX90614Component::setup() { + ESP_LOGCONFIG(TAG, "Setting up MLX90614..."); + if (!this->write_emissivity_()) { + ESP_LOGE(TAG, "Communication with MLX90614 failed!"); + this->mark_failed(); + return; + } +} + +bool MLX90614Component::write_emissivity_() { + if (std::isnan(this->emissivity_)) + return true; + uint16_t value = (uint16_t) (this->emissivity_ * 65535); + if (!this->write_bytes_(MLX90614_EMISSIVITY, 0)) { + return false; + } + delay(10); + if (!this->write_bytes_(MLX90614_EMISSIVITY, value)) { + return false; + } + delay(10); + return true; +} + +uint8_t MLX90614Component::crc8_pec_(const uint8_t *data, uint8_t len) { + uint8_t crc = 0; + for (uint8_t i = 0; i < len; i++) { + uint8_t in = data[i]; + for (uint8_t j = 0; j < 8; j++) { + bool carry = (crc ^ in) & 0x80; + crc <<= 1; + if (carry) + crc ^= 0x07; + in <<= 1; + } + } + return crc; +} + +bool MLX90614Component::write_bytes_(uint8_t reg, uint16_t data) { + uint8_t buf[5]; + buf[0] = this->address_ << 1; + buf[1] = reg; + buf[2] = data & 0xFF; + buf[3] = data >> 8; + buf[4] = this->crc8_pec_(buf, 4); + return this->write_bytes(reg, buf + 2, 3); +} + +void MLX90614Component::dump_config() { + ESP_LOGCONFIG(TAG, "MLX90614:"); + LOG_I2C_DEVICE(this); + if (this->is_failed()) { + ESP_LOGE(TAG, "Communication with MLX90614 failed!"); + } + LOG_UPDATE_INTERVAL(this); + LOG_SENSOR(" ", "Ambient", this->ambient_sensor_); + LOG_SENSOR(" ", "Object", this->object_sensor_); +} + +float MLX90614Component::get_setup_priority() const { return setup_priority::DATA; } + +void MLX90614Component::update() { + uint8_t emissivity[3]; + if (this->read_register(MLX90614_EMISSIVITY, emissivity, 3, false) != i2c::ERROR_OK) { + this->status_set_warning(); + return; + } + uint8_t raw_object[3]; + if (this->read_register(MLX90614_TEMPERATURE_OBJECT_1, raw_object, 3, false) != i2c::ERROR_OK) { + this->status_set_warning(); + return; + } + + uint8_t raw_ambient[3]; + if (this->read_register(MLX90614_TEMPERATURE_AMBIENT, raw_ambient, 3, false) != i2c::ERROR_OK) { + this->status_set_warning(); + return; + } + + float ambient = raw_ambient[1] & 0x80 ? NAN : encode_uint16(raw_ambient[1], raw_ambient[0]) * 0.02f - 273.15f; + float object = raw_object[1] & 0x80 ? NAN : encode_uint16(raw_object[1], raw_object[0]) * 0.02f - 273.15f; + + ESP_LOGD(TAG, "Got Temperature=%.1f°C Ambient=%.1f°C", object, ambient); + + if (this->ambient_sensor_ != nullptr && !std::isnan(ambient)) + this->ambient_sensor_->publish_state(ambient); + if (this->object_sensor_ != nullptr && !std::isnan(object)) + this->object_sensor_->publish_state(object); + this->status_clear_warning(); +} + +} // namespace mlx90614 +} // namespace esphome diff --git a/esphome/components/mlx90614/mlx90614.h b/esphome/components/mlx90614/mlx90614.h new file mode 100644 index 0000000000..b6bd44172d --- /dev/null +++ b/esphome/components/mlx90614/mlx90614.h @@ -0,0 +1,34 @@ +#pragma once + +#include "esphome/components/i2c/i2c.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/core/component.h" + +namespace esphome { +namespace mlx90614 { + +class MLX90614Component : public PollingComponent, public i2c::I2CDevice { + public: + void setup() override; + void dump_config() override; + void update() override; + float get_setup_priority() const override; + + void set_ambient_sensor(sensor::Sensor *ambient_sensor) { ambient_sensor_ = ambient_sensor; } + void set_object_sensor(sensor::Sensor *object_sensor) { object_sensor_ = object_sensor; } + + void set_emissivity(float emissivity) { emissivity_ = emissivity; } + + protected: + bool write_emissivity_(); + + uint8_t crc8_pec_(const uint8_t *data, uint8_t len); + bool write_bytes_(uint8_t reg, uint16_t data); + + sensor::Sensor *ambient_sensor_{nullptr}; + sensor::Sensor *object_sensor_{nullptr}; + + float emissivity_{NAN}; +}; +} // namespace mlx90614 +} // namespace esphome diff --git a/esphome/components/mlx90614/sensor.py b/esphome/components/mlx90614/sensor.py new file mode 100644 index 0000000000..3e90d19e45 --- /dev/null +++ b/esphome/components/mlx90614/sensor.py @@ -0,0 +1,63 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import i2c, sensor +from esphome.const import ( + CONF_ID, + DEVICE_CLASS_TEMPERATURE, + STATE_CLASS_MEASUREMENT, + UNIT_CELSIUS, +) + +CODEOWNERS = ["@jesserockz"] +DEPENDENCIES = ["i2c"] + +CONF_AMBIENT = "ambient" +CONF_EMISSIVITY = "emissivity" +CONF_OBJECT = "object" + +mlx90614_ns = cg.esphome_ns.namespace("mlx90614") +MLX90614Component = mlx90614_ns.class_( + "MLX90614Component", cg.PollingComponent, i2c.I2CDevice +) + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(MLX90614Component), + cv.Optional(CONF_AMBIENT): sensor.sensor_schema( + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=1, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_OBJECT): sensor.sensor_schema( + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=1, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + ).extend( + { + cv.Optional(CONF_EMISSIVITY, default=1.0): cv.percentage, + } + ), + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x5A)) +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await i2c.register_i2c_device(var, config) + + if CONF_AMBIENT in config: + sens = await sensor.new_sensor(config[CONF_AMBIENT]) + cg.add(var.set_ambient_sensor(sens)) + + if CONF_OBJECT in config: + sens = await sensor.new_sensor(config[CONF_OBJECT]) + cg.add(var.set_object_sensor(sens)) + + cg.add(var.set_emissivity(config[CONF_OBJECT][CONF_EMISSIVITY])) diff --git a/tests/test1.yaml b/tests/test1.yaml index c2a2ed5c95..a235ff1502 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -1239,6 +1239,13 @@ sensor: temperature: name: Max9611 Temp update_interval: 1s + - platform: mlx90614 + i2c_id: i2c_bus + ambient: + name: Ambient + object: + name: Object + emissivity: 1.0 - platform: mpl3115a2 i2c_id: i2c_bus temperature: From 6f27126c8d9056bdcb53fda7186633fbb4dae2c4 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 27 Apr 2023 13:24:42 +1200 Subject: [PATCH 32/71] Move am43 sensor code and remove auto load on cover (#4631) --- CODEOWNERS | 1 + esphome/components/am43/__init__.py | 1 + esphome/components/am43/cover/__init__.py | 10 +++++----- .../components/am43/{sensor.py => sensor/__init__.py} | 11 ++++++----- .../am43/{am43.cpp => sensor/am43_sensor.cpp} | 4 ++-- .../components/am43/{am43.h => sensor/am43_sensor.h} | 0 6 files changed, 15 insertions(+), 12 deletions(-) rename esphome/components/am43/{sensor.py => sensor/__init__.py} (83%) rename esphome/components/am43/{am43.cpp => sensor/am43_sensor.cpp} (99%) rename esphome/components/am43/{am43.h => sensor/am43_sensor.h} (100%) diff --git a/CODEOWNERS b/CODEOWNERS index 32bf2c2d2c..82aa071dc4 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -21,6 +21,7 @@ esphome/components/airthings_wave_mini/* @ncareau esphome/components/airthings_wave_plus/* @jeromelaban esphome/components/am43/* @buxtronix esphome/components/am43/cover/* @buxtronix +esphome/components/am43/sensor/* @buxtronix esphome/components/analog_threshold/* @ianchi esphome/components/animation/* @syndlex esphome/components/anova/* @buxtronix diff --git a/esphome/components/am43/__init__.py b/esphome/components/am43/__init__.py index e69de29bb2..f21a15ce0a 100644 --- a/esphome/components/am43/__init__.py +++ b/esphome/components/am43/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@buxtronix"] diff --git a/esphome/components/am43/cover/__init__.py b/esphome/components/am43/cover/__init__.py index 79eeb2eef3..103ac809e6 100644 --- a/esphome/components/am43/cover/__init__.py +++ b/esphome/components/am43/cover/__init__.py @@ -5,7 +5,7 @@ from esphome.const import CONF_ID, CONF_PIN CODEOWNERS = ["@buxtronix"] DEPENDENCIES = ["ble_client"] -AUTO_LOAD = ["am43", "sensor"] +AUTO_LOAD = ["am43"] CONF_INVERT_POSITION = "invert_position" @@ -27,10 +27,10 @@ CONFIG_SCHEMA = ( ) -def to_code(config): +async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) cg.add(var.set_pin(config[CONF_PIN])) cg.add(var.set_invert_position(config[CONF_INVERT_POSITION])) - yield cg.register_component(var, config) - yield cover.register_cover(var, config) - yield ble_client.register_ble_node(var, config) + await cg.register_component(var, config) + await cover.register_cover(var, config) + await ble_client.register_ble_node(var, config) diff --git a/esphome/components/am43/sensor.py b/esphome/components/am43/sensor/__init__.py similarity index 83% rename from esphome/components/am43/sensor.py rename to esphome/components/am43/sensor/__init__.py index 68c85d0e9c..01588f2299 100644 --- a/esphome/components/am43/sensor.py +++ b/esphome/components/am43/sensor/__init__.py @@ -11,6 +11,7 @@ from esphome.const import ( UNIT_PERCENT, ) +AUTO_LOAD = ["am43"] CODEOWNERS = ["@buxtronix"] am43_ns = cg.esphome_ns.namespace("am43") @@ -38,15 +39,15 @@ CONFIG_SCHEMA = ( ) -def to_code(config): +async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) - yield cg.register_component(var, config) - yield ble_client.register_ble_node(var, config) + await cg.register_component(var, config) + await ble_client.register_ble_node(var, config) if CONF_BATTERY_LEVEL in config: - sens = yield sensor.new_sensor(config[CONF_BATTERY_LEVEL]) + sens = await sensor.new_sensor(config[CONF_BATTERY_LEVEL]) cg.add(var.set_battery(sens)) if CONF_ILLUMINANCE in config: - sens = yield sensor.new_sensor(config[CONF_ILLUMINANCE]) + sens = await sensor.new_sensor(config[CONF_ILLUMINANCE]) cg.add(var.set_illuminance(sens)) diff --git a/esphome/components/am43/am43.cpp b/esphome/components/am43/sensor/am43_sensor.cpp similarity index 99% rename from esphome/components/am43/am43.cpp rename to esphome/components/am43/sensor/am43_sensor.cpp index 09723496d9..008c7768ed 100644 --- a/esphome/components/am43/am43.cpp +++ b/esphome/components/am43/sensor/am43_sensor.cpp @@ -1,6 +1,6 @@ -#include "am43.h" -#include "esphome/core/log.h" +#include "am43_sensor.h" #include "esphome/core/hal.h" +#include "esphome/core/log.h" #ifdef USE_ESP32 diff --git a/esphome/components/am43/am43.h b/esphome/components/am43/sensor/am43_sensor.h similarity index 100% rename from esphome/components/am43/am43.h rename to esphome/components/am43/sensor/am43_sensor.h From 55ec082628c04abcadfef63f0f12ab70fd6f829f Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 27 Apr 2023 16:22:12 +1200 Subject: [PATCH 33/71] Only request VA port from first client that is subscribed (#4747) --- esphome/components/api/api_server.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/esphome/components/api/api_server.cpp b/esphome/components/api/api_server.cpp index 97a7d6fbf6..068f74315c 100644 --- a/esphome/components/api/api_server.cpp +++ b/esphome/components/api/api_server.cpp @@ -429,15 +429,16 @@ void APIServer::on_shutdown() { #ifdef USE_VOICE_ASSISTANT bool APIServer::start_voice_assistant() { - bool result = false; for (auto &c : this->clients_) { - result |= c->request_voice_assistant(true); + if (c->request_voice_assistant(true)) + return true; } - return result; + return false; } void APIServer::stop_voice_assistant() { for (auto &c : this->clients_) { - c->request_voice_assistant(false); + if (c->request_voice_assistant(false)) + return; } } #endif From 70aa38f5bd9494357978479d2ac03e364df3ce32 Mon Sep 17 00:00:00 2001 From: itpeters <59966384+itpeters@users.noreply.github.com> Date: Wed, 26 Apr 2023 22:34:20 -0600 Subject: [PATCH 34/71] Don't allow fingerprint_grow enroll cancellation when no enrollment started (#4745) --- esphome/components/fingerprint_grow/fingerprint_grow.cpp | 6 ++++-- esphome/components/fingerprint_grow/fingerprint_grow.h | 4 +++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/esphome/components/fingerprint_grow/fingerprint_grow.cpp b/esphome/components/fingerprint_grow/fingerprint_grow.cpp index d27b0ca4cd..4043f32dcb 100644 --- a/esphome/components/fingerprint_grow/fingerprint_grow.cpp +++ b/esphome/components/fingerprint_grow/fingerprint_grow.cpp @@ -77,10 +77,12 @@ void FingerprintGrowComponent::finish_enrollment(uint8_t result) { this->enrollment_done_callback_.call(this->enrollment_slot_); this->get_fingerprint_count_(); } else { - this->enrollment_failed_callback_.call(this->enrollment_slot_); + if (this->enrollment_slot_ != ENROLLMENT_SLOT_UNUSED) { + this->enrollment_failed_callback_.call(this->enrollment_slot_); + } } this->enrollment_image_ = 0; - this->enrollment_slot_ = 0; + this->enrollment_slot_ = ENROLLMENT_SLOT_UNUSED; if (this->enrolling_binary_sensor_ != nullptr) { this->enrolling_binary_sensor_->publish_state(false); } diff --git a/esphome/components/fingerprint_grow/fingerprint_grow.h b/esphome/components/fingerprint_grow/fingerprint_grow.h index fd316237f7..f414146e64 100644 --- a/esphome/components/fingerprint_grow/fingerprint_grow.h +++ b/esphome/components/fingerprint_grow/fingerprint_grow.h @@ -13,6 +13,8 @@ namespace fingerprint_grow { static const uint16_t START_CODE = 0xEF01; +static const uint16_t ENROLLMENT_SLOT_UNUSED = 0xFFFF; + enum GrowPacketType { COMMAND = 0x01, DATA = 0x02, @@ -158,7 +160,7 @@ class FingerprintGrowComponent : public PollingComponent, public uart::UARTDevic uint32_t new_password_ = -1; GPIOPin *sensing_pin_{nullptr}; uint8_t enrollment_image_ = 0; - uint16_t enrollment_slot_ = 0; + uint16_t enrollment_slot_ = ENROLLMENT_SLOT_UNUSED; uint8_t enrollment_buffers_ = 5; bool waiting_removal_ = false; uint32_t last_aura_led_control_ = 0; From 568e65a6ab2b74c4c1e2741d59c8dbf2986e32d2 Mon Sep 17 00:00:00 2001 From: RoboMagus <68224306+RoboMagus@users.noreply.github.com> Date: Sun, 30 Apr 2023 21:28:21 +0200 Subject: [PATCH 35/71] Fix assumed_state switch webserver (#4259) --- esphome/components/web_server/web_server.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index 80a53a7515..00b2e20015 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -428,6 +428,9 @@ void WebServer::on_switch_update(switch_::Switch *obj, bool state) { std::string WebServer::switch_json(switch_::Switch *obj, bool value, JsonDetail start_config) { return json::build_json([obj, value, start_config](JsonObject root) { set_json_icon_state_value(root, obj, "switch-" + obj->get_object_id(), value ? "ON" : "OFF", value, start_config); + if (start_config == DETAIL_ALL) { + root["assumed_state"] = obj->assumed_state(); + } }); } void WebServer::handle_switch_request(AsyncWebServerRequest *request, const UrlMatch &match) { From c2a43c733ac908dca93d34370f5b8bb56ece8b78 Mon Sep 17 00:00:00 2001 From: Keith Burzinski Date: Sun, 30 Apr 2023 14:52:05 -0500 Subject: [PATCH 36/71] Fix sprinkler switch restore_mode (#4756) --- esphome/components/sprinkler/__init__.py | 20 +++++++++++++++----- esphome/components/sprinkler/sprinkler.cpp | 15 +++++++++++++++ esphome/components/sprinkler/sprinkler.h | 7 ++++++- 3 files changed, 36 insertions(+), 6 deletions(-) diff --git a/esphome/components/sprinkler/__init__.py b/esphome/components/sprinkler/__init__.py index d49b1ba381..6aa76dcd2f 100644 --- a/esphome/components/sprinkler/__init__.py +++ b/esphome/components/sprinkler/__init__.py @@ -286,7 +286,9 @@ SPRINKLER_VALVE_SCHEMA = cv.Schema( { cv.Optional(CONF_ENABLE_SWITCH): cv.maybe_simple_value( switch.switch_schema( - SprinklerControllerSwitch, entity_category=ENTITY_CATEGORY_CONFIG + SprinklerControllerSwitch, + entity_category=ENTITY_CATEGORY_CONFIG, + default_restore_mode="RESTORE_DEFAULT_OFF", ), key=CONF_NAME, ), @@ -333,7 +335,9 @@ SPRINKLER_CONTROLLER_SCHEMA = cv.Schema( cv.Optional(CONF_NAME): cv.string, cv.Optional(CONF_AUTO_ADVANCE_SWITCH): cv.maybe_simple_value( switch.switch_schema( - SprinklerControllerSwitch, entity_category=ENTITY_CATEGORY_CONFIG + SprinklerControllerSwitch, + entity_category=ENTITY_CATEGORY_CONFIG, + default_restore_mode="RESTORE_DEFAULT_OFF", ), key=CONF_NAME, ), @@ -343,19 +347,25 @@ SPRINKLER_CONTROLLER_SCHEMA = cv.Schema( ), cv.Optional(CONF_QUEUE_ENABLE_SWITCH): cv.maybe_simple_value( switch.switch_schema( - SprinklerControllerSwitch, entity_category=ENTITY_CATEGORY_CONFIG + SprinklerControllerSwitch, + entity_category=ENTITY_CATEGORY_CONFIG, + default_restore_mode="RESTORE_DEFAULT_OFF", ), key=CONF_NAME, ), cv.Optional(CONF_REVERSE_SWITCH): cv.maybe_simple_value( switch.switch_schema( - SprinklerControllerSwitch, entity_category=ENTITY_CATEGORY_CONFIG + SprinklerControllerSwitch, + entity_category=ENTITY_CATEGORY_CONFIG, + default_restore_mode="RESTORE_DEFAULT_OFF", ), key=CONF_NAME, ), cv.Optional(CONF_STANDBY_SWITCH): cv.maybe_simple_value( switch.switch_schema( - SprinklerControllerSwitch, entity_category=ENTITY_CATEGORY_CONFIG + SprinklerControllerSwitch, + entity_category=ENTITY_CATEGORY_CONFIG, + default_restore_mode="RESTORE_DEFAULT_OFF", ), key=CONF_NAME, ), diff --git a/esphome/components/sprinkler/sprinkler.cpp b/esphome/components/sprinkler/sprinkler.cpp index 6169185d60..52a6cd2af4 100644 --- a/esphome/components/sprinkler/sprinkler.cpp +++ b/esphome/components/sprinkler/sprinkler.cpp @@ -1176,6 +1176,21 @@ optional Sprinkler::time_remaining_current_operation() { return nullopt; } +bool Sprinkler::any_controller_is_active() { + if (this->state_ != IDLE) { + return true; + } + + for (auto &controller : this->other_controllers_) { + if (controller != this) { // dummy check + if (controller->controller_state() != IDLE) { + return true; + } + } + } + return false; +} + SprinklerControllerSwitch *Sprinkler::control_switch(size_t valve_number) { if (this->is_a_valid_valve(valve_number)) { return this->valve_[valve_number].controller_switch; diff --git a/esphome/components/sprinkler/sprinkler.h b/esphome/components/sprinkler/sprinkler.h index 7952c4533f..7a8285ae73 100644 --- a/esphome/components/sprinkler/sprinkler.h +++ b/esphome/components/sprinkler/sprinkler.h @@ -406,6 +406,12 @@ class Sprinkler : public Component { /// returns the amount of time remaining in seconds for all valves remaining, including the active valve, if any optional time_remaining_current_operation(); + /// returns true if this or any sprinkler controller this controller knows about is active + bool any_controller_is_active(); + + /// returns the current state of the sprinkler controller + SprinklerState controller_state() { return this->state_; }; + /// returns a pointer to a valve's control switch object SprinklerControllerSwitch *control_switch(size_t valve_number); @@ -503,7 +509,6 @@ class Sprinkler : public Component { /// callback functions for timers void valve_selection_callback_(); void sm_timer_callback_(); - void pump_stop_delay_callback_(); /// Maximum allowed queue size const uint8_t max_queue_size_{100}; From 980cfaf295acb06299a5d3b6401b4a8712a3a8cc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 May 2023 07:57:02 +1200 Subject: [PATCH 37/71] Bump aioesphomeapi from 13.7.1 to 13.7.2 (#4753) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 41fedc88cf..be5133f5f9 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.7.1 +aioesphomeapi==13.7.2 zeroconf==0.56.0 # esp-idf requires this, but doesn't bundle it by default From 2d56b70a361524a1dc9b881e1a50bd972f86bb97 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 1 May 2023 08:51:46 +1200 Subject: [PATCH 38/71] Bump git version in Dockerfile (#4763) --- docker/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index a59a470394..21c8f2a0ae 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -26,7 +26,7 @@ RUN \ python3-cryptography=3.3.2-1 \ python3-venv=3.9.2-3 \ iputils-ping=3:20210202-1 \ - git=1:2.30.2-1 \ + git=1:2.30.2-1+deb11u2 \ curl=7.74.0-1.3+deb11u7 \ openssh-client=1:8.4p1-5+deb11u1 \ && rm -rf \ From f4b98f5e320800d1477602bf2a655e15ed661230 Mon Sep 17 00:00:00 2001 From: tracestep <16390082+tracestep@users.noreply.github.com> Date: Sun, 30 Apr 2023 18:24:15 -0300 Subject: [PATCH 39/71] Power down PN532 before deep sleep (#4707) --- esphome/components/pn532/pn532.cpp | 25 +++++++++++++++++++++++++ esphome/components/pn532/pn532.h | 4 ++++ 2 files changed, 29 insertions(+) diff --git a/esphome/components/pn532/pn532.cpp b/esphome/components/pn532/pn532.cpp index dc831ef6e0..cc28d7078b 100644 --- a/esphome/components/pn532/pn532.cpp +++ b/esphome/components/pn532/pn532.cpp @@ -81,7 +81,32 @@ void PN532::setup() { this->turn_off_rf_(); } +bool PN532::powerdown() { + updates_enabled_ = false; + requested_read_ = false; + ESP_LOGI(TAG, "Powering down PN532"); + if (!this->write_command_({PN532_COMMAND_POWERDOWN, 0b10100000})) { // enable i2c,spi wakeup + ESP_LOGE(TAG, "Error writing powerdown command to PN532"); + return false; + } + std::vector response; + if (!this->read_response(PN532_COMMAND_POWERDOWN, response)) { + ESP_LOGE(TAG, "Error reading PN532 powerdown response"); + return false; + } + if (response[0] != 0x00) { + ESP_LOGE(TAG, "Error on PN532 powerdown: %02x", response[0]); + return false; + } + ESP_LOGV(TAG, "Powerdown successful"); + delay(1); + return true; +} + void PN532::update() { + if (!updates_enabled_) + return; + for (auto *obj : this->binary_sensors_) obj->on_scan_end(); diff --git a/esphome/components/pn532/pn532.h b/esphome/components/pn532/pn532.h index fee94a29b8..73b349e328 100644 --- a/esphome/components/pn532/pn532.h +++ b/esphome/components/pn532/pn532.h @@ -17,6 +17,7 @@ static const uint8_t PN532_COMMAND_SAMCONFIGURATION = 0x14; static const uint8_t PN532_COMMAND_RFCONFIGURATION = 0x32; static const uint8_t PN532_COMMAND_INDATAEXCHANGE = 0x40; static const uint8_t PN532_COMMAND_INLISTPASSIVETARGET = 0x4A; +static const uint8_t PN532_COMMAND_POWERDOWN = 0x16; class PN532BinarySensor; @@ -30,6 +31,7 @@ class PN532 : public PollingComponent { float get_setup_priority() const override; void loop() override; + void on_shutdown() override { powerdown(); } void register_tag(PN532BinarySensor *tag) { this->binary_sensors_.push_back(tag); } void register_ontag_trigger(nfc::NfcOnTagTrigger *trig) { this->triggers_ontag_.push_back(trig); } @@ -45,6 +47,7 @@ class PN532 : public PollingComponent { void clean_mode(); void format_mode(); void write_mode(nfc::NdefMessage *message); + bool powerdown(); protected: void turn_off_rf_(); @@ -79,6 +82,7 @@ class PN532 : public PollingComponent { bool write_mifare_ultralight_tag_(std::vector &uid, nfc::NdefMessage *message); bool clean_mifare_ultralight_(); + bool updates_enabled_{true}; bool requested_read_{false}; std::vector binary_sensors_; std::vector triggers_ontag_; From 56e0923c2295e264553f0b7c579c71c9f8c18dda Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 1 May 2023 11:09:01 +1200 Subject: [PATCH 40/71] Switch ESPAsyncTCP-esphome to esphome fork (#4764) --- esphome/components/async_tcp/__init__.py | 4 ++-- platformio.ini | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/esphome/components/async_tcp/__init__.py b/esphome/components/async_tcp/__init__.py index c693544a2e..1d127623f1 100644 --- a/esphome/components/async_tcp/__init__.py +++ b/esphome/components/async_tcp/__init__.py @@ -18,5 +18,5 @@ async def to_code(config): # https://github.com/esphome/AsyncTCP/blob/master/library.json cg.add_library("esphome/AsyncTCP-esphome", "1.2.2") elif CORE.is_esp8266: - # https://github.com/OttoWinter/ESPAsyncTCP - cg.add_library("ottowinter/ESPAsyncTCP-esphome", "1.2.3") + # https://github.com/esphome/ESPAsyncTCP + cg.add_library("esphome/ESPAsyncTCP-esphome", "1.2.3") diff --git a/platformio.ini b/platformio.ini index 7f301e560c..da3bb9d29f 100644 --- a/platformio.ini +++ b/platformio.ini @@ -88,7 +88,7 @@ lib_deps = ${common:arduino.lib_deps} ESP8266WiFi ; wifi (Arduino built-in) Update ; ota (Arduino built-in) - ottowinter/ESPAsyncTCP-esphome@1.2.3 ; async_tcp + esphome/ESPAsyncTCP-esphome@1.2.3 ; async_tcp ESP8266HTTPClient ; http_request (Arduino built-in) ESP8266mDNS ; mdns (Arduino built-in) DNSServer ; captive_portal (Arduino built-in) From d6f7876e6814b13bdb264522218dfa0270c1c476 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 May 2023 00:01:52 +0000 Subject: [PATCH 41/71] Bump pyupgrade from 3.3.1 to 3.3.2 (#4751) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- requirements_test.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index be82fc826b..b858b40e6f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -27,7 +27,7 @@ repos: - --branch=release - --branch=beta - repo: https://github.com/asottile/pyupgrade - rev: v3.3.1 + rev: v3.3.2 hooks: - id: pyupgrade args: [--py39-plus] diff --git a/requirements_test.txt b/requirements_test.txt index b18aabe7b4..55f8da245e 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,7 +1,7 @@ pylint==2.17.3 flake8==6.0.0 # also change in .pre-commit-config.yaml when updating black==23.3.0 # also change in .pre-commit-config.yaml when updating -pyupgrade==3.3.1 # also change in .pre-commit-config.yaml when updating +pyupgrade==3.3.2 # also change in .pre-commit-config.yaml when updating pre-commit # Unit tests From 57e909e7909b5a11024fe630c506ba166243a68c Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 1 May 2023 15:57:57 +1200 Subject: [PATCH 42/71] Only pre-install libraries in docker images (#4766) --- docker/Dockerfile | 2 +- script/platformio_install_deps.py | 20 +++++++++++++++----- script/setup | 2 +- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 21c8f2a0ae..720241242f 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -63,7 +63,7 @@ RUN \ COPY requirements.txt requirements_optional.txt script/platformio_install_deps.py platformio.ini / RUN \ pip3 install --no-cache-dir -r /requirements.txt -r /requirements_optional.txt \ - && /platformio_install_deps.py /platformio.ini + && /platformio_install_deps.py /platformio.ini --libraries # ======================= docker-type image ======================= diff --git a/script/platformio_install_deps.py b/script/platformio_install_deps.py index 2340410161..ed133ecb47 100755 --- a/script/platformio_install_deps.py +++ b/script/platformio_install_deps.py @@ -2,12 +2,22 @@ # This script is used to preinstall # all platformio libraries in the global storage +import argparse import configparser import subprocess -import sys config = configparser.ConfigParser(inline_comment_prefixes=(";",)) -config.read(sys.argv[1]) + +parser = argparse.ArgumentParser(description="") +parser.add_argument("file", help="Path to platformio.ini", nargs=1) +parser.add_argument("-l", "--libraries", help="Install libraries", action="store_true") +parser.add_argument("-p", "--platforms", help="Install platforms", action="store_true") +parser.add_argument("-t", "--tools", help="Install tools", action="store_true") + +args = parser.parse_args() + +config.read(args.file) + libs = [] tools = [] @@ -15,7 +25,7 @@ platforms = [] # Extract from every lib_deps key in all sections for section in config.sections(): conf = config[section] - if "lib_deps" in conf: + if "lib_deps" in conf and args.libraries: for lib_dep in conf["lib_deps"].splitlines(): if not lib_dep: # Empty line or comment @@ -28,10 +38,10 @@ for section in config.sections(): continue libs.append("-l") libs.append(lib_dep) - if "platform" in conf: + if "platform" in conf and args.platforms: platforms.append("-p") platforms.append(conf["platform"]) - if "platform_packages" in conf: + if "platform_packages" in conf and args.tools: for tool in conf["platform_packages"].splitlines(): if not tool: # Empty line or comment diff --git a/script/setup b/script/setup index 5acd1a9f13..656e95eba6 100755 --- a/script/setup +++ b/script/setup @@ -15,4 +15,4 @@ pip3 install --no-use-pep517 -e . pre-commit install -script/platformio_install_deps.py platformio.ini +script/platformio_install_deps.py platformio.ini --libraries --tools --platforms From 76b6fcf554a2f3b26d01808a38a4b5c93828f519 Mon Sep 17 00:00:00 2001 From: Mat931 <49403702+Mat931@users.noreply.github.com> Date: Mon, 1 May 2023 04:00:21 +0000 Subject: [PATCH 43/71] Add PCA6416A Support (#4681) --- CODEOWNERS | 1 + esphome/components/pca6416a/__init__.py | 78 ++++++++++ esphome/components/pca6416a/pca6416a.cpp | 174 +++++++++++++++++++++++ esphome/components/pca6416a/pca6416a.h | 63 ++++++++ tests/test1.yaml | 12 ++ 5 files changed, 328 insertions(+) create mode 100644 esphome/components/pca6416a/__init__.py create mode 100644 esphome/components/pca6416a/pca6416a.cpp create mode 100644 esphome/components/pca6416a/pca6416a.h diff --git a/CODEOWNERS b/CODEOWNERS index 82aa071dc4..5a8ef76c44 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -188,6 +188,7 @@ esphome/components/nfc/* @jesserockz esphome/components/number/* @esphome/core esphome/components/ota/* @esphome/core esphome/components/output/* @esphome/core +esphome/components/pca6416a/* @Mat931 esphome/components/pca9554/* @hwstar esphome/components/pcf85063/* @brogon esphome/components/pid/* @OttoWinter diff --git a/esphome/components/pca6416a/__init__.py b/esphome/components/pca6416a/__init__.py new file mode 100644 index 0000000000..574d8dce91 --- /dev/null +++ b/esphome/components/pca6416a/__init__.py @@ -0,0 +1,78 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome import pins +from esphome.components import i2c +from esphome.const import ( + CONF_ID, + CONF_INPUT, + CONF_NUMBER, + CONF_MODE, + CONF_INVERTED, + CONF_OUTPUT, + CONF_PULLUP, +) + +CODEOWNERS = ["@Mat931"] +DEPENDENCIES = ["i2c"] +MULTI_CONF = True +pca6416a_ns = cg.esphome_ns.namespace("pca6416a") + +PCA6416AComponent = pca6416a_ns.class_("PCA6416AComponent", cg.Component, i2c.I2CDevice) +PCA6416AGPIOPin = pca6416a_ns.class_( + "PCA6416AGPIOPin", cg.GPIOPin, cg.Parented.template(PCA6416AComponent) +) + +CONF_PCA6416A = "pca6416a" +CONFIG_SCHEMA = ( + cv.Schema({cv.Required(CONF_ID): cv.declare_id(PCA6416AComponent)}) + .extend(cv.COMPONENT_SCHEMA) + .extend(i2c.i2c_device_schema(0x21)) +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await i2c.register_i2c_device(var, config) + + +def validate_mode(value): + if not (value[CONF_INPUT] or value[CONF_OUTPUT]): + raise cv.Invalid("Mode must be either input or output") + if value[CONF_INPUT] and value[CONF_OUTPUT]: + raise cv.Invalid("Mode must be either input or output") + if value[CONF_PULLUP] and not value[CONF_INPUT]: + raise cv.Invalid("Pullup only available with input") + return value + + +PCA6416A_PIN_SCHEMA = cv.All( + { + cv.GenerateID(): cv.declare_id(PCA6416AGPIOPin), + cv.Required(CONF_PCA6416A): cv.use_id(PCA6416AComponent), + cv.Required(CONF_NUMBER): cv.int_range(min=0, max=16), + cv.Optional(CONF_MODE, default={}): cv.All( + { + cv.Optional(CONF_INPUT, default=False): cv.boolean, + cv.Optional(CONF_PULLUP, default=False): cv.boolean, + cv.Optional(CONF_OUTPUT, default=False): cv.boolean, + }, + validate_mode, + ), + cv.Optional(CONF_INVERTED, default=False): cv.boolean, + } +) + + +@pins.PIN_SCHEMA_REGISTRY.register("pca6416a", PCA6416A_PIN_SCHEMA) +async def pca6416a_pin_to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + parent = await cg.get_variable(config[CONF_PCA6416A]) + + cg.add(var.set_parent(parent)) + + num = config[CONF_NUMBER] + cg.add(var.set_pin(num)) + cg.add(var.set_inverted(config[CONF_INVERTED])) + cg.add(var.set_flags(pins.gpio_flags_expr(config[CONF_MODE]))) + return var diff --git a/esphome/components/pca6416a/pca6416a.cpp b/esphome/components/pca6416a/pca6416a.cpp new file mode 100644 index 0000000000..1f4e315644 --- /dev/null +++ b/esphome/components/pca6416a/pca6416a.cpp @@ -0,0 +1,174 @@ +#include "pca6416a.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace pca6416a { + +enum PCA6416AGPIORegisters { + // 0 side + PCA6416A_INPUT0 = 0x00, + PCA6416A_OUTPUT0 = 0x02, + PCA6416A_INVERT0 = 0x04, + PCA6416A_CONFIG0 = 0x06, + PCAL6416A_PULL_EN0 = 0x46, + PCAL6416A_PULL_DIR0 = 0x48, + // 1 side + PCA6416A_INPUT1 = 0x01, + PCA6416A_OUTPUT1 = 0x03, + PCA6416A_INVERT1 = 0x05, + PCA6416A_CONFIG1 = 0x07, + PCAL6416A_PULL_EN1 = 0x47, + PCAL6416A_PULL_DIR1 = 0x49, +}; + +static const char *const TAG = "pca6416a"; + +void PCA6416AComponent::setup() { + ESP_LOGCONFIG(TAG, "Setting up PCA6416A..."); + // Test to see if device exists + uint8_t value; + if (!this->read_register_(PCA6416A_INPUT0, &value)) { + ESP_LOGE(TAG, "PCA6416A not available under 0x%02X", this->address_); + this->mark_failed(); + return; + } + + // Test to see if the device supports pull-up resistors + if (this->read_register(PCAL6416A_PULL_EN0, &value, 1, true) == esphome::i2c::ERROR_OK) { + this->has_pullup_ = true; + } + + // No polarity inversion + this->write_register_(PCA6416A_INVERT0, 0); + this->write_register_(PCA6416A_INVERT1, 0); + // Set all pins to input + this->write_register_(PCA6416A_CONFIG0, 0xff); + this->write_register_(PCA6416A_CONFIG1, 0xff); + // Read current output register state + this->read_register_(PCA6416A_OUTPUT0, &this->output_0_); + this->read_register_(PCA6416A_OUTPUT1, &this->output_1_); + + ESP_LOGD(TAG, "Initialization complete. Warning: %d, Error: %d", this->status_has_warning(), + this->status_has_error()); +} + +void PCA6416AComponent::dump_config() { + if (this->has_pullup_) { + ESP_LOGCONFIG(TAG, "PCAL6416A:"); + } else { + ESP_LOGCONFIG(TAG, "PCA6416A:"); + } + LOG_I2C_DEVICE(this) + if (this->is_failed()) { + ESP_LOGE(TAG, "Communication with PCA6416A failed!"); + } +} + +bool PCA6416AComponent::digital_read(uint8_t pin) { + uint8_t bit = pin % 8; + uint8_t reg_addr = pin < 8 ? PCA6416A_INPUT0 : PCA6416A_INPUT1; + uint8_t value = 0; + this->read_register_(reg_addr, &value); + return value & (1 << bit); +} + +void PCA6416AComponent::digital_write(uint8_t pin, bool value) { + uint8_t reg_addr = pin < 8 ? PCA6416A_OUTPUT0 : PCA6416A_OUTPUT1; + this->update_register_(pin, value, reg_addr); +} + +void PCA6416AComponent::pin_mode(uint8_t pin, gpio::Flags flags) { + uint8_t io_dir = pin < 8 ? PCA6416A_CONFIG0 : PCA6416A_CONFIG1; + uint8_t pull_en = pin < 8 ? PCAL6416A_PULL_EN0 : PCAL6416A_PULL_EN1; + uint8_t pull_dir = pin < 8 ? PCAL6416A_PULL_DIR0 : PCAL6416A_PULL_DIR1; + if (flags == gpio::FLAG_INPUT) { + this->update_register_(pin, true, io_dir); + if (has_pullup_) { + this->update_register_(pin, true, pull_dir); + this->update_register_(pin, false, pull_en); + } + } else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLUP)) { + this->update_register_(pin, true, io_dir); + if (has_pullup_) { + this->update_register_(pin, true, pull_dir); + this->update_register_(pin, true, pull_en); + } else { + ESP_LOGW(TAG, "Your PCA6416A does not support pull-up resistors"); + } + } else if (flags == gpio::FLAG_OUTPUT) { + this->update_register_(pin, false, io_dir); + } +} + +bool PCA6416AComponent::read_register_(uint8_t reg, uint8_t *value) { + if (this->is_failed()) { + ESP_LOGD(TAG, "Device marked failed"); + return false; + } + + if ((this->last_error_ = this->read_register(reg, value, 1, true)) != esphome::i2c::ERROR_OK) { + this->status_set_warning(); + ESP_LOGE(TAG, "read_register_(): I2C I/O error: %d", (int) this->last_error_); + return false; + } + + this->status_clear_warning(); + return true; +} + +bool PCA6416AComponent::write_register_(uint8_t reg, uint8_t value) { + if (this->is_failed()) { + ESP_LOGD(TAG, "Device marked failed"); + return false; + } + + if ((this->last_error_ = this->write_register(reg, &value, 1, true)) != esphome::i2c::ERROR_OK) { + this->status_set_warning(); + ESP_LOGE(TAG, "write_register_(): I2C I/O error: %d", (int) this->last_error_); + return false; + } + + this->status_clear_warning(); + return true; +} + +void PCA6416AComponent::update_register_(uint8_t pin, bool pin_value, uint8_t reg_addr) { + uint8_t bit = pin % 8; + uint8_t reg_value = 0; + if (reg_addr == PCA6416A_OUTPUT0) { + reg_value = this->output_0_; + } else if (reg_addr == PCA6416A_OUTPUT1) { + reg_value = this->output_1_; + } else { + this->read_register_(reg_addr, ®_value); + } + + if (pin_value) { + reg_value |= 1 << bit; + } else { + reg_value &= ~(1 << bit); + } + + this->write_register_(reg_addr, reg_value); + + if (reg_addr == PCA6416A_OUTPUT0) { + this->output_0_ = reg_value; + } else if (reg_addr == PCA6416A_OUTPUT1) { + this->output_1_ = reg_value; + } +} + +float PCA6416AComponent::get_setup_priority() const { return setup_priority::IO; } + +void PCA6416AGPIOPin::setup() { pin_mode(flags_); } +void PCA6416AGPIOPin::pin_mode(gpio::Flags flags) { this->parent_->pin_mode(this->pin_, flags); } +bool PCA6416AGPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) != this->inverted_; } +void PCA6416AGPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_, value != this->inverted_); } +std::string PCA6416AGPIOPin::dump_summary() const { + char buffer[32]; + snprintf(buffer, sizeof(buffer), "%u via PCA6416A", pin_); + return buffer; +} + +} // namespace pca6416a +} // namespace esphome diff --git a/esphome/components/pca6416a/pca6416a.h b/esphome/components/pca6416a/pca6416a.h new file mode 100644 index 0000000000..247f443e87 --- /dev/null +++ b/esphome/components/pca6416a/pca6416a.h @@ -0,0 +1,63 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/hal.h" +#include "esphome/components/i2c/i2c.h" + +namespace esphome { +namespace pca6416a { + +class PCA6416AComponent : public Component, public i2c::I2CDevice { + public: + PCA6416AComponent() = default; + + /// Check i2c availability and setup masks + void setup() override; + /// Helper function to read the value of a pin. + bool digital_read(uint8_t pin); + /// Helper function to write the value of a pin. + void digital_write(uint8_t pin, bool value); + /// Helper function to set the pin mode of a pin. + void pin_mode(uint8_t pin, gpio::Flags flags); + + float get_setup_priority() const override; + + void dump_config() override; + + protected: + bool read_register_(uint8_t reg, uint8_t *value); + bool write_register_(uint8_t reg, uint8_t value); + void update_register_(uint8_t pin, bool pin_value, uint8_t reg_addr); + + /// The mask to write as output state - 1 means HIGH, 0 means LOW + uint8_t output_0_{0x00}; + uint8_t output_1_{0x00}; + /// Storage for last I2C error seen + esphome::i2c::ErrorCode last_error_; + /// Only the PCAL6416A has pull-up resistors + bool has_pullup_{false}; +}; + +/// Helper class to expose a PCA6416A pin as an internal input GPIO pin. +class PCA6416AGPIOPin : public GPIOPin { + public: + void setup() override; + void pin_mode(gpio::Flags flags) override; + bool digital_read() override; + void digital_write(bool value) override; + std::string dump_summary() const override; + + void set_parent(PCA6416AComponent *parent) { parent_ = parent; } + void set_pin(uint8_t pin) { pin_ = pin; } + void set_inverted(bool inverted) { inverted_ = inverted; } + void set_flags(gpio::Flags flags) { flags_ = flags; } + + protected: + PCA6416AComponent *parent_; + uint8_t pin_; + bool inverted_; + gpio::Flags flags_; +}; + +} // namespace pca6416a +} // namespace esphome diff --git a/tests/test1.yaml b/tests/test1.yaml index a235ff1502..c5cca7aa59 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -1449,6 +1449,13 @@ binary_sensor: number: 1 mode: INPUT inverted: true + - platform: gpio + name: PCA6416A binary sensor + pin: + pca6416a: pca6416a_hub + number: 15 + mode: INPUT + inverted: true - platform: gpio name: MCP21 binary sensor pin: @@ -2934,6 +2941,11 @@ pca9554: address: 0x3F i2c_id: i2c_bus +pca6416a: + - id: pca6416a_hub + address: 0x21 + i2c_id: i2c_bus + mcp23017: - id: mcp23017_hub open_drain_interrupt: true From c13e20643b71209f14f0da4831cd4a99bc251a93 Mon Sep 17 00:00:00 2001 From: Luis Andrade Date: Mon, 1 May 2023 00:01:24 -0400 Subject: [PATCH 44/71] play_folder bugfix and addition of play_mp3 (#4758) --- esphome/components/dfplayer/__init__.py | 20 ++++++++++++++++++++ esphome/components/dfplayer/dfplayer.cpp | 4 ++-- esphome/components/dfplayer/dfplayer.h | 14 ++++++++++++++ 3 files changed, 36 insertions(+), 2 deletions(-) diff --git a/esphome/components/dfplayer/__init__.py b/esphome/components/dfplayer/__init__.py index caa05c27b5..5ea04b4804 100644 --- a/esphome/components/dfplayer/__init__.py +++ b/esphome/components/dfplayer/__init__.py @@ -40,6 +40,7 @@ DEVICE = { NextAction = dfplayer_ns.class_("NextAction", automation.Action) PreviousAction = dfplayer_ns.class_("PreviousAction", automation.Action) +PlayMp3Action = dfplayer_ns.class_("PlayMp3Action", automation.Action) PlayFileAction = dfplayer_ns.class_("PlayFileAction", automation.Action) PlayFolderAction = dfplayer_ns.class_("PlayFolderAction", automation.Action) SetVolumeAction = dfplayer_ns.class_("SetVolumeAction", automation.Action) @@ -113,6 +114,25 @@ async def dfplayer_previous_to_code(config, action_id, template_arg, args): return var +@automation.register_action( + "dfplayer.play_mp3", + PlayMp3Action, + cv.maybe_simple_value( + { + cv.GenerateID(): cv.use_id(DFPlayer), + cv.Required(CONF_FILE): cv.templatable(cv.int_), + }, + key=CONF_FILE, + ), +) +async def dfplayer_play_mp3_to_code(config, action_id, template_arg, args): + var = cg.new_Pvariable(action_id, template_arg) + await cg.register_parented(var, config[CONF_ID]) + template_ = await cg.templatable(config[CONF_FILE], args, float) + cg.add(var.set_file(template_)) + return var + + @automation.register_action( "dfplayer.play", PlayFileAction, diff --git a/esphome/components/dfplayer/dfplayer.cpp b/esphome/components/dfplayer/dfplayer.cpp index e16479570f..a6339dc988 100644 --- a/esphome/components/dfplayer/dfplayer.cpp +++ b/esphome/components/dfplayer/dfplayer.cpp @@ -7,10 +7,10 @@ namespace dfplayer { static const char *const TAG = "dfplayer"; void DFPlayer::play_folder(uint16_t folder, uint16_t file) { - if (folder < 100 && file < 256) { + if (folder <= 10 && file <= 1000) { this->ack_set_is_playing_ = true; this->send_cmd_(0x0F, (uint8_t) folder, (uint8_t) file); - } else if (folder <= 10 && file <= 1000) { + } else if (folder < 100 && file < 256) { this->ack_set_is_playing_ = true; this->send_cmd_(0x14, (((uint16_t) folder) << 12) | file); } else { diff --git a/esphome/components/dfplayer/dfplayer.h b/esphome/components/dfplayer/dfplayer.h index ae47cb33f1..26e90fd410 100644 --- a/esphome/components/dfplayer/dfplayer.h +++ b/esphome/components/dfplayer/dfplayer.h @@ -35,6 +35,10 @@ class DFPlayer : public uart::UARTDevice, public Component { this->ack_set_is_playing_ = true; this->send_cmd_(0x02); } + void play_mp3(uint16_t file) { + this->ack_set_is_playing_ = true; + this->send_cmd_(0x12, file); + } void play_file(uint16_t file) { this->ack_set_is_playing_ = true; this->send_cmd_(0x03, file); @@ -113,6 +117,16 @@ class DFPlayer : public uart::UARTDevice, public Component { DFPLAYER_SIMPLE_ACTION(NextAction, next) DFPLAYER_SIMPLE_ACTION(PreviousAction, previous) +template class PlayMp3Action : public Action, public Parented { + public: + TEMPLATABLE_VALUE(uint16_t, file) + + void play(Ts... x) override { + auto file = this->file_.value(x...); + this->parent_->play_mp3(file); + } +}; + template class PlayFileAction : public Action, public Parented { public: TEMPLATABLE_VALUE(uint16_t, file) From 379b1d84dd5983eb7b54858d80d3ef95366ceec3 Mon Sep 17 00:00:00 2001 From: marshn Date: Mon, 1 May 2023 05:12:53 +0100 Subject: [PATCH 45/71] RF Codec for Drayton Digistat heating controller (#4494) --- esphome/components/remote_base/__init__.py | 51 +++++ .../remote_base/drayton_protocol.cpp | 213 ++++++++++++++++++ .../components/remote_base/drayton_protocol.h | 44 ++++ 3 files changed, 308 insertions(+) create mode 100644 esphome/components/remote_base/drayton_protocol.cpp create mode 100644 esphome/components/remote_base/drayton_protocol.h diff --git a/esphome/components/remote_base/__init__.py b/esphome/components/remote_base/__init__.py index 4d9196c9c5..2ef33f3711 100644 --- a/esphome/components/remote_base/__init__.py +++ b/esphome/components/remote_base/__init__.py @@ -791,6 +791,57 @@ async def raw_action(var, config, args): cg.add(var.set_carrier_frequency(templ)) +# Drayton +( + DraytonData, + DraytonBinarySensor, + DraytonTrigger, + DraytonAction, + DraytonDumper, +) = declare_protocol("Drayton") +DRAYTON_SCHEMA = cv.Schema( + { + cv.Required(CONF_ADDRESS): cv.All(cv.hex_int, cv.Range(min=0, max=0xFFFF)), + cv.Required(CONF_CHANNEL): cv.All(cv.hex_int, cv.Range(min=0, max=0x1F)), + cv.Required(CONF_COMMAND): cv.All(cv.hex_int, cv.Range(min=0, max=0x7F)), + } +) + + +@register_binary_sensor("drayton", DraytonBinarySensor, DRAYTON_SCHEMA) +def drayton_binary_sensor(var, config): + cg.add( + var.set_data( + cg.StructInitializer( + DraytonData, + ("address", config[CONF_ADDRESS]), + ("channel", config[CONF_CHANNEL]), + ("command", config[CONF_COMMAND]), + ) + ) + ) + + +@register_trigger("drayton", DraytonTrigger, DraytonData) +def drayton_trigger(var, config): + pass + + +@register_dumper("drayton", DraytonDumper) +def drayton_dumper(var, config): + pass + + +@register_action("drayton", DraytonAction, DRAYTON_SCHEMA) +async def drayton_action(var, config, args): + template_ = await cg.templatable(config[CONF_ADDRESS], args, cg.uint16) + cg.add(var.set_address(template_)) + template_ = await cg.templatable(config[CONF_CHANNEL], args, cg.uint8) + cg.add(var.set_channel(template_)) + template_ = await cg.templatable(config[CONF_COMMAND], args, cg.uint8) + cg.add(var.set_command(template_)) + + # RC5 RC5Data, RC5BinarySensor, RC5Trigger, RC5Action, RC5Dumper = declare_protocol("RC5") RC5_SCHEMA = cv.Schema( diff --git a/esphome/components/remote_base/drayton_protocol.cpp b/esphome/components/remote_base/drayton_protocol.cpp new file mode 100644 index 0000000000..f5eae49058 --- /dev/null +++ b/esphome/components/remote_base/drayton_protocol.cpp @@ -0,0 +1,213 @@ +#include "drayton_protocol.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace remote_base { + +static const char *const TAG = "remote.drayton"; + +static const uint32_t BIT_TIME_US = 500; +static const uint8_t CARRIER_KHZ = 2; +static const uint8_t NBITS_PREAMBLE = 12; +static const uint8_t NBITS_SYNC = 4; +static const uint8_t NBITS_ADDRESS = 16; +static const uint8_t NBITS_CHANNEL = 5; +static const uint8_t NBITS_COMMAND = 7; +static const uint8_t NBITS = NBITS_ADDRESS + NBITS_CHANNEL + NBITS_COMMAND; + +static const uint8_t CMD_ON = 0x41; +static const uint8_t CMD_OFF = 0x02; + +/* +Drayton Protocol +Using an oscilloscope to capture the data transmitted by the Digistat two +distinct packets for 'On' and 'Off' are transmitted. Each transmitted bit +has a period of 500us, a bit rate of 2000 baud. + +Each packet consists of an initial 1010 pattern to set up the receiver bias. +The number of these bits seen at the receiver varies depending on the state +of the bias when the packet transmission starts. The receiver algoritmn takes +account of this. + +The packet appears to be Manchester encoded, with a '10' tranmitted pair +representing a '1' bit and a '01' pair representing a '0' bit. Each packet is +begun with a '1100' syncronisation symbol which breaks this rule. Following +the sync are 28 '01' or '10' pairs. + +-------------------- + +Boiler On Command as received: +101010101010110001101001010101101001010101010101100101010101101001011001 +ppppppppppppSSSS-0-1-1-0-0-0-0-1-1-0-0-0-0-0-0-0-1-0-0-0-0-0-1-1-0-0-1-0 + +(Where pppp represents the preamble bits and SSSS represents the sync symbol) + +28 bits of data received 01100001100000001000001 10010 (bin) or 6180832 (hex) + +Boiler Off Command as received: +101010101010110001101001010101101001010101010101010101010110011001011001 +ppppppppppppSSSS-0-1-1-0-0-0-0-1-1-0-0-0-0-0-0-0-0-0-0-0-0-1-0-1-0-0-1-0 + +28 bits of data received 0110000110000000000001010010 (bin) or 6180052 (hex) + +-------------------- + +I have used 'RFLink' software (RLink Firmware Version: 1.1 Revision: 48) to +capture and retransmit the Digistat packets. RFLink splits each packet into an +ID, SWITCH, and CMD field. + +0;17;Drayton;ID=c300;SWITCH=12;CMD=ON; +20;18;Drayton;ID=c300;SWITCH=12;CMD=OFF; + +-------------------- + +Spliting my received data into three parts of 16, 7 and 5 bits gives address, +channel and Command values of: + +On 6180832 0110000110000000 1000001 10010 +address: '0x6180' channel: '0x12' command: '0x41' + +Off 6180052 0110000110000000 0000010 10010 +address: '0x6180' channel: '0x12' command: '0x02' + +These values are slightly different to those used by RFLink (the RFLink +ID/Adress value is rotated/manipulated), and I don't know who's interpretation +is correct. A larger data sample would help (I have only found five different +packet captures online) or definitive information from Drayton. + +Splitting each packet in this way works well for me with esphome. Any +corrections or additional data samples would be gratefully received. + +marshn + +*/ + +void DraytonProtocol::encode(RemoteTransmitData *dst, const DraytonData &data) { + uint16_t khz = CARRIER_KHZ; + dst->set_carrier_frequency(khz * 1000); + + // Preamble = 101010101010 + uint32_t out_data = 0x0AAA; + for (uint32_t mask = 1UL << (NBITS_PREAMBLE - 1); mask != 0; mask >>= 1) { + if (out_data & mask) { + dst->mark(BIT_TIME_US); + } else { + dst->space(BIT_TIME_US); + } + } + + // Sync = 1100 + out_data = 0x000C; + for (uint32_t mask = 1UL << (NBITS_SYNC - 1); mask != 0; mask >>= 1) { + if (out_data & mask) { + dst->mark(BIT_TIME_US); + } else { + dst->space(BIT_TIME_US); + } + } + + ESP_LOGD(TAG, "Send Drayton: address=%04x channel=%03x cmd=%02x", data.address, data.channel, data.command); + + out_data = data.address; + out_data <<= NBITS_COMMAND; + out_data |= data.command; + out_data <<= NBITS_CHANNEL; + out_data |= data.channel; + + ESP_LOGV(TAG, "Send Drayton: out_data %08x", out_data); + + for (uint32_t mask = 1UL << (NBITS - 1); mask != 0; mask >>= 1) { + if (out_data & mask) { + dst->mark(BIT_TIME_US); + dst->space(BIT_TIME_US); + } else { + dst->space(BIT_TIME_US); + dst->mark(BIT_TIME_US); + } + } +} + +optional DraytonProtocol::decode(RemoteReceiveData src) { + DraytonData out{ + .address = 0, + .channel = 0, + .command = 0, + }; + + if (src.size() < 45) { + return {}; + } + + ESP_LOGVV(TAG, "Decode Drayton: %d, %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d", src.size(), + src.peek(0), src.peek(1), src.peek(2), src.peek(3), src.peek(4), src.peek(5), src.peek(6), src.peek(7), + src.peek(8), src.peek(9), src.peek(10), src.peek(11), src.peek(12), src.peek(13), src.peek(14), + src.peek(15), src.peek(16), src.peek(17), src.peek(18), src.peek(19)); + + // If first preamble item is a space, skip it + if (src.peek_space_at_least(1)) { + src.advance(1); + } + + // Look for sync pulse, after. If sucessful index points to space of sync symbol + for (uint16_t preamble = 0; preamble <= NBITS_PREAMBLE * 2; preamble += 2) { + ESP_LOGVV(TAG, "Decode Drayton: preamble %d %d %d", preamble, src.peek(preamble), src.peek(preamble + 1)); + if (src.peek_mark(2 * BIT_TIME_US, preamble) && + (src.peek_space(2 * BIT_TIME_US, preamble + 1) || src.peek_space(3 * BIT_TIME_US, preamble + 1))) { + src.advance(preamble + 1); + break; + } + } + + // Read data. Index points to space of sync symbol + // Extract first bit + // Checks next bit to leave index pointing correctly + uint32_t out_data = 0; + uint8_t bit = NBITS_ADDRESS + NBITS_COMMAND + NBITS_CHANNEL - 1; + if (src.expect_space(3 * BIT_TIME_US) && (src.expect_mark(BIT_TIME_US) || src.peek_mark(2 * BIT_TIME_US))) { + out_data |= 0 << bit; + } else if (src.expect_space(2 * BIT_TIME_US) && src.expect_mark(BIT_TIME_US) && + (src.expect_space(BIT_TIME_US) || src.peek_space(2 * BIT_TIME_US))) { + out_data |= 1 << bit; + } else { + ESP_LOGV(TAG, "Decode Drayton: Fail 1, - %d", src.get_index()); + return {}; + } + + // Before/after each bit is read the index points to the transition at the start of the bit period or, + // if there is no transition at the start of the bit period, then the transition in the middle of + // the previous bit period. + while (--bit >= 1) { + ESP_LOGVV(TAG, "Decode Drayton: Data, %2d %08x", bit, out_data); + if ((src.expect_space(BIT_TIME_US) || src.expect_space(2 * BIT_TIME_US)) && + (src.expect_mark(BIT_TIME_US) || src.peek_mark(2 * BIT_TIME_US))) { + out_data |= 0 << bit; + } else if ((src.expect_mark(BIT_TIME_US) || src.expect_mark(2 * BIT_TIME_US)) && + (src.expect_space(BIT_TIME_US) || src.peek_space(2 * BIT_TIME_US))) { + out_data |= 1 << bit; + } else { + ESP_LOGVV(TAG, "Decode Drayton: Fail 2, %2d %08x", bit, out_data); + return {}; + } + } + if (src.expect_space(BIT_TIME_US) || src.expect_space(2 * BIT_TIME_US)) { + out_data |= 0; + } else if (src.expect_mark(BIT_TIME_US) || src.expect_mark(2 * BIT_TIME_US)) { + out_data |= 1; + } + ESP_LOGV(TAG, "Decode Drayton: Data, %2d %08x", bit, out_data); + + out.channel = (uint8_t) (out_data & 0x1F); + out_data >>= NBITS_CHANNEL; + out.command = (uint8_t) (out_data & 0x7F); + out_data >>= NBITS_COMMAND; + out.address = (uint16_t) (out_data & 0xFFFF); + + return out; +} +void DraytonProtocol::dump(const DraytonData &data) { + ESP_LOGD(TAG, "Received Drayton: address=0x%04X (0x%04x), channel=0x%03x command=0x%03X", data.address, + ((data.address << 1) & 0xffff), data.channel, data.command); +} + +} // namespace remote_base +} // namespace esphome diff --git a/esphome/components/remote_base/drayton_protocol.h b/esphome/components/remote_base/drayton_protocol.h new file mode 100644 index 0000000000..f468e7b57e --- /dev/null +++ b/esphome/components/remote_base/drayton_protocol.h @@ -0,0 +1,44 @@ +#pragma once + +#include "esphome/core/component.h" +#include "remote_base.h" + +namespace esphome { +namespace remote_base { + +struct DraytonData { + uint16_t address; + uint8_t channel; + uint8_t command; + + bool operator==(const DraytonData &rhs) const { + return address == rhs.address && channel == rhs.channel && command == rhs.command; + } +}; + +class DraytonProtocol : public RemoteProtocol { + public: + void encode(RemoteTransmitData *dst, const DraytonData &data) override; + optional decode(RemoteReceiveData src) override; + void dump(const DraytonData &data) override; +}; + +DECLARE_REMOTE_PROTOCOL(Drayton) + +template class DraytonAction : public RemoteTransmitterActionBase { + public: + TEMPLATABLE_VALUE(uint16_t, address) + TEMPLATABLE_VALUE(uint8_t, channel) + TEMPLATABLE_VALUE(uint8_t, command) + + void encode(RemoteTransmitData *dst, Ts... x) override { + DraytonData data{}; + data.address = this->address_.value(x...); + data.channel = this->channel_.value(x...); + data.command = this->command_.value(x...); + DraytonProtocol().encode(dst, data); + } +}; + +} // namespace remote_base +} // namespace esphome From c97d361b6c3445420b44ba37698eb749515f104f Mon Sep 17 00:00:00 2001 From: Philippe FOUQUET Date: Mon, 1 May 2023 06:18:31 +0200 Subject: [PATCH 46/71] Add support for hyt271 (#4282) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- CODEOWNERS | 1 + esphome/components/hyt271/__init__.py | 1 + esphome/components/hyt271/hyt271.cpp | 52 +++++++++++++++++++++++++ esphome/components/hyt271/hyt271.h | 27 +++++++++++++ esphome/components/hyt271/sensor.py | 56 +++++++++++++++++++++++++++ tests/test1.yaml | 7 ++++ 6 files changed, 144 insertions(+) create mode 100644 esphome/components/hyt271/__init__.py create mode 100644 esphome/components/hyt271/hyt271.cpp create mode 100644 esphome/components/hyt271/hyt271.h create mode 100644 esphome/components/hyt271/sensor.py diff --git a/CODEOWNERS b/CODEOWNERS index 5a8ef76c44..d71232ea07 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -110,6 +110,7 @@ esphome/components/honeywellabp/* @RubyBailey esphome/components/hrxl_maxsonar_wr/* @netmikey esphome/components/hte501/* @Stock-M esphome/components/hydreon_rgxx/* @functionpointer +esphome/components/hyt271/* @Philippe12 esphome/components/i2c/* @esphome/core esphome/components/i2s_audio/* @jesserockz esphome/components/i2s_audio/media_player/* @jesserockz diff --git a/esphome/components/hyt271/__init__.py b/esphome/components/hyt271/__init__.py new file mode 100644 index 0000000000..2e88d4f366 --- /dev/null +++ b/esphome/components/hyt271/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@Philippe12"] diff --git a/esphome/components/hyt271/hyt271.cpp b/esphome/components/hyt271/hyt271.cpp new file mode 100644 index 0000000000..94558fff04 --- /dev/null +++ b/esphome/components/hyt271/hyt271.cpp @@ -0,0 +1,52 @@ +#include "hyt271.h" +#include "esphome/core/log.h" +#include "esphome/core/hal.h" + +namespace esphome { +namespace hyt271 { + +static const char *const TAG = "hyt271"; + +static const uint8_t HYT271_ADDRESS = 0x28; + +void HYT271Component::dump_config() { + ESP_LOGCONFIG(TAG, "HYT271:"); + LOG_I2C_DEVICE(this); + LOG_UPDATE_INTERVAL(this); + LOG_SENSOR(" ", "Temperature", this->temperature_); + LOG_SENSOR(" ", "Humidity", this->humidity_); +} +void HYT271Component::update() { + uint8_t raw_data[4]; + + if (this->write(&raw_data[0], 0) != i2c::ERROR_OK) { + this->status_set_warning(); + ESP_LOGE(TAG, "Communication with HYT271 failed! => Ask new values"); + return; + } + this->set_timeout("wait_convert", 50, [this]() { + uint8_t raw_data[4]; + if (this->read(raw_data, 4) != i2c::ERROR_OK) { + this->status_set_warning(); + ESP_LOGE(TAG, "Communication with HYT271 failed! => Read values"); + return; + } + uint16_t raw_temperature = ((raw_data[2] << 8) | raw_data[3]) >> 2; + uint16_t raw_humidity = ((raw_data[0] & 0x3F) << 8) | raw_data[1]; + + float temperature = ((float(raw_temperature)) * (165.0f / 16383.0f)) - 40.0f; + float humidity = (float(raw_humidity)) * (100.0f / 16383.0f); + + ESP_LOGD(TAG, "Got Temperature=%.1f°C Humidity=%.1f%%", temperature, humidity); + + if (this->temperature_ != nullptr) + this->temperature_->publish_state(temperature); + if (this->humidity_ != nullptr) + this->humidity_->publish_state(humidity); + this->status_clear_warning(); + }); +} +float HYT271Component::get_setup_priority() const { return setup_priority::DATA; } + +} // namespace hyt271 +} // namespace esphome diff --git a/esphome/components/hyt271/hyt271.h b/esphome/components/hyt271/hyt271.h new file mode 100644 index 0000000000..64f32a651c --- /dev/null +++ b/esphome/components/hyt271/hyt271.h @@ -0,0 +1,27 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/i2c/i2c.h" + +namespace esphome { +namespace hyt271 { + +class HYT271Component : public PollingComponent, public i2c::I2CDevice { + public: + void set_temperature(sensor::Sensor *temperature) { temperature_ = temperature; } + void set_humidity(sensor::Sensor *humidity) { humidity_ = humidity; } + + void dump_config() override; + /// Update the sensor values (temperature+humidity). + void update() override; + + float get_setup_priority() const override; + + protected: + sensor::Sensor *temperature_{nullptr}; + sensor::Sensor *humidity_{nullptr}; +}; + +} // namespace hyt271 +} // namespace esphome diff --git a/esphome/components/hyt271/sensor.py b/esphome/components/hyt271/sensor.py new file mode 100644 index 0000000000..2ec2836461 --- /dev/null +++ b/esphome/components/hyt271/sensor.py @@ -0,0 +1,56 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import i2c, sensor +from esphome.const import ( + CONF_HUMIDITY, + CONF_ID, + CONF_TEMPERATURE, + DEVICE_CLASS_HUMIDITY, + DEVICE_CLASS_TEMPERATURE, + STATE_CLASS_MEASUREMENT, + UNIT_CELSIUS, + UNIT_PERCENT, +) + +DEPENDENCIES = ["i2c"] + +hyt271_ns = cg.esphome_ns.namespace("hyt271") +HYT271Component = hyt271_ns.class_( + "HYT271Component", cg.PollingComponent, i2c.I2CDevice +) + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(HYT271Component), + cv.Required(CONF_TEMPERATURE): sensor.sensor_schema( + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=1, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Required(CONF_HUMIDITY): sensor.sensor_schema( + unit_of_measurement=UNIT_PERCENT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_HUMIDITY, + state_class=STATE_CLASS_MEASUREMENT, + ), + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x28)) +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await i2c.register_i2c_device(var, config) + + if CONF_TEMPERATURE in config: + sens = await sensor.new_sensor(config[CONF_TEMPERATURE]) + cg.add(var.set_temperature(sens)) + + if CONF_HUMIDITY in config: + sens = await sensor.new_sensor(config[CONF_HUMIDITY]) + cg.add(var.set_humidity(sens)) diff --git a/tests/test1.yaml b/tests/test1.yaml index c5cca7aa59..56b7c8595a 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -1276,6 +1276,13 @@ sensor: name: DHT Absolute Humidity temperature: dht_temperature humidity: dht_humidity + - platform: hyt271 + i2c_id: i2c_bus + temperature: + name: "Temperature hyt271" + id: temp_etuve + humidity: + name: "Humidity hyt271" esp32_touch: setup_mode: false From 1c4af08ed359b29231315fed814f5018bdffa0f7 Mon Sep 17 00:00:00 2001 From: Mat931 <49403702+Mat931@users.noreply.github.com> Date: Mon, 1 May 2023 21:25:10 +0000 Subject: [PATCH 47/71] Add support for BLE passkey authentication (#4258) Co-authored-by: Branden Cash <203336+ammmze@users.noreply.github.com> --- esphome/components/ble_client/__init__.py | 132 ++++++++++++++++++++- esphome/components/ble_client/automation.h | 118 ++++++++++++++++++ esphome/components/ble_client/ble_client.h | 2 +- esphome/components/esp32_ble/__init__.py | 13 ++ esphome/components/esp32_ble/ble.cpp | 25 +++- esphome/components/esp32_ble/ble.h | 11 ++ tests/test1.yaml | 16 +++ 7 files changed, 312 insertions(+), 5 deletions(-) diff --git a/esphome/components/ble_client/__init__.py b/esphome/components/ble_client/__init__.py index 03e8f0b0b2..8f70ad3417 100644 --- a/esphome/components/ble_client/__init__.py +++ b/esphome/components/ble_client/__init__.py @@ -29,8 +29,35 @@ BLEClientConnectTrigger = ble_client_ns.class_( BLEClientDisconnectTrigger = ble_client_ns.class_( "BLEClientDisconnectTrigger", automation.Trigger.template(BLEClientNodeConstRef) ) +BLEClientPasskeyRequestTrigger = ble_client_ns.class_( + "BLEClientPasskeyRequestTrigger", automation.Trigger.template(BLEClientNodeConstRef) +) +BLEClientPasskeyNotificationTrigger = ble_client_ns.class_( + "BLEClientPasskeyNotificationTrigger", + automation.Trigger.template(BLEClientNodeConstRef, cg.uint32), +) +BLEClientNumericComparisonRequestTrigger = ble_client_ns.class_( + "BLEClientNumericComparisonRequestTrigger", + automation.Trigger.template(BLEClientNodeConstRef, cg.uint32), +) + # Actions BLEWriteAction = ble_client_ns.class_("BLEClientWriteAction", automation.Action) +BLEPasskeyReplyAction = ble_client_ns.class_( + "BLEClientPasskeyReplyAction", automation.Action +) +BLENumericComparisonReplyAction = ble_client_ns.class_( + "BLEClientNumericComparisonReplyAction", automation.Action +) +BLERemoveBondAction = ble_client_ns.class_( + "BLEClientRemoveBondAction", automation.Action +) + +CONF_PASSKEY = "passkey" +CONF_ACCEPT = "accept" +CONF_ON_PASSKEY_REQUEST = "on_passkey_request" +CONF_ON_PASSKEY_NOTIFICATION = "on_passkey_notification" +CONF_ON_NUMERIC_COMPARISON_REQUEST = "on_numeric_comparison_request" # Espressif platformio framework is built with MAX_BLE_CONN to 3, so # enforce this in yaml checks. @@ -56,6 +83,29 @@ CONFIG_SCHEMA = ( ), } ), + cv.Optional(CONF_ON_PASSKEY_REQUEST): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( + BLEClientPasskeyRequestTrigger + ), + } + ), + cv.Optional(CONF_ON_PASSKEY_NOTIFICATION): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( + BLEClientPasskeyNotificationTrigger + ), + } + ), + cv.Optional( + CONF_ON_NUMERIC_COMPARISON_REQUEST + ): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( + BLEClientNumericComparisonRequestTrigger + ), + } + ), } ) .extend(cv.COMPONENT_SCHEMA) @@ -85,13 +135,34 @@ BLE_WRITE_ACTION_SCHEMA = cv.Schema( } ) +BLE_NUMERIC_COMPARISON_REPLY_ACTION_SCHEMA = cv.Schema( + { + cv.GenerateID(CONF_ID): cv.use_id(BLEClient), + cv.Required(CONF_ACCEPT): cv.templatable(cv.boolean), + } +) + +BLE_PASSKEY_REPLY_ACTION_SCHEMA = cv.Schema( + { + cv.GenerateID(CONF_ID): cv.use_id(BLEClient), + cv.Required(CONF_PASSKEY): cv.templatable(cv.int_range(min=0, max=999999)), + } +) + + +BLE_REMOVE_BOND_ACTION_SCHEMA = cv.Schema( + { + cv.GenerateID(CONF_ID): cv.use_id(BLEClient), + } +) + @automation.register_action( "ble_client.ble_write", BLEWriteAction, BLE_WRITE_ACTION_SCHEMA ) async def ble_write_to_code(config, action_id, template_arg, args): - paren = await cg.get_variable(config[CONF_ID]) - var = cg.new_Pvariable(action_id, template_arg, paren) + parent = await cg.get_variable(config[CONF_ID]) + var = cg.new_Pvariable(action_id, template_arg, parent) value = config[CONF_VALUE] if cg.is_template(value): @@ -137,6 +208,54 @@ async def ble_write_to_code(config, action_id, template_arg, args): return var +@automation.register_action( + "ble_client.numeric_comparison_reply", + BLENumericComparisonReplyAction, + BLE_NUMERIC_COMPARISON_REPLY_ACTION_SCHEMA, +) +async def numeric_comparison_reply_to_code(config, action_id, template_arg, args): + parent = await cg.get_variable(config[CONF_ID]) + var = cg.new_Pvariable(action_id, template_arg, parent) + + accept = config[CONF_ACCEPT] + if cg.is_template(accept): + templ = await cg.templatable(accept, args, cg.bool_) + cg.add(var.set_value_template(templ)) + else: + cg.add(var.set_value_simple(accept)) + + return var + + +@automation.register_action( + "ble_client.passkey_reply", BLEPasskeyReplyAction, BLE_PASSKEY_REPLY_ACTION_SCHEMA +) +async def passkey_reply_to_code(config, action_id, template_arg, args): + parent = await cg.get_variable(config[CONF_ID]) + var = cg.new_Pvariable(action_id, template_arg, parent) + + passkey = config[CONF_PASSKEY] + if cg.is_template(passkey): + templ = await cg.templatable(passkey, args, cg.uint32) + cg.add(var.set_value_template(templ)) + else: + cg.add(var.set_value_simple(passkey)) + + return var + + +@automation.register_action( + "ble_client.remove_bond", + BLERemoveBondAction, + BLE_REMOVE_BOND_ACTION_SCHEMA, +) +async def remove_bond_to_code(config, action_id, template_arg, args): + parent = await cg.get_variable(config[CONF_ID]) + var = cg.new_Pvariable(action_id, template_arg, parent) + + return var + + async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) @@ -148,3 +267,12 @@ async def to_code(config): for conf in config.get(CONF_ON_DISCONNECT, []): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) await automation.build_automation(trigger, [], conf) + for conf in config.get(CONF_ON_PASSKEY_REQUEST, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [], conf) + for conf in config.get(CONF_ON_PASSKEY_NOTIFICATION, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [(cg.uint32, "passkey")], conf) + for conf in config.get(CONF_ON_NUMERIC_COMPARISON_REQUEST, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [(cg.uint32, "passkey")], conf) diff --git a/esphome/components/ble_client/automation.h b/esphome/components/ble_client/automation.h index 45ddba9782..423f74b85a 100644 --- a/esphome/components/ble_client/automation.h +++ b/esphome/components/ble_client/automation.h @@ -37,6 +37,44 @@ class BLEClientDisconnectTrigger : public Trigger<>, public BLEClientNode { } }; +class BLEClientPasskeyRequestTrigger : public Trigger<>, public BLEClientNode { + public: + explicit BLEClientPasskeyRequestTrigger(BLEClient *parent) { parent->register_ble_node(this); } + void loop() override {} + void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override { + if (event == ESP_GAP_BLE_PASSKEY_REQ_EVT && + memcmp(param->ble_security.auth_cmpl.bd_addr, this->parent_->get_remote_bda(), 6) == 0) { + this->trigger(); + } + } +}; + +class BLEClientPasskeyNotificationTrigger : public Trigger, public BLEClientNode { + public: + explicit BLEClientPasskeyNotificationTrigger(BLEClient *parent) { parent->register_ble_node(this); } + void loop() override {} + void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override { + if (event == ESP_GAP_BLE_PASSKEY_NOTIF_EVT && + memcmp(param->ble_security.auth_cmpl.bd_addr, this->parent_->get_remote_bda(), 6) == 0) { + uint32_t passkey = param->ble_security.key_notif.passkey; + this->trigger(passkey); + } + } +}; + +class BLEClientNumericComparisonRequestTrigger : public Trigger, public BLEClientNode { + public: + explicit BLEClientNumericComparisonRequestTrigger(BLEClient *parent) { parent->register_ble_node(this); } + void loop() override {} + void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override { + if (event == ESP_GAP_BLE_NC_REQ_EVT && + memcmp(param->ble_security.auth_cmpl.bd_addr, this->parent_->get_remote_bda(), 6) == 0) { + uint32_t passkey = param->ble_security.key_notif.passkey; + this->trigger(passkey); + } + } +}; + class BLEWriterClientNode : public BLEClientNode { public: BLEWriterClientNode(BLEClient *ble_client) { @@ -94,6 +132,86 @@ template class BLEClientWriteAction : public Action, publ std::function(Ts...)> value_template_{}; }; +template class BLEClientPasskeyReplyAction : public Action { + public: + BLEClientPasskeyReplyAction(BLEClient *ble_client) { parent_ = ble_client; } + + void play(Ts... x) override { + uint32_t passkey; + if (has_simple_value_) { + passkey = this->value_simple_; + } else { + passkey = this->value_template_(x...); + } + if (passkey > 999999) + return; + esp_bd_addr_t remote_bda; + memcpy(remote_bda, parent_->get_remote_bda(), sizeof(esp_bd_addr_t)); + esp_ble_passkey_reply(remote_bda, true, passkey); + } + + void set_value_template(std::function func) { + this->value_template_ = std::move(func); + has_simple_value_ = false; + } + + void set_value_simple(const uint32_t &value) { + this->value_simple_ = value; + has_simple_value_ = true; + } + + private: + BLEClient *parent_{nullptr}; + bool has_simple_value_ = true; + uint32_t value_simple_{0}; + std::function value_template_{}; +}; + +template class BLEClientNumericComparisonReplyAction : public Action { + public: + BLEClientNumericComparisonReplyAction(BLEClient *ble_client) { parent_ = ble_client; } + + void play(Ts... x) override { + esp_bd_addr_t remote_bda; + memcpy(remote_bda, parent_->get_remote_bda(), sizeof(esp_bd_addr_t)); + if (has_simple_value_) { + esp_ble_confirm_reply(remote_bda, this->value_simple_); + } else { + esp_ble_confirm_reply(remote_bda, this->value_template_(x...)); + } + } + + void set_value_template(std::function func) { + this->value_template_ = std::move(func); + has_simple_value_ = false; + } + + void set_value_simple(const bool &value) { + this->value_simple_ = value; + has_simple_value_ = true; + } + + private: + BLEClient *parent_{nullptr}; + bool has_simple_value_ = true; + bool value_simple_{false}; + std::function value_template_{}; +}; + +template class BLEClientRemoveBondAction : public Action { + public: + BLEClientRemoveBondAction(BLEClient *ble_client) { parent_ = ble_client; } + + void play(Ts... x) override { + esp_bd_addr_t remote_bda; + memcpy(remote_bda, parent_->get_remote_bda(), sizeof(esp_bd_addr_t)); + esp_ble_remove_bond_device(remote_bda); + } + + private: + BLEClient *parent_{nullptr}; +}; + } // namespace ble_client } // namespace esphome diff --git a/esphome/components/ble_client/ble_client.h b/esphome/components/ble_client/ble_client.h index ceca94c86a..e04f4a8042 100644 --- a/esphome/components/ble_client/ble_client.h +++ b/esphome/components/ble_client/ble_client.h @@ -27,7 +27,7 @@ class BLEClient; class BLEClientNode { public: virtual void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, - esp_ble_gattc_cb_param_t *param) = 0; + esp_ble_gattc_cb_param_t *param){}; virtual void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) {} virtual void loop() {} void set_address(uint64_t address) { address_ = address; } diff --git a/esphome/components/esp32_ble/__init__.py b/esphome/components/esp32_ble/__init__.py index 7db6fff6b9..f508cecb87 100644 --- a/esphome/components/esp32_ble/__init__.py +++ b/esphome/components/esp32_ble/__init__.py @@ -9,6 +9,7 @@ CODEOWNERS = ["@jesserockz"] CONFLICTS_WITH = ["esp32_ble_beacon"] CONF_BLE_ID = "ble_id" +CONF_IO_CAPABILITY = "io_capability" NO_BLUETOOTH_VARIANTS = [const.VARIANT_ESP32S2] @@ -19,10 +20,21 @@ GAPEventHandler = esp32_ble_ns.class_("GAPEventHandler") GATTcEventHandler = esp32_ble_ns.class_("GATTcEventHandler") GATTsEventHandler = esp32_ble_ns.class_("GATTsEventHandler") +IoCapability = esp32_ble_ns.enum("IoCapability") +IO_CAPABILITY = { + "none": IoCapability.IO_CAP_NONE, + "keyboard_only": IoCapability.IO_CAP_IN, + "keyboard_display": IoCapability.IO_CAP_KBDISP, + "display_only": IoCapability.IO_CAP_OUT, + "display_yes_no": IoCapability.IO_CAP_IO, +} CONFIG_SCHEMA = cv.Schema( { cv.GenerateID(): cv.declare_id(ESP32BLE), + cv.Optional(CONF_IO_CAPABILITY, default="none"): cv.enum( + IO_CAPABILITY, lower=True + ), } ).extend(cv.COMPONENT_SCHEMA) @@ -39,6 +51,7 @@ FINAL_VALIDATE_SCHEMA = validate_variant async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) + cg.add(var.set_io_capability(config[CONF_IO_CAPABILITY])) if CORE.using_esp_idf: add_idf_sdkconfig_option("CONFIG_BT_ENABLED", True) diff --git a/esphome/components/esp32_ble/ble.cpp b/esphome/components/esp32_ble/ble.cpp index 502399f97a..21ec005e07 100644 --- a/esphome/components/esp32_ble/ble.cpp +++ b/esphome/components/esp32_ble/ble.cpp @@ -134,8 +134,7 @@ bool ESP32BLE::ble_setup_() { return false; } - esp_ble_io_cap_t iocap = ESP_IO_CAP_NONE; - err = esp_ble_gap_set_security_param(ESP_BLE_SM_IOCAP_MODE, &iocap, sizeof(uint8_t)); + err = esp_ble_gap_set_security_param(ESP_BLE_SM_IOCAP_MODE, &(this->io_cap_), sizeof(uint8_t)); if (err != ESP_OK) { ESP_LOGE(TAG, "esp_ble_gap_set_security_param failed: %d", err); return false; @@ -215,9 +214,31 @@ float ESP32BLE::get_setup_priority() const { return setup_priority::BLUETOOTH; } void ESP32BLE::dump_config() { const uint8_t *mac_address = esp_bt_dev_get_address(); if (mac_address) { + const char *io_capability_s; + switch (this->io_cap_) { + case ESP_IO_CAP_OUT: + io_capability_s = "display_only"; + break; + case ESP_IO_CAP_IO: + io_capability_s = "display_yes_no"; + break; + case ESP_IO_CAP_IN: + io_capability_s = "keyboard_only"; + break; + case ESP_IO_CAP_NONE: + io_capability_s = "none"; + break; + case ESP_IO_CAP_KBDISP: + io_capability_s = "keyboard_display"; + break; + default: + io_capability_s = "invalid"; + break; + } ESP_LOGCONFIG(TAG, "ESP32 BLE:"); ESP_LOGCONFIG(TAG, " MAC address: %02X:%02X:%02X:%02X:%02X:%02X", mac_address[0], mac_address[1], mac_address[2], mac_address[3], mac_address[4], mac_address[5]); + ESP_LOGCONFIG(TAG, " IO Capability: %s", io_capability_s); } else { ESP_LOGCONFIG(TAG, "ESP32 BLE: bluetooth stack is not enabled"); } diff --git a/esphome/components/esp32_ble/ble.h b/esphome/components/esp32_ble/ble.h index 5970b43688..11ae826544 100644 --- a/esphome/components/esp32_ble/ble.h +++ b/esphome/components/esp32_ble/ble.h @@ -25,6 +25,14 @@ typedef struct { uint16_t mtu; } conn_status_t; +enum IoCapability { + IO_CAP_OUT = ESP_IO_CAP_OUT, + IO_CAP_IO = ESP_IO_CAP_IO, + IO_CAP_IN = ESP_IO_CAP_IN, + IO_CAP_NONE = ESP_IO_CAP_NONE, + IO_CAP_KBDISP = ESP_IO_CAP_KBDISP, +}; + class GAPEventHandler { public: virtual void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) = 0; @@ -44,6 +52,8 @@ class GATTsEventHandler { class ESP32BLE : public Component { public: + void set_io_capability(IoCapability io_capability) { this->io_cap_ = (esp_ble_io_cap_t) io_capability; } + void setup() override; void loop() override; void dump_config() override; @@ -72,6 +82,7 @@ class ESP32BLE : public Component { Queue ble_events_; BLEAdvertising *advertising_; + esp_ble_io_cap_t io_cap_{ESP_IO_CAP_NONE}; }; // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) diff --git a/tests/test1.yaml b/tests/test1.yaml index 56b7c8595a..46c6bb80c6 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -294,6 +294,9 @@ wled: adalight: +esp32_ble: + io_capability: keyboard_only + esp32_ble_tracker: ble_client: @@ -307,6 +310,19 @@ ble_client: on_disconnect: then: - switch.turn_on: ble1_status + on_passkey_request: + then: + - ble_client.passkey_reply: + id: ble_blah + passkey: 123456 + on_passkey_notification: + then: + - logger.log: "Passkey notification received" + on_numeric_comparison_request: + then: + - ble_client.numeric_comparison_reply: + id: ble_blah + accept: True - mac_address: C4:4F:33:11:22:33 id: my_bedjet_ble_client bedjet: From bd6d6caa8a1a4f41c873814c8f172545be3aa4de Mon Sep 17 00:00:00 2001 From: cooki35 <33752572+cooki35@users.noreply.github.com> Date: Mon, 1 May 2023 23:36:20 +0200 Subject: [PATCH 48/71] Add support for V2 of the waveshare 5.83in e-paper display. (#3660) --- .../components/waveshare_epaper/display.py | 4 + .../waveshare_epaper/waveshare_epaper.cpp | 82 +++++++++++++++++++ .../waveshare_epaper/waveshare_epaper.h | 43 ++++++++++ 3 files changed, 129 insertions(+) diff --git a/esphome/components/waveshare_epaper/display.py b/esphome/components/waveshare_epaper/display.py index 747794b631..d0276f119a 100644 --- a/esphome/components/waveshare_epaper/display.py +++ b/esphome/components/waveshare_epaper/display.py @@ -39,6 +39,9 @@ WaveshareEPaper4P2InBV2 = waveshare_epaper_ns.class_( WaveshareEPaper5P8In = waveshare_epaper_ns.class_( "WaveshareEPaper5P8In", WaveshareEPaper ) +WaveshareEPaper5P8InV2 = waveshare_epaper_ns.class_( + "WaveshareEPaper5P8InV2", WaveshareEPaper +) WaveshareEPaper7P5In = waveshare_epaper_ns.class_( "WaveshareEPaper7P5In", WaveshareEPaper ) @@ -80,6 +83,7 @@ MODELS = { "4.20in": ("b", WaveshareEPaper4P2In), "4.20in-bv2": ("b", WaveshareEPaper4P2InBV2), "5.83in": ("b", WaveshareEPaper5P8In), + "5.83inv2": ("b", WaveshareEPaper5P8InV2), "7.50in": ("b", WaveshareEPaper7P5In), "7.50in-bv2": ("b", WaveshareEPaper7P5InBV2), "7.50in-bc": ("b", WaveshareEPaper7P5InBC), diff --git a/esphome/components/waveshare_epaper/waveshare_epaper.cpp b/esphome/components/waveshare_epaper/waveshare_epaper.cpp index 8c4b137514..42f5bc54e3 100644 --- a/esphome/components/waveshare_epaper/waveshare_epaper.cpp +++ b/esphome/components/waveshare_epaper/waveshare_epaper.cpp @@ -1037,6 +1037,88 @@ void WaveshareEPaper5P8In::dump_config() { LOG_PIN(" Busy Pin: ", this->busy_pin_); LOG_UPDATE_INTERVAL(this); } + +// ======================================================== +// 5.83in V2 +// Datasheet/Specification/Reference: +// - https://www.waveshare.com/w/upload/3/37/5.83inch_e-Paper_V2_Specification.pdf +// - https://github.com/waveshare/e-Paper/blob/master/Arduino/epd5in83_V2/epd5in83_V2.cpp +// ======================================================== +void WaveshareEPaper5P8InV2::initialize() { + // COMMAND POWER SETTING + this->command(0x01); + this->data(0x07); + this->data(0x07); + this->data(0x3f); + this->data(0x3f); + + // COMMAND POWER ON + this->command(0x04); + delay(10); + this->wait_until_idle_(); + + // PANNEL SETTING + this->command(0x00); + this->data(0x1F); + + // COMMAND RESOLUTION SETTING + this->command(0x61); + this->data(0x02); + this->data(0x88); + this->data(0x01); + this->data(0xE0); + + this->command(0x15); + this->data(0x00); + + // COMMAND TCON SETTING + this->command(0x60); + this->data(0x22); + + // Do we need this? + // COMMAND PLL CONTROL + this->command(0x30); + this->data(0x3C); // 3A 100HZ 29 150Hz 39 200HZ 31 171HZ +} +void HOT WaveshareEPaper5P8InV2::display() { + // Reuse the code from WaveshareEPaper4P2In::display() + // COMMAND VCM DC SETTING REGISTER + this->command(0x82); + this->data(0x12); + + // COMMAND VCOM AND DATA INTERVAL SETTING + this->command(0x50); + this->data(0x97); + + // COMMAND DATA START TRANSMISSION 1 + this->command(0x10); + delay(2); + this->start_data_(); + this->write_array(this->buffer_, this->get_buffer_length_()); + this->end_data_(); + delay(2); + + // COMMAND DATA START TRANSMISSION 2 + this->command(0x13); + delay(2); + this->start_data_(); + this->write_array(this->buffer_, this->get_buffer_length_()); + this->end_data_(); + + // COMMAND DISPLAY REFRESH + this->command(0x12); +} +int WaveshareEPaper5P8InV2::get_width_internal() { return 648; } +int WaveshareEPaper5P8InV2::get_height_internal() { return 480; } +void WaveshareEPaper5P8InV2::dump_config() { + LOG_DISPLAY("", "Waveshare E-Paper", this); + ESP_LOGCONFIG(TAG, " Model: 5.83inv2"); + LOG_PIN(" Reset Pin: ", this->reset_pin_); + LOG_PIN(" DC Pin: ", this->dc_pin_); + LOG_PIN(" Busy Pin: ", this->busy_pin_); + LOG_UPDATE_INTERVAL(this); +} + void WaveshareEPaper7P5InBV2::initialize() { // COMMAND POWER SETTING this->command(0x01); diff --git a/esphome/components/waveshare_epaper/waveshare_epaper.h b/esphome/components/waveshare_epaper/waveshare_epaper.h index a674d3af0c..1cb46bdb9d 100644 --- a/esphome/components/waveshare_epaper/waveshare_epaper.h +++ b/esphome/components/waveshare_epaper/waveshare_epaper.h @@ -284,6 +284,49 @@ class WaveshareEPaper5P8In : public WaveshareEPaper { int get_height_internal() override; }; +class WaveshareEPaper5P8InV2 : public WaveshareEPaper { + public: + void initialize() override; + + void display() override; + + void dump_config() override; + + void deep_sleep() override { + // COMMAND VCOM AND DATA INTERVAL SETTING + this->command(0x50); + this->data(0x17); // border floating + + // COMMAND VCM DC SETTING + this->command(0x82); + // COMMAND PANEL SETTING + this->command(0x00); + + delay(100); // NOLINT + + // COMMAND POWER SETTING + this->command(0x01); + this->data(0x00); + this->data(0x00); + this->data(0x00); + this->data(0x00); + this->data(0x00); + delay(100); // NOLINT + + // COMMAND POWER OFF + this->command(0x02); + this->wait_until_idle_(); + // COMMAND DEEP SLEEP + this->command(0x07); + this->data(0xA5); // check byte + } + + protected: + int get_width_internal() override; + + int get_height_internal() override; +}; + class WaveshareEPaper7P5In : public WaveshareEPaper { public: void initialize() override; From de10b356cfc7a26f7dca73bec25989cea4428e6f Mon Sep 17 00:00:00 2001 From: looping40 <36304961+looping40@users.noreply.github.com> Date: Mon, 1 May 2023 23:51:48 +0200 Subject: [PATCH 49/71] Max6956 support added (#3764) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- CODEOWNERS | 1 + esphome/components/max6956/__init__.py | 148 +++++++++++++++ esphome/components/max6956/automation.h | 40 +++++ esphome/components/max6956/max6956.cpp | 170 ++++++++++++++++++ esphome/components/max6956/max6956.h | 94 ++++++++++ esphome/components/max6956/output/__init__.py | 28 +++ .../max6956/output/max6956_led_output.cpp | 26 +++ .../max6956/output/max6956_led_output.h | 28 +++ tests/test4.yaml | 14 ++ 9 files changed, 549 insertions(+) create mode 100644 esphome/components/max6956/__init__.py create mode 100644 esphome/components/max6956/automation.h create mode 100644 esphome/components/max6956/max6956.cpp create mode 100644 esphome/components/max6956/max6956.h create mode 100644 esphome/components/max6956/output/__init__.py create mode 100644 esphome/components/max6956/output/max6956_led_output.cpp create mode 100644 esphome/components/max6956/output/max6956_led_output.h diff --git a/CODEOWNERS b/CODEOWNERS index d71232ea07..3032e7dd88 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -140,6 +140,7 @@ esphome/components/ltr390/* @sjtrny esphome/components/matrix_keypad/* @ssieb esphome/components/max31865/* @DAVe3283 esphome/components/max44009/* @berfenger +esphome/components/max6956/* @looping40 esphome/components/max7219digit/* @rspaargaren esphome/components/max9611/* @mckaymatthew esphome/components/mcp23008/* @jesserockz diff --git a/esphome/components/max6956/__init__.py b/esphome/components/max6956/__init__.py new file mode 100644 index 0000000000..77e0d37e76 --- /dev/null +++ b/esphome/components/max6956/__init__.py @@ -0,0 +1,148 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome import pins, automation +from esphome.components import i2c +from esphome.const import ( + CONF_ID, + CONF_NUMBER, + CONF_MODE, + CONF_INVERTED, + CONF_INPUT, + CONF_OUTPUT, + CONF_PULLUP, +) + +CODEOWNERS = ["@looping40"] + +DEPENDENCIES = ["i2c"] +MULTI_CONF = True + +CONF_BRIGHTNESS_MODE = "brightness_mode" +CONF_BRIGHTNESS_GLOBAL = "brightness_global" + + +max6956_ns = cg.esphome_ns.namespace("max6956") + +MAX6956 = max6956_ns.class_("MAX6956", cg.Component, i2c.I2CDevice) +MAX6956GPIOPin = max6956_ns.class_("MAX6956GPIOPin", cg.GPIOPin) + +# Actions +SetCurrentGlobalAction = max6956_ns.class_("SetCurrentGlobalAction", automation.Action) +SetCurrentModeAction = max6956_ns.class_("SetCurrentModeAction", automation.Action) + +MAX6956_CURRENTMODE = max6956_ns.enum("MAX6956CURRENTMODE") +CURRENT_MODES = { + "global": MAX6956_CURRENTMODE.GLOBAL, + "segment": MAX6956_CURRENTMODE.SEGMENT, +} + + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.Required(CONF_ID): cv.declare_id(MAX6956), + cv.Optional(CONF_BRIGHTNESS_GLOBAL, default="0"): cv.int_range( + min=0, max=15 + ), + cv.Optional(CONF_BRIGHTNESS_MODE, default="global"): cv.enum( + CURRENT_MODES, lower=True + ), + } + ) + .extend(cv.COMPONENT_SCHEMA) + .extend(i2c.i2c_device_schema(0x40)) +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await i2c.register_i2c_device(var, config) + cg.add(var.set_brightness_mode(config[CONF_BRIGHTNESS_MODE])) + cg.add(var.set_brightness_global(config[CONF_BRIGHTNESS_GLOBAL])) + + +def validate_mode(value): + if not (value[CONF_INPUT] or value[CONF_OUTPUT]): + raise cv.Invalid("Mode must be either input or output") + if value[CONF_INPUT] and value[CONF_OUTPUT]: + raise cv.Invalid("Mode must be either input or output") + if value[CONF_PULLUP] and not value[CONF_INPUT]: + raise cv.Invalid("Pullup only available with input") + return value + + +CONF_MAX6956 = "max6956" + +MAX6956_PIN_SCHEMA = cv.All( + { + cv.GenerateID(): cv.declare_id(MAX6956GPIOPin), + cv.Required(CONF_MAX6956): cv.use_id(MAX6956), + cv.Required(CONF_NUMBER): cv.int_range(min=4, max=31), + cv.Optional(CONF_MODE, default={}): cv.All( + { + cv.Optional(CONF_INPUT, default=False): cv.boolean, + cv.Optional(CONF_PULLUP, default=False): cv.boolean, + cv.Optional(CONF_OUTPUT, default=False): cv.boolean, + }, + validate_mode, + ), + cv.Optional(CONF_INVERTED, default=False): cv.boolean, + } +) + + +@pins.PIN_SCHEMA_REGISTRY.register(CONF_MAX6956, MAX6956_PIN_SCHEMA) +async def max6956_pin_to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + parent = await cg.get_variable(config[CONF_MAX6956]) + + cg.add(var.set_parent(parent)) + + num = config[CONF_NUMBER] + cg.add(var.set_pin(num)) + cg.add(var.set_inverted(config[CONF_INVERTED])) + cg.add(var.set_flags(pins.gpio_flags_expr(config[CONF_MODE]))) + return var + + +@automation.register_action( + "max6956.set_brightness_global", + SetCurrentGlobalAction, + cv.maybe_simple_value( + { + cv.GenerateID(CONF_ID): cv.use_id(MAX6956), + cv.Required(CONF_BRIGHTNESS_GLOBAL): cv.templatable( + cv.int_range(min=0, max=15) + ), + }, + key=CONF_BRIGHTNESS_GLOBAL, + ), +) +async def max6956_set_brightness_global_to_code(config, action_id, template_arg, args): + paren = await cg.get_variable(config[CONF_ID]) + var = cg.new_Pvariable(action_id, template_arg, paren) + template_ = await cg.templatable(config[CONF_BRIGHTNESS_GLOBAL], args, float) + cg.add(var.set_brightness_global(template_)) + return var + + +@automation.register_action( + "max6956.set_brightness_mode", + SetCurrentModeAction, + cv.maybe_simple_value( + { + cv.Required(CONF_ID): cv.use_id(MAX6956), + cv.Required(CONF_BRIGHTNESS_MODE): cv.templatable( + cv.enum(CURRENT_MODES, lower=True) + ), + }, + key=CONF_BRIGHTNESS_MODE, + ), +) +async def max6956_set_brightness_mode_to_code(config, action_id, template_arg, args): + paren = await cg.get_variable(config[CONF_ID]) + var = cg.new_Pvariable(action_id, template_arg, paren) + template_ = await cg.templatable(config[CONF_BRIGHTNESS_MODE], args, float) + cg.add(var.set_brightness_mode(template_)) + return var diff --git a/esphome/components/max6956/automation.h b/esphome/components/max6956/automation.h new file mode 100644 index 0000000000..c0b491dc7f --- /dev/null +++ b/esphome/components/max6956/automation.h @@ -0,0 +1,40 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/automation.h" +#include "esphome/components/max6956/max6956.h" + +namespace esphome { +namespace max6956 { + +template class SetCurrentGlobalAction : public Action { + public: + SetCurrentGlobalAction(MAX6956 *max6956) : max6956_(max6956) {} + + TEMPLATABLE_VALUE(uint8_t, brightness_global) + + void play(Ts... x) override { + this->max6956_->set_brightness_global(this->brightness_global_.value(x...)); + this->max6956_->write_brightness_global(); + } + + protected: + MAX6956 *max6956_; +}; + +template class SetCurrentModeAction : public Action { + public: + SetCurrentModeAction(MAX6956 *max6956) : max6956_(max6956) {} + + TEMPLATABLE_VALUE(max6956::MAX6956CURRENTMODE, brightness_mode) + + void play(Ts... x) override { + this->max6956_->set_brightness_mode(this->brightness_mode_.value(x...)); + this->max6956_->write_brightness_mode(); + } + + protected: + MAX6956 *max6956_; +}; +} // namespace max6956 +} // namespace esphome diff --git a/esphome/components/max6956/max6956.cpp b/esphome/components/max6956/max6956.cpp new file mode 100644 index 0000000000..c2d9ba0175 --- /dev/null +++ b/esphome/components/max6956/max6956.cpp @@ -0,0 +1,170 @@ +#include "max6956.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace max6956 { + +static const char *const TAG = "max6956"; + +/// Masks for MAX6956 Configuration register +const uint32_t MASK_TRANSITION_DETECTION = 0x80; +const uint32_t MASK_INDIVIDUAL_CURRENT = 0x40; +const uint32_t MASK_NORMAL_OPERATION = 0x01; + +const uint32_t MASK_1PORT_VALUE = 0x03; +const uint32_t MASK_PORT_CONFIG = 0x03; +const uint8_t MASK_CONFIG_CURRENT = 0x40; +const uint8_t MASK_CURRENT_PIN = 0x0F; + +/************************************** + * MAX6956 * + **************************************/ +void MAX6956::setup() { + ESP_LOGCONFIG(TAG, "Setting up MAX6956..."); + uint8_t configuration; + if (!this->read_reg_(MAX6956_CONFIGURATION, &configuration)) { + this->mark_failed(); + return; + } + + write_brightness_global(); + write_brightness_mode(); + + /** TO DO : read transition detection in yaml + TO DO : read indivdual current in yaml **/ + this->read_reg_(MAX6956_CONFIGURATION, &configuration); + ESP_LOGD(TAG, "Initial reg[0x%.2X]=0x%.2X", MAX6956_CONFIGURATION, configuration); + configuration = configuration | MASK_NORMAL_OPERATION; + this->write_reg_(MAX6956_CONFIGURATION, configuration); + + ESP_LOGCONFIG(TAG, "Enabling normal operation"); + ESP_LOGD(TAG, "setup reg[0x%.2X]=0x%.2X", MAX6956_CONFIGURATION, configuration); +} + +bool MAX6956::digital_read(uint8_t pin) { + uint8_t reg_addr = MAX6956_1PORT_VALUE_START + pin; + uint8_t value = 0; + this->read_reg_(reg_addr, &value); + return (value & MASK_1PORT_VALUE); +} + +void MAX6956::digital_write(uint8_t pin, bool value) { + uint8_t reg_addr = MAX6956_1PORT_VALUE_START + pin; + this->write_reg_(reg_addr, value); +} + +void MAX6956::pin_mode(uint8_t pin, gpio::Flags flags) { + uint8_t reg_addr = MAX6956_PORT_CONFIG_START + (pin - MAX6956_MIN) / 4; + uint8_t config = 0; + uint8_t shift = 2 * (pin % 4); + MAX6956GPIOMode mode = MAX6956_INPUT; + + if (flags == gpio::FLAG_INPUT) { + mode = MAX6956GPIOMode::MAX6956_INPUT; + } else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLUP)) { + mode = MAX6956GPIOMode::MAX6956_INPUT_PULLUP; + } else if (flags == gpio::FLAG_OUTPUT) { + mode = MAX6956GPIOMode::MAX6956_OUTPUT; + } + + this->read_reg_(reg_addr, &config); + config &= ~(MASK_PORT_CONFIG << shift); + config |= (mode << shift); + this->write_reg_(reg_addr, config); +} + +void MAX6956::pin_mode(uint8_t pin, max6956::MAX6956GPIOFlag flags) { + uint8_t reg_addr = MAX6956_PORT_CONFIG_START + (pin - MAX6956_MIN) / 4; + uint8_t config = 0; + uint8_t shift = 2 * (pin % 4); + MAX6956GPIOMode mode = MAX6956GPIOMode::MAX6956_LED; + + if (flags == max6956::FLAG_LED) { + mode = MAX6956GPIOMode::MAX6956_LED; + } + + this->read_reg_(reg_addr, &config); + config &= ~(MASK_PORT_CONFIG << shift); + config |= (mode << shift); + this->write_reg_(reg_addr, config); +} + +void MAX6956::set_brightness_global(uint8_t current) { + if (current > 15) { + ESP_LOGE(TAG, "Global brightness out off range (%u)", current); + return; + } + global_brightness_ = current; +} + +void MAX6956::write_brightness_global() { this->write_reg_(MAX6956_GLOBAL_CURRENT, global_brightness_); } + +void MAX6956::set_brightness_mode(max6956::MAX6956CURRENTMODE brightness_mode) { brightness_mode_ = brightness_mode; }; + +void MAX6956::write_brightness_mode() { + uint8_t reg_addr = MAX6956_CONFIGURATION; + uint8_t config = 0; + + this->read_reg_(reg_addr, &config); + config &= ~MASK_CONFIG_CURRENT; + config |= brightness_mode_ << 6; + this->write_reg_(reg_addr, config); +} + +void MAX6956::set_pin_brightness(uint8_t pin, float brightness) { + uint8_t reg_addr = MAX6956_CURRENT_START + (pin - MAX6956_MIN) / 2; + uint8_t config = 0; + uint8_t shift = 4 * (pin % 2); + uint8_t bright = roundf(brightness * 15); + + if (prev_bright_[pin - MAX6956_MIN] == bright) + return; + + prev_bright_[pin - MAX6956_MIN] = bright; + + this->read_reg_(reg_addr, &config); + config &= ~(MASK_CURRENT_PIN << shift); + config |= (bright << shift); + this->write_reg_(reg_addr, config); +} + +bool MAX6956::read_reg_(uint8_t reg, uint8_t *value) { + if (this->is_failed()) + return false; + + return this->read_byte(reg, value); +} + +bool MAX6956::write_reg_(uint8_t reg, uint8_t value) { + if (this->is_failed()) + return false; + + return this->write_byte(reg, value); +} + +void MAX6956::dump_config() { + ESP_LOGCONFIG(TAG, "MAX6956"); + + if (brightness_mode_ == MAX6956CURRENTMODE::GLOBAL) { + ESP_LOGCONFIG(TAG, "current mode: global"); + ESP_LOGCONFIG(TAG, "global brightness: %u", global_brightness_); + } else { + ESP_LOGCONFIG(TAG, "current mode: segment"); + } +} + +/************************************** + * MAX6956GPIOPin * + **************************************/ +void MAX6956GPIOPin::setup() { pin_mode(flags_); } +void MAX6956GPIOPin::pin_mode(gpio::Flags flags) { this->parent_->pin_mode(this->pin_, flags); } +bool MAX6956GPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) != this->inverted_; } +void MAX6956GPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_, value != this->inverted_); } +std::string MAX6956GPIOPin::dump_summary() const { + char buffer[32]; + snprintf(buffer, sizeof(buffer), "%u via Max6956", pin_); + return buffer; +} + +} // namespace max6956 +} // namespace esphome diff --git a/esphome/components/max6956/max6956.h b/esphome/components/max6956/max6956.h new file mode 100644 index 0000000000..141164ab30 --- /dev/null +++ b/esphome/components/max6956/max6956.h @@ -0,0 +1,94 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/hal.h" +#include "esphome/components/i2c/i2c.h" + +namespace esphome { +namespace max6956 { + +/// Modes for MAX6956 pins +enum MAX6956GPIOMode : uint8_t { + MAX6956_LED = 0x00, + MAX6956_OUTPUT = 0x01, + MAX6956_INPUT = 0x02, + MAX6956_INPUT_PULLUP = 0x03 +}; + +/// Range for MAX6956 pins +enum MAX6956GPIORange : uint8_t { + MAX6956_MIN = 4, + MAX6956_MAX = 31, +}; + +enum MAX6956GPIORegisters { + MAX6956_GLOBAL_CURRENT = 0x02, + MAX6956_CONFIGURATION = 0x04, + MAX6956_TRANSITION_DETECT_MASK = 0x06, + MAX6956_DISPLAY_TEST = 0x07, + MAX6956_PORT_CONFIG_START = 0x09, // Port Configuration P7, P6, P5, P4 + MAX6956_CURRENT_START = 0x12, // Current054 + MAX6956_1PORT_VALUE_START = 0x20, // Port 0 only (virtual port, no action) + MAX6956_8PORTS_VALUE_START = 0x44, // 8 ports 4–11 (data bits D0–D7) +}; + +enum MAX6956GPIOFlag { FLAG_LED = 0x20 }; + +enum MAX6956CURRENTMODE { GLOBAL = 0x00, SEGMENT = 0x01 }; + +class MAX6956 : public Component, public i2c::I2CDevice { + public: + MAX6956() = default; + + void setup() override; + + bool digital_read(uint8_t pin); + void digital_write(uint8_t pin, bool value); + void pin_mode(uint8_t pin, gpio::Flags flags); + void pin_mode(uint8_t pin, max6956::MAX6956GPIOFlag flags); + + float get_setup_priority() const override { return setup_priority::HARDWARE; } + + void set_brightness_global(uint8_t current); + void set_brightness_mode(max6956::MAX6956CURRENTMODE brightness_mode); + void set_pin_brightness(uint8_t pin, float brightness); + + void dump_config() override; + + void write_brightness_global(); + void write_brightness_mode(); + + protected: + // read a given register + bool read_reg_(uint8_t reg, uint8_t *value); + // write a value to a given register + bool write_reg_(uint8_t reg, uint8_t value); + max6956::MAX6956CURRENTMODE brightness_mode_; + uint8_t global_brightness_; + + private: + int8_t prev_bright_[28] = {0}; +}; + +class MAX6956GPIOPin : public GPIOPin { + public: + void setup() override; + void pin_mode(gpio::Flags flags) override; + bool digital_read() override; + void digital_write(bool value) override; + std::string dump_summary() const override; + + void set_parent(MAX6956 *parent) { parent_ = parent; } + void set_pin(uint8_t pin) { pin_ = pin; } + void set_inverted(bool inverted) { inverted_ = inverted; } + void set_flags(gpio::Flags flags) { flags_ = flags; } + + protected: + MAX6956 *parent_; + uint8_t pin_; + bool inverted_; + gpio::Flags flags_; +}; + +} // namespace max6956 +} // namespace esphome diff --git a/esphome/components/max6956/output/__init__.py b/esphome/components/max6956/output/__init__.py new file mode 100644 index 0000000000..1caf8c8a44 --- /dev/null +++ b/esphome/components/max6956/output/__init__.py @@ -0,0 +1,28 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import output +from esphome.const import CONF_PIN, CONF_ID +from .. import MAX6956, max6956_ns, CONF_MAX6956 + +DEPENDENCIES = ["max6956"] + +MAX6956LedChannel = max6956_ns.class_( + "MAX6956LedChannel", output.FloatOutput, cg.Component +) + +CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend( + { + cv.Required(CONF_ID): cv.declare_id(MAX6956LedChannel), + cv.GenerateID(CONF_MAX6956): cv.use_id(MAX6956), + cv.Required(CONF_PIN): cv.int_range(min=4, max=31), + } +).extend(cv.COMPONENT_SCHEMA) + + +async def to_code(config): + parent = await cg.get_variable(config[CONF_MAX6956]) + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await output.register_output(var, config) + cg.add(var.set_pin(config[CONF_PIN])) + cg.add(var.set_parent(parent)) diff --git a/esphome/components/max6956/output/max6956_led_output.cpp b/esphome/components/max6956/output/max6956_led_output.cpp new file mode 100644 index 0000000000..5fa2dd9b34 --- /dev/null +++ b/esphome/components/max6956/output/max6956_led_output.cpp @@ -0,0 +1,26 @@ +#include "max6956_led_output.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace max6956 { + +static const char *const TAG = "max6956_led_channel"; + +void MAX6956LedChannel::write_state(float state) { this->parent_->set_pin_brightness(this->pin_, state); } + +void MAX6956LedChannel::write_state(bool state) { this->parent_->digital_write(this->pin_, state); } + +void MAX6956LedChannel::setup() { + this->parent_->pin_mode(this->pin_, max6956::FLAG_LED); + this->turn_off(); +} + +void MAX6956LedChannel::dump_config() { + ESP_LOGCONFIG(TAG, "MAX6956 current:"); + ESP_LOGCONFIG(TAG, " MAX6956 pin: %d", this->pin_); + LOG_FLOAT_OUTPUT(this); +} + +} // namespace max6956 +} // namespace esphome diff --git a/esphome/components/max6956/output/max6956_led_output.h b/esphome/components/max6956/output/max6956_led_output.h new file mode 100644 index 0000000000..b844a7ceee --- /dev/null +++ b/esphome/components/max6956/output/max6956_led_output.h @@ -0,0 +1,28 @@ +#pragma once + +#include "esphome/components/max6956/max6956.h" +#include "esphome/components/output/float_output.h" + +namespace esphome { +namespace max6956 { + +class MAX6956; + +class MAX6956LedChannel : public output::FloatOutput, public Component { + public: + void set_parent(MAX6956 *parent) { this->parent_ = parent; } + void set_pin(uint8_t pin) { pin_ = pin; } + void setup() override; + void dump_config() override; + float get_setup_priority() const override { return setup_priority::HARDWARE; } + + protected: + void write_state(float state) override; + void write_state(bool state) override; + + MAX6956 *parent_; + uint8_t pin_; +}; + +} // namespace max6956 +} // namespace esphome diff --git a/tests/test4.yaml b/tests/test4.yaml index 7b8f139a43..04d6f4678e 100644 --- a/tests/test4.yaml +++ b/tests/test4.yaml @@ -374,6 +374,16 @@ binary_sensor: on_press: - logger.log: Touched + - platform: gpio + name: MaxIn Pin 4 + pin: + max6956: max6956_1 + number: 4 + mode: + input: true + pullup: true + inverted: false + climate: - platform: tuya id: tuya_climate @@ -716,3 +726,7 @@ voice_assistant: - logger.log: format: "Voice assistant error - code %s, message: %s" args: [code.c_str(), message.c_str()] + +max6956: + - id: max6956_1 + address: 0x40 From fb094fca0f83d9c640d2008cea478278fb9fdf92 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 May 2023 09:52:42 +1200 Subject: [PATCH 50/71] Bump zeroconf from 0.56.0 to 0.60.0 (#4767) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index be5133f5f9..a62c48e235 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,7 +11,7 @@ esptool==4.5.1 click==8.1.3 esphome-dashboard==20230214.0 aioesphomeapi==13.7.2 -zeroconf==0.56.0 +zeroconf==0.60.0 # esp-idf requires this, but doesn't bundle it by default # https://github.com/espressif/esp-idf/blob/220590d599e134d7a5e7f1e683cc4550349ffbf8/requirements.txt#L24 From 4a3f9712b2b71af405f103470823d7e8d94bfd58 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 2 May 2023 16:57:40 +1200 Subject: [PATCH 51/71] Fix i2s media player on devices with no internal DAC (#4768) --- .../i2s_audio/media_player/i2s_audio_media_player.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/i2s_audio/media_player/i2s_audio_media_player.cpp b/esphome/components/i2s_audio/media_player/i2s_audio_media_player.cpp index 64f83a5ea6..2e9ded601d 100644 --- a/esphome/components/i2s_audio/media_player/i2s_audio_media_player.cpp +++ b/esphome/components/i2s_audio/media_player/i2s_audio_media_player.cpp @@ -141,7 +141,7 @@ void I2SAudioMediaPlayer::start_() { this->audio_ = make_unique