From 32b927de7e319a4a1357a047959d365ccb85e919 Mon Sep 17 00:00:00 2001 From: Kevin Ahrendt Date: Fri, 19 Jul 2024 15:15:11 -0400 Subject: [PATCH 01/19] revert bit shift to match previous behavior (#7109) --- .../components/i2s_audio/microphone/i2s_audio_microphone.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/esphome/components/i2s_audio/microphone/i2s_audio_microphone.cpp b/esphome/components/i2s_audio/microphone/i2s_audio_microphone.cpp index 009fecdf90..cb49a744fc 100644 --- a/esphome/components/i2s_audio/microphone/i2s_audio_microphone.cpp +++ b/esphome/components/i2s_audio/microphone/i2s_audio_microphone.cpp @@ -174,7 +174,8 @@ size_t I2SAudioMicrophone::read(int16_t *buf, size_t len) { size_t samples_read = bytes_read / sizeof(int32_t); samples.resize(samples_read); for (size_t i = 0; i < samples_read; i++) { - samples[i] = reinterpret_cast(buf)[i] >> 16; + int32_t temp = reinterpret_cast(buf)[i] >> 14; + samples[i] = clamp(temp, INT16_MIN, INT16_MAX); } memcpy(buf, samples.data(), samples_read * sizeof(int16_t)); return samples_read * sizeof(int16_t); From 43b818f2b1ca949ccc1ec216db9b5682e051541c Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 22 Jul 2024 07:54:16 +1200 Subject: [PATCH 02/19] [validation] Add ``host`` to ``require_framework_version`` (#7107) --- esphome/config_validation.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/esphome/config_validation.py b/esphome/config_validation.py index 7259e3c062..3ef92ad460 100644 --- a/esphome/config_validation.py +++ b/esphome/config_validation.py @@ -829,7 +829,6 @@ def time_of_day(value): def date_time(date: bool, time: bool): - pattern_str = r"^" # Start of string if date: pattern_str += r"\d{4}-\d{1,2}-\d{1,2}" @@ -2031,6 +2030,7 @@ def require_framework_version( esp32_arduino=None, esp8266_arduino=None, rp2040_arduino=None, + host=None, max_version=False, extra_message=None, ): @@ -2065,6 +2065,13 @@ def require_framework_version( msg += f". {extra_message}" raise Invalid(msg) required = rp2040_arduino + elif CORE.is_host and framework == "host": + if host is None: + msg = "This feature is incompatible with host platform" + if extra_message: + msg += f". {extra_message}" + raise Invalid(msg) + required = host else: raise Invalid( f""" From cfb20abb9f0e7fc0b25b4cc792a560b4d91eb748 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 22 Jul 2024 08:09:06 +1200 Subject: [PATCH 03/19] [code-quality] Tidy up some duplicate CONFIG_SCHEMA assignments (#7106) --- esphome/components/ade7953/sensor.py | 2 +- esphome/components/bmp3xx/sensor.py | 2 +- esphome/components/ens160/sensor.py | 2 +- esphome/components/kalman_combinator/sensor.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/esphome/components/ade7953/sensor.py b/esphome/components/ade7953/sensor.py index 0caa2ef454..fc79888129 100644 --- a/esphome/components/ade7953/sensor.py +++ b/esphome/components/ade7953/sensor.py @@ -1,5 +1,5 @@ import esphome.config_validation as cv -CONFIG_SCHEMA = CONFIG_SCHEMA = cv.invalid( +CONFIG_SCHEMA = cv.invalid( "The ade7953 sensor component has been renamed to ade7953_i2c." ) diff --git a/esphome/components/bmp3xx/sensor.py b/esphome/components/bmp3xx/sensor.py index 89753768c3..3c7927f1a8 100644 --- a/esphome/components/bmp3xx/sensor.py +++ b/esphome/components/bmp3xx/sensor.py @@ -2,6 +2,6 @@ import esphome.config_validation as cv CODEOWNERS = ["@latonita"] -CONFIG_SCHEMA = CONFIG_SCHEMA = cv.invalid( +CONFIG_SCHEMA = cv.invalid( "The bmp3xx sensor component has been renamed to bmp3xx_i2c." ) diff --git a/esphome/components/ens160/sensor.py b/esphome/components/ens160/sensor.py index f666b530b3..441671fb7b 100644 --- a/esphome/components/ens160/sensor.py +++ b/esphome/components/ens160/sensor.py @@ -2,6 +2,6 @@ import esphome.config_validation as cv CODEOWNERS = ["@latonita"] -CONFIG_SCHEMA = CONFIG_SCHEMA = cv.invalid( +CONFIG_SCHEMA = cv.invalid( "The ens160 sensor component has been renamed to ens160_i2c." ) diff --git a/esphome/components/kalman_combinator/sensor.py b/esphome/components/kalman_combinator/sensor.py index eca1ba7b85..c19a17462d 100644 --- a/esphome/components/kalman_combinator/sensor.py +++ b/esphome/components/kalman_combinator/sensor.py @@ -1,6 +1,6 @@ import esphome.config_validation as cv -CONFIG_SCHEMA = CONFIG_SCHEMA = cv.invalid( +CONFIG_SCHEMA = cv.invalid( "The kalman_combinator sensor has moved.\nPlease use the combination platform instead with type: kalman.\n" "See https://esphome.io/components/sensor/combination.html" ) From fbc830176f612a341b5ace5418ab4f76cd4a578c Mon Sep 17 00:00:00 2001 From: Lucio Tarantino Date: Sun, 21 Jul 2024 23:16:51 +0200 Subject: [PATCH 04/19] [heatpumpir] Fix BK72XX Compile error with IRremoteESP8266 (#6955) --- esphome/components/heatpumpir/climate.py | 3 +++ tests/components/heatpumpir/test.bk72xx-ard.yaml | 7 +++++++ 2 files changed, 10 insertions(+) diff --git a/esphome/components/heatpumpir/climate.py b/esphome/components/heatpumpir/climate.py index 80900d7db9..9d31668deb 100644 --- a/esphome/components/heatpumpir/climate.py +++ b/esphome/components/heatpumpir/climate.py @@ -8,6 +8,7 @@ from esphome.const import ( CONF_PROTOCOL, CONF_VISUAL, ) +from esphome.core import CORE CODEOWNERS = ["@rob-deutsch"] @@ -127,3 +128,5 @@ def to_code(config): cg.add(var.set_min_temperature(config[CONF_MIN_TEMPERATURE])) cg.add_library("tonia/HeatpumpIR", "1.0.27") + if CORE.is_libretiny: + CORE.add_platformio_option("lib_ignore", "IRremoteESP8266") diff --git a/tests/components/heatpumpir/test.bk72xx-ard.yaml b/tests/components/heatpumpir/test.bk72xx-ard.yaml index 90259f1244..b616f9157c 100644 --- a/tests/components/heatpumpir/test.bk72xx-ard.yaml +++ b/tests/components/heatpumpir/test.bk72xx-ard.yaml @@ -3,6 +3,13 @@ remote_transmitter: carrier_duty_percent: 50% climate: + - platform: heatpumpir + protocol: mitsubishi_heavy_zm + horizontal_default: left + vertical_default: up + name: HeatpumpIR Climate + min_temperature: 18 + max_temperature: 30 - platform: heatpumpir protocol: daikin horizontal_default: mleft From 368662969ed4c4df267499c720497d195acbed06 Mon Sep 17 00:00:00 2001 From: Markus <974709+Links2004@users.noreply.github.com> Date: Sun, 21 Jul 2024 23:36:46 +0200 Subject: [PATCH 05/19] Move MQTT ip discovery to deticated config option. (#6673) --- esphome/components/mqtt/__init__.py | 8 ++++++++ esphome/components/mqtt/mqtt_client.cpp | 18 +++++++++++++++--- esphome/components/mqtt/mqtt_client.h | 6 +++++- 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/esphome/components/mqtt/__init__.py b/esphome/components/mqtt/__init__.py index 96a02cb60e..f4bd34bfd3 100644 --- a/esphome/components/mqtt/__init__.py +++ b/esphome/components/mqtt/__init__.py @@ -61,6 +61,7 @@ def AUTO_LOAD(): return ["json"] +CONF_DISCOVER_IP = "discover_ip" CONF_IDF_SEND_ASYNC = "idf_send_async" CONF_SKIP_CERT_CN_CHECK = "skip_cert_cn_check" @@ -225,6 +226,7 @@ CONFIG_SCHEMA = cv.All( cv.boolean, cv.one_of("CLEAN", upper=True) ), cv.Optional(CONF_DISCOVERY_RETAIN, default=True): cv.boolean, + cv.Optional(CONF_DISCOVER_IP, default=True): cv.boolean, cv.Optional( CONF_DISCOVERY_PREFIX, default="homeassistant" ): cv.publish_topic, @@ -328,8 +330,12 @@ async def to_code(config): discovery_prefix = config[CONF_DISCOVERY_PREFIX] discovery_unique_id_generator = config[CONF_DISCOVERY_UNIQUE_ID_GENERATOR] discovery_object_id_generator = config[CONF_DISCOVERY_OBJECT_ID_GENERATOR] + discover_ip = config[CONF_DISCOVER_IP] if not discovery: + discovery_prefix = "" + + if not discovery and not discover_ip: cg.add(var.disable_discovery()) elif discovery == "CLEAN": cg.add( @@ -338,6 +344,7 @@ async def to_code(config): discovery_unique_id_generator, discovery_object_id_generator, discovery_retain, + discover_ip, True, ) ) @@ -348,6 +355,7 @@ async def to_code(config): discovery_unique_id_generator, discovery_object_id_generator, discovery_retain, + discover_ip, ) ) diff --git a/esphome/components/mqtt/mqtt_client.cpp b/esphome/components/mqtt/mqtt_client.cpp index d70b9cbd30..876367aaea 100644 --- a/esphome/components/mqtt/mqtt_client.cpp +++ b/esphome/components/mqtt/mqtt_client.cpp @@ -66,7 +66,7 @@ void MQTTClientComponent::setup() { } #endif - if (this->is_discovery_enabled()) { + if (this->is_discovery_ip_enabled()) { this->subscribe( "esphome/discover", [this](const std::string &topic, const std::string &payload) { this->send_device_info_(); }, 2); @@ -82,7 +82,7 @@ void MQTTClientComponent::setup() { } void MQTTClientComponent::send_device_info_() { - if (!this->is_connected() or !this->is_discovery_enabled()) { + if (!this->is_connected() or !this->is_discovery_ip_enabled()) { return; } std::string topic = "esphome/discover/"; @@ -99,6 +99,9 @@ void MQTTClientComponent::send_device_info_() { } } root["name"] = App.get_name(); + if (!App.get_friendly_name().empty()) { + root["friendly_name"] = App.get_friendly_name(); + } #ifdef USE_API root["port"] = api::global_api_server->get_port(); #endif @@ -130,6 +133,10 @@ void MQTTClientComponent::send_device_info_() { #ifdef USE_DASHBOARD_IMPORT root["package_import_url"] = dashboard_import::get_package_import_url(); #endif + +#ifdef USE_API_NOISE + root["api_encryption"] = "Noise_NNpsk0_25519_ChaChaPoly_SHA256"; +#endif }, 2, this->discovery_info_.retain); } @@ -140,6 +147,9 @@ void MQTTClientComponent::dump_config() { this->ip_.str().c_str()); ESP_LOGCONFIG(TAG, " Username: " LOG_SECRET("'%s'"), this->credentials_.username.c_str()); ESP_LOGCONFIG(TAG, " Client ID: " LOG_SECRET("'%s'"), this->credentials_.client_id.c_str()); + if (this->is_discovery_ip_enabled()) { + ESP_LOGCONFIG(TAG, " Discovery IP enabled"); + } if (!this->discovery_info_.prefix.empty()) { ESP_LOGCONFIG(TAG, " Discovery prefix: '%s'", this->discovery_info_.prefix.c_str()); ESP_LOGCONFIG(TAG, " Discovery retain: %s", YESNO(this->discovery_info_.retain)); @@ -581,6 +591,7 @@ void MQTTClientComponent::disable_shutdown_message() { this->recalculate_availability_(); } bool MQTTClientComponent::is_discovery_enabled() const { return !this->discovery_info_.prefix.empty(); } +bool MQTTClientComponent::is_discovery_ip_enabled() const { return this->discovery_info_.discover_ip; } const Availability &MQTTClientComponent::get_availability() { return this->availability_; } void MQTTClientComponent::recalculate_availability_() { if (this->birth_message_.topic.empty() || this->birth_message_.topic != this->last_will_.topic) { @@ -606,8 +617,9 @@ void MQTTClientComponent::set_shutdown_message(MQTTMessage &&message) { this->sh void MQTTClientComponent::set_discovery_info(std::string &&prefix, MQTTDiscoveryUniqueIdGenerator unique_id_generator, MQTTDiscoveryObjectIdGenerator object_id_generator, bool retain, - bool clean) { + bool discover_ip, bool clean) { this->discovery_info_.prefix = std::move(prefix); + this->discovery_info_.discover_ip = discover_ip; this->discovery_info_.unique_id_generator = unique_id_generator; this->discovery_info_.object_id_generator = object_id_generator; this->discovery_info_.retain = retain; diff --git a/esphome/components/mqtt/mqtt_client.h b/esphome/components/mqtt/mqtt_client.h index 454316aa87..b0d3bbe66d 100644 --- a/esphome/components/mqtt/mqtt_client.h +++ b/esphome/components/mqtt/mqtt_client.h @@ -79,6 +79,7 @@ enum MQTTDiscoveryObjectIdGenerator { struct MQTTDiscoveryInfo { std::string prefix; ///< The Home Assistant discovery prefix. Empty means disabled. bool retain; ///< Whether to retain discovery messages. + bool discover_ip; ///< Enable the Home Assistant device discovery. bool clean; MQTTDiscoveryUniqueIdGenerator unique_id_generator; MQTTDiscoveryObjectIdGenerator object_id_generator; @@ -122,12 +123,14 @@ class MQTTClientComponent : public Component { * @param retain Whether to retain discovery messages. */ void set_discovery_info(std::string &&prefix, MQTTDiscoveryUniqueIdGenerator unique_id_generator, - MQTTDiscoveryObjectIdGenerator object_id_generator, bool retain, bool clean = false); + MQTTDiscoveryObjectIdGenerator object_id_generator, bool retain, bool discover_ip, + bool clean = false); /// Get Home Assistant discovery info. const MQTTDiscoveryInfo &get_discovery_info() const; /// Globally disable Home Assistant discovery. void disable_discovery(); bool is_discovery_enabled() const; + bool is_discovery_ip_enabled() const; #if ASYNC_TCP_SSL_ENABLED /** Add a SSL fingerprint to use for TCP SSL connections to the MQTT broker. @@ -290,6 +293,7 @@ class MQTTClientComponent : public Component { MQTTDiscoveryInfo discovery_info_{ .prefix = "homeassistant", .retain = true, + .discover_ip = true, .clean = false, .unique_id_generator = MQTT_LEGACY_UNIQUE_ID_GENERATOR, .object_id_generator = MQTT_NONE_OBJECT_ID_GENERATOR, From 40e79299d55cd61e2f81b9f694e37ac3598c3d56 Mon Sep 17 00:00:00 2001 From: rnauber <7414650+rnauber@users.noreply.github.com> Date: Sun, 21 Jul 2024 23:57:59 +0200 Subject: [PATCH 06/19] Feature/m5angle8: Add support for m5angle8 input device (#6799) Co-authored-by: Richard Nauber Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- CODEOWNERS | 1 + esphome/components/m5stack_8angle/__init__.py | 33 +++++++++ .../m5stack_8angle/binary_sensor/__init__.py | 30 ++++++++ .../m5stack_8angle_binary_sensor.cpp | 17 +++++ .../m5stack_8angle_binary_sensor.h | 19 +++++ .../m5stack_8angle/light/__init__.py | 31 ++++++++ .../light/m5stack_8angle_light.cpp | 45 +++++++++++ .../light/m5stack_8angle_light.h | 37 ++++++++++ .../m5stack_8angle/m5stack_8angle.cpp | 74 +++++++++++++++++++ .../m5stack_8angle/m5stack_8angle.h | 34 +++++++++ .../m5stack_8angle/sensor/__init__.py | 66 +++++++++++++++++ .../sensor/m5stack_8angle_sensor.cpp | 24 ++++++ .../sensor/m5stack_8angle_sensor.h | 27 +++++++ tests/components/m5stack_8angle/common.yaml | 30 ++++++++ .../m5stack_8angle/test.esp32-ard.yaml | 1 + .../m5stack_8angle/test.esp32-c3-ard.yaml | 1 + .../m5stack_8angle/test.esp32-c3-idf.yaml | 1 + .../m5stack_8angle/test.esp32-idf.yaml | 1 + .../m5stack_8angle/test.esp8266-ard.yaml | 1 + .../m5stack_8angle/test.rp2040-ard.yaml | 1 + 20 files changed, 474 insertions(+) create mode 100644 esphome/components/m5stack_8angle/__init__.py create mode 100644 esphome/components/m5stack_8angle/binary_sensor/__init__.py create mode 100644 esphome/components/m5stack_8angle/binary_sensor/m5stack_8angle_binary_sensor.cpp create mode 100644 esphome/components/m5stack_8angle/binary_sensor/m5stack_8angle_binary_sensor.h create mode 100644 esphome/components/m5stack_8angle/light/__init__.py create mode 100644 esphome/components/m5stack_8angle/light/m5stack_8angle_light.cpp create mode 100644 esphome/components/m5stack_8angle/light/m5stack_8angle_light.h create mode 100644 esphome/components/m5stack_8angle/m5stack_8angle.cpp create mode 100644 esphome/components/m5stack_8angle/m5stack_8angle.h create mode 100644 esphome/components/m5stack_8angle/sensor/__init__.py create mode 100644 esphome/components/m5stack_8angle/sensor/m5stack_8angle_sensor.cpp create mode 100644 esphome/components/m5stack_8angle/sensor/m5stack_8angle_sensor.h create mode 100644 tests/components/m5stack_8angle/common.yaml create mode 100644 tests/components/m5stack_8angle/test.esp32-ard.yaml create mode 100644 tests/components/m5stack_8angle/test.esp32-c3-ard.yaml create mode 100644 tests/components/m5stack_8angle/test.esp32-c3-idf.yaml create mode 100644 tests/components/m5stack_8angle/test.esp32-idf.yaml create mode 100644 tests/components/m5stack_8angle/test.esp8266-ard.yaml create mode 100644 tests/components/m5stack_8angle/test.rp2040-ard.yaml diff --git a/CODEOWNERS b/CODEOWNERS index 210c567f78..ee76a072fc 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -216,6 +216,7 @@ esphome/components/lock/* @esphome/core esphome/components/logger/* @esphome/core esphome/components/ltr390/* @latonita @sjtrny esphome/components/ltr_als_ps/* @latonita +esphome/components/m5stack_8angle/* @rnauber esphome/components/matrix_keypad/* @ssieb esphome/components/max31865/* @DAVe3283 esphome/components/max44009/* @berfenger diff --git a/esphome/components/m5stack_8angle/__init__.py b/esphome/components/m5stack_8angle/__init__.py new file mode 100644 index 0000000000..1aaa86a6fd --- /dev/null +++ b/esphome/components/m5stack_8angle/__init__.py @@ -0,0 +1,33 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import i2c +from esphome.const import CONF_ID + + +DEPENDENCIES = ["i2c"] +CODEOWNERS = ["@rnauber"] +MULTI_CONF = True + +CONF_M5STACK_8ANGLE_ID = "m5stack_8angle_id" + +m5stack_8angle_ns = cg.esphome_ns.namespace("m5stack_8angle") +M5Stack8AngleComponent = m5stack_8angle_ns.class_( + "M5Stack8AngleComponent", + i2c.I2CDevice, + cg.Component, +) + +AnalogBits = m5stack_8angle_ns.enum("AnalogBits") + + +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(M5Stack8AngleComponent), + } +).extend(i2c.i2c_device_schema(0x43)) + + +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) diff --git a/esphome/components/m5stack_8angle/binary_sensor/__init__.py b/esphome/components/m5stack_8angle/binary_sensor/__init__.py new file mode 100644 index 0000000000..a8b2690083 --- /dev/null +++ b/esphome/components/m5stack_8angle/binary_sensor/__init__.py @@ -0,0 +1,30 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import binary_sensor + +from .. import M5Stack8AngleComponent, m5stack_8angle_ns, CONF_M5STACK_8ANGLE_ID + + +M5Stack8AngleSwitchBinarySensor = m5stack_8angle_ns.class_( + "M5Stack8AngleSwitchBinarySensor", + binary_sensor.BinarySensor, + cg.PollingComponent, +) + + +CONFIG_SCHEMA = cv.All( + cv.Schema( + { + cv.GenerateID(CONF_M5STACK_8ANGLE_ID): cv.use_id(M5Stack8AngleComponent), + } + ) + .extend(binary_sensor.binary_sensor_schema(M5Stack8AngleSwitchBinarySensor)) + .extend(cv.polling_component_schema("10s")) +) + + +async def to_code(config): + hub = await cg.get_variable(config[CONF_M5STACK_8ANGLE_ID]) + sens = await binary_sensor.new_binary_sensor(config) + cg.add(sens.set_parent(hub)) + await cg.register_component(sens, config) diff --git a/esphome/components/m5stack_8angle/binary_sensor/m5stack_8angle_binary_sensor.cpp b/esphome/components/m5stack_8angle/binary_sensor/m5stack_8angle_binary_sensor.cpp new file mode 100644 index 0000000000..2f68d9f254 --- /dev/null +++ b/esphome/components/m5stack_8angle/binary_sensor/m5stack_8angle_binary_sensor.cpp @@ -0,0 +1,17 @@ +#include "m5stack_8angle_binary_sensor.h" + +namespace esphome { +namespace m5stack_8angle { + +void M5Stack8AngleSwitchBinarySensor::update() { + int8_t out = this->parent_->read_switch(); + if (out == -1) { + this->status_set_warning("Could not read binary sensor state from M5Stack 8Angle."); + return; + } + this->publish_state(out != 0); + this->status_clear_warning(); +} + +} // namespace m5stack_8angle +} // namespace esphome diff --git a/esphome/components/m5stack_8angle/binary_sensor/m5stack_8angle_binary_sensor.h b/esphome/components/m5stack_8angle/binary_sensor/m5stack_8angle_binary_sensor.h new file mode 100644 index 0000000000..b8bb601525 --- /dev/null +++ b/esphome/components/m5stack_8angle/binary_sensor/m5stack_8angle_binary_sensor.h @@ -0,0 +1,19 @@ +#pragma once + +#include "esphome/components/binary_sensor/binary_sensor.h" +#include "esphome/core/component.h" + +#include "../m5stack_8angle.h" + +namespace esphome { +namespace m5stack_8angle { + +class M5Stack8AngleSwitchBinarySensor : public binary_sensor::BinarySensor, + public PollingComponent, + public Parented { + public: + void update() override; +}; + +} // namespace m5stack_8angle +} // namespace esphome diff --git a/esphome/components/m5stack_8angle/light/__init__.py b/esphome/components/m5stack_8angle/light/__init__.py new file mode 100644 index 0000000000..07384ecd61 --- /dev/null +++ b/esphome/components/m5stack_8angle/light/__init__.py @@ -0,0 +1,31 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import light + +from esphome.const import CONF_OUTPUT_ID + +from .. import M5Stack8AngleComponent, m5stack_8angle_ns, CONF_M5STACK_8ANGLE_ID + + +M5Stack8AngleLightsComponent = m5stack_8angle_ns.class_( + "M5Stack8AngleLightOutput", + light.AddressableLight, +) + + +CONFIG_SCHEMA = cv.All( + light.ADDRESSABLE_LIGHT_SCHEMA.extend( + { + cv.GenerateID(CONF_M5STACK_8ANGLE_ID): cv.use_id(M5Stack8AngleComponent), + cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(M5Stack8AngleLightsComponent), + } + ) +) + + +async def to_code(config): + hub = await cg.get_variable(config[CONF_M5STACK_8ANGLE_ID]) + lights = cg.new_Pvariable(config[CONF_OUTPUT_ID]) + await light.register_light(lights, config) + await cg.register_component(lights, config) + cg.add(lights.set_parent(hub)) diff --git a/esphome/components/m5stack_8angle/light/m5stack_8angle_light.cpp b/esphome/components/m5stack_8angle/light/m5stack_8angle_light.cpp new file mode 100644 index 0000000000..95fd8cb98f --- /dev/null +++ b/esphome/components/m5stack_8angle/light/m5stack_8angle_light.cpp @@ -0,0 +1,45 @@ +#include "m5stack_8angle_light.h" + +#include "esphome/core/log.h" + +namespace esphome { +namespace m5stack_8angle { + +static const char *const TAG = "m5stack_8angle.light"; + +void M5Stack8AngleLightOutput::setup() { + ExternalRAMAllocator allocator(ExternalRAMAllocator::ALLOW_FAILURE); + this->buf_ = allocator.allocate(M5STACK_8ANGLE_NUM_LEDS * M5STACK_8ANGLE_BYTES_PER_LED); + if (this->buf_ == nullptr) { + ESP_LOGE(TAG, "Failed to allocate buffer of size %u", M5STACK_8ANGLE_NUM_LEDS * M5STACK_8ANGLE_BYTES_PER_LED); + this->mark_failed(); + return; + }; + memset(this->buf_, 0xFF, M5STACK_8ANGLE_NUM_LEDS * M5STACK_8ANGLE_BYTES_PER_LED); + + this->effect_data_ = allocator.allocate(M5STACK_8ANGLE_NUM_LEDS); + if (this->effect_data_ == nullptr) { + ESP_LOGE(TAG, "Failed to allocate effect data of size %u", M5STACK_8ANGLE_NUM_LEDS); + this->mark_failed(); + return; + }; + memset(this->effect_data_, 0x00, M5STACK_8ANGLE_NUM_LEDS); +} + +void M5Stack8AngleLightOutput::write_state(light::LightState *state) { + for (int i = 0; i < M5STACK_8ANGLE_NUM_LEDS; + i++) { // write one LED at a time, otherwise the message will be truncated + this->parent_->write_register(M5STACK_8ANGLE_REGISTER_RGB_24B + i * M5STACK_8ANGLE_BYTES_PER_LED, + this->buf_ + i * M5STACK_8ANGLE_BYTES_PER_LED, M5STACK_8ANGLE_BYTES_PER_LED); + } +} + +light::ESPColorView M5Stack8AngleLightOutput::get_view_internal(int32_t index) const { + size_t pos = index * M5STACK_8ANGLE_BYTES_PER_LED; + // red, green, blue, white, effect_data, color_correction + return {this->buf_ + pos, this->buf_ + pos + 1, this->buf_ + pos + 2, + nullptr, this->effect_data_ + index, &this->correction_}; +} + +} // namespace m5stack_8angle +} // namespace esphome diff --git a/esphome/components/m5stack_8angle/light/m5stack_8angle_light.h b/esphome/components/m5stack_8angle/light/m5stack_8angle_light.h new file mode 100644 index 0000000000..204f2c04c7 --- /dev/null +++ b/esphome/components/m5stack_8angle/light/m5stack_8angle_light.h @@ -0,0 +1,37 @@ +#pragma once + +#include "esphome/components/light/addressable_light.h" +#include "esphome/components/light/light_output.h" + +#include "../m5stack_8angle.h" + +namespace esphome { +namespace m5stack_8angle { + +static const uint8_t M5STACK_8ANGLE_NUM_LEDS = 9; +static const uint8_t M5STACK_8ANGLE_BYTES_PER_LED = 4; + +class M5Stack8AngleLightOutput : public light::AddressableLight, public Parented { + public: + void setup() override; + + void write_state(light::LightState *state) override; + + int32_t size() const override { return M5STACK_8ANGLE_NUM_LEDS; } + light::LightTraits get_traits() override { + auto traits = light::LightTraits(); + traits.set_supported_color_modes({light::ColorMode::RGB}); + return traits; + }; + + void clear_effect_data() override { memset(this->effect_data_, 0x00, M5STACK_8ANGLE_NUM_LEDS); }; + + protected: + light::ESPColorView get_view_internal(int32_t index) const override; + + uint8_t *buf_{nullptr}; + uint8_t *effect_data_{nullptr}; +}; + +} // namespace m5stack_8angle +} // namespace esphome diff --git a/esphome/components/m5stack_8angle/m5stack_8angle.cpp b/esphome/components/m5stack_8angle/m5stack_8angle.cpp new file mode 100644 index 0000000000..6a584eddbc --- /dev/null +++ b/esphome/components/m5stack_8angle/m5stack_8angle.cpp @@ -0,0 +1,74 @@ +#include "m5stack_8angle.h" +#include "esphome/core/hal.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace m5stack_8angle { + +static const char *const TAG = "m5stack_8angle"; + +void M5Stack8AngleComponent::setup() { + ESP_LOGCONFIG(TAG, "Setting up M5STACK_8ANGLE..."); + i2c::ErrorCode err; + + err = this->read(nullptr, 0); + if (err != i2c::NO_ERROR) { + ESP_LOGE(TAG, "I2C error %02X...", err); + this->mark_failed(); + return; + }; + + err = this->read_register(M5STACK_8ANGLE_REGISTER_FW_VERSION, &this->fw_version_, 1); + if (err != i2c::NO_ERROR) { + ESP_LOGE(TAG, "I2C error %02X...", err); + this->mark_failed(); + return; + }; +} + +void M5Stack8AngleComponent::dump_config() { + ESP_LOGCONFIG(TAG, "M5STACK_8ANGLE:"); + LOG_I2C_DEVICE(this); + ESP_LOGCONFIG(TAG, " Firmware version: %d ", this->fw_version_); +} + +float M5Stack8AngleComponent::read_knob_pos(uint8_t channel, AnalogBits bits) { + int32_t raw_pos = this->read_knob_pos_raw(channel, bits); + if (raw_pos == -1) { + return NAN; + } + return (float) raw_pos / ((1 << bits) - 1); +} + +int32_t M5Stack8AngleComponent::read_knob_pos_raw(uint8_t channel, AnalogBits bits) { + uint16_t knob_pos = 0; + i2c::ErrorCode err; + if (bits == BITS_8) { + err = this->read_register(M5STACK_8ANGLE_REGISTER_ANALOG_INPUT_8B + channel, (uint8_t *) &knob_pos, 1); + } else if (bits == BITS_12) { + err = this->read_register(M5STACK_8ANGLE_REGISTER_ANALOG_INPUT_12B + (channel * 2), (uint8_t *) &knob_pos, 2); + } else { + ESP_LOGE(TAG, "Invalid number of bits: %d", bits); + return -1; + } + if (err == i2c::NO_ERROR) { + return knob_pos; + } else { + return -1; + } +} + +int8_t M5Stack8AngleComponent::read_switch() { + uint8_t out; + i2c::ErrorCode err = this->read_register(M5STACK_8ANGLE_REGISTER_DIGITAL_INPUT, (uint8_t *) &out, 1); + if (err == i2c::NO_ERROR) { + return out ? 1 : 0; + } else { + return -1; + } +} + +float M5Stack8AngleComponent::get_setup_priority() const { return setup_priority::DATA; } + +} // namespace m5stack_8angle +} // namespace esphome diff --git a/esphome/components/m5stack_8angle/m5stack_8angle.h b/esphome/components/m5stack_8angle/m5stack_8angle.h new file mode 100644 index 0000000000..831b1422fd --- /dev/null +++ b/esphome/components/m5stack_8angle/m5stack_8angle.h @@ -0,0 +1,34 @@ +#pragma once + +#include "esphome/components/i2c/i2c.h" +#include "esphome/core/component.h" + +namespace esphome { +namespace m5stack_8angle { + +static const uint8_t M5STACK_8ANGLE_REGISTER_ANALOG_INPUT_12B = 0x00; +static const uint8_t M5STACK_8ANGLE_REGISTER_ANALOG_INPUT_8B = 0x10; +static const uint8_t M5STACK_8ANGLE_REGISTER_DIGITAL_INPUT = 0x20; +static const uint8_t M5STACK_8ANGLE_REGISTER_RGB_24B = 0x30; +static const uint8_t M5STACK_8ANGLE_REGISTER_FW_VERSION = 0xFE; + +enum AnalogBits : uint8_t { + BITS_8 = 8, + BITS_12 = 12, +}; + +class M5Stack8AngleComponent : public i2c::I2CDevice, public Component { + public: + void setup() override; + void dump_config() override; + float get_setup_priority() const override; + float read_knob_pos(uint8_t channel, AnalogBits bits = AnalogBits::BITS_8); + int32_t read_knob_pos_raw(uint8_t channel, AnalogBits bits = AnalogBits::BITS_8); + int8_t read_switch(); + + protected: + uint8_t fw_version_; +}; + +} // namespace m5stack_8angle +} // namespace esphome diff --git a/esphome/components/m5stack_8angle/sensor/__init__.py b/esphome/components/m5stack_8angle/sensor/__init__.py new file mode 100644 index 0000000000..70744a59e6 --- /dev/null +++ b/esphome/components/m5stack_8angle/sensor/__init__.py @@ -0,0 +1,66 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import sensor + +from esphome.const import ( + CONF_BIT_DEPTH, + CONF_CHANNEL, + CONF_RAW, + ICON_ROTATE_RIGHT, + STATE_CLASS_MEASUREMENT, +) + +from .. import ( + AnalogBits, + M5Stack8AngleComponent, + m5stack_8angle_ns, + CONF_M5STACK_8ANGLE_ID, +) + + +M5Stack8AngleKnobSensor = m5stack_8angle_ns.class_( + "M5Stack8AngleKnobSensor", + sensor.Sensor, + cg.PollingComponent, +) + + +BIT_DEPTHS = { + 8: AnalogBits.BITS_8, + 12: AnalogBits.BITS_12, +} + +_validate_bits = cv.float_with_unit("bits", "bit") + + +CONFIG_SCHEMA = cv.All( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(M5Stack8AngleKnobSensor), + cv.GenerateID(CONF_M5STACK_8ANGLE_ID): cv.use_id(M5Stack8AngleComponent), + cv.Required(CONF_CHANNEL): cv.int_range(min=1, max=8), + cv.Optional(CONF_BIT_DEPTH, default="8bit"): cv.All( + _validate_bits, cv.enum(BIT_DEPTHS) + ), + cv.Optional(CONF_RAW, default=False): cv.boolean, + } + ) + .extend( + sensor.sensor_schema( + M5Stack8AngleKnobSensor, + accuracy_decimals=2, + icon=ICON_ROTATE_RIGHT, + state_class=STATE_CLASS_MEASUREMENT, + ) + ) + .extend(cv.polling_component_schema("10s")) +) + + +async def to_code(config): + var = await sensor.new_sensor(config) + await cg.register_component(var, config) + await cg.register_parented(var, config[CONF_M5STACK_8ANGLE_ID]) + cg.add(var.set_channel(config[CONF_CHANNEL] - 1)) + cg.add(var.set_bit_depth(BIT_DEPTHS[config[CONF_BIT_DEPTH]])) + cg.add(var.set_raw(config[CONF_RAW])) diff --git a/esphome/components/m5stack_8angle/sensor/m5stack_8angle_sensor.cpp b/esphome/components/m5stack_8angle/sensor/m5stack_8angle_sensor.cpp new file mode 100644 index 0000000000..5e034f1dd3 --- /dev/null +++ b/esphome/components/m5stack_8angle/sensor/m5stack_8angle_sensor.cpp @@ -0,0 +1,24 @@ +#include "m5stack_8angle_sensor.h" + +namespace esphome { +namespace m5stack_8angle { + +void M5Stack8AngleKnobSensor::update() { + if (this->parent_ != nullptr) { + int32_t raw_pos = this->parent_->read_knob_pos_raw(this->channel_, this->bits_); + if (raw_pos == -1) { + this->status_set_warning("Could not read knob position from M5Stack 8Angle."); + return; + } + if (this->raw_) { + this->publish_state(raw_pos); + } else { + float knob_pos = (float) raw_pos / ((1 << this->bits_) - 1); + this->publish_state(knob_pos); + } + this->status_clear_warning(); + }; +} + +} // namespace m5stack_8angle +} // namespace esphome diff --git a/esphome/components/m5stack_8angle/sensor/m5stack_8angle_sensor.h b/esphome/components/m5stack_8angle/sensor/m5stack_8angle_sensor.h new file mode 100644 index 0000000000..4848f8f80f --- /dev/null +++ b/esphome/components/m5stack_8angle/sensor/m5stack_8angle_sensor.h @@ -0,0 +1,27 @@ +#pragma once + +#include "esphome/components/sensor/sensor.h" +#include "esphome/core/component.h" + +#include "../m5stack_8angle.h" + +namespace esphome { +namespace m5stack_8angle { + +class M5Stack8AngleKnobSensor : public sensor::Sensor, + public PollingComponent, + public Parented { + public: + void update() override; + void set_channel(uint8_t channel) { this->channel_ = channel; }; + void set_bit_depth(AnalogBits bits) { this->bits_ = bits; }; + void set_raw(bool raw) { this->raw_ = raw; }; + + protected: + uint8_t channel_; + AnalogBits bits_; + bool raw_; +}; + +} // namespace m5stack_8angle +} // namespace esphome diff --git a/tests/components/m5stack_8angle/common.yaml b/tests/components/m5stack_8angle/common.yaml new file mode 100644 index 0000000000..d7f988ed3a --- /dev/null +++ b/tests/components/m5stack_8angle/common.yaml @@ -0,0 +1,30 @@ +i2c: + sda: 0 + scl: 1 + id: bus_external + +m5stack_8angle: + i2c_id: bus_external + id: m5stack_8angle_base + +light: + - platform: m5stack_8angle + m5stack_8angle_id: m5stack_8angle_base + id: m8_angle_leds + name: Lights + effects: + - addressable_scan: + +sensor: + - platform: m5stack_8angle + m5stack_8angle_id: m5stack_8angle_base + channel: 1 + name: Knob 1 + - platform: m5stack_8angle + m5stack_8angle_id: m5stack_8angle_base + channel: 2 + name: Knob 2 +binary_sensor: + - platform: m5stack_8angle + m5stack_8angle_id: m5stack_8angle_base + name: Switch diff --git a/tests/components/m5stack_8angle/test.esp32-ard.yaml b/tests/components/m5stack_8angle/test.esp32-ard.yaml new file mode 100644 index 0000000000..dade44d145 --- /dev/null +++ b/tests/components/m5stack_8angle/test.esp32-ard.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/m5stack_8angle/test.esp32-c3-ard.yaml b/tests/components/m5stack_8angle/test.esp32-c3-ard.yaml new file mode 100644 index 0000000000..dade44d145 --- /dev/null +++ b/tests/components/m5stack_8angle/test.esp32-c3-ard.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/m5stack_8angle/test.esp32-c3-idf.yaml b/tests/components/m5stack_8angle/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..dade44d145 --- /dev/null +++ b/tests/components/m5stack_8angle/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/m5stack_8angle/test.esp32-idf.yaml b/tests/components/m5stack_8angle/test.esp32-idf.yaml new file mode 100644 index 0000000000..dade44d145 --- /dev/null +++ b/tests/components/m5stack_8angle/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/m5stack_8angle/test.esp8266-ard.yaml b/tests/components/m5stack_8angle/test.esp8266-ard.yaml new file mode 100644 index 0000000000..dade44d145 --- /dev/null +++ b/tests/components/m5stack_8angle/test.esp8266-ard.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/m5stack_8angle/test.rp2040-ard.yaml b/tests/components/m5stack_8angle/test.rp2040-ard.yaml new file mode 100644 index 0000000000..dade44d145 --- /dev/null +++ b/tests/components/m5stack_8angle/test.rp2040-ard.yaml @@ -0,0 +1 @@ +<<: !include common.yaml From 1f4829598a62424170fdd3fcc58dcc4bd2224162 Mon Sep 17 00:00:00 2001 From: Olivier ARCHER Date: Mon, 22 Jul 2024 01:29:09 +0200 Subject: [PATCH 07/19] [http_request] allow basic auth for idf (#7086) --- esphome/components/http_request/http_request_idf.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/esphome/components/http_request/http_request_idf.cpp b/esphome/components/http_request/http_request_idf.cpp index d6fac7a133..68e0639b99 100644 --- a/esphome/components/http_request/http_request_idf.cpp +++ b/esphome/components/http_request/http_request_idf.cpp @@ -52,6 +52,7 @@ std::shared_ptr HttpRequestIDF::start(std::string url, std::strin config.timeout_ms = this->timeout_; config.disable_auto_redirect = !this->follow_redirects_; config.max_redirection_count = this->redirect_limit_; + config.auth_type = HTTP_AUTH_TYPE_BASIC; #if CONFIG_MBEDTLS_CERTIFICATE_BUNDLE if (secure) { config.crt_bundle_attach = esp_crt_bundle_attach; From f322ec8f3d1f70543578f0ec179d0049681c48c8 Mon Sep 17 00:00:00 2001 From: tomaszduda23 Date: Mon, 22 Jul 2024 01:33:26 +0200 Subject: [PATCH 08/19] use cache to build tests for compoenents (#7059) --- .github/workflows/ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 45fe336d77..a8e93248bb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -468,6 +468,8 @@ jobs: - name: Compile config run: | . venv/bin/activate + mkdir build_cache + export PLATFORMIO_BUILD_CACHE_DIR=$PWD/build_cache for component in ${{ matrix.components }}; do ./script/test_build_components -e compile -c $component done From a464e46d4d5630f6fe18d5bcffe7026739fd8254 Mon Sep 17 00:00:00 2001 From: irgendwienet Date: Mon, 22 Jul 2024 01:42:09 +0200 Subject: [PATCH 09/19] Fixes sml parser to process extended length lists with a number of items that is dividable by 16 (#6148) --- esphome/components/sml/sml_parser.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/esphome/components/sml/sml_parser.cpp b/esphome/components/sml/sml_parser.cpp index 3b23522b21..c782c0fc5e 100644 --- a/esphome/components/sml/sml_parser.cpp +++ b/esphome/components/sml/sml_parser.cpp @@ -27,7 +27,7 @@ bool SmlFile::setup_node(SmlNode *node) { uint8_t parse_length = length; if (has_extended_length) { length = (length << 4) + (this->buffer_[this->pos_ + 1] & 0x0f); - parse_length = length - 1; + parse_length = length; this->pos_ += 1; } @@ -37,7 +37,9 @@ bool SmlFile::setup_node(SmlNode *node) { node->type = type & 0x07; node->nodes.clear(); node->value_bytes.clear(); - if (this->buffer_[this->pos_] == 0x00) { // end of message + + // if the list is a has_extended_length list with e.g. 16 elements this is a 0x00 byte but not the end of message + if (!has_extended_length && this->buffer_[this->pos_] == 0x00) { // end of message this->pos_ += 1; } else if (is_list) { // list this->pos_ += 1; From d187340fc4d5781de91c2db3958e7a2fb295d795 Mon Sep 17 00:00:00 2001 From: Alex Cortelyou <1689668+acortelyou@users.noreply.github.com> Date: Thu, 18 Jul 2024 15:04:11 -0700 Subject: [PATCH 10/19] Prevent rename from deleting new config (#7104) --- esphome/__main__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/esphome/__main__.py b/esphome/__main__.py index 5ff1a28ec7..b13f96daf7 100644 --- a/esphome/__main__.py +++ b/esphome/__main__.py @@ -695,7 +695,8 @@ def command_rename(args, config): os.remove(new_path) return 1 - os.remove(CORE.config_path) + if CORE.config_path != new_path: + os.remove(CORE.config_path) print(color(Fore.BOLD_GREEN, "SUCCESS")) print() From 74aee1d453f39b84d5783e9b051fe788e5fadb12 Mon Sep 17 00:00:00 2001 From: Kevin Ahrendt Date: Fri, 19 Jul 2024 15:15:11 -0400 Subject: [PATCH 11/19] revert bit shift to match previous behavior (#7109) --- .../components/i2s_audio/microphone/i2s_audio_microphone.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/esphome/components/i2s_audio/microphone/i2s_audio_microphone.cpp b/esphome/components/i2s_audio/microphone/i2s_audio_microphone.cpp index 009fecdf90..cb49a744fc 100644 --- a/esphome/components/i2s_audio/microphone/i2s_audio_microphone.cpp +++ b/esphome/components/i2s_audio/microphone/i2s_audio_microphone.cpp @@ -174,7 +174,8 @@ size_t I2SAudioMicrophone::read(int16_t *buf, size_t len) { size_t samples_read = bytes_read / sizeof(int32_t); samples.resize(samples_read); for (size_t i = 0; i < samples_read; i++) { - samples[i] = reinterpret_cast(buf)[i] >> 16; + int32_t temp = reinterpret_cast(buf)[i] >> 14; + samples[i] = clamp(temp, INT16_MIN, INT16_MAX); } memcpy(buf, samples.data(), samples_read * sizeof(int16_t)); return samples_read * sizeof(int16_t); From 626ed815fb289b7532eb64d210bbff142f6c7666 Mon Sep 17 00:00:00 2001 From: Lucio Tarantino Date: Sun, 21 Jul 2024 23:16:51 +0200 Subject: [PATCH 12/19] [heatpumpir] Fix BK72XX Compile error with IRremoteESP8266 (#6955) --- esphome/components/heatpumpir/climate.py | 3 +++ tests/components/heatpumpir/test.bk72xx-ard.yaml | 7 +++++++ 2 files changed, 10 insertions(+) diff --git a/esphome/components/heatpumpir/climate.py b/esphome/components/heatpumpir/climate.py index 80900d7db9..9d31668deb 100644 --- a/esphome/components/heatpumpir/climate.py +++ b/esphome/components/heatpumpir/climate.py @@ -8,6 +8,7 @@ from esphome.const import ( CONF_PROTOCOL, CONF_VISUAL, ) +from esphome.core import CORE CODEOWNERS = ["@rob-deutsch"] @@ -127,3 +128,5 @@ def to_code(config): cg.add(var.set_min_temperature(config[CONF_MIN_TEMPERATURE])) cg.add_library("tonia/HeatpumpIR", "1.0.27") + if CORE.is_libretiny: + CORE.add_platformio_option("lib_ignore", "IRremoteESP8266") diff --git a/tests/components/heatpumpir/test.bk72xx-ard.yaml b/tests/components/heatpumpir/test.bk72xx-ard.yaml index 90259f1244..b616f9157c 100644 --- a/tests/components/heatpumpir/test.bk72xx-ard.yaml +++ b/tests/components/heatpumpir/test.bk72xx-ard.yaml @@ -3,6 +3,13 @@ remote_transmitter: carrier_duty_percent: 50% climate: + - platform: heatpumpir + protocol: mitsubishi_heavy_zm + horizontal_default: left + vertical_default: up + name: HeatpumpIR Climate + min_temperature: 18 + max_temperature: 30 - platform: heatpumpir protocol: daikin horizontal_default: mleft From 5bec0a6534028a834df91aad8e6d9c95cc825c5f Mon Sep 17 00:00:00 2001 From: Olivier ARCHER Date: Mon, 22 Jul 2024 01:29:09 +0200 Subject: [PATCH 13/19] [http_request] allow basic auth for idf (#7086) --- esphome/components/http_request/http_request_idf.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/esphome/components/http_request/http_request_idf.cpp b/esphome/components/http_request/http_request_idf.cpp index d6fac7a133..68e0639b99 100644 --- a/esphome/components/http_request/http_request_idf.cpp +++ b/esphome/components/http_request/http_request_idf.cpp @@ -52,6 +52,7 @@ std::shared_ptr HttpRequestIDF::start(std::string url, std::strin config.timeout_ms = this->timeout_; config.disable_auto_redirect = !this->follow_redirects_; config.max_redirection_count = this->redirect_limit_; + config.auth_type = HTTP_AUTH_TYPE_BASIC; #if CONFIG_MBEDTLS_CERTIFICATE_BUNDLE if (secure) { config.crt_bundle_attach = esp_crt_bundle_attach; From 4690e227b858a7d2a34cd53aa55645fc03b93878 Mon Sep 17 00:00:00 2001 From: irgendwienet Date: Mon, 22 Jul 2024 01:42:09 +0200 Subject: [PATCH 14/19] Fixes sml parser to process extended length lists with a number of items that is dividable by 16 (#6148) --- esphome/components/sml/sml_parser.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/esphome/components/sml/sml_parser.cpp b/esphome/components/sml/sml_parser.cpp index 3b23522b21..c782c0fc5e 100644 --- a/esphome/components/sml/sml_parser.cpp +++ b/esphome/components/sml/sml_parser.cpp @@ -27,7 +27,7 @@ bool SmlFile::setup_node(SmlNode *node) { uint8_t parse_length = length; if (has_extended_length) { length = (length << 4) + (this->buffer_[this->pos_ + 1] & 0x0f); - parse_length = length - 1; + parse_length = length; this->pos_ += 1; } @@ -37,7 +37,9 @@ bool SmlFile::setup_node(SmlNode *node) { node->type = type & 0x07; node->nodes.clear(); node->value_bytes.clear(); - if (this->buffer_[this->pos_] == 0x00) { // end of message + + // if the list is a has_extended_length list with e.g. 16 elements this is a 0x00 byte but not the end of message + if (!has_extended_length && this->buffer_[this->pos_] == 0x00) { // end of message this->pos_ += 1; } else if (is_list) { // list this->pos_ += 1; From 41813b0a1ffeada67d4c5abbdaf9e4ac6095be75 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 22 Jul 2024 12:35:06 +1200 Subject: [PATCH 15/19] Bump version to 2024.7.1 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index d581f68470..ff5f7b699d 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2024.7.0" +__version__ = "2024.7.1" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" VALID_SUBSTITUTIONS_CHARACTERS = ( From 0a7d8836339ffc59c37d3363414a827ff4cf42ec Mon Sep 17 00:00:00 2001 From: leejoow Date: Mon, 22 Jul 2024 03:33:11 +0200 Subject: [PATCH 16/19] [modbus_controller] Add on_command_sent trigger (#7078) Co-authored-by: Leo Schelvis --- .../components/modbus_controller/__init__.py | 28 +++++++++++++++++-- .../components/modbus_controller/automation.h | 19 +++++++++++++ esphome/components/modbus_controller/const.py | 1 + .../modbus_controller/modbus_controller.cpp | 8 ++++++ .../modbus_controller/modbus_controller.h | 3 ++ 5 files changed, 57 insertions(+), 2 deletions(-) create mode 100644 esphome/components/modbus_controller/automation.h diff --git a/esphome/components/modbus_controller/__init__.py b/esphome/components/modbus_controller/__init__.py index b8ab48fcc6..1d0f406783 100644 --- a/esphome/components/modbus_controller/__init__.py +++ b/esphome/components/modbus_controller/__init__.py @@ -1,8 +1,16 @@ import binascii import esphome.codegen as cg import esphome.config_validation as cv +from esphome import automation from esphome.components import modbus -from esphome.const import CONF_ADDRESS, CONF_ID, CONF_NAME, CONF_LAMBDA, CONF_OFFSET +from esphome.const import ( + CONF_ADDRESS, + CONF_ID, + CONF_NAME, + CONF_LAMBDA, + CONF_OFFSET, + CONF_TRIGGER_ID, +) from esphome.cpp_helpers import logging from .const import ( CONF_BITMASK, @@ -12,6 +20,7 @@ from .const import ( CONF_CUSTOM_COMMAND, CONF_FORCE_NEW_RANGE, CONF_MODBUS_CONTROLLER_ID, + CONF_ON_COMMAND_SENT, CONF_REGISTER_COUNT, CONF_REGISTER_TYPE, CONF_RESPONSE_SIZE, @@ -97,6 +106,10 @@ TYPE_REGISTER_MAP = { "FP32_R": 2, } +ModbusCommandSentTrigger = modbus_controller_ns.class_( + "ModbusCommandSentTrigger", automation.Trigger.template(cg.int_, cg.int_) +) + _LOGGER = logging.getLogger(__name__) ModbusServerRegisterSchema = cv.Schema( @@ -120,13 +133,19 @@ CONFIG_SCHEMA = cv.All( cv.Optional( CONF_SERVER_REGISTERS, ): cv.ensure_list(ModbusServerRegisterSchema), + cv.Optional(CONF_ON_COMMAND_SENT): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( + ModbusCommandSentTrigger + ), + } + ), } ) .extend(cv.polling_component_schema("60s")) .extend(modbus.modbus_device_schema(0x01)) ) - ModbusItemBaseSchema = cv.Schema( { cv.GenerateID(CONF_MODBUS_CONTROLLER_ID): cv.use_id(ModbusController), @@ -254,6 +273,11 @@ async def to_code(config): ) ) await register_modbus_device(var, config) + for conf in config.get(CONF_ON_COMMAND_SENT, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation( + trigger, [(int, "function_code"), (int, "address")], conf + ) async def register_modbus_device(var, config): diff --git a/esphome/components/modbus_controller/automation.h b/esphome/components/modbus_controller/automation.h new file mode 100644 index 0000000000..ad8de4b05d --- /dev/null +++ b/esphome/components/modbus_controller/automation.h @@ -0,0 +1,19 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/automation.h" +#include "esphome/components/modbus_controller/modbus_controller.h" + +namespace esphome { +namespace modbus_controller { + +class ModbusCommandSentTrigger : public Trigger { + public: + ModbusCommandSentTrigger(ModbusController *a_modbuscontroller) { + a_modbuscontroller->add_on_command_sent_callback( + [this](int function_code, int address) { this->trigger(function_code, address); }); + } +}; + +} // namespace modbus_controller +} // namespace esphome diff --git a/esphome/components/modbus_controller/const.py b/esphome/components/modbus_controller/const.py index 1a23640e17..1f5c39895c 100644 --- a/esphome/components/modbus_controller/const.py +++ b/esphome/components/modbus_controller/const.py @@ -6,6 +6,7 @@ CONF_CUSTOM_COMMAND = "custom_command" CONF_FORCE_NEW_RANGE = "force_new_range" CONF_MODBUS_CONTROLLER_ID = "modbus_controller_id" CONF_MODBUS_FUNCTIONCODE = "modbus_functioncode" +CONF_ON_COMMAND_SENT = "on_command_sent" CONF_RAW_ENCODE = "raw_encode" CONF_REGISTER_COUNT = "register_count" CONF_REGISTER_TYPE = "register_type" diff --git a/esphome/components/modbus_controller/modbus_controller.cpp b/esphome/components/modbus_controller/modbus_controller.cpp index 29d3137603..378e5c06c0 100644 --- a/esphome/components/modbus_controller/modbus_controller.cpp +++ b/esphome/components/modbus_controller/modbus_controller.cpp @@ -43,7 +43,11 @@ bool ModbusController::send_next_command_() { ESP_LOGV(TAG, "Sending next modbus command to device %d register 0x%02X count %d", this->address_, command->register_address, command->register_count); command->send(); + this->last_command_timestamp_ = millis(); + + this->command_sent_callback_.call((int) command->function_code, command->register_address); + // remove from queue if no handler is defined if (!command->on_data_func) { command_queue_.pop_front(); @@ -659,5 +663,9 @@ int64_t payload_to_number(const std::vector &data, SensorValueType sens return value; } +void ModbusController::add_on_command_sent_callback(std::function &&callback) { + this->command_sent_callback_.add(std::move(callback)); +} + } // namespace modbus_controller } // namespace esphome diff --git a/esphome/components/modbus_controller/modbus_controller.h b/esphome/components/modbus_controller/modbus_controller.h index 9b7d59c93f..3bc11da879 100644 --- a/esphome/components/modbus_controller/modbus_controller.h +++ b/esphome/components/modbus_controller/modbus_controller.h @@ -456,6 +456,8 @@ class ModbusController : public PollingComponent, public modbus::ModbusDevice { size_t get_command_queue_length() { return command_queue_.size(); } /// get if the module is offline, didn't respond the last command bool get_module_offline() { return module_offline_; } + /// Set callback for commands + void add_on_command_sent_callback(std::function &&callback); protected: /// parse sensormap_ and create range of sequential addresses @@ -488,6 +490,7 @@ class ModbusController : public PollingComponent, public modbus::ModbusDevice { bool module_offline_; /// how many updates to skip if module is offline uint16_t offline_skip_updates_; + CallbackManager command_sent_callback_{}; }; /** Convert vector response payload to float. From 8fc42694f69743561ee5d4474d768aeb2be9d35b Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Mon, 22 Jul 2024 11:42:25 +1000 Subject: [PATCH 17/19] [ili9xxx] Rework delay handling (#7115) --- esphome/components/ili9xxx/ili9xxx_defines.h | 4 ++- .../components/ili9xxx/ili9xxx_display.cpp | 31 ++++++------------- esphome/components/ili9xxx/ili9xxx_display.h | 12 +++---- esphome/components/ili9xxx/ili9xxx_init.h | 8 ++--- 4 files changed, 21 insertions(+), 34 deletions(-) diff --git a/esphome/components/ili9xxx/ili9xxx_defines.h b/esphome/components/ili9xxx/ili9xxx_defines.h index 744013db3d..f4c5aad957 100644 --- a/esphome/components/ili9xxx/ili9xxx_defines.h +++ b/esphome/components/ili9xxx/ili9xxx_defines.h @@ -92,7 +92,9 @@ static const uint8_t ILI9XXX_GMCTRN1 = 0xE1; static const uint8_t ILI9XXX_CSCON = 0xF0; static const uint8_t ILI9XXX_ADJCTL3 = 0xF7; -static const uint8_t ILI9XXX_DELAY = 0xFF; // followed by one byte of delay time in ms +static const uint8_t ILI9XXX_DELAY_FLAG = 0xFF; +// special marker for delay - command byte reprents ms, length byte is an impossible value +#define ILI9XXX_DELAY(ms) ((uint8_t) ((ms) | 0x80)), ILI9XXX_DELAY_FLAG } // namespace ili9xxx } // namespace esphome diff --git a/esphome/components/ili9xxx/ili9xxx_display.cpp b/esphome/components/ili9xxx/ili9xxx_display.cpp index de03df5d41..4f035edbb0 100644 --- a/esphome/components/ili9xxx/ili9xxx_display.cpp +++ b/esphome/components/ili9xxx/ili9xxx_display.cpp @@ -34,8 +34,8 @@ void ILI9XXXDisplay::setup() { ESP_LOGD(TAG, "Setting up ILI9xxx"); this->setup_pins_(); - this->init_lcd(this->init_sequence_); - this->init_lcd(this->extra_init_sequence_.data()); + this->init_lcd_(this->init_sequence_); + this->init_lcd_(this->extra_init_sequence_.data()); switch (this->pixel_mode_) { case PIXEL_MODE_16: if (this->is_18bitdisplay_) { @@ -405,42 +405,29 @@ void ILI9XXXDisplay::reset_() { } } -void ILI9XXXDisplay::init_lcd(const uint8_t *addr) { +void ILI9XXXDisplay::init_lcd_(const uint8_t *addr) { if (addr == nullptr) return; uint8_t cmd, x, num_args; while ((cmd = *addr++) != 0) { x = *addr++; - if (cmd == ILI9XXX_DELAY) { - ESP_LOGD(TAG, "Delay %dms", x); - delay(x); + if (x == ILI9XXX_DELAY_FLAG) { + cmd &= 0x7F; + ESP_LOGV(TAG, "Delay %dms", cmd); + delay(cmd); } else { num_args = x & 0x7F; - ESP_LOGD(TAG, "Command %02X, length %d, bits %02X", cmd, num_args, *addr); + ESP_LOGV(TAG, "Command %02X, length %d, bits %02X", cmd, num_args, *addr); this->send_command(cmd, addr, num_args); addr += num_args; if (x & 0x80) { - ESP_LOGD(TAG, "Delay 150ms"); + ESP_LOGV(TAG, "Delay 150ms"); delay(150); // NOLINT } } } } -void ILI9XXXGC9A01A::init_lcd(const uint8_t *addr) { - if (addr == nullptr) - return; - uint8_t cmd, x, num_args; - while ((cmd = *addr++) != 0) { - x = *addr++; - num_args = x & 0x7F; - this->send_command(cmd, addr, num_args); - addr += num_args; - if (x & 0x80) - delay(150); // NOLINT - } -} - // Tell the display controller where we want to draw pixels. void ILI9XXXDisplay::set_addr_window_(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2) { x1 += this->offset_x_; diff --git a/esphome/components/ili9xxx/ili9xxx_display.h b/esphome/components/ili9xxx/ili9xxx_display.h index b60047a8c3..6121488d15 100644 --- a/esphome/components/ili9xxx/ili9xxx_display.h +++ b/esphome/components/ili9xxx/ili9xxx_display.h @@ -33,7 +33,9 @@ class ILI9XXXDisplay : public display::DisplayBuffer, uint8_t cmd, num_args, bits; const uint8_t *addr = init_sequence; while ((cmd = *addr++) != 0) { - num_args = *addr++ & 0x7F; + num_args = *addr++; + if (num_args == ILI9XXX_DELAY_FLAG) + continue; bits = *addr; switch (cmd) { case ILI9XXX_MADCTL: { @@ -50,13 +52,10 @@ class ILI9XXXDisplay : public display::DisplayBuffer, break; } - case ILI9XXX_DELAY: - continue; // no args to skip - default: break; } - addr += num_args; + addr += (num_args & 0x7F); } } @@ -109,7 +108,7 @@ class ILI9XXXDisplay : public display::DisplayBuffer, virtual void set_madctl(); void display_(); - virtual void init_lcd(const uint8_t *addr); + void init_lcd_(const uint8_t *addr); void set_addr_window_(uint16_t x, uint16_t y, uint16_t x2, uint16_t y2); void reset_(); @@ -269,7 +268,6 @@ class ILI9XXXS3BoxLite : public ILI9XXXDisplay { class ILI9XXXGC9A01A : public ILI9XXXDisplay { public: ILI9XXXGC9A01A() : ILI9XXXDisplay(INITCMD_GC9A01A, 240, 240, true) {} - void init_lcd(const uint8_t *addr) override; }; //----------- ILI9XXX_24_TFT display -------------- diff --git a/esphome/components/ili9xxx/ili9xxx_init.h b/esphome/components/ili9xxx/ili9xxx_init.h index 260bde4c80..5a67812bc1 100644 --- a/esphome/components/ili9xxx/ili9xxx_init.h +++ b/esphome/components/ili9xxx/ili9xxx_init.h @@ -372,9 +372,9 @@ static const uint8_t PROGMEM INITCMD_GC9A01A[] = { static const uint8_t PROGMEM INITCMD_ST7735[] = { ILI9XXX_SWRESET, 0, // Soft reset, then delay 10ms - ILI9XXX_DELAY, 10, + ILI9XXX_DELAY(10), ILI9XXX_SLPOUT , 0, // Exit Sleep, delay - ILI9XXX_DELAY, 10, + ILI9XXX_DELAY(10), ILI9XXX_PIXFMT , 1, 0x05, ILI9XXX_FRMCTR1, 3, // 4: Frame rate control, 3 args + delay: 0x01, 0x2C, 0x2D, // Rate = fosc/(1x2+40) * (LINE+2C+2D) @@ -415,9 +415,9 @@ static const uint8_t PROGMEM INITCMD_ST7735[] = { 0x00, 0x00, 0x02, 0x10, ILI9XXX_MADCTL , 1, 0x00, // Memory Access Control, BGR ILI9XXX_NORON , 0, - ILI9XXX_DELAY, 10, + ILI9XXX_DELAY(10), ILI9XXX_DISPON , 0, // Display on - ILI9XXX_DELAY, 10, + ILI9XXX_DELAY(10), 00, // endo of list }; From 5d5f3276e9ec73f8463554137ee67f02aa4d91e5 Mon Sep 17 00:00:00 2001 From: Jan-Philipp Benecke Date: Mon, 22 Jul 2024 06:20:09 +0200 Subject: [PATCH 18/19] Inherit `esp32_ble_beacon` from `esp32_ble` (#6908) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/esp32_ble/__init__.py | 19 ++- esphome/components/esp32_ble/ble.cpp | 10 +- esphome/components/esp32_ble/ble.h | 9 + .../components/esp32_ble/ble_advertising.cpp | 51 +++++- .../components/esp32_ble/ble_advertising.h | 22 ++- .../components/esp32_ble_beacon/__init__.py | 18 +- .../esp32_ble_beacon/esp32_ble_beacon.cpp | 160 +++++------------- .../esp32_ble_beacon/esp32_ble_beacon.h | 36 ++-- .../components/esp32_ble_server/__init__.py | 1 - esphome/components/esp32_improv/__init__.py | 1 - 10 files changed, 176 insertions(+), 151 deletions(-) diff --git a/esphome/components/esp32_ble/__init__.py b/esphome/components/esp32_ble/__init__.py index d88161e3e0..472669a381 100644 --- a/esphome/components/esp32_ble/__init__.py +++ b/esphome/components/esp32_ble/__init__.py @@ -7,10 +7,10 @@ from esphome.components.esp32 import add_idf_sdkconfig_option, get_esp32_variant DEPENDENCIES = ["esp32"] CODEOWNERS = ["@jesserockz", "@Rapsssito"] -CONFLICTS_WITH = ["esp32_ble_beacon"] CONF_BLE_ID = "ble_id" CONF_IO_CAPABILITY = "io_capability" +CONF_ADVERTISING_CYCLE_TIME = "advertising_cycle_time" NO_BLUETOOTH_VARIANTS = [const.VARIANT_ESP32S2] @@ -34,6 +34,19 @@ IO_CAPABILITY = { "display_yes_no": IoCapability.IO_CAP_IO, } +esp_power_level_t = cg.global_ns.enum("esp_power_level_t") + +TX_POWER_LEVELS = { + -12: esp_power_level_t.ESP_PWR_LVL_N12, + -9: esp_power_level_t.ESP_PWR_LVL_N9, + -6: esp_power_level_t.ESP_PWR_LVL_N6, + -3: esp_power_level_t.ESP_PWR_LVL_N3, + 0: esp_power_level_t.ESP_PWR_LVL_N0, + 3: esp_power_level_t.ESP_PWR_LVL_P3, + 6: esp_power_level_t.ESP_PWR_LVL_P6, + 9: esp_power_level_t.ESP_PWR_LVL_P9, +} + CONFIG_SCHEMA = cv.Schema( { cv.GenerateID(): cv.declare_id(ESP32BLE), @@ -41,6 +54,9 @@ CONFIG_SCHEMA = cv.Schema( IO_CAPABILITY, lower=True ), cv.Optional(CONF_ENABLE_ON_BOOT, default=True): cv.boolean, + cv.Optional( + CONF_ADVERTISING_CYCLE_TIME, default="10s" + ): cv.positive_time_period_milliseconds, } ).extend(cv.COMPONENT_SCHEMA) @@ -58,6 +74,7 @@ async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) cg.add(var.set_enable_on_boot(config[CONF_ENABLE_ON_BOOT])) cg.add(var.set_io_capability(config[CONF_IO_CAPABILITY])) + cg.add(var.set_advertising_cycle_time(config[CONF_ADVERTISING_CYCLE_TIME])) await cg.register_component(var, config) if CORE.using_esp_idf: diff --git a/esphome/components/esp32_ble/ble.cpp b/esphome/components/esp32_ble/ble.cpp index ceb6516a02..5d08b6e973 100644 --- a/esphome/components/esp32_ble/ble.cpp +++ b/esphome/components/esp32_ble/ble.cpp @@ -78,6 +78,11 @@ void ESP32BLE::advertising_set_manufacturer_data(const std::vector &dat this->advertising_start(); } +void ESP32BLE::advertising_register_raw_advertisement_callback(std::function &&callback) { + this->advertising_init_(); + this->advertising_->register_raw_advertisement_callback(std::move(callback)); +} + void ESP32BLE::advertising_add_service_uuid(ESPBTUUID uuid) { this->advertising_init_(); this->advertising_->add_service_uuid(uuid); @@ -102,7 +107,7 @@ bool ESP32BLE::ble_pre_setup_() { void ESP32BLE::advertising_init_() { if (this->advertising_ != nullptr) return; - this->advertising_ = new BLEAdvertising(); // NOLINT(cppcoreguidelines-owning-memory) + this->advertising_ = new BLEAdvertising(this->advertising_cycle_time_); // NOLINT(cppcoreguidelines-owning-memory) this->advertising_->set_scan_response(true); this->advertising_->set_min_preferred_interval(0x06); @@ -312,6 +317,9 @@ void ESP32BLE::loop() { delete ble_event; // NOLINT(cppcoreguidelines-owning-memory) ble_event = this->ble_events_.pop(); } + if (this->advertising_ != nullptr) { + this->advertising_->loop(); + } } void ESP32BLE::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) { diff --git a/esphome/components/esp32_ble/ble.h b/esphome/components/esp32_ble/ble.h index 023960d6e4..7c55583852 100644 --- a/esphome/components/esp32_ble/ble.h +++ b/esphome/components/esp32_ble/ble.h @@ -3,6 +3,8 @@ #include "ble_advertising.h" #include "ble_uuid.h" +#include + #include "esphome/core/automation.h" #include "esphome/core/component.h" #include "esphome/core/defines.h" @@ -76,6 +78,11 @@ class ESP32BLE : public Component { public: void set_io_capability(IoCapability io_capability) { this->io_cap_ = (esp_ble_io_cap_t) io_capability; } + void set_advertising_cycle_time(uint32_t advertising_cycle_time) { + this->advertising_cycle_time_ = advertising_cycle_time; + } + uint32_t get_advertising_cycle_time() const { return this->advertising_cycle_time_; } + void enable(); void disable(); bool is_active(); @@ -89,6 +96,7 @@ class ESP32BLE : public Component { void advertising_set_manufacturer_data(const std::vector &data); void advertising_add_service_uuid(ESPBTUUID uuid); void advertising_remove_service_uuid(ESPBTUUID uuid); + void advertising_register_raw_advertisement_callback(std::function &&callback); void register_gap_event_handler(GAPEventHandler *handler) { this->gap_event_handlers_.push_back(handler); } void register_gattc_event_handler(GATTcEventHandler *handler) { this->gattc_event_handlers_.push_back(handler); } @@ -121,6 +129,7 @@ class ESP32BLE : public Component { Queue ble_events_; BLEAdvertising *advertising_; esp_ble_io_cap_t io_cap_{ESP_IO_CAP_NONE}; + uint32_t advertising_cycle_time_; bool enable_on_boot_; }; diff --git a/esphome/components/esp32_ble/ble_advertising.cpp b/esphome/components/esp32_ble/ble_advertising.cpp index 59d2398829..1d340c76d9 100644 --- a/esphome/components/esp32_ble/ble_advertising.cpp +++ b/esphome/components/esp32_ble/ble_advertising.cpp @@ -10,9 +10,9 @@ namespace esphome { namespace esp32_ble { -static const char *const TAG = "esp32_ble"; +static const char *const TAG = "esp32_ble.advertising"; -BLEAdvertising::BLEAdvertising() { +BLEAdvertising::BLEAdvertising(uint32_t advertising_cycle_time) : advertising_cycle_time_(advertising_cycle_time) { this->advertising_data_.set_scan_rsp = false; this->advertising_data_.include_name = true; this->advertising_data_.include_txpower = true; @@ -64,7 +64,7 @@ void BLEAdvertising::set_manufacturer_data(const std::vector &data) { } } -void BLEAdvertising::start() { +esp_err_t BLEAdvertising::services_advertisement_() { int num_services = this->advertising_uuids_.size(); if (num_services == 0) { this->advertising_data_.service_uuid_len = 0; @@ -87,8 +87,8 @@ void BLEAdvertising::start() { this->advertising_data_.include_txpower = !this->scan_response_; err = esp_ble_gap_config_adv_data(&this->advertising_data_); if (err != ESP_OK) { - ESP_LOGE(TAG, "esp_ble_gap_config_adv_data failed (Advertising): %d", err); - return; + ESP_LOGE(TAG, "esp_ble_gap_config_adv_data failed (Advertising): %s", esp_err_to_name(err)); + return err; } if (this->scan_response_) { @@ -101,8 +101,8 @@ void BLEAdvertising::start() { this->scan_response_data_.flag = 0; err = esp_ble_gap_config_adv_data(&this->scan_response_data_); if (err != ESP_OK) { - ESP_LOGE(TAG, "esp_ble_gap_config_adv_data failed (Scan response): %d", err); - return; + ESP_LOGE(TAG, "esp_ble_gap_config_adv_data failed (Scan response): %s", esp_err_to_name(err)); + return err; } } @@ -113,8 +113,18 @@ void BLEAdvertising::start() { err = esp_ble_gap_start_advertising(&this->advertising_params_); if (err != ESP_OK) { - ESP_LOGE(TAG, "esp_ble_gap_start_advertising failed: %d", err); - return; + ESP_LOGE(TAG, "esp_ble_gap_start_advertising failed: %s", esp_err_to_name(err)); + return err; + } + + return ESP_OK; +} + +void BLEAdvertising::start() { + if (this->current_adv_index_ == -1) { + this->services_advertisement_(); + } else { + this->raw_advertisements_callbacks_[this->current_adv_index_](true); } } @@ -124,6 +134,29 @@ void BLEAdvertising::stop() { ESP_LOGE(TAG, "esp_ble_gap_stop_advertising failed: %d", err); return; } + if (this->current_adv_index_ != -1) { + this->raw_advertisements_callbacks_[this->current_adv_index_](false); + } +} + +void BLEAdvertising::loop() { + if (this->raw_advertisements_callbacks_.empty()) { + return; + } + const uint32_t now = millis(); + if (now - this->last_advertisement_time_ > this->advertising_cycle_time_) { + this->stop(); + this->current_adv_index_ += 1; + if (this->current_adv_index_ >= this->raw_advertisements_callbacks_.size()) { + this->current_adv_index_ = -1; + } + this->start(); + this->last_advertisement_time_ = now; + } +} + +void BLEAdvertising::register_raw_advertisement_callback(std::function &&callback) { + this->raw_advertisements_callbacks_.push_back(std::move(callback)); } } // namespace esp32_ble diff --git a/esphome/components/esp32_ble/ble_advertising.h b/esphome/components/esp32_ble/ble_advertising.h index 16a7dd1d8e..946e414c1d 100644 --- a/esphome/components/esp32_ble/ble_advertising.h +++ b/esphome/components/esp32_ble/ble_advertising.h @@ -1,20 +1,31 @@ #pragma once +#include +#include #include #ifdef USE_ESP32 +#include #include #include namespace esphome { namespace esp32_ble { +using raw_adv_data_t = struct { + uint8_t *data; + size_t length; + esp_power_level_t power_level; +}; + class ESPBTUUID; class BLEAdvertising { public: - BLEAdvertising(); + BLEAdvertising(uint32_t advertising_cycle_time); + + void loop(); void add_service_uuid(ESPBTUUID uuid); void remove_service_uuid(ESPBTUUID uuid); @@ -22,16 +33,25 @@ class BLEAdvertising { void set_min_preferred_interval(uint16_t interval) { this->advertising_data_.min_interval = interval; } void set_manufacturer_data(const std::vector &data); void set_service_data(const std::vector &data); + void register_raw_advertisement_callback(std::function &&callback); void start(); void stop(); protected: + esp_err_t services_advertisement_(); + bool scan_response_; esp_ble_adv_data_t advertising_data_; esp_ble_adv_data_t scan_response_data_; esp_ble_adv_params_t advertising_params_; std::vector advertising_uuids_; + + std::vector> raw_advertisements_callbacks_; + + const uint32_t advertising_cycle_time_; + uint32_t last_advertisement_time_{0}; + int8_t current_adv_index_{-1}; // -1 means standard scan response }; } // namespace esp32_ble diff --git a/esphome/components/esp32_ble_beacon/__init__.py b/esphome/components/esp32_ble_beacon/__init__.py index 9aac48cbb2..d063209478 100644 --- a/esphome/components/esp32_ble_beacon/__init__.py +++ b/esphome/components/esp32_ble_beacon/__init__.py @@ -1,16 +1,21 @@ import esphome.codegen as cg import esphome.config_validation as cv +from esphome.components.esp32_ble import CONF_BLE_ID from esphome.const import CONF_ID, CONF_TYPE, CONF_UUID, CONF_TX_POWER from esphome.core import CORE, TimePeriod from esphome.components.esp32 import add_idf_sdkconfig_option from esphome.components import esp32_ble +AUTO_LOAD = ["esp32_ble"] DEPENDENCIES = ["esp32"] -CONFLICTS_WITH = ["esp32_ble_tracker"] esp32_ble_beacon_ns = cg.esphome_ns.namespace("esp32_ble_beacon") -ESP32BLEBeacon = esp32_ble_beacon_ns.class_("ESP32BLEBeacon", cg.Component) - +ESP32BLEBeacon = esp32_ble_beacon_ns.class_( + "ESP32BLEBeacon", + cg.Component, + esp32_ble.GAPEventHandler, + cg.Parented.template(esp32_ble.ESP32BLE), +) CONF_MAJOR = "major" CONF_MINOR = "minor" CONF_MIN_INTERVAL = "min_interval" @@ -28,6 +33,7 @@ CONFIG_SCHEMA = cv.All( cv.Schema( { cv.GenerateID(): cv.declare_id(ESP32BLEBeacon), + cv.GenerateID(CONF_BLE_ID): cv.use_id(esp32_ble.ESP32BLE), cv.Required(CONF_TYPE): cv.one_of("IBEACON", upper=True), cv.Required(CONF_UUID): cv.uuid, cv.Optional(CONF_MAJOR, default=10167): cv.uint16_t, @@ -48,7 +54,7 @@ CONFIG_SCHEMA = cv.All( min=-128, max=0 ), cv.Optional(CONF_TX_POWER, default="3dBm"): cv.All( - cv.decibel, cv.one_of(-12, -9, -6, -3, 0, 3, 6, 9, int=True) + cv.decibel, cv.enum(esp32_ble.TX_POWER_LEVELS, int=True) ), } ).extend(cv.COMPONENT_SCHEMA), @@ -62,6 +68,10 @@ async def to_code(config): uuid = config[CONF_UUID].hex uuid_arr = [cg.RawExpression(f"0x{uuid[i:i + 2]}") for i in range(0, len(uuid), 2)] var = cg.new_Pvariable(config[CONF_ID], uuid_arr) + + parent = await cg.get_variable(config[esp32_ble.CONF_BLE_ID]) + cg.add(parent.register_gap_event_handler(var)) + await cg.register_component(var, config) cg.add(var.set_major(config[CONF_MAJOR])) cg.add(var.set_minor(config[CONF_MINOR])) diff --git a/esphome/components/esp32_ble_beacon/esp32_ble_beacon.cpp b/esphome/components/esp32_ble_beacon/esp32_ble_beacon.cpp index 589fcc1e82..423fe61592 100644 --- a/esphome/components/esp32_ble_beacon/esp32_ble_beacon.cpp +++ b/esphome/components/esp32_ble_beacon/esp32_ble_beacon.cpp @@ -3,14 +3,16 @@ #ifdef USE_ESP32 -#include -#include -#include #include -#include +#include #include +#include +#include +#include #include + #include "esphome/core/hal.h" +#include "esphome/core/helpers.h" #ifdef USE_ARDUINO #include @@ -21,20 +23,6 @@ namespace esp32_ble_beacon { static const char *const TAG = "esp32_ble_beacon"; -// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) -static esp_ble_adv_params_t ble_adv_params = { - .adv_int_min = 0x20, - .adv_int_max = 0x40, - .adv_type = ADV_TYPE_NONCONN_IND, - .own_addr_type = BLE_ADDR_TYPE_PUBLIC, - .peer_addr = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, - .peer_addr_type = BLE_ADDR_TYPE_PUBLIC, - .channel_map = ADV_CHNL_ALL, - .adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY, -}; - -#define ENDIAN_CHANGE_U16(x) ((((x) &0xFF00) >> 8) + (((x) &0xFF) << 8)) - static const esp_ble_ibeacon_head_t IBEACON_COMMON_HEAD = { .flags = {0x02, 0x01, 0x06}, .length = 0x1A, .type = 0xFF, .company_id = {0x4C, 0x00}, .beacon_type = {0x02, 0x15}}; @@ -53,117 +41,62 @@ void ESP32BLEBeacon::dump_config() { " UUID: %s, Major: %u, Minor: %u, Min Interval: %ums, Max Interval: %ums, Measured Power: %d" ", TX Power: %ddBm", uuid, this->major_, this->minor_, this->min_interval_, this->max_interval_, this->measured_power_, - this->tx_power_); + (this->tx_power_ * 3) - 12); } +float ESP32BLEBeacon::get_setup_priority() const { return setup_priority::AFTER_BLUETOOTH; } + void ESP32BLEBeacon::setup() { - ESP_LOGCONFIG(TAG, "Setting up ESP32 BLE beacon..."); - global_esp32_ble_beacon = this; + this->ble_adv_params_ = { + .adv_int_min = static_cast(this->min_interval_ / 0.625f), + .adv_int_max = static_cast(this->max_interval_ / 0.625f), + .adv_type = ADV_TYPE_NONCONN_IND, + .own_addr_type = BLE_ADDR_TYPE_PUBLIC, + .peer_addr = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + .peer_addr_type = BLE_ADDR_TYPE_PUBLIC, + .channel_map = ADV_CHNL_ALL, + .adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY, + }; - xTaskCreatePinnedToCore(ESP32BLEBeacon::ble_core_task, - "ble_task", // name - 10000, // stack size (in words) - nullptr, // input params - 1, // priority - nullptr, // Handle, not needed - 0 // core - ); + global_ble->advertising_register_raw_advertisement_callback([this](bool advertise) { + this->advertising_ = advertise; + if (advertise) { + this->on_advertise_(); + } + }); } -float ESP32BLEBeacon::get_setup_priority() const { return setup_priority::BLUETOOTH; } -void ESP32BLEBeacon::ble_core_task(void *params) { - ble_setup(); - - while (true) { - delay(1000); // NOLINT - } -} - -void ESP32BLEBeacon::ble_setup() { - ble_adv_params.adv_int_min = static_cast(global_esp32_ble_beacon->min_interval_ / 0.625f); - ble_adv_params.adv_int_max = static_cast(global_esp32_ble_beacon->max_interval_ / 0.625f); - - // Initialize non-volatile storage for the bluetooth controller - esp_err_t err = nvs_flash_init(); - if (err != ESP_OK) { - ESP_LOGE(TAG, "nvs_flash_init failed: %d", err); - return; - } - -#ifdef USE_ARDUINO - if (!btStart()) { - ESP_LOGE(TAG, "btStart failed: %d", esp_bt_controller_get_status()); - return; - } -#else - if (esp_bt_controller_get_status() != ESP_BT_CONTROLLER_STATUS_ENABLED) { - // start bt controller - if (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_IDLE) { - esp_bt_controller_config_t cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT(); - err = esp_bt_controller_init(&cfg); - if (err != ESP_OK) { - ESP_LOGE(TAG, "esp_bt_controller_init failed: %s", esp_err_to_name(err)); - return; - } - while (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_IDLE) - ; - } - if (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_INITED) { - err = esp_bt_controller_enable(ESP_BT_MODE_BLE); - if (err != ESP_OK) { - ESP_LOGE(TAG, "esp_bt_controller_enable failed: %s", esp_err_to_name(err)); - return; - } - } - if (esp_bt_controller_get_status() != ESP_BT_CONTROLLER_STATUS_ENABLED) { - ESP_LOGE(TAG, "esp bt controller enable failed"); - return; - } - } -#endif - - esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT); - - err = esp_bluedroid_init(); - if (err != ESP_OK) { - ESP_LOGE(TAG, "esp_bluedroid_init failed: %d", err); - return; - } - err = esp_bluedroid_enable(); - if (err != ESP_OK) { - ESP_LOGE(TAG, "esp_bluedroid_enable failed: %d", err); - return; - } - err = esp_ble_tx_power_set(ESP_BLE_PWR_TYPE_ADV, - static_cast((global_esp32_ble_beacon->tx_power_ + 12) / 3)); - if (err != ESP_OK) { - ESP_LOGE(TAG, "esp_ble_tx_power_set failed: %s", esp_err_to_name(err)); - return; - } - err = esp_ble_gap_register_callback(ESP32BLEBeacon::gap_event_handler); - if (err != ESP_OK) { - ESP_LOGE(TAG, "esp_ble_gap_register_callback failed: %d", err); - return; - } - +void ESP32BLEBeacon::on_advertise_() { esp_ble_ibeacon_t ibeacon_adv_data; memcpy(&ibeacon_adv_data.ibeacon_head, &IBEACON_COMMON_HEAD, sizeof(esp_ble_ibeacon_head_t)); - memcpy(&ibeacon_adv_data.ibeacon_vendor.proximity_uuid, global_esp32_ble_beacon->uuid_.data(), + memcpy(&ibeacon_adv_data.ibeacon_vendor.proximity_uuid, this->uuid_.data(), sizeof(ibeacon_adv_data.ibeacon_vendor.proximity_uuid)); - ibeacon_adv_data.ibeacon_vendor.minor = ENDIAN_CHANGE_U16(global_esp32_ble_beacon->minor_); - ibeacon_adv_data.ibeacon_vendor.major = ENDIAN_CHANGE_U16(global_esp32_ble_beacon->major_); - ibeacon_adv_data.ibeacon_vendor.measured_power = static_cast(global_esp32_ble_beacon->measured_power_); + ibeacon_adv_data.ibeacon_vendor.minor = byteswap(this->minor_); + ibeacon_adv_data.ibeacon_vendor.major = byteswap(this->major_); + ibeacon_adv_data.ibeacon_vendor.measured_power = static_cast(this->measured_power_); - esp_ble_gap_config_adv_data_raw((uint8_t *) &ibeacon_adv_data, sizeof(ibeacon_adv_data)); + ESP_LOGD(TAG, "Setting BLE TX power"); + esp_err_t err = esp_ble_tx_power_set(ESP_BLE_PWR_TYPE_ADV, this->tx_power_); + if (err != ESP_OK) { + ESP_LOGW(TAG, "esp_ble_tx_power_set failed: %s", esp_err_to_name(err)); + } + err = esp_ble_gap_config_adv_data_raw((uint8_t *) &ibeacon_adv_data, sizeof(ibeacon_adv_data)); + if (err != ESP_OK) { + ESP_LOGE(TAG, "esp_ble_gap_config_adv_data_raw failed: %s", esp_err_to_name(err)); + return; + } } void ESP32BLEBeacon::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) { + if (!this->advertising_) + return; + esp_err_t err; switch (event) { case ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT: { - err = esp_ble_gap_start_advertising(&ble_adv_params); + err = esp_ble_gap_start_advertising(&this->ble_adv_params_); if (err != ESP_OK) { - ESP_LOGE(TAG, "esp_ble_gap_start_advertising failed: %d", err); + ESP_LOGE(TAG, "esp_ble_gap_start_advertising failed: %s", esp_err_to_name(err)); } break; } @@ -181,6 +114,7 @@ void ESP32BLEBeacon::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap } else { ESP_LOGD(TAG, "BLE stopped advertising successfully"); } + // this->advertising_ = false; break; } default: @@ -188,8 +122,6 @@ void ESP32BLEBeacon::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap } } -ESP32BLEBeacon *global_esp32_ble_beacon = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) - } // namespace esp32_ble_beacon } // namespace esphome diff --git a/esphome/components/esp32_ble_beacon/esp32_ble_beacon.h b/esphome/components/esp32_ble_beacon/esp32_ble_beacon.h index 5208b67ea3..e37edf6cde 100644 --- a/esphome/components/esp32_ble_beacon/esp32_ble_beacon.h +++ b/esphome/components/esp32_ble_beacon/esp32_ble_beacon.h @@ -1,39 +1,39 @@ #pragma once +#include "esphome/components/esp32_ble/ble.h" #include "esphome/core/component.h" #ifdef USE_ESP32 -#include #include +#include namespace esphome { namespace esp32_ble_beacon { -// NOLINTNEXTLINE(modernize-use-using) -typedef struct { +using esp_ble_ibeacon_head_t = struct { uint8_t flags[3]; uint8_t length; uint8_t type; uint8_t company_id[2]; uint8_t beacon_type[2]; -} __attribute__((packed)) esp_ble_ibeacon_head_t; +} __attribute__((packed)); -// NOLINTNEXTLINE(modernize-use-using) -typedef struct { +using esp_ble_ibeacon_vendor_t = struct { uint8_t proximity_uuid[16]; uint16_t major; uint16_t minor; uint8_t measured_power; -} __attribute__((packed)) esp_ble_ibeacon_vendor_t; +} __attribute__((packed)); -// NOLINTNEXTLINE(modernize-use-using) -typedef struct { +using esp_ble_ibeacon_t = struct { esp_ble_ibeacon_head_t ibeacon_head; esp_ble_ibeacon_vendor_t ibeacon_vendor; -} __attribute__((packed)) esp_ble_ibeacon_t; +} __attribute__((packed)); -class ESP32BLEBeacon : public Component { +using namespace esp32_ble; + +class ESP32BLEBeacon : public Component, public GAPEventHandler, public Parented { public: explicit ESP32BLEBeacon(const std::array &uuid) : uuid_(uuid) {} @@ -46,12 +46,11 @@ class ESP32BLEBeacon : public Component { void set_min_interval(uint16_t val) { this->min_interval_ = val; } void set_max_interval(uint16_t val) { this->max_interval_ = val; } void set_measured_power(int8_t val) { this->measured_power_ = val; } - void set_tx_power(int8_t val) { this->tx_power_ = val; } + void set_tx_power(esp_power_level_t val) { this->tx_power_ = val; } + void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override; protected: - static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param); - static void ble_core_task(void *params); - static void ble_setup(); + void on_advertise_(); std::array uuid_; uint16_t major_{}; @@ -59,12 +58,11 @@ class ESP32BLEBeacon : public Component { uint16_t min_interval_{}; uint16_t max_interval_{}; int8_t measured_power_{}; - int8_t tx_power_{}; + esp_power_level_t tx_power_{}; + esp_ble_adv_params_t ble_adv_params_; + bool advertising_{false}; }; -// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) -extern ESP32BLEBeacon *global_esp32_ble_beacon; - } // namespace esp32_ble_beacon } // namespace esphome diff --git a/esphome/components/esp32_ble_server/__init__.py b/esphome/components/esp32_ble_server/__init__.py index f53c9450f4..ce9fdc2cf3 100644 --- a/esphome/components/esp32_ble_server/__init__.py +++ b/esphome/components/esp32_ble_server/__init__.py @@ -7,7 +7,6 @@ from esphome.components.esp32 import add_idf_sdkconfig_option AUTO_LOAD = ["esp32_ble"] CODEOWNERS = ["@jesserockz", "@clydebarrow", "@Rapsssito"] -CONFLICTS_WITH = ["esp32_ble_beacon"] DEPENDENCIES = ["esp32"] CONF_MANUFACTURER = "manufacturer" diff --git a/esphome/components/esp32_improv/__init__.py b/esphome/components/esp32_improv/__init__.py index 49d95d89e5..62d9cd376c 100644 --- a/esphome/components/esp32_improv/__init__.py +++ b/esphome/components/esp32_improv/__init__.py @@ -6,7 +6,6 @@ from esphome.const import CONF_ID AUTO_LOAD = ["esp32_ble_server"] CODEOWNERS = ["@jesserockz"] -CONFLICTS_WITH = ["esp32_ble_beacon"] DEPENDENCIES = ["wifi", "esp32"] CONF_AUTHORIZED_DURATION = "authorized_duration" From f1aa254e4810999d7bb70619ba9c09bb9be5cf98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aodren=20Auffr=C3=A9dou-Heinicke?= <54121510+aodrenah@users.noreply.github.com> Date: Sun, 21 Jul 2024 22:29:54 -0700 Subject: [PATCH 19/19] APDS9306 Ambient Light Sensor (#6709) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Co-authored-by: Keith Burzinski Co-authored-by: Mat931 <49403702+Mat931@users.noreply.github.com> --- CODEOWNERS | 1 + esphome/components/apds9306/__init__.py | 4 + esphome/components/apds9306/apds9306.cpp | 151 ++++++++++++++++++ esphome/components/apds9306/apds9306.h | 66 ++++++++ esphome/components/apds9306/sensor.py | 95 +++++++++++ tests/components/apds9306/common.yaml | 12 ++ tests/components/apds9306/test.esp32-ard.yaml | 5 + .../apds9306/test.esp32-c3-ard.yaml | 5 + .../apds9306/test.esp32-c3-idf.yaml | 5 + tests/components/apds9306/test.esp32-idf.yaml | 5 + .../components/apds9306/test.esp8266-ard.yaml | 5 + .../components/apds9306/test.rp2040-ard.yaml | 5 + 12 files changed, 359 insertions(+) create mode 100644 esphome/components/apds9306/__init__.py create mode 100644 esphome/components/apds9306/apds9306.cpp create mode 100644 esphome/components/apds9306/apds9306.h create mode 100644 esphome/components/apds9306/sensor.py create mode 100644 tests/components/apds9306/common.yaml create mode 100644 tests/components/apds9306/test.esp32-ard.yaml create mode 100644 tests/components/apds9306/test.esp32-c3-ard.yaml create mode 100644 tests/components/apds9306/test.esp32-c3-idf.yaml create mode 100644 tests/components/apds9306/test.esp32-idf.yaml create mode 100644 tests/components/apds9306/test.esp8266-ard.yaml create mode 100644 tests/components/apds9306/test.rp2040-ard.yaml diff --git a/CODEOWNERS b/CODEOWNERS index ee76a072fc..c5e144bdfa 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -37,6 +37,7 @@ esphome/components/am43/sensor/* @buxtronix esphome/components/analog_threshold/* @ianchi esphome/components/animation/* @syndlex esphome/components/anova/* @buxtronix +esphome/components/apds9306/* @aodrenah esphome/components/api/* @OttoWinter esphome/components/as5600/* @ammmze esphome/components/as5600/sensor/* @ammmze diff --git a/esphome/components/apds9306/__init__.py b/esphome/components/apds9306/__init__.py new file mode 100644 index 0000000000..3dc8fcf5ff --- /dev/null +++ b/esphome/components/apds9306/__init__.py @@ -0,0 +1,4 @@ +# Based on this datasheet: +# https://www.mouser.ca/datasheet/2/678/AVGO_S_A0002854364_1-2574547.pdf + +CODEOWNERS = ["@aodrenah"] diff --git a/esphome/components/apds9306/apds9306.cpp b/esphome/components/apds9306/apds9306.cpp new file mode 100644 index 0000000000..7b79b0964c --- /dev/null +++ b/esphome/components/apds9306/apds9306.cpp @@ -0,0 +1,151 @@ +// Based on this datasheet: +// https://www.mouser.ca/datasheet/2/678/AVGO_S_A0002854364_1-2574547.pdf + +#include "apds9306.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace apds9306 { + +static const char *const TAG = "apds9306"; + +enum { // APDS9306 registers + APDS9306_MAIN_CTRL = 0x00, + APDS9306_ALS_MEAS_RATE = 0x04, + APDS9306_ALS_GAIN = 0x05, + APDS9306_PART_ID = 0x06, + APDS9306_MAIN_STATUS = 0x07, + APDS9306_CLEAR_DATA_0 = 0x0A, // LSB + APDS9306_CLEAR_DATA_1 = 0x0B, + APDS9306_CLEAR_DATA_2 = 0x0C, // MSB + APDS9306_ALS_DATA_0 = 0x0D, // LSB + APDS9306_ALS_DATA_1 = 0x0E, + APDS9306_ALS_DATA_2 = 0x0F, // MSB + APDS9306_INT_CFG = 0x19, + APDS9306_INT_PERSISTENCE = 0x1A, + APDS9306_ALS_THRES_UP_0 = 0x21, // LSB + APDS9306_ALS_THRES_UP_1 = 0x22, + APDS9306_ALS_THRES_UP_2 = 0x23, // MSB + APDS9306_ALS_THRES_LOW_0 = 0x24, // LSB + APDS9306_ALS_THRES_LOW_1 = 0x25, + APDS9306_ALS_THRES_LOW_2 = 0x26, // MSB + APDS9306_ALS_THRES_VAR = 0x27 +}; + +#define APDS9306_ERROR_CHECK(func, error) \ + if (!(func)) { \ + ESP_LOGE(TAG, error); \ + this->mark_failed(); \ + return; \ + } +#define APDS9306_WARNING_CHECK(func, warning) \ + if (!(func)) { \ + ESP_LOGW(TAG, warning); \ + this->status_set_warning(); \ + return; \ + } +#define APDS9306_WRITE_BYTE(reg, value) \ + ESP_LOGV(TAG, "Writing 0x%02x to 0x%02x", value, reg); \ + if (!this->write_byte(reg, value)) { \ + ESP_LOGE(TAG, "Failed writing 0x%02x to 0x%02x", value, reg); \ + this->mark_failed(); \ + return; \ + } + +void APDS9306::setup() { + ESP_LOGCONFIG(TAG, "Setting up APDS9306..."); + + uint8_t id; + if (!this->read_byte(APDS9306_PART_ID, &id)) { // Part ID register + this->error_code_ = COMMUNICATION_FAILED; + this->mark_failed(); + return; + } + + if (id != 0xB1 && id != 0xB3) { // 0xB1 for APDS9306 0xB3 for APDS9306-065 + this->error_code_ = WRONG_ID; + this->mark_failed(); + return; + } + + // ALS resolution and measurement, see datasheet or init.py for options + uint8_t als_meas_rate = ((this->bit_width_ & 0x07) << 4) | (this->measurement_rate_ & 0x07); + APDS9306_WRITE_BYTE(APDS9306_ALS_MEAS_RATE, als_meas_rate); + + // ALS gain, see datasheet or init.py for options + uint8_t als_gain = (this->gain_ & 0x07); + APDS9306_WRITE_BYTE(APDS9306_ALS_GAIN, als_gain); + + // Set to standby mode + APDS9306_WRITE_BYTE(APDS9306_MAIN_CTRL, 0x00); + + // Check for data, clear main status + uint8_t status; + APDS9306_WARNING_CHECK(this->read_byte(APDS9306_MAIN_STATUS, &status), "Reading MAIN STATUS failed."); + + // Set to active mode + APDS9306_WRITE_BYTE(APDS9306_MAIN_CTRL, 0x02); + + ESP_LOGCONFIG(TAG, "APDS9306 setup complete"); +} + +void APDS9306::dump_config() { + LOG_SENSOR("", "APDS9306", this); + LOG_I2C_DEVICE(this); + + if (this->is_failed()) { + switch (this->error_code_) { + case COMMUNICATION_FAILED: + ESP_LOGE(TAG, "Communication with APDS9306 failed!"); + break; + case WRONG_ID: + ESP_LOGE(TAG, "APDS9306 has invalid id!"); + break; + default: + ESP_LOGE(TAG, "Setting up APDS9306 registers failed!"); + break; + } + } + + ESP_LOGCONFIG(TAG, " Gain: %u", AMBIENT_LIGHT_GAIN_VALUES[this->gain_]); + ESP_LOGCONFIG(TAG, " Measurement rate: %u", MEASUREMENT_RATE_VALUES[this->measurement_rate_]); + ESP_LOGCONFIG(TAG, " Measurement Resolution/Bit width: %d", MEASUREMENT_BIT_WIDTH_VALUES[this->bit_width_]); + + LOG_UPDATE_INTERVAL(this); +} + +void APDS9306::update() { + // Check for new data + uint8_t status; + APDS9306_WARNING_CHECK(this->read_byte(APDS9306_MAIN_STATUS, &status), "Reading MAIN STATUS failed."); + + this->status_clear_warning(); + + if (!(status &= 0b00001000)) { // No new data + return; + } + + // Set to standby mode + APDS9306_WRITE_BYTE(APDS9306_MAIN_CTRL, 0x00); + + // Clear MAIN STATUS + APDS9306_WARNING_CHECK(this->read_byte(APDS9306_MAIN_STATUS, &status), "Reading MAIN STATUS failed."); + + uint8_t als_data[3]; + APDS9306_WARNING_CHECK(this->read_bytes(APDS9306_ALS_DATA_0, als_data, 3), "Reading ALS data has failed."); + + // Set to active mode + APDS9306_WRITE_BYTE(APDS9306_MAIN_CTRL, 0x02); + + uint32_t light_level = 0x00 | encode_uint24(als_data[2], als_data[1], als_data[0]); + + float lux = ((float) light_level / AMBIENT_LIGHT_GAIN_VALUES[this->gain_]) * + (100.0f / MEASUREMENT_RATE_VALUES[this->measurement_rate_]); + + ESP_LOGD(TAG, "Got illuminance=%.1flx from", lux); + this->publish_state(lux); +} + +} // namespace apds9306 +} // namespace esphome diff --git a/esphome/components/apds9306/apds9306.h b/esphome/components/apds9306/apds9306.h new file mode 100644 index 0000000000..44362908c8 --- /dev/null +++ b/esphome/components/apds9306/apds9306.h @@ -0,0 +1,66 @@ +// Based on this datasheet: +// https://www.mouser.ca/datasheet/2/678/AVGO_S_A0002854364_1-2574547.pdf + +#pragma once + +#include "esphome/components/i2c/i2c.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/core/component.h" + +namespace esphome { +namespace apds9306 { + +enum MeasurementBitWidth : uint8_t { + MEASUREMENT_BIT_WIDTH_20 = 0, + MEASUREMENT_BIT_WIDTH_19 = 1, + MEASUREMENT_BIT_WIDTH_18 = 2, + MEASUREMENT_BIT_WIDTH_17 = 3, + MEASUREMENT_BIT_WIDTH_16 = 4, + MEASUREMENT_BIT_WIDTH_13 = 5, +}; +static const uint8_t MEASUREMENT_BIT_WIDTH_VALUES[] = {20, 19, 18, 17, 16, 13}; + +enum MeasurementRate : uint8_t { + MEASUREMENT_RATE_25 = 0, + MEASUREMENT_RATE_50 = 1, + MEASUREMENT_RATE_100 = 2, + MEASUREMENT_RATE_200 = 3, + MEASUREMENT_RATE_500 = 4, + MEASUREMENT_RATE_1000 = 5, + MEASUREMENT_RATE_2000 = 6, +}; +static const uint16_t MEASUREMENT_RATE_VALUES[] = {25, 50, 100, 200, 500, 1000, 2000}; + +enum AmbientLightGain : uint8_t { + AMBIENT_LIGHT_GAIN_1 = 0, + AMBIENT_LIGHT_GAIN_3 = 1, + AMBIENT_LIGHT_GAIN_6 = 2, + AMBIENT_LIGHT_GAIN_9 = 3, + AMBIENT_LIGHT_GAIN_18 = 4, +}; +static const uint8_t AMBIENT_LIGHT_GAIN_VALUES[] = {1, 3, 6, 9, 18}; + +class APDS9306 : public sensor::Sensor, public PollingComponent, public i2c::I2CDevice { + public: + void setup() override; + float get_setup_priority() const override { return setup_priority::BUS; } + void dump_config() override; + void update() override; + void set_bit_width(MeasurementBitWidth bit_width) { this->bit_width_ = bit_width; } + void set_measurement_rate(MeasurementRate measurement_rate) { this->measurement_rate_ = measurement_rate; } + void set_ambient_light_gain(AmbientLightGain gain) { this->gain_ = gain; } + + protected: + enum ErrorCode { + NONE = 0, + COMMUNICATION_FAILED, + WRONG_ID, + } error_code_{NONE}; + + MeasurementBitWidth bit_width_; + MeasurementRate measurement_rate_; + AmbientLightGain gain_; +}; + +} // namespace apds9306 +} // namespace esphome diff --git a/esphome/components/apds9306/sensor.py b/esphome/components/apds9306/sensor.py new file mode 100644 index 0000000000..25b301444f --- /dev/null +++ b/esphome/components/apds9306/sensor.py @@ -0,0 +1,95 @@ +# Based on this datasheet: +# https://www.mouser.ca/datasheet/2/678/AVGO_S_A0002854364_1-2574547.pdf + +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import i2c, sensor +from esphome.const import ( + CONF_GAIN, + DEVICE_CLASS_ILLUMINANCE, + ICON_LIGHTBULB, + STATE_CLASS_MEASUREMENT, + UNIT_LUX, +) + +DEPENDENCIES = ["i2c"] + +CONF_APDS9306_ID = "apds9306_id" +CONF_BIT_WIDTH = "bit_width" +CONF_MEASUREMENT_RATE = "measurement_rate" + +apds9306_ns = cg.esphome_ns.namespace("apds9306") +APDS9306 = apds9306_ns.class_( + "APDS9306", sensor.Sensor, cg.PollingComponent, i2c.I2CDevice +) + +MeasurementBitWidth = apds9306_ns.enum("MeasurementBitWidth") +MeasurementRate = apds9306_ns.enum("MeasurementRate") +AmbientLightGain = apds9306_ns.enum("AmbientLightGain") + +MEASUREMENT_BIT_WIDTHS = { + 20: MeasurementBitWidth.MEASUREMENT_BIT_WIDTH_20, + 19: MeasurementBitWidth.MEASUREMENT_BIT_WIDTH_19, + 18: MeasurementBitWidth.MEASUREMENT_BIT_WIDTH_18, + 17: MeasurementBitWidth.MEASUREMENT_BIT_WIDTH_17, + 16: MeasurementBitWidth.MEASUREMENT_BIT_WIDTH_16, + 13: MeasurementBitWidth.MEASUREMENT_BIT_WIDTH_13, +} + +MEASUREMENT_RATES = { + 25: MeasurementRate.MEASUREMENT_RATE_25, + 50: MeasurementRate.MEASUREMENT_RATE_50, + 100: MeasurementRate.MEASUREMENT_RATE_100, + 200: MeasurementRate.MEASUREMENT_RATE_200, + 500: MeasurementRate.MEASUREMENT_RATE_500, + 1000: MeasurementRate.MEASUREMENT_RATE_1000, + 2000: MeasurementRate.MEASUREMENT_RATE_2000, +} + +AMBIENT_LIGHT_GAINS = { + 1: AmbientLightGain.AMBIENT_LIGHT_GAIN_1, + 3: AmbientLightGain.AMBIENT_LIGHT_GAIN_3, + 6: AmbientLightGain.AMBIENT_LIGHT_GAIN_6, + 9: AmbientLightGain.AMBIENT_LIGHT_GAIN_9, + 18: AmbientLightGain.AMBIENT_LIGHT_GAIN_18, +} + + +def _validate_measurement_rate(value): + value = cv.positive_time_period_milliseconds(value) + return cv.enum(MEASUREMENT_RATES, int=True)(value.total_milliseconds) + + +CONFIG_SCHEMA = ( + sensor.sensor_schema( + APDS9306, + unit_of_measurement=UNIT_LUX, + accuracy_decimals=1, + device_class=DEVICE_CLASS_ILLUMINANCE, + state_class=STATE_CLASS_MEASUREMENT, + icon=ICON_LIGHTBULB, + ) + .extend( + { + cv.Optional(CONF_GAIN, default="1"): cv.enum(AMBIENT_LIGHT_GAINS, int=True), + cv.Optional(CONF_BIT_WIDTH, default="18"): cv.enum( + MEASUREMENT_BIT_WIDTHS, int=True + ), + cv.Optional( + CONF_MEASUREMENT_RATE, default="100ms" + ): _validate_measurement_rate, + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x52)) +) + + +async def to_code(config): + var = await sensor.new_sensor(config) + await cg.register_component(var, config) + await i2c.register_i2c_device(var, config) + + cg.add(var.set_bit_width(config[CONF_BIT_WIDTH])) + cg.add(var.set_measurement_rate(config[CONF_MEASUREMENT_RATE])) + cg.add(var.set_ambient_light_gain(config[CONF_GAIN])) diff --git a/tests/components/apds9306/common.yaml b/tests/components/apds9306/common.yaml new file mode 100644 index 0000000000..b3828e62ff --- /dev/null +++ b/tests/components/apds9306/common.yaml @@ -0,0 +1,12 @@ +i2c: + - id: i2c_apds9306 + scl: ${scl_pin} + sda: ${sda_pin} + +sensor: + - platform: apds9306 + name: "APDS9306 Light Level" + gain: 3 + bit_width: 16 + measurement_rate: 2000ms + update_interval: 60s diff --git a/tests/components/apds9306/test.esp32-ard.yaml b/tests/components/apds9306/test.esp32-ard.yaml new file mode 100644 index 0000000000..3b761d3fc1 --- /dev/null +++ b/tests/components/apds9306/test.esp32-ard.yaml @@ -0,0 +1,5 @@ +substitutions: + scl_pin: GPIO22 + sda_pin: GPIO21 + +<<: !include common.yaml diff --git a/tests/components/apds9306/test.esp32-c3-ard.yaml b/tests/components/apds9306/test.esp32-c3-ard.yaml new file mode 100644 index 0000000000..ee2c29ca4e --- /dev/null +++ b/tests/components/apds9306/test.esp32-c3-ard.yaml @@ -0,0 +1,5 @@ +substitutions: + scl_pin: GPIO5 + sda_pin: GPIO4 + +<<: !include common.yaml diff --git a/tests/components/apds9306/test.esp32-c3-idf.yaml b/tests/components/apds9306/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..ee2c29ca4e --- /dev/null +++ b/tests/components/apds9306/test.esp32-c3-idf.yaml @@ -0,0 +1,5 @@ +substitutions: + scl_pin: GPIO5 + sda_pin: GPIO4 + +<<: !include common.yaml diff --git a/tests/components/apds9306/test.esp32-idf.yaml b/tests/components/apds9306/test.esp32-idf.yaml new file mode 100644 index 0000000000..3b761d3fc1 --- /dev/null +++ b/tests/components/apds9306/test.esp32-idf.yaml @@ -0,0 +1,5 @@ +substitutions: + scl_pin: GPIO22 + sda_pin: GPIO21 + +<<: !include common.yaml diff --git a/tests/components/apds9306/test.esp8266-ard.yaml b/tests/components/apds9306/test.esp8266-ard.yaml new file mode 100644 index 0000000000..ee2c29ca4e --- /dev/null +++ b/tests/components/apds9306/test.esp8266-ard.yaml @@ -0,0 +1,5 @@ +substitutions: + scl_pin: GPIO5 + sda_pin: GPIO4 + +<<: !include common.yaml diff --git a/tests/components/apds9306/test.rp2040-ard.yaml b/tests/components/apds9306/test.rp2040-ard.yaml new file mode 100644 index 0000000000..ee2c29ca4e --- /dev/null +++ b/tests/components/apds9306/test.rp2040-ard.yaml @@ -0,0 +1,5 @@ +substitutions: + scl_pin: GPIO5 + sda_pin: GPIO4 + +<<: !include common.yaml