From 343a8c063e145ecf0d8e32478581a57ff421d50b Mon Sep 17 00:00:00 2001 From: Samuel Sieb Date: Fri, 12 Jan 2024 01:11:42 -0800 Subject: [PATCH 01/24] fix sen5x negative temperature (#6082) --- esphome/components/sen5x/sen5x.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/sen5x/sen5x.cpp b/esphome/components/sen5x/sen5x.cpp index c90880bc9f..0efc961943 100644 --- a/esphome/components/sen5x/sen5x.cpp +++ b/esphome/components/sen5x/sen5x.cpp @@ -352,7 +352,7 @@ void SEN5XComponent::update() { float humidity = measurements[4] / 100.0; if (measurements[4] == 0xFFFF) humidity = NAN; - float temperature = measurements[5] / 200.0; + float temperature = (int16_t) measurements[5] / 200.0; if (measurements[5] == 0xFFFF) temperature = NAN; float voc = measurements[6] / 10.0; From aa04a3caaf83ae71e4edac681bc9a534ada265be Mon Sep 17 00:00:00 2001 From: Samuel Sieb Date: Fri, 12 Jan 2024 01:20:08 -0800 Subject: [PATCH 02/24] negative values for all DHT22 variants (#6074) Co-authored-by: Samuel Sieb --- esphome/components/dht/dht.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/esphome/components/dht/dht.cpp b/esphome/components/dht/dht.cpp index c70b227330..07634cafdf 100644 --- a/esphome/components/dht/dht.cpp +++ b/esphome/components/dht/dht.cpp @@ -217,8 +217,12 @@ bool HOT IRAM_ATTR DHT::read_sensor_(float *temperature, float *humidity, bool r uint16_t raw_humidity = (uint16_t(data[0] & 0xFF) << 8) | (data[1] & 0xFF); uint16_t raw_temperature = (uint16_t(data[2] & 0xFF) << 8) | (data[3] & 0xFF); - if (this->model_ != DHT_MODEL_DHT22_TYPE2 && (raw_temperature & 0x8000) != 0) - raw_temperature = ~(raw_temperature & 0x7FFF); + if (raw_temperature & 0x8000) { + if (!(raw_temperature & 0x4000)) + raw_temperature = ~(raw_temperature & 0x7FFF); + } else if (raw_temperature & 0x800) { + raw_temperature |= 0xf000; + } if (raw_temperature == 1 && raw_humidity == 10) { if (report_errors) { From ed2ab9e96287cd4ff6a96b0086cdf0ef9d845446 Mon Sep 17 00:00:00 2001 From: Samuel Sieb Date: Fri, 12 Jan 2024 01:47:50 -0800 Subject: [PATCH 03/24] fix negative temperature for pmsx003 (#6083) * fix negative temperature for pmsx003 * Update esphome/components/pmsx003/pmsx003.cpp --- esphome/components/pmsx003/pmsx003.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/pmsx003/pmsx003.cpp b/esphome/components/pmsx003/pmsx003.cpp index 04aba4382b..62488b765c 100644 --- a/esphome/components/pmsx003/pmsx003.cpp +++ b/esphome/components/pmsx003/pmsx003.cpp @@ -195,7 +195,7 @@ void PMSX003Component::send_command_(uint8_t cmd, uint16_t data) { void PMSX003Component::parse_data_() { switch (this->type_) { case PMSX003_TYPE_5003ST: { - float temperature = this->get_16_bit_uint_(30) / 10.0f; + float temperature = (int16_t) this->get_16_bit_uint_(30) / 10.0f; float humidity = this->get_16_bit_uint_(32) / 10.0f; ESP_LOGD(TAG, "Got Temperature: %.1f°C, Humidity: %.1f%%", temperature, humidity); From d551a2eba2c006758eb97cd3cf4c7110d1c15f50 Mon Sep 17 00:00:00 2001 From: Keith Burzinski Date: Sat, 13 Jan 2024 02:21:28 -0600 Subject: [PATCH 04/24] Improv Serial -- don't wait for incoming bytes (#6089) --- esphome/components/improv_serial/improv_serial_component.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/improv_serial/improv_serial_component.cpp b/esphome/components/improv_serial/improv_serial_component.cpp index 600069b781..2318fd43cb 100644 --- a/esphome/components/improv_serial/improv_serial_component.cpp +++ b/esphome/components/improv_serial/improv_serial_component.cpp @@ -52,7 +52,7 @@ optional ImprovSerialComponent::read_byte_() { size_t available; uart_get_buffered_data_len(this->uart_num_, &available); if (available) { - uart_read_bytes(this->uart_num_, &data, 1, 20 / portTICK_PERIOD_MS); + uart_read_bytes(this->uart_num_, &data, 1, 0); byte = data; } } @@ -71,7 +71,7 @@ optional ImprovSerialComponent::read_byte_() { #endif // USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3 #if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32S3) case logger::UART_SELECTION_USB_SERIAL_JTAG: { - if (usb_serial_jtag_read_bytes((char *) &data, 1, 20 / portTICK_PERIOD_MS)) { + if (usb_serial_jtag_read_bytes((char *) &data, 1, 0)) { byte = data; } break; From 8e83c7dd193083ace6d0faabf81dd60993fe8d5d Mon Sep 17 00:00:00 2001 From: guillempages Date: Sat, 13 Jan 2024 22:01:32 +0100 Subject: [PATCH 05/24] Let show_*_page actions depend on "Display" (#6092) Instead of forcing a DisplayBuffer, let the display page actions use Displays without buffer. --- esphome/components/display/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/esphome/components/display/__init__.py b/esphome/components/display/__init__.py index 91f10c5458..992799008a 100644 --- a/esphome/components/display/__init__.py +++ b/esphome/components/display/__init__.py @@ -145,7 +145,7 @@ async def display_page_show_to_code(config, action_id, template_arg, args): DisplayPageShowNextAction, maybe_simple_id( { - cv.Required(CONF_ID): cv.templatable(cv.use_id(DisplayBuffer)), + cv.Required(CONF_ID): cv.templatable(cv.use_id(Display)), } ), ) @@ -159,7 +159,7 @@ async def display_page_show_next_to_code(config, action_id, template_arg, args): DisplayPageShowPrevAction, maybe_simple_id( { - cv.Required(CONF_ID): cv.templatable(cv.use_id(DisplayBuffer)), + cv.Required(CONF_ID): cv.templatable(cv.use_id(Display)), } ), ) @@ -173,7 +173,7 @@ async def display_page_show_previous_to_code(config, action_id, template_arg, ar DisplayIsDisplayingPageCondition, cv.maybe_simple_value( { - cv.GenerateID(CONF_ID): cv.use_id(DisplayBuffer), + cv.GenerateID(CONF_ID): cv.use_id(Display), cv.Required(CONF_PAGE_ID): cv.use_id(DisplayPage), }, key=CONF_PAGE_ID, From f567b5d28b8c75340cb64a9237219c09ee9d6c50 Mon Sep 17 00:00:00 2001 From: tomaszduda23 Date: Mon, 15 Jan 2024 00:01:20 +0100 Subject: [PATCH 06/24] add STATE_CLASS_TOTAL_INCREASING to bl0940 and bl0942 (#6090) --- esphome/components/bl0940/sensor.py | 2 ++ esphome/components/bl0942/sensor.py | 2 ++ 2 files changed, 4 insertions(+) diff --git a/esphome/components/bl0940/sensor.py b/esphome/components/bl0940/sensor.py index 702230e020..a197becef8 100644 --- a/esphome/components/bl0940/sensor.py +++ b/esphome/components/bl0940/sensor.py @@ -18,6 +18,7 @@ from esphome.const import ( UNIT_KILOWATT_HOURS, UNIT_VOLT, UNIT_WATT, + STATE_CLASS_TOTAL_INCREASING, ) DEPENDENCIES = ["uart"] @@ -54,6 +55,7 @@ CONFIG_SCHEMA = ( unit_of_measurement=UNIT_KILOWATT_HOURS, accuracy_decimals=0, device_class=DEVICE_CLASS_ENERGY, + state_class=STATE_CLASS_TOTAL_INCREASING, ), cv.Optional(CONF_INTERNAL_TEMPERATURE): sensor.sensor_schema( unit_of_measurement=UNIT_CELSIUS, diff --git a/esphome/components/bl0942/sensor.py b/esphome/components/bl0942/sensor.py index 663eea0c4d..9612df6d4c 100644 --- a/esphome/components/bl0942/sensor.py +++ b/esphome/components/bl0942/sensor.py @@ -19,6 +19,7 @@ from esphome.const import ( UNIT_VOLT, UNIT_WATT, UNIT_HERTZ, + STATE_CLASS_TOTAL_INCREASING, ) DEPENDENCIES = ["uart"] @@ -52,6 +53,7 @@ CONFIG_SCHEMA = ( unit_of_measurement=UNIT_KILOWATT_HOURS, accuracy_decimals=0, device_class=DEVICE_CLASS_ENERGY, + state_class=STATE_CLASS_TOTAL_INCREASING, ), cv.Optional(CONF_FREQUENCY): sensor.sensor_schema( unit_of_measurement=UNIT_HERTZ, From 5220c9edf82aad55e1df811260675486ac338fb1 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 14 Jan 2024 13:06:13 -1000 Subject: [PATCH 07/24] Fallback to pure-python loader for better error when YAML loading fails (#6081) --- esphome/yaml_util.py | 113 ++++++++++-------- .../yaml_util/broken_includetest.yaml | 18 +++ .../includes/broken_included.yaml.txt | 5 + tests/unit_tests/test_yaml_util.py | 11 ++ 4 files changed, 98 insertions(+), 49 deletions(-) create mode 100644 tests/unit_tests/fixtures/yaml_util/broken_includetest.yaml create mode 100644 tests/unit_tests/fixtures/yaml_util/includes/broken_included.yaml.txt diff --git a/esphome/yaml_util.py b/esphome/yaml_util.py index aa9fe45ebb..f5e36b79e7 100644 --- a/esphome/yaml_util.py +++ b/esphome/yaml_util.py @@ -1,36 +1,37 @@ +from __future__ import annotations + import fnmatch import functools import inspect import logging import math import os - import uuid +from typing import Any + import yaml import yaml.constructor +from yaml import SafeLoader as PurePythonLoader + +try: + from yaml import CSafeLoader as FastestAvailableSafeLoader +except ImportError: + FastestAvailableSafeLoader = PurePythonLoader from esphome import core -from esphome.config_helpers import read_config_file, Extend, Remove +from esphome.config_helpers import Extend, Remove, read_config_file from esphome.core import ( + CORE, + DocumentRange, EsphomeError, IPAddress, Lambda, MACAddress, TimePeriod, - DocumentRange, - CORE, ) from esphome.helpers import add_class_to_obj from esphome.util import OrderedDict, filter_yaml_files -try: - from yaml import CSafeLoader as FastestAvailableSafeLoader -except ImportError: - from yaml import ( # type: ignore[assignment] - SafeLoader as FastestAvailableSafeLoader, - ) - - _LOGGER = logging.getLogger(__name__) # Mostly copied from Home Assistant because that code works fine and @@ -97,7 +98,7 @@ def _add_data_ref(fn): return wrapped -class ESPHomeLoader(FastestAvailableSafeLoader): +class ESPHomeLoaderMixin: """Loader class that keeps track of line numbers.""" @_add_data_ref @@ -282,8 +283,8 @@ class ESPHomeLoader(FastestAvailableSafeLoader): return file, vars def substitute_vars(config, vars): - from esphome.const import CONF_SUBSTITUTIONS, CONF_DEFAULTS from esphome.components import substitutions + from esphome.const import CONF_DEFAULTS, CONF_SUBSTITUTIONS org_subs = None result = config @@ -375,50 +376,64 @@ class ESPHomeLoader(FastestAvailableSafeLoader): return Remove(str(node.value)) -ESPHomeLoader.add_constructor("tag:yaml.org,2002:int", ESPHomeLoader.construct_yaml_int) -ESPHomeLoader.add_constructor( - "tag:yaml.org,2002:float", ESPHomeLoader.construct_yaml_float -) -ESPHomeLoader.add_constructor( - "tag:yaml.org,2002:binary", ESPHomeLoader.construct_yaml_binary -) -ESPHomeLoader.add_constructor( - "tag:yaml.org,2002:omap", ESPHomeLoader.construct_yaml_omap -) -ESPHomeLoader.add_constructor("tag:yaml.org,2002:str", ESPHomeLoader.construct_yaml_str) -ESPHomeLoader.add_constructor("tag:yaml.org,2002:seq", ESPHomeLoader.construct_yaml_seq) -ESPHomeLoader.add_constructor("tag:yaml.org,2002:map", ESPHomeLoader.construct_yaml_map) -ESPHomeLoader.add_constructor("!env_var", ESPHomeLoader.construct_env_var) -ESPHomeLoader.add_constructor("!secret", ESPHomeLoader.construct_secret) -ESPHomeLoader.add_constructor("!include", ESPHomeLoader.construct_include) -ESPHomeLoader.add_constructor( - "!include_dir_list", ESPHomeLoader.construct_include_dir_list -) -ESPHomeLoader.add_constructor( - "!include_dir_merge_list", ESPHomeLoader.construct_include_dir_merge_list -) -ESPHomeLoader.add_constructor( - "!include_dir_named", ESPHomeLoader.construct_include_dir_named -) -ESPHomeLoader.add_constructor( - "!include_dir_merge_named", ESPHomeLoader.construct_include_dir_merge_named -) -ESPHomeLoader.add_constructor("!lambda", ESPHomeLoader.construct_lambda) -ESPHomeLoader.add_constructor("!force", ESPHomeLoader.construct_force) -ESPHomeLoader.add_constructor("!extend", ESPHomeLoader.construct_extend) -ESPHomeLoader.add_constructor("!remove", ESPHomeLoader.construct_remove) +class ESPHomeLoader(ESPHomeLoaderMixin, FastestAvailableSafeLoader): + """Loader class that keeps track of line numbers.""" -def load_yaml(fname, clear_secrets=True): +class ESPHomePurePythonLoader(ESPHomeLoaderMixin, PurePythonLoader): + """Loader class that keeps track of line numbers.""" + + +for _loader in (ESPHomeLoader, ESPHomePurePythonLoader): + _loader.add_constructor("tag:yaml.org,2002:int", _loader.construct_yaml_int) + _loader.add_constructor("tag:yaml.org,2002:float", _loader.construct_yaml_float) + _loader.add_constructor("tag:yaml.org,2002:binary", _loader.construct_yaml_binary) + _loader.add_constructor("tag:yaml.org,2002:omap", _loader.construct_yaml_omap) + _loader.add_constructor("tag:yaml.org,2002:str", _loader.construct_yaml_str) + _loader.add_constructor("tag:yaml.org,2002:seq", _loader.construct_yaml_seq) + _loader.add_constructor("tag:yaml.org,2002:map", _loader.construct_yaml_map) + _loader.add_constructor("!env_var", _loader.construct_env_var) + _loader.add_constructor("!secret", _loader.construct_secret) + _loader.add_constructor("!include", _loader.construct_include) + _loader.add_constructor("!include_dir_list", _loader.construct_include_dir_list) + _loader.add_constructor( + "!include_dir_merge_list", _loader.construct_include_dir_merge_list + ) + _loader.add_constructor("!include_dir_named", _loader.construct_include_dir_named) + _loader.add_constructor( + "!include_dir_merge_named", _loader.construct_include_dir_merge_named + ) + _loader.add_constructor("!lambda", _loader.construct_lambda) + _loader.add_constructor("!force", _loader.construct_force) + _loader.add_constructor("!extend", _loader.construct_extend) + _loader.add_constructor("!remove", _loader.construct_remove) + + +def load_yaml(fname: str, clear_secrets: bool = True) -> Any: if clear_secrets: _SECRET_VALUES.clear() _SECRET_CACHE.clear() return _load_yaml_internal(fname) -def _load_yaml_internal(fname): +def _load_yaml_internal(fname: str) -> Any: + """Load a YAML file.""" content = read_config_file(fname) - loader = ESPHomeLoader(content) + try: + return _load_yaml_internal_with_type(ESPHomeLoader, fname, content) + except EsphomeError: + # Loading failed, so we now load with the Python loader which has more + # readable exceptions + return _load_yaml_internal_with_type(ESPHomePurePythonLoader, fname, content) + + +def _load_yaml_internal_with_type( + loader_type: type[ESPHomeLoader] | type[ESPHomePurePythonLoader], + fname: str, + content: str, +) -> Any: + """Load a YAML file.""" + loader = loader_type(content) loader.name = fname try: return loader.get_single_data() or OrderedDict() diff --git a/tests/unit_tests/fixtures/yaml_util/broken_includetest.yaml b/tests/unit_tests/fixtures/yaml_util/broken_includetest.yaml new file mode 100644 index 0000000000..aaca55b807 --- /dev/null +++ b/tests/unit_tests/fixtures/yaml_util/broken_includetest.yaml @@ -0,0 +1,18 @@ +--- +substitutions: + name: original + +wifi: !include + file: includes/broken_included.yaml.txt + vars: + name: my_custom_ssid + +esphome: + # should be substituted as 'original', + # not overwritten by vars in the !include above + name: ${name} + name_add_mac_suffix: true + platform: esp8266 + board: !include {file: includes/scalar.yaml, vars: {var1: nodemcu}} + + libraries: !include {file: includes/list.yaml, vars: {var1: Wire}} diff --git a/tests/unit_tests/fixtures/yaml_util/includes/broken_included.yaml.txt b/tests/unit_tests/fixtures/yaml_util/includes/broken_included.yaml.txt new file mode 100644 index 0000000000..6e53395c86 --- /dev/null +++ b/tests/unit_tests/fixtures/yaml_util/includes/broken_included.yaml.txt @@ -0,0 +1,5 @@ +--- +# yamllint disable-line + ssid: ${name} +# yamllint disable-line + fdf: error diff --git a/tests/unit_tests/test_yaml_util.py b/tests/unit_tests/test_yaml_util.py index 8ee991f5b3..78b6a2ad84 100644 --- a/tests/unit_tests/test_yaml_util.py +++ b/tests/unit_tests/test_yaml_util.py @@ -1,5 +1,6 @@ from esphome import yaml_util from esphome.components import substitutions +from esphome.core import EsphomeError def test_include_with_vars(fixture_path): @@ -11,3 +12,13 @@ def test_include_with_vars(fixture_path): assert actual["esphome"]["libraries"][0] == "Wire" assert actual["esphome"]["board"] == "nodemcu" assert actual["wifi"]["ssid"] == "my_custom_ssid" + + +def test_loading_a_broken_yaml_file(fixture_path): + """Ensure we fallback to pure python to give good errors.""" + yaml_file = fixture_path / "yaml_util" / "broken_includetest.yaml" + + try: + yaml_util.load_yaml(yaml_file) + except EsphomeError as err: + assert "broken_included.yaml" in str(err) From dd2dca4d08593dfddbac7145188a759acbba6423 Mon Sep 17 00:00:00 2001 From: Pieter Frenssen Date: Mon, 15 Jan 2024 01:48:02 +0200 Subject: [PATCH 08/24] Bump pillow to 10.2.0. (#6091) --- esphome/components/font/__init__.py | 8 ++++---- requirements_optional.txt | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/esphome/components/font/__init__.py b/esphome/components/font/__init__.py index a803c7567b..5b4682a808 100644 --- a/esphome/components/font/__init__.py +++ b/esphome/components/font/__init__.py @@ -67,13 +67,13 @@ def validate_pillow_installed(value): except ImportError as err: raise cv.Invalid( "Please install the pillow python package to use this feature. " - '(pip install "pillow==10.1.0")' + '(pip install "pillow==10.2.0")' ) from err - if version.parse(PIL.__version__) != version.parse("10.1.0"): + if version.parse(PIL.__version__) != version.parse("10.2.0"): raise cv.Invalid( - "Please update your pillow installation to 10.1.0. " - '(pip install "pillow==10.1.0")' + "Please update your pillow installation to 10.2.0. " + '(pip install "pillow==10.2.0")' ) return value diff --git a/requirements_optional.txt b/requirements_optional.txt index bc4ea08c92..54494b4585 100644 --- a/requirements_optional.txt +++ b/requirements_optional.txt @@ -1,3 +1,3 @@ -pillow==10.1.0 +pillow==10.2.0 cairosvg==2.7.1 cryptography==41.0.4 From 8b2d76e8cebca0055b08319076f5cc8d94f25383 Mon Sep 17 00:00:00 2001 From: Samuel Sieb Date: Sun, 14 Jan 2024 16:05:47 -0800 Subject: [PATCH 09/24] convert cse7766 to non-polling (#6095) Co-authored-by: Samuel Sieb --- esphome/components/cse7766/cse7766.cpp | 52 +++++--------------- esphome/components/cse7766/cse7766.h | 11 +---- esphome/components/cse7766/sensor.py | 66 ++++++++++++-------------- 3 files changed, 44 insertions(+), 85 deletions(-) diff --git a/esphome/components/cse7766/cse7766.cpp b/esphome/components/cse7766/cse7766.cpp index 60132fd98f..9c5016c503 100644 --- a/esphome/components/cse7766/cse7766.cpp +++ b/esphome/components/cse7766/cse7766.cpp @@ -113,8 +113,9 @@ void CSE7766Component::parse_data_() { bool have_voltage = adj & 0x40; if (have_voltage) { // voltage cycle of serial port outputted is a complete cycle; - this->voltage_acc_ += voltage_calib / float(voltage_cycle); - this->voltage_counts_ += 1; + float voltage = voltage_calib / float(voltage_cycle); + if (this->voltage_sensor_ != nullptr) + this->voltage_sensor_->publish_state(voltage); } bool have_power = adj & 0x10; @@ -126,8 +127,8 @@ void CSE7766Component::parse_data_() { if (!power_cycle_exceeds_range) { power = power_calib / float(power_cycle); } - this->power_acc_ += power; - this->power_counts_ += 1; + if (this->power_sensor_ != nullptr) + this->power_sensor_->publish_state(power); uint32_t difference; if (this->cf_pulses_last_ == 0) { @@ -141,7 +142,10 @@ void CSE7766Component::parse_data_() { } this->cf_pulses_last_ = cf_pulses; this->energy_total_ += difference * float(power_calib) / 1000000.0f / 3600.0f; - this->energy_total_counts_ += 1; + if (this->energy_sensor_ != nullptr) + this->energy_sensor_->publish_state(this->energy_total_); + } else if ((this->energy_sensor_ != nullptr) && !this->energy_sensor_->has_state()) { + this->energy_sensor_->publish_state(0); } if (adj & 0x20) { @@ -150,42 +154,13 @@ void CSE7766Component::parse_data_() { if (have_voltage && !have_power) { // Testing has shown that when we have voltage and current but not power, that means the power is 0. // We report a power of 0, which in turn means we should report a current of 0. - this->power_counts_ += 1; + if (this->power_sensor_ != nullptr) + this->power_sensor_->publish_state(0); } else if (power != 0.0f) { current = current_calib / float(current_cycle); } - this->current_acc_ += current; - this->current_counts_ += 1; - } -} -void CSE7766Component::update() { - const auto publish_state = [](const char *name, sensor::Sensor *sensor, float &acc, uint32_t &counts) { - if (counts != 0) { - const auto avg = acc / counts; - - ESP_LOGV(TAG, "Got %s_acc=%.2f %s_counts=%" PRIu32 " %s=%.1f", name, acc, name, counts, name, avg); - - if (sensor != nullptr) { - sensor->publish_state(avg); - } - - acc = 0.0f; - counts = 0; - } - }; - - publish_state("voltage", this->voltage_sensor_, this->voltage_acc_, this->voltage_counts_); - publish_state("current", this->current_sensor_, this->current_acc_, this->current_counts_); - publish_state("power", this->power_sensor_, this->power_acc_, this->power_counts_); - - if (this->energy_total_counts_ != 0) { - ESP_LOGV(TAG, "Got energy_total=%.2f energy_total_counts=%" PRIu32, this->energy_total_, - this->energy_total_counts_); - - if (this->energy_sensor_ != nullptr) { - this->energy_sensor_->publish_state(this->energy_total_); - } - this->energy_total_counts_ = 0; + if (this->current_sensor_ != nullptr) + this->current_sensor_->publish_state(current); } } @@ -196,7 +171,6 @@ uint32_t CSE7766Component::get_24_bit_uint_(uint8_t start_index) { void CSE7766Component::dump_config() { ESP_LOGCONFIG(TAG, "CSE7766:"); - LOG_UPDATE_INTERVAL(this); LOG_SENSOR(" ", "Voltage", this->voltage_sensor_); LOG_SENSOR(" ", "Current", this->current_sensor_); LOG_SENSOR(" ", "Power", this->power_sensor_); diff --git a/esphome/components/cse7766/cse7766.h b/esphome/components/cse7766/cse7766.h index 2f30eec09f..3ab8d609bd 100644 --- a/esphome/components/cse7766/cse7766.h +++ b/esphome/components/cse7766/cse7766.h @@ -7,7 +7,7 @@ namespace esphome { namespace cse7766 { -class CSE7766Component : public PollingComponent, public uart::UARTDevice { +class CSE7766Component : public Component, public uart::UARTDevice { public: void set_voltage_sensor(sensor::Sensor *voltage_sensor) { voltage_sensor_ = voltage_sensor; } void set_current_sensor(sensor::Sensor *current_sensor) { current_sensor_ = current_sensor; } @@ -16,7 +16,6 @@ class CSE7766Component : public PollingComponent, public uart::UARTDevice { void loop() override; float get_setup_priority() const override; - void update() override; void dump_config() override; protected: @@ -31,16 +30,8 @@ class CSE7766Component : public PollingComponent, public uart::UARTDevice { sensor::Sensor *current_sensor_{nullptr}; sensor::Sensor *power_sensor_{nullptr}; sensor::Sensor *energy_sensor_{nullptr}; - float voltage_acc_{0.0f}; - float current_acc_{0.0f}; - float power_acc_{0.0f}; float energy_total_{0.0f}; uint32_t cf_pulses_last_{0}; - uint32_t voltage_counts_{0}; - uint32_t current_counts_{0}; - uint32_t power_counts_{0}; - // Setting this to 1 means it will always publish 0 once at startup - uint32_t energy_total_counts_{1}; }; } // namespace cse7766 diff --git a/esphome/components/cse7766/sensor.py b/esphome/components/cse7766/sensor.py index d98b351287..f2750bb4f2 100644 --- a/esphome/components/cse7766/sensor.py +++ b/esphome/components/cse7766/sensor.py @@ -22,43 +22,37 @@ from esphome.const import ( DEPENDENCIES = ["uart"] cse7766_ns = cg.esphome_ns.namespace("cse7766") -CSE7766Component = cse7766_ns.class_( - "CSE7766Component", cg.PollingComponent, uart.UARTDevice -) +CSE7766Component = cse7766_ns.class_("CSE7766Component", cg.Component, uart.UARTDevice) -CONFIG_SCHEMA = ( - cv.Schema( - { - cv.GenerateID(): cv.declare_id(CSE7766Component), - cv.Optional(CONF_VOLTAGE): sensor.sensor_schema( - unit_of_measurement=UNIT_VOLT, - accuracy_decimals=1, - device_class=DEVICE_CLASS_VOLTAGE, - state_class=STATE_CLASS_MEASUREMENT, - ), - cv.Optional(CONF_CURRENT): sensor.sensor_schema( - unit_of_measurement=UNIT_AMPERE, - accuracy_decimals=2, - device_class=DEVICE_CLASS_CURRENT, - state_class=STATE_CLASS_MEASUREMENT, - ), - cv.Optional(CONF_POWER): sensor.sensor_schema( - unit_of_measurement=UNIT_WATT, - accuracy_decimals=1, - device_class=DEVICE_CLASS_POWER, - state_class=STATE_CLASS_MEASUREMENT, - ), - cv.Optional(CONF_ENERGY): sensor.sensor_schema( - unit_of_measurement=UNIT_WATT_HOURS, - accuracy_decimals=3, - device_class=DEVICE_CLASS_ENERGY, - state_class=STATE_CLASS_TOTAL_INCREASING, - ), - } - ) - .extend(cv.polling_component_schema("60s")) - .extend(uart.UART_DEVICE_SCHEMA) -) +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(CSE7766Component), + cv.Optional(CONF_VOLTAGE): sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_CURRENT): sensor.sensor_schema( + unit_of_measurement=UNIT_AMPERE, + accuracy_decimals=2, + device_class=DEVICE_CLASS_CURRENT, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_POWER): sensor.sensor_schema( + unit_of_measurement=UNIT_WATT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_ENERGY): sensor.sensor_schema( + unit_of_measurement=UNIT_WATT_HOURS, + accuracy_decimals=3, + device_class=DEVICE_CLASS_ENERGY, + state_class=STATE_CLASS_TOTAL_INCREASING, + ), + } +).extend(uart.UART_DEVICE_SCHEMA) FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema( "cse7766", baud_rate=4800, require_rx=True ) From 83baa24022ef56100dfb6ae2ce02d2bdc0fe0ebe Mon Sep 17 00:00:00 2001 From: NP v/d Spek Date: Mon, 15 Jan 2024 03:07:06 +0100 Subject: [PATCH 10/24] Use touch state from ft63x6 driver. (#6055) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/ft63x6/ft63x6.cpp | 65 +++++++++++++--------------- 1 file changed, 29 insertions(+), 36 deletions(-) diff --git a/esphome/components/ft63x6/ft63x6.cpp b/esphome/components/ft63x6/ft63x6.cpp index b674ded22c..f796f0242a 100644 --- a/esphome/components/ft63x6/ft63x6.cpp +++ b/esphome/components/ft63x6/ft63x6.cpp @@ -13,14 +13,14 @@ namespace esphome { namespace ft63x6 { -static const uint8_t FT63X6_ADDR_TOUCH_COUNT = 0x02; - -static const uint8_t FT63X6_ADDR_TOUCH1_ID = 0x05; +static const uint8_t FT63X6_ADDR_TOUCH1_STATE = 0x03; static const uint8_t FT63X6_ADDR_TOUCH1_X = 0x03; +static const uint8_t FT63X6_ADDR_TOUCH1_ID = 0x05; static const uint8_t FT63X6_ADDR_TOUCH1_Y = 0x05; -static const uint8_t FT63X6_ADDR_TOUCH2_ID = 0x0B; +static const uint8_t FT63X6_ADDR_TOUCH2_STATE = 0x09; static const uint8_t FT63X6_ADDR_TOUCH2_X = 0x09; +static const uint8_t FT63X6_ADDR_TOUCH2_ID = 0x0B; static const uint8_t FT63X6_ADDR_TOUCH2_Y = 0x0B; static const char *const TAG = "FT63X6Touchscreen"; @@ -40,26 +40,11 @@ void FT63X6Touchscreen::setup() { this->hard_reset_(); // Get touch resolution - this->x_raw_max_ = 320; - this->y_raw_max_ = 480; -} - -void FT63X6Touchscreen::update_touches() { - int touch_count = this->read_touch_count_(); - if (touch_count == 0) { - return; + if (this->x_raw_max_ == this->x_raw_min_) { + this->x_raw_max_ = 320; } - - uint8_t touch_id = this->read_touch_id_(FT63X6_ADDR_TOUCH1_ID); // id1 = 0 or 1 - int16_t x = this->read_touch_coordinate_(FT63X6_ADDR_TOUCH1_X); - int16_t y = this->read_touch_coordinate_(FT63X6_ADDR_TOUCH1_Y); - this->add_raw_touch_position_(touch_id, x, y); - - if (touch_count >= 2) { - touch_id = this->read_touch_id_(FT63X6_ADDR_TOUCH2_ID); // id2 = 0 or 1(~id1 & 0x01) - x = this->read_touch_coordinate_(FT63X6_ADDR_TOUCH2_X); - y = this->read_touch_coordinate_(FT63X6_ADDR_TOUCH2_Y); - this->add_raw_touch_position_(touch_id, x, y); + if (this->y_raw_max_ == this->y_raw_min_) { + this->y_raw_max_ = 480; } } @@ -76,23 +61,31 @@ void FT63X6Touchscreen::dump_config() { LOG_I2C_DEVICE(this); LOG_PIN(" Interrupt Pin: ", this->interrupt_pin_); LOG_PIN(" Reset Pin: ", this->reset_pin_); + LOG_UPDATE_INTERVAL(this); } -uint8_t FT63X6Touchscreen::read_touch_count_() { return this->read_byte_(FT63X6_ADDR_TOUCH_COUNT); } +void FT63X6Touchscreen::update_touches() { + uint8_t data[15]; + uint16_t touch_id, x, y; -// Touch functions -uint16_t FT63X6Touchscreen::read_touch_coordinate_(uint8_t coordinate) { - uint8_t read_buf[2]; - read_buf[0] = this->read_byte_(coordinate); - read_buf[1] = this->read_byte_(coordinate + 1); - return ((read_buf[0] & 0x0f) << 8) | read_buf[1]; -} -uint8_t FT63X6Touchscreen::read_touch_id_(uint8_t id_address) { return this->read_byte_(id_address) >> 4; } + if (!this->read_bytes(0x00, (uint8_t *) data, 15)) { + ESP_LOGE(TAG, "Failed to read touch data"); + this->skip_update_ = true; + return; + } -uint8_t FT63X6Touchscreen::read_byte_(uint8_t addr) { - uint8_t byte = 0; - this->read_byte(addr, &byte); - return byte; + if (((data[FT63X6_ADDR_TOUCH1_STATE] >> 6) & 0x01) == 0) { + touch_id = data[FT63X6_ADDR_TOUCH1_ID] >> 4; // id1 = 0 or 1 + x = encode_uint16(data[FT63X6_ADDR_TOUCH1_X] & 0x0F, data[FT63X6_ADDR_TOUCH1_X + 1]); + y = encode_uint16(data[FT63X6_ADDR_TOUCH1_Y] & 0x0F, data[FT63X6_ADDR_TOUCH1_Y + 1]); + this->add_raw_touch_position_(touch_id, x, y); + } + if (((data[FT63X6_ADDR_TOUCH2_STATE] >> 6) & 0x01) == 0) { + touch_id = data[FT63X6_ADDR_TOUCH2_ID] >> 4; // id1 = 0 or 1 + x = encode_uint16(data[FT63X6_ADDR_TOUCH2_X] & 0x0F, data[FT63X6_ADDR_TOUCH2_X + 1]); + y = encode_uint16(data[FT63X6_ADDR_TOUCH2_Y] & 0x0F, data[FT63X6_ADDR_TOUCH2_Y + 1]); + this->add_raw_touch_position_(touch_id, x, y); + } } } // namespace ft63x6 From e39099137d04c1a7d2214cd33614b6b2449e8c79 Mon Sep 17 00:00:00 2001 From: NP v/d Spek Date: Mon, 15 Jan 2024 03:08:10 +0100 Subject: [PATCH 11/24] update script/setup so it works fine on windows (#6087) --- script/setup | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/script/setup b/script/setup index 9f448cf5c4..f286b4672a 100755 --- a/script/setup +++ b/script/setup @@ -4,10 +4,13 @@ set -e cd "$(dirname "$0")/.." - +location="venv/bin/activate" if [ ! -n "$DEVCONTAINER" ] && [ ! -n "$VIRTUAL_ENV" ] && [ ! "$ESPHOME_NO_VENV" ]; then python3 -m venv venv - source venv/bin/activate + if [ -f venv/Scripts/activate ]; then + location="venv/Scripts/activate" + fi + source $location; fi # Avoid unsafe git error when running inside devcontainer @@ -25,4 +28,4 @@ script/platformio_install_deps.py platformio.ini --libraries --tools --platforms echo echo -echo "Virtual environment created; source venv/bin/activate to use it" +echo "Virtual environment created. Run 'source $location' to use it." From 8cd17986740e6433e6b557965dfb1b7db79cad0c Mon Sep 17 00:00:00 2001 From: NP v/d Spek Date: Mon, 15 Jan 2024 03:09:35 +0100 Subject: [PATCH 12/24] add Pico-ResTouch-LCD-3.5 (#6078) --- esphome/components/ili9xxx/display.py | 6 +++++ .../components/ili9xxx/ili9xxx_display.cpp | 3 +++ esphome/components/ili9xxx/ili9xxx_display.h | 10 ++++++++- esphome/components/ili9xxx/ili9xxx_init.h | 22 +++++++++++++++++++ 4 files changed, 40 insertions(+), 1 deletion(-) diff --git a/esphome/components/ili9xxx/display.py b/esphome/components/ili9xxx/display.py index b3fe8b2b41..af8141f9fc 100644 --- a/esphome/components/ili9xxx/display.py +++ b/esphome/components/ili9xxx/display.py @@ -66,6 +66,7 @@ MODELS = { "ST7789V": ili9xxx_ns.class_("ILI9XXXST7789V", ILI9XXXDisplay), "S3BOX": ili9xxx_ns.class_("ILI9XXXS3Box", ILI9XXXDisplay), "S3BOX_LITE": ili9xxx_ns.class_("ILI9XXXS3BoxLite", ILI9XXXDisplay), + "WSPICOLCD": ili9xxx_ns.class_("ILI9XXXWSPICOLCD", ILI9XXXDisplay), } COLOR_ORDERS = { @@ -78,6 +79,7 @@ COLOR_PALETTE = cv.one_of("NONE", "GRAYSCALE", "IMAGE_ADAPTIVE") CONF_LED_PIN = "led_pin" CONF_COLOR_PALETTE_IMAGES = "color_palette_images" CONF_INVERT_DISPLAY = "invert_display" +CONF_18BIT_MODE = "18bit_mode" def _validate(config): @@ -139,6 +141,7 @@ CONFIG_SCHEMA = cv.All( "'invert_display' has been replaced by 'invert_colors'" ), cv.Optional(CONF_INVERT_COLORS): cv.boolean, + cv.Optional(CONF_18BIT_MODE): cv.boolean, cv.Optional(CONF_COLOR_ORDER): cv.one_of(*COLOR_ORDERS.keys(), upper=True), cv.Exclusive(CONF_ROTATION, CONF_ROTATION): validate_rotation, cv.Exclusive(CONF_TRANSFORM, CONF_ROTATION): cv.Schema( @@ -241,3 +244,6 @@ async def to_code(config): if CONF_INVERT_COLORS in config: cg.add(var.invert_colors(config[CONF_INVERT_COLORS])) + + if CONF_18BIT_MODE in config: + cg.add(var.set_18bit_mode(config[CONF_18BIT_MODE])) diff --git a/esphome/components/ili9xxx/ili9xxx_display.cpp b/esphome/components/ili9xxx/ili9xxx_display.cpp index ab577b3875..2844856246 100644 --- a/esphome/components/ili9xxx/ili9xxx_display.cpp +++ b/esphome/components/ili9xxx/ili9xxx_display.cpp @@ -34,6 +34,9 @@ void ILI9XXXDisplay::setup() { mad |= MADCTL_MY; this->send_command(ILI9XXX_MADCTL, &mad, 1); + mad = this->is_18bitdisplay_ ? 0x66 : 0x55; + this->send_command(ILI9XXX_PIXFMT, &mad, 1); + this->x_low_ = this->width_; this->y_low_ = this->height_; this->x_high_ = 0; diff --git a/esphome/components/ili9xxx/ili9xxx_display.h b/esphome/components/ili9xxx/ili9xxx_display.h index 590be3e364..c470f1e75d 100644 --- a/esphome/components/ili9xxx/ili9xxx_display.h +++ b/esphome/components/ili9xxx/ili9xxx_display.h @@ -53,9 +53,9 @@ class ILI9XXXDisplay : public display::DisplayBuffer, addr += num_args; } } + float get_setup_priority() const override; void set_dc_pin(GPIOPin *dc_pin) { dc_pin_ = dc_pin; } - float get_setup_priority() const override; void set_reset_pin(GPIOPin *reset) { this->reset_pin_ = reset; } void set_palette(const uint8_t *palette) { this->palette_ = palette; } void set_buffer_color_mode(ILI9XXXColorMode color_mode) { this->buffer_color_mode_ = color_mode; } @@ -67,7 +67,10 @@ class ILI9XXXDisplay : public display::DisplayBuffer, this->offset_x_ = offset_x; this->offset_y_ = offset_y; } + void set_18bit_mode(bool mode) { this->is_18bitdisplay_ = mode; }; + void invert_colors(bool invert); + void command(uint8_t value); void data(uint8_t value); void send_command(uint8_t command_byte, const uint8_t *data_bytes, uint8_t num_data_bytes); @@ -209,5 +212,10 @@ class ILI9XXXS3BoxLite : public ILI9XXXDisplay { ILI9XXXS3BoxLite() : ILI9XXXDisplay(INITCMD_S3BOXLITE, 320, 240, true) {} }; +class ILI9XXXWSPICOLCD : public ILI9XXXDisplay { + public: + ILI9XXXWSPICOLCD() : ILI9XXXDisplay(INITCMD_WSPICOLCD, 320, 480, true) {} +}; + } // namespace ili9xxx } // namespace esphome diff --git a/esphome/components/ili9xxx/ili9xxx_init.h b/esphome/components/ili9xxx/ili9xxx_init.h index a74824052f..0bf1d5761d 100644 --- a/esphome/components/ili9xxx/ili9xxx_init.h +++ b/esphome/components/ili9xxx/ili9xxx_init.h @@ -316,6 +316,28 @@ static const uint8_t PROGMEM INITCMD_ST7789V[] = { 0x00 // End of list }; +static const uint8_t PROGMEM INITCMD_WSPICOLCD[] = { +ILI9XXX_SLPOUT, 0x80, +ILI9XXX_PIXFMT, 1, 0x66, +ILI9XXX_PWCTR1, 2, 0x17, 0x15, // VRH1 VRH2 -ok +ILI9XXX_PWCTR2, 1, 0x41, // VGH, VGL - ok +ILI9XXX_PWCTR3, 1, 0x44, +//ILI9XXX_VMCTR1, 4, 0x00, 0x00, 0x00, 0x00, +ILI9XXX_VMCTR1, 3, 0x00, 0x12, 0x80, // nVM VCM_REG VCM_REG_EN - not ok? +ILI9XXX_IFMODE, 1, 0x00, // -ok +ILI9XXX_FRMCTR1, 1, 0xA0, // Frame rate = 60Hz -- seems to help the background! -ok +ILI9XXX_INVCTR, 1, 0x02, // Display Inversion Control = 2dot - ok +ILI9XXX_DFUNCTR, 2, 0x02, 0x02, // Nomal scan -ok +ILI9XXX_ADJCTL3, 4, 0xA9, 0x51, 0x2C, 0x82, // Adjust Control 3 +ILI9XXX_GMCTRP1,15, 0x00, 0x03, 0x09, 0x08, 0x16, 0x0A, 0x3F, 0x78, 0x4C, 0x09, 0x0A, 0x08, 0x16, 0x1A, 0x0F, +ILI9XXX_GMCTRN1,15, 0x00, 0x16, 0x19, 0x03, 0x0F, 0x05, 0x32, 0x45, 0x46, 0x04, 0x0E, 0x0D, 0x35, 0x37, 0x0F, + +ILI9XXX_INVON, 0x80, +ILI9XXX_MADCTL, 1, 0x48, +ILI9XXX_DISPON, 0x80, +0x00 // End of list +}; + // clang-format on } // namespace ili9xxx } // namespace esphome From 412c999f1497b2935fedaf27d62d1fd3259c54e7 Mon Sep 17 00:00:00 2001 From: NP v/d Spek Date: Mon, 15 Jan 2024 07:41:01 +0100 Subject: [PATCH 13/24] Revert "add Pico-ResTouch-LCD-3.5" (#6098) --- esphome/components/ili9xxx/display.py | 6 ----- .../components/ili9xxx/ili9xxx_display.cpp | 3 --- esphome/components/ili9xxx/ili9xxx_display.h | 10 +-------- esphome/components/ili9xxx/ili9xxx_init.h | 22 ------------------- 4 files changed, 1 insertion(+), 40 deletions(-) diff --git a/esphome/components/ili9xxx/display.py b/esphome/components/ili9xxx/display.py index af8141f9fc..b3fe8b2b41 100644 --- a/esphome/components/ili9xxx/display.py +++ b/esphome/components/ili9xxx/display.py @@ -66,7 +66,6 @@ MODELS = { "ST7789V": ili9xxx_ns.class_("ILI9XXXST7789V", ILI9XXXDisplay), "S3BOX": ili9xxx_ns.class_("ILI9XXXS3Box", ILI9XXXDisplay), "S3BOX_LITE": ili9xxx_ns.class_("ILI9XXXS3BoxLite", ILI9XXXDisplay), - "WSPICOLCD": ili9xxx_ns.class_("ILI9XXXWSPICOLCD", ILI9XXXDisplay), } COLOR_ORDERS = { @@ -79,7 +78,6 @@ COLOR_PALETTE = cv.one_of("NONE", "GRAYSCALE", "IMAGE_ADAPTIVE") CONF_LED_PIN = "led_pin" CONF_COLOR_PALETTE_IMAGES = "color_palette_images" CONF_INVERT_DISPLAY = "invert_display" -CONF_18BIT_MODE = "18bit_mode" def _validate(config): @@ -141,7 +139,6 @@ CONFIG_SCHEMA = cv.All( "'invert_display' has been replaced by 'invert_colors'" ), cv.Optional(CONF_INVERT_COLORS): cv.boolean, - cv.Optional(CONF_18BIT_MODE): cv.boolean, cv.Optional(CONF_COLOR_ORDER): cv.one_of(*COLOR_ORDERS.keys(), upper=True), cv.Exclusive(CONF_ROTATION, CONF_ROTATION): validate_rotation, cv.Exclusive(CONF_TRANSFORM, CONF_ROTATION): cv.Schema( @@ -244,6 +241,3 @@ async def to_code(config): if CONF_INVERT_COLORS in config: cg.add(var.invert_colors(config[CONF_INVERT_COLORS])) - - if CONF_18BIT_MODE in config: - cg.add(var.set_18bit_mode(config[CONF_18BIT_MODE])) diff --git a/esphome/components/ili9xxx/ili9xxx_display.cpp b/esphome/components/ili9xxx/ili9xxx_display.cpp index 2844856246..ab577b3875 100644 --- a/esphome/components/ili9xxx/ili9xxx_display.cpp +++ b/esphome/components/ili9xxx/ili9xxx_display.cpp @@ -34,9 +34,6 @@ void ILI9XXXDisplay::setup() { mad |= MADCTL_MY; this->send_command(ILI9XXX_MADCTL, &mad, 1); - mad = this->is_18bitdisplay_ ? 0x66 : 0x55; - this->send_command(ILI9XXX_PIXFMT, &mad, 1); - this->x_low_ = this->width_; this->y_low_ = this->height_; this->x_high_ = 0; diff --git a/esphome/components/ili9xxx/ili9xxx_display.h b/esphome/components/ili9xxx/ili9xxx_display.h index c470f1e75d..590be3e364 100644 --- a/esphome/components/ili9xxx/ili9xxx_display.h +++ b/esphome/components/ili9xxx/ili9xxx_display.h @@ -53,9 +53,9 @@ class ILI9XXXDisplay : public display::DisplayBuffer, addr += num_args; } } - float get_setup_priority() const override; void set_dc_pin(GPIOPin *dc_pin) { dc_pin_ = dc_pin; } + float get_setup_priority() const override; void set_reset_pin(GPIOPin *reset) { this->reset_pin_ = reset; } void set_palette(const uint8_t *palette) { this->palette_ = palette; } void set_buffer_color_mode(ILI9XXXColorMode color_mode) { this->buffer_color_mode_ = color_mode; } @@ -67,10 +67,7 @@ class ILI9XXXDisplay : public display::DisplayBuffer, this->offset_x_ = offset_x; this->offset_y_ = offset_y; } - void set_18bit_mode(bool mode) { this->is_18bitdisplay_ = mode; }; - void invert_colors(bool invert); - void command(uint8_t value); void data(uint8_t value); void send_command(uint8_t command_byte, const uint8_t *data_bytes, uint8_t num_data_bytes); @@ -212,10 +209,5 @@ class ILI9XXXS3BoxLite : public ILI9XXXDisplay { ILI9XXXS3BoxLite() : ILI9XXXDisplay(INITCMD_S3BOXLITE, 320, 240, true) {} }; -class ILI9XXXWSPICOLCD : public ILI9XXXDisplay { - public: - ILI9XXXWSPICOLCD() : ILI9XXXDisplay(INITCMD_WSPICOLCD, 320, 480, true) {} -}; - } // namespace ili9xxx } // namespace esphome diff --git a/esphome/components/ili9xxx/ili9xxx_init.h b/esphome/components/ili9xxx/ili9xxx_init.h index 0bf1d5761d..a74824052f 100644 --- a/esphome/components/ili9xxx/ili9xxx_init.h +++ b/esphome/components/ili9xxx/ili9xxx_init.h @@ -316,28 +316,6 @@ static const uint8_t PROGMEM INITCMD_ST7789V[] = { 0x00 // End of list }; -static const uint8_t PROGMEM INITCMD_WSPICOLCD[] = { -ILI9XXX_SLPOUT, 0x80, -ILI9XXX_PIXFMT, 1, 0x66, -ILI9XXX_PWCTR1, 2, 0x17, 0x15, // VRH1 VRH2 -ok -ILI9XXX_PWCTR2, 1, 0x41, // VGH, VGL - ok -ILI9XXX_PWCTR3, 1, 0x44, -//ILI9XXX_VMCTR1, 4, 0x00, 0x00, 0x00, 0x00, -ILI9XXX_VMCTR1, 3, 0x00, 0x12, 0x80, // nVM VCM_REG VCM_REG_EN - not ok? -ILI9XXX_IFMODE, 1, 0x00, // -ok -ILI9XXX_FRMCTR1, 1, 0xA0, // Frame rate = 60Hz -- seems to help the background! -ok -ILI9XXX_INVCTR, 1, 0x02, // Display Inversion Control = 2dot - ok -ILI9XXX_DFUNCTR, 2, 0x02, 0x02, // Nomal scan -ok -ILI9XXX_ADJCTL3, 4, 0xA9, 0x51, 0x2C, 0x82, // Adjust Control 3 -ILI9XXX_GMCTRP1,15, 0x00, 0x03, 0x09, 0x08, 0x16, 0x0A, 0x3F, 0x78, 0x4C, 0x09, 0x0A, 0x08, 0x16, 0x1A, 0x0F, -ILI9XXX_GMCTRN1,15, 0x00, 0x16, 0x19, 0x03, 0x0F, 0x05, 0x32, 0x45, 0x46, 0x04, 0x0E, 0x0D, 0x35, 0x37, 0x0F, - -ILI9XXX_INVON, 0x80, -ILI9XXX_MADCTL, 1, 0x48, -ILI9XXX_DISPON, 0x80, -0x00 // End of list -}; - // clang-format on } // namespace ili9xxx } // namespace esphome From 87cab92af66ee4340f0cd578f4162eb64533ff27 Mon Sep 17 00:00:00 2001 From: aschmitz <29508+aschmitz@users.noreply.github.com> Date: Mon, 15 Jan 2024 01:08:19 -0600 Subject: [PATCH 14/24] fix: negative temperatures on PMS5003T sensors (#6100) --- esphome/components/pmsx003/pmsx003.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/pmsx003/pmsx003.cpp b/esphome/components/pmsx003/pmsx003.cpp index 62488b765c..de2b23b8eb 100644 --- a/esphome/components/pmsx003/pmsx003.cpp +++ b/esphome/components/pmsx003/pmsx003.cpp @@ -279,7 +279,7 @@ void PMSX003Component::parse_data_() { // Note the pm particles 50um & 100um are not returned, // as PMS5003T uses those data values for temperature and humidity. - float temperature = this->get_16_bit_uint_(24) / 10.0f; + float temperature = (int16_t) this->get_16_bit_uint_(24) / 10.0f; float humidity = this->get_16_bit_uint_(26) / 10.0f; ESP_LOGD(TAG, From 72ab1700e71305038d8f8c71f3adb53bed64c526 Mon Sep 17 00:00:00 2001 From: mathieu-mp Date: Tue, 16 Jan 2024 07:38:09 +0100 Subject: [PATCH 15/24] Add triangle shapes to display component (#6096) --- esphome/components/display/display.cpp | 116 +++++++++++++++++++++++++ esphome/components/display/display.h | 15 ++++ 2 files changed, 131 insertions(+) diff --git a/esphome/components/display/display.cpp b/esphome/components/display/display.cpp index f32fda4794..0c3631342e 100644 --- a/esphome/components/display/display.cpp +++ b/esphome/components/display/display.cpp @@ -141,6 +141,122 @@ void Display::filled_circle(int center_x, int center_y, int radius, Color color) } } while (dx <= 0); } +void HOT Display::triangle(int x1, int y1, int x2, int y2, int x3, int y3, Color color) { + this->line(x1, y1, x2, y2); + this->line(x1, y1, x3, y3); + this->line(x2, y2, x3, y3); +} +void Display::sort_triangle_points_by_y_(int *x1, int *y1, int *x2, int *y2, int *x3, int *y3) { + if (*y1 > *y2) { + int x_temp = *x1, y_temp = *y1; + *x1 = *x2, *y1 = *y2; + *x2 = x_temp, *y2 = y_temp; + } + if (*y1 > *y3) { + int x_temp = *x1, y_temp = *y1; + *x1 = *x3, *y1 = *y3; + *x3 = x_temp, *y3 = y_temp; + } + if (*y2 > *y3) { + int x_temp = *x2, y_temp = *y2; + *x2 = *x3, *y2 = *y3; + *x3 = x_temp, *y3 = y_temp; + } +} +void Display::filled_flat_side_triangle_(int x1, int y1, int x2, int y2, int x3, int y3, Color color) { + // y2 must be equal to y3 (same horizontal line) + + // Initialize Bresenham's algorithm for side 1 + int s1_current_x = x1; + int s1_current_y = y1; + bool s1_axis_swap = false; + int s1_dx = abs(x2 - x1); + int s1_dy = abs(y2 - y1); + int s1_sign_x = ((x2 - x1) >= 0) ? 1 : -1; + int s1_sign_y = ((y2 - y1) >= 0) ? 1 : -1; + if (s1_dy > s1_dx) { // swap values + int tmp = s1_dx; + s1_dx = s1_dy; + s1_dy = tmp; + s1_axis_swap = true; + } + int s1_error = 2 * s1_dy - s1_dx; + + // Initialize Bresenham's algorithm for side 2 + int s2_current_x = x1; + int s2_current_y = y1; + bool s2_axis_swap = false; + int s2_dx = abs(x3 - x1); + int s2_dy = abs(y3 - y1); + int s2_sign_x = ((x3 - x1) >= 0) ? 1 : -1; + int s2_sign_y = ((y3 - y1) >= 0) ? 1 : -1; + if (s2_dy > s2_dx) { // swap values + int tmp = s2_dx; + s2_dx = s2_dy; + s2_dy = tmp; + s2_axis_swap = true; + } + int s2_error = 2 * s2_dy - s2_dx; + + // Iterate on side 1 and allow side 2 to be processed to match the advance of the y-axis. + for (int i = 0; i <= s1_dx; i++) { + if (s1_current_x <= s2_current_x) { + this->horizontal_line(s1_current_x, s1_current_y, s2_current_x - s1_current_x + 1, color); + } else { + this->horizontal_line(s2_current_x, s2_current_y, s1_current_x - s2_current_x + 1, color); + } + + // Bresenham's #1 + // Side 1 s1_current_x and s1_current_y calculation + while (s1_error >= 0) { + if (s1_axis_swap) { + s1_current_x += s1_sign_x; + } else { + s1_current_y += s1_sign_y; + } + s1_error = s1_error - 2 * s1_dx; + } + if (s1_axis_swap) { + s1_current_y += s1_sign_y; + } else { + s1_current_x += s1_sign_x; + } + s1_error = s1_error + 2 * s1_dy; + + // Bresenham's #2 + // Side 2 s2_current_x and s2_current_y calculation + while (s2_current_y != s1_current_y) { + while (s2_error >= 0) { + if (s2_axis_swap) { + s2_current_x += s2_sign_x; + } else { + s2_current_y += s2_sign_y; + } + s2_error = s2_error - 2 * s2_dx; + } + if (s2_axis_swap) { + s2_current_y += s2_sign_y; + } else { + s2_current_x += s2_sign_x; + } + s2_error = s2_error + 2 * s2_dy; + } + } +} +void Display::filled_triangle(int x1, int y1, int x2, int y2, int x3, int y3, Color color) { + // Sort the three points by y-coordinate ascending, so [x1,y1] is the topmost point + this->sort_triangle_points_by_y_(&x1, &y1, &x2, &y2, &x3, &y3); + + if (y2 == y3) { // Check for special case of a bottom-flat triangle + this->filled_flat_side_triangle_(x1, y1, x2, y2, x3, y3, color); + } else if (y1 == y2) { // Check for special case of a top-flat triangle + this->filled_flat_side_triangle_(x3, y3, x1, y1, x2, y2, color); + } else { // General case: split the no-flat-side triangle in a top-flat triangle and bottom-flat triangle + int x_temp = (int) (x1 + ((float) (y2 - y1) / (float) (y3 - y1)) * (x3 - x1)), y_temp = y2; + this->filled_flat_side_triangle_(x1, y1, x2, y2, x_temp, y_temp, color); + this->filled_flat_side_triangle_(x3, y3, x2, y2, x_temp, y_temp, color); + } +} void Display::print(int x, int y, BaseFont *font, Color color, TextAlign align, const char *text) { int x_start, y_start; diff --git a/esphome/components/display/display.h b/esphome/components/display/display.h index 2a2a9b80c8..daa5028d6b 100644 --- a/esphome/components/display/display.h +++ b/esphome/components/display/display.h @@ -236,6 +236,12 @@ class Display : public PollingComponent { /// Fill a circle centered around [center_x,center_y] with the radius radius with the given color. void filled_circle(int center_x, int center_y, int radius, Color color = COLOR_ON); + /// Draw the outline of a triangle contained between the points [x1,y1], [x2,y2] and [x3,y3] with the given color. + void triangle(int x1, int y1, int x2, int y2, int x3, int y3, Color color = COLOR_ON); + + /// Fill a triangle contained between the points [x1,y1], [x2,y2] and [x3,y3] with the given color. + void filled_triangle(int x1, int y1, int x2, int y2, int x3, int y3, Color color = COLOR_ON); + /** Print `text` with the anchor point at [x,y] with `font`. * * @param x The x coordinate of the text alignment anchor point. @@ -532,6 +538,15 @@ class Display : public PollingComponent { void do_update_(); void clear_clipping_(); + /** + * This method fills a triangle using only integer variables by using a + * modified bresenham algorithm. + * It is mandatory that [x2,y2] and [x3,y3] lie on the same horizontal line, + * so y2 must be equal to y3. + */ + void filled_flat_side_triangle_(int x1, int y1, int x2, int y2, int x3, int y3, Color color); + void sort_triangle_points_by_y_(int *x1, int *y1, int *x2, int *y2, int *x3, int *y3); + DisplayRotation rotation_{DISPLAY_ROTATION_0_DEGREES}; optional writer_{}; DisplayPage *page_{nullptr}; From 249cd6758879c56b72cc1b165a02cd8df1e551f0 Mon Sep 17 00:00:00 2001 From: alexborro Date: Tue, 16 Jan 2024 08:38:19 +0100 Subject: [PATCH 16/24] Fingerprint_grow: Trigger on finger scan start and on finger scan misplaced (#6003) --- .../components/fingerprint_grow/__init__.py | 32 +++++++++++++ .../fingerprint_grow/fingerprint_grow.cpp | 46 +++++++++++++------ .../fingerprint_grow/fingerprint_grow.h | 24 ++++++++++ esphome/const.py | 2 + tests/test3.yaml | 6 +++ 5 files changed, 96 insertions(+), 14 deletions(-) diff --git a/esphome/components/fingerprint_grow/__init__.py b/esphome/components/fingerprint_grow/__init__.py index 5249107f17..26a01fc1d2 100644 --- a/esphome/components/fingerprint_grow/__init__.py +++ b/esphome/components/fingerprint_grow/__init__.py @@ -13,8 +13,10 @@ from esphome.const import ( CONF_ON_ENROLLMENT_DONE, CONF_ON_ENROLLMENT_FAILED, CONF_ON_ENROLLMENT_SCAN, + CONF_ON_FINGER_SCAN_START, CONF_ON_FINGER_SCAN_MATCHED, CONF_ON_FINGER_SCAN_UNMATCHED, + CONF_ON_FINGER_SCAN_MISPLACED, CONF_ON_FINGER_SCAN_INVALID, CONF_PASSWORD, CONF_SENSING_PIN, @@ -35,6 +37,10 @@ FingerprintGrowComponent = fingerprint_grow_ns.class_( "FingerprintGrowComponent", cg.PollingComponent, uart.UARTDevice ) +FingerScanStartTrigger = fingerprint_grow_ns.class_( + "FingerScanStartTrigger", automation.Trigger.template() +) + FingerScanMatchedTrigger = fingerprint_grow_ns.class_( "FingerScanMatchedTrigger", automation.Trigger.template(cg.uint16, cg.uint16) ) @@ -43,6 +49,10 @@ FingerScanUnmatchedTrigger = fingerprint_grow_ns.class_( "FingerScanUnmatchedTrigger", automation.Trigger.template() ) +FingerScanMisplacedTrigger = fingerprint_grow_ns.class_( + "FingerScanMisplacedTrigger", automation.Trigger.template() +) + FingerScanInvalidTrigger = fingerprint_grow_ns.class_( "FingerScanInvalidTrigger", automation.Trigger.template() ) @@ -99,6 +109,13 @@ CONFIG_SCHEMA = ( cv.Optional(CONF_SENSING_PIN): pins.gpio_input_pin_schema, cv.Optional(CONF_PASSWORD): cv.uint32_t, cv.Optional(CONF_NEW_PASSWORD): cv.uint32_t, + cv.Optional(CONF_ON_FINGER_SCAN_START): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( + FingerScanStartTrigger + ), + } + ), cv.Optional(CONF_ON_FINGER_SCAN_MATCHED): automation.validate_automation( { cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( @@ -113,6 +130,13 @@ CONFIG_SCHEMA = ( ), } ), + cv.Optional(CONF_ON_FINGER_SCAN_MISPLACED): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( + FingerScanMisplacedTrigger + ), + } + ), cv.Optional(CONF_ON_FINGER_SCAN_INVALID): automation.validate_automation( { cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( @@ -164,6 +188,10 @@ async def to_code(config): sensing_pin = await cg.gpio_pin_expression(config[CONF_SENSING_PIN]) cg.add(var.set_sensing_pin(sensing_pin)) + for conf in config.get(CONF_ON_FINGER_SCAN_START, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [], conf) + for conf in config.get(CONF_ON_FINGER_SCAN_MATCHED, []): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) await automation.build_automation( @@ -174,6 +202,10 @@ async def to_code(config): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) await automation.build_automation(trigger, [], conf) + for conf in config.get(CONF_ON_FINGER_SCAN_MISPLACED, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [], conf) + for conf in config.get(CONF_ON_FINGER_SCAN_INVALID, []): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) await automation.build_automation(trigger, [], conf) diff --git a/esphome/components/fingerprint_grow/fingerprint_grow.cpp b/esphome/components/fingerprint_grow/fingerprint_grow.cpp index 2486e02964..0a46755bd3 100644 --- a/esphome/components/fingerprint_grow/fingerprint_grow.cpp +++ b/esphome/components/fingerprint_grow/fingerprint_grow.cpp @@ -15,16 +15,18 @@ void FingerprintGrowComponent::update() { return; } - if (this->sensing_pin_ != nullptr) { + if (this->has_sensing_pin_) { if (this->sensing_pin_->digital_read()) { ESP_LOGV(TAG, "No touch sensing"); this->waiting_removal_ = false; return; + } else if (!this->waiting_removal_) { + this->finger_scan_start_callback_.call(); } } if (this->waiting_removal_) { - if (this->scan_image_(1) == NO_FINGER) { + if ((!this->has_sensing_pin_) && (this->scan_image_(1) == NO_FINGER)) { ESP_LOGD(TAG, "Finger removed"); this->waiting_removal_ = false; } @@ -51,6 +53,7 @@ void FingerprintGrowComponent::update() { void FingerprintGrowComponent::setup() { ESP_LOGCONFIG(TAG, "Setting up Grow Fingerprint Reader..."); + this->has_sensing_pin_ = (this->sensing_pin_ != nullptr); if (this->check_password_()) { if (this->new_password_ != -1) { if (this->set_password_()) @@ -91,7 +94,7 @@ void FingerprintGrowComponent::finish_enrollment(uint8_t result) { } void FingerprintGrowComponent::scan_and_match_() { - if (this->sensing_pin_ != nullptr) { + if (this->has_sensing_pin_) { ESP_LOGD(TAG, "Scan and match"); } else { ESP_LOGV(TAG, "Scan and match"); @@ -122,33 +125,38 @@ void FingerprintGrowComponent::scan_and_match_() { } uint8_t FingerprintGrowComponent::scan_image_(uint8_t buffer) { - if (this->sensing_pin_ != nullptr) { + if (this->has_sensing_pin_) { ESP_LOGD(TAG, "Getting image %d", buffer); } else { ESP_LOGV(TAG, "Getting image %d", buffer); } this->data_ = {GET_IMAGE}; - switch (this->send_command_()) { + uint8_t send_result = this->send_command_(); + switch (send_result) { case OK: break; case NO_FINGER: - if (this->sensing_pin_ != nullptr) { - ESP_LOGD(TAG, "No finger"); - this->finger_scan_invalid_callback_.call(); + if (this->has_sensing_pin_) { + this->waiting_removal_ = true; + ESP_LOGD(TAG, "Finger Misplaced"); + this->finger_scan_misplaced_callback_.call(); } else { ESP_LOGV(TAG, "No finger"); } - return this->data_[0]; + return send_result; case IMAGE_FAIL: ESP_LOGE(TAG, "Imaging error"); this->finger_scan_invalid_callback_.call(); + return send_result; default: - return this->data_[0]; + ESP_LOGD(TAG, "Unknown Scan Error: %d", send_result); + return send_result; } ESP_LOGD(TAG, "Processing image %d", buffer); this->data_ = {IMAGE_2_TZ, buffer}; - switch (this->send_command_()) { + send_result = this->send_command_(); + switch (send_result) { case OK: ESP_LOGI(TAG, "Processed image %d", buffer); break; @@ -162,7 +170,7 @@ uint8_t FingerprintGrowComponent::scan_image_(uint8_t buffer) { this->finger_scan_invalid_callback_.call(); break; } - return this->data_[0]; + return send_result; } uint8_t FingerprintGrowComponent::save_fingerprint_() { @@ -225,10 +233,11 @@ bool FingerprintGrowComponent::get_parameters_() { ESP_LOGD(TAG, "Getting parameters"); this->data_ = {READ_SYS_PARAM}; if (this->send_command_() == OK) { - ESP_LOGD(TAG, "Got parameters"); - if (this->status_sensor_ != nullptr) { + ESP_LOGD(TAG, "Got parameters"); // Bear in mind data_[0] is the transfer status, + if (this->status_sensor_ != nullptr) { // the parameters table start at data_[1] this->status_sensor_->publish_state(((uint16_t) this->data_[1] << 8) | this->data_[2]); } + this->system_identifier_code_ = ((uint16_t) this->data_[3] << 8) | this->data_[4]; this->capacity_ = ((uint16_t) this->data_[5] << 8) | this->data_[6]; if (this->capacity_sensor_ != nullptr) { this->capacity_sensor_->publish_state(this->capacity_); @@ -430,13 +439,22 @@ uint8_t FingerprintGrowComponent::send_command_() { void FingerprintGrowComponent::dump_config() { ESP_LOGCONFIG(TAG, "GROW_FINGERPRINT_READER:"); + ESP_LOGCONFIG(TAG, " System Identifier Code: 0x%.4X", this->system_identifier_code_); + ESP_LOGCONFIG(TAG, " Touch Sensing Pin: %s", + this->has_sensing_pin_ ? this->sensing_pin_->dump_summary().c_str() : "None"); LOG_UPDATE_INTERVAL(this); LOG_SENSOR(" ", "Fingerprint Count", this->fingerprint_count_sensor_); + ESP_LOGCONFIG(TAG, " Current Value: %d", (uint16_t) this->fingerprint_count_sensor_->get_state()); LOG_SENSOR(" ", "Status", this->status_sensor_); + ESP_LOGCONFIG(TAG, " Current Value: %d", (uint8_t) this->status_sensor_->get_state()); LOG_SENSOR(" ", "Capacity", this->capacity_sensor_); + ESP_LOGCONFIG(TAG, " Current Value: %d", (uint16_t) this->capacity_sensor_->get_state()); LOG_SENSOR(" ", "Security Level", this->security_level_sensor_); + ESP_LOGCONFIG(TAG, " Current Value: %d", (uint8_t) this->security_level_sensor_->get_state()); LOG_SENSOR(" ", "Last Finger ID", this->last_finger_id_sensor_); + ESP_LOGCONFIG(TAG, " Current Value: %d", (uint32_t) this->last_finger_id_sensor_->get_state()); LOG_SENSOR(" ", "Last Confidence", this->last_confidence_sensor_); + ESP_LOGCONFIG(TAG, " Current Value: %d", (uint32_t) this->last_confidence_sensor_->get_state()); } } // namespace fingerprint_grow diff --git a/esphome/components/fingerprint_grow/fingerprint_grow.h b/esphome/components/fingerprint_grow/fingerprint_grow.h index 9aad94fc2a..1ab38d9fb5 100644 --- a/esphome/components/fingerprint_grow/fingerprint_grow.h +++ b/esphome/components/fingerprint_grow/fingerprint_grow.h @@ -118,12 +118,18 @@ class FingerprintGrowComponent : public PollingComponent, public uart::UARTDevic void set_enrolling_binary_sensor(binary_sensor::BinarySensor *enrolling_binary_sensor) { this->enrolling_binary_sensor_ = enrolling_binary_sensor; } + void add_on_finger_scan_start_callback(std::function callback) { + this->finger_scan_start_callback_.add(std::move(callback)); + } void add_on_finger_scan_matched_callback(std::function callback) { this->finger_scan_matched_callback_.add(std::move(callback)); } void add_on_finger_scan_unmatched_callback(std::function callback) { this->finger_scan_unmatched_callback_.add(std::move(callback)); } + void add_on_finger_scan_misplaced_callback(std::function callback) { + this->finger_scan_misplaced_callback_.add(std::move(callback)); + } void add_on_finger_scan_invalid_callback(std::function callback) { this->finger_scan_invalid_callback_.add(std::move(callback)); } @@ -166,8 +172,10 @@ class FingerprintGrowComponent : public PollingComponent, public uart::UARTDevic uint16_t enrollment_slot_ = ENROLLMENT_SLOT_UNUSED; uint8_t enrollment_buffers_ = 5; bool waiting_removal_ = false; + bool has_sensing_pin_ = false; uint32_t last_aura_led_control_ = 0; uint16_t last_aura_led_duration_ = 0; + uint16_t system_identifier_code_ = 0; sensor::Sensor *fingerprint_count_sensor_{nullptr}; sensor::Sensor *status_sensor_{nullptr}; sensor::Sensor *capacity_sensor_{nullptr}; @@ -176,13 +184,22 @@ class FingerprintGrowComponent : public PollingComponent, public uart::UARTDevic sensor::Sensor *last_confidence_sensor_{nullptr}; binary_sensor::BinarySensor *enrolling_binary_sensor_{nullptr}; CallbackManager finger_scan_invalid_callback_; + CallbackManager finger_scan_start_callback_; CallbackManager finger_scan_matched_callback_; CallbackManager finger_scan_unmatched_callback_; + CallbackManager finger_scan_misplaced_callback_; CallbackManager enrollment_scan_callback_; CallbackManager enrollment_done_callback_; CallbackManager enrollment_failed_callback_; }; +class FingerScanStartTrigger : public Trigger<> { + public: + explicit FingerScanStartTrigger(FingerprintGrowComponent *parent) { + parent->add_on_finger_scan_start_callback([this]() { this->trigger(); }); + } +}; + class FingerScanMatchedTrigger : public Trigger { public: explicit FingerScanMatchedTrigger(FingerprintGrowComponent *parent) { @@ -198,6 +215,13 @@ class FingerScanUnmatchedTrigger : public Trigger<> { } }; +class FingerScanMisplacedTrigger : public Trigger<> { + public: + explicit FingerScanMisplacedTrigger(FingerprintGrowComponent *parent) { + parent->add_on_finger_scan_misplaced_callback([this]() { this->trigger(); }); + } +}; + class FingerScanInvalidTrigger : public Trigger<> { public: explicit FingerScanInvalidTrigger(FingerprintGrowComponent *parent) { diff --git a/esphome/const.py b/esphome/const.py index 7e27254d76..c35adc74ee 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -508,6 +508,8 @@ CONF_ON_ENROLLMENT_FAILED = "on_enrollment_failed" CONF_ON_ENROLLMENT_SCAN = "on_enrollment_scan" CONF_ON_FINGER_SCAN_INVALID = "on_finger_scan_invalid" CONF_ON_FINGER_SCAN_MATCHED = "on_finger_scan_matched" +CONF_ON_FINGER_SCAN_MISPLACED = "on_finger_scan_misplaced" +CONF_ON_FINGER_SCAN_START = "on_finger_scan_start" CONF_ON_FINGER_SCAN_UNMATCHED = "on_finger_scan_unmatched" CONF_ON_JSON_MESSAGE = "on_json_message" CONF_ON_LOCK = "on_lock" diff --git a/tests/test3.yaml b/tests/test3.yaml index c31eb45fbd..cbd3d15b8a 100644 --- a/tests/test3.yaml +++ b/tests/test3.yaml @@ -1258,6 +1258,9 @@ fingerprint_grow: number: 4 password: 0x12FE37DC new_password: 0xA65B9840 + on_finger_scan_start: + - homeassistant.event: + event: esphome.${device_name}_fingerprint_grow_finger_scan_start on_finger_scan_invalid: - homeassistant.event: event: esphome.${device_name}_fingerprint_grow_finger_scan_invalid @@ -1270,6 +1273,9 @@ fingerprint_grow: on_finger_scan_unmatched: - homeassistant.event: event: esphome.${device_name}_fingerprint_grow_finger_scan_unmatched + on_finger_scan_misplaced: + - homeassistant.event: + event: esphome.${device_name}_fingerprint_grow_finger_scan_misplaced on_enrollment_scan: - homeassistant.event: event: esphome.${device_name}_fingerprint_grow_enrollment_scan From 26acbbedbf2626eec1232343901a01348369a8ce Mon Sep 17 00:00:00 2001 From: Samuel Sieb Date: Mon, 15 Jan 2024 23:44:12 -0800 Subject: [PATCH 17/24] Add continuous option to the graph (#6093) Co-authored-by: Samuel Sieb --- esphome/components/graph/__init__.py | 4 +++ esphome/components/graph/graph.cpp | 37 +++++++++++++++++++++++----- esphome/components/graph/graph.h | 3 +++ 3 files changed, 38 insertions(+), 6 deletions(-) diff --git a/esphome/components/graph/__init__.py b/esphome/components/graph/__init__.py index 046f59ca1a..0b83b71fe4 100644 --- a/esphome/components/graph/__init__.py +++ b/esphome/components/graph/__init__.py @@ -61,6 +61,7 @@ VALUE_POSITION_TYPE = { "BELOW": ValuePositionType.VALUE_POSITION_TYPE_BELOW, } +CONF_CONTINUOUS = "continuous" GRAPH_TRACE_SCHEMA = cv.Schema( { @@ -70,6 +71,7 @@ GRAPH_TRACE_SCHEMA = cv.Schema( cv.Optional(CONF_LINE_THICKNESS): cv.positive_int, cv.Optional(CONF_LINE_TYPE): cv.enum(LINE_TYPE, upper=True), cv.Optional(CONF_COLOR): cv.use_id(color.ColorStruct), + cv.Optional(CONF_CONTINUOUS): cv.boolean, } ) @@ -186,6 +188,8 @@ async def to_code(config): if CONF_COLOR in trace: c = await cg.get_variable(trace[CONF_COLOR]) cg.add(tr.set_line_color(c)) + if CONF_CONTINUOUS in trace: + cg.add(tr.set_continuous(trace[CONF_CONTINUOUS])) cg.add(var.add_trace(tr)) # Add legend if CONF_LEGEND in config: diff --git a/esphome/components/graph/graph.cpp b/esphome/components/graph/graph.cpp index 294e16dbb1..0e437a3425 100644 --- a/esphome/components/graph/graph.cpp +++ b/esphome/components/graph/graph.cpp @@ -165,17 +165,42 @@ void Graph::draw(Display *buff, uint16_t x_offset, uint16_t y_offset, Color colo for (auto *trace : traces_) { Color c = trace->get_line_color(); uint16_t thick = trace->get_line_thickness(); + bool continuous = trace->get_continuous(); + bool has_prev = false; + bool prev_b = false; + int16_t prev_y = 0; for (uint32_t i = 0; i < this->width_; i++) { float v = (trace->get_tracedata()->get_value(i) - ymin) / yrange; if (!std::isnan(v) && (thick > 0)) { - int16_t x = this->width_ - 1 - i; - uint8_t b = (i % (thick * LineType::PATTERN_LENGTH)) / thick; - if (((uint8_t) trace->get_line_type() & (1 << b)) == (1 << b)) { - int16_t y = (int16_t) roundf((this->height_ - 1) * (1.0 - v)) - thick / 2; - for (uint16_t t = 0; t < thick; t++) { - buff->draw_pixel_at(x_offset + x, y_offset + y + t, c); + int16_t x = this->width_ - 1 - i + x_offset; + uint8_t bit = 1 << ((i % (thick * LineType::PATTERN_LENGTH)) / thick); + bool b = (trace->get_line_type() & bit) == bit; + if (b) { + int16_t y = (int16_t) roundf((this->height_ - 1) * (1.0 - v)) - thick / 2 + y_offset; + if (!continuous || !has_prev || !prev_b || (abs(y - prev_y) <= thick)) { + for (uint16_t t = 0; t < thick; t++) { + buff->draw_pixel_at(x, y + t, c); + } + } else { + int16_t mid_y = (y + prev_y + thick) / 2; + if (y > prev_y) { + for (uint16_t t = prev_y + thick; t <= mid_y; t++) + buff->draw_pixel_at(x + 1, t, c); + for (uint16_t t = mid_y + 1; t < y + thick; t++) + buff->draw_pixel_at(x, t, c); + } else { + for (uint16_t t = prev_y - 1; t >= mid_y; t--) + buff->draw_pixel_at(x + 1, t, c); + for (uint16_t t = mid_y - 1; t >= y; t--) + buff->draw_pixel_at(x, t, c); + } } + prev_y = y; } + prev_b = b; + has_prev = true; + } else { + has_prev = false; } } } diff --git a/esphome/components/graph/graph.h b/esphome/components/graph/graph.h index 339a6f6d94..34accb7d3a 100644 --- a/esphome/components/graph/graph.h +++ b/esphome/components/graph/graph.h @@ -116,6 +116,8 @@ class GraphTrace { void set_line_type(enum LineType val) { this->line_type_ = val; } Color get_line_color() { return this->line_color_; } void set_line_color(Color val) { this->line_color_ = val; } + bool get_continuous() { return this->continuous_; } + void set_continuous(bool continuous) { this->continuous_ = continuous; } std::string get_name() { return name_; } const HistoryData *get_tracedata() { return &data_; } @@ -125,6 +127,7 @@ class GraphTrace { uint8_t line_thickness_{3}; enum LineType line_type_ { LINE_TYPE_SOLID }; Color line_color_{COLOR_ON}; + bool continuous_{false}; HistoryData data_; friend Graph; From e35cab018a464c0d39fe7916a2b5de9d7c9487e7 Mon Sep 17 00:00:00 2001 From: Keith Burzinski Date: Tue, 16 Jan 2024 02:05:13 -0600 Subject: [PATCH 18/24] Add NFC binary sensor platform (#6068) --- CODEOWNERS | 2 +- esphome/components/nfc/__init__.py | 5 +- .../components/nfc/binary_sensor/__init__.py | 72 +++++++++++ .../nfc/binary_sensor/binary_sensor.cpp | 114 ++++++++++++++++++ .../nfc/binary_sensor/binary_sensor.h | 38 ++++++ esphome/components/nfc/nfc.h | 14 +++ esphome/components/pn7150/__init__.py | 2 +- esphome/components/pn7150/pn7150.cpp | 6 + esphome/components/pn7150/pn7150.h | 2 +- esphome/components/pn7160/__init__.py | 2 +- esphome/components/pn7160/pn7160.cpp | 6 + esphome/components/pn7160/pn7160.h | 2 +- tests/test1.yaml | 13 ++ 13 files changed, 271 insertions(+), 7 deletions(-) create mode 100644 esphome/components/nfc/binary_sensor/__init__.py create mode 100644 esphome/components/nfc/binary_sensor/binary_sensor.cpp create mode 100644 esphome/components/nfc/binary_sensor/binary_sensor.h diff --git a/CODEOWNERS b/CODEOWNERS index 0ff5ce4508..c497a82eab 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -227,7 +227,7 @@ esphome/components/nextion/binary_sensor/* @senexcrenshaw esphome/components/nextion/sensor/* @senexcrenshaw esphome/components/nextion/switch/* @senexcrenshaw esphome/components/nextion/text_sensor/* @senexcrenshaw -esphome/components/nfc/* @jesserockz +esphome/components/nfc/* @jesserockz @kbx81 esphome/components/noblex/* @AGalfra esphome/components/number/* @esphome/core esphome/components/ota/* @esphome/core diff --git a/esphome/components/nfc/__init__.py b/esphome/components/nfc/__init__.py index c3bbc50bf9..eea1a47b24 100644 --- a/esphome/components/nfc/__init__.py +++ b/esphome/components/nfc/__init__.py @@ -1,12 +1,13 @@ from esphome import automation import esphome.codegen as cg -CODEOWNERS = ["@jesserockz"] +CODEOWNERS = ["@jesserockz", "@kbx81"] nfc_ns = cg.esphome_ns.namespace("nfc") +Nfcc = nfc_ns.class_("Nfcc") NfcTag = nfc_ns.class_("NfcTag") - +NfcTagListener = nfc_ns.class_("NfcTagListener") NfcOnTagTrigger = nfc_ns.class_( "NfcOnTagTrigger", automation.Trigger.template(cg.std_string, NfcTag) ) diff --git a/esphome/components/nfc/binary_sensor/__init__.py b/esphome/components/nfc/binary_sensor/__init__.py new file mode 100644 index 0000000000..21c8298ea8 --- /dev/null +++ b/esphome/components/nfc/binary_sensor/__init__.py @@ -0,0 +1,72 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import binary_sensor +from esphome.const import CONF_UID +from esphome.core import HexInt +from .. import nfc_ns, Nfcc, NfcTagListener + +DEPENDENCIES = ["nfc"] + +CONF_NDEF_CONTAINS = "ndef_contains" +CONF_NFCC_ID = "nfcc_id" +CONF_TAG_ID = "tag_id" + +NfcTagBinarySensor = nfc_ns.class_( + "NfcTagBinarySensor", + binary_sensor.BinarySensor, + cg.Component, + NfcTagListener, + cg.Parented.template(Nfcc), +) + + +def validate_uid(value): + value = cv.string_strict(value) + for x in value.split("-"): + if len(x) != 2: + raise cv.Invalid( + "Each part (separated by '-') of the UID must be two characters " + "long." + ) + try: + x = int(x, 16) + except ValueError as err: + raise cv.Invalid( + "Valid characters for parts of a UID are 0123456789ABCDEF." + ) from err + if x < 0 or x > 255: + raise cv.Invalid( + "Valid values for UID parts (separated by '-') are 00 to FF" + ) + return value + + +CONFIG_SCHEMA = cv.All( + binary_sensor.binary_sensor_schema(NfcTagBinarySensor) + .extend( + { + cv.GenerateID(CONF_NFCC_ID): cv.use_id(Nfcc), + cv.Optional(CONF_NDEF_CONTAINS): cv.string, + cv.Optional(CONF_TAG_ID): cv.string, + cv.Optional(CONF_UID): validate_uid, + } + ) + .extend(cv.COMPONENT_SCHEMA), + cv.has_exactly_one_key(CONF_NDEF_CONTAINS, CONF_TAG_ID, CONF_UID), +) + + +async def to_code(config): + var = await binary_sensor.new_binary_sensor(config) + await cg.register_component(var, config) + await cg.register_parented(var, config[CONF_NFCC_ID]) + + hub = await cg.get_variable(config[CONF_NFCC_ID]) + cg.add(hub.register_listener(var)) + if CONF_NDEF_CONTAINS in config: + cg.add(var.set_ndef_match_string(config[CONF_NDEF_CONTAINS])) + if CONF_TAG_ID in config: + cg.add(var.set_tag_name(config[CONF_TAG_ID])) + elif CONF_UID in config: + addr = [HexInt(int(x, 16)) for x in config[CONF_UID].split("-")] + cg.add(var.set_uid(addr)) diff --git a/esphome/components/nfc/binary_sensor/binary_sensor.cpp b/esphome/components/nfc/binary_sensor/binary_sensor.cpp new file mode 100644 index 0000000000..8f1f6acd51 --- /dev/null +++ b/esphome/components/nfc/binary_sensor/binary_sensor.cpp @@ -0,0 +1,114 @@ +#include "binary_sensor.h" +#include "../nfc_helpers.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace nfc { + +static const char *const TAG = "nfc.binary_sensor"; + +void NfcTagBinarySensor::setup() { + this->parent_->register_listener(this); + this->publish_initial_state(false); +} + +void NfcTagBinarySensor::dump_config() { + std::string match_str = "name"; + + LOG_BINARY_SENSOR("", "NFC Tag Binary Sensor", this); + if (!this->match_string_.empty()) { + if (!this->match_tag_name_) { + match_str = "contains"; + } + ESP_LOGCONFIG(TAG, " Tag %s: %s", match_str.c_str(), this->match_string_.c_str()); + return; + } + if (!this->uid_.empty()) { + ESP_LOGCONFIG(TAG, " Tag UID: %s", format_bytes(this->uid_).c_str()); + } +} + +void NfcTagBinarySensor::set_ndef_match_string(const std::string &str) { + this->match_string_ = str; + this->match_tag_name_ = false; +} + +void NfcTagBinarySensor::set_tag_name(const std::string &str) { + this->match_string_ = str; + this->match_tag_name_ = true; +} + +void NfcTagBinarySensor::set_uid(const std::vector &uid) { this->uid_ = uid; } + +bool NfcTagBinarySensor::tag_match_ndef_string(const std::shared_ptr &msg) { + for (const auto &record : msg->get_records()) { + if (record->get_payload().find(this->match_string_) != std::string::npos) { + return true; + } + } + return false; +} + +bool NfcTagBinarySensor::tag_match_tag_name(const std::shared_ptr &msg) { + for (const auto &record : msg->get_records()) { + if (record->get_payload().find(HA_TAG_ID_PREFIX) != std::string::npos) { + auto rec_substr = record->get_payload().substr(sizeof(HA_TAG_ID_PREFIX) - 1); + if (rec_substr.find(this->match_string_) != std::string::npos) { + return true; + } + } + } + return false; +} + +bool NfcTagBinarySensor::tag_match_uid(const std::vector &data) { + if (data.size() != this->uid_.size()) { + return false; + } + + for (size_t i = 0; i < data.size(); i++) { + if (data[i] != this->uid_[i]) { + return false; + } + } + return true; +} + +void NfcTagBinarySensor::tag_off(NfcTag &tag) { + if (!this->match_string_.empty() && tag.has_ndef_message()) { + if (this->match_tag_name_) { + if (this->tag_match_tag_name(tag.get_ndef_message())) { + this->publish_state(false); + } + } else { + if (this->tag_match_ndef_string(tag.get_ndef_message())) { + this->publish_state(false); + } + } + return; + } + if (!this->uid_.empty() && this->tag_match_uid(tag.get_uid())) { + this->publish_state(false); + } +} + +void NfcTagBinarySensor::tag_on(NfcTag &tag) { + if (!this->match_string_.empty() && tag.has_ndef_message()) { + if (this->match_tag_name_) { + if (this->tag_match_tag_name(tag.get_ndef_message())) { + this->publish_state(true); + } + } else { + if (this->tag_match_ndef_string(tag.get_ndef_message())) { + this->publish_state(true); + } + } + return; + } + if (!this->uid_.empty() && this->tag_match_uid(tag.get_uid())) { + this->publish_state(true); + } +} + +} // namespace nfc +} // namespace esphome diff --git a/esphome/components/nfc/binary_sensor/binary_sensor.h b/esphome/components/nfc/binary_sensor/binary_sensor.h new file mode 100644 index 0000000000..cc313c2f2b --- /dev/null +++ b/esphome/components/nfc/binary_sensor/binary_sensor.h @@ -0,0 +1,38 @@ +#pragma once + +#include "esphome/components/binary_sensor/binary_sensor.h" +#include "esphome/components/nfc/nfc.h" +#include "esphome/components/nfc/nfc_tag.h" +#include "esphome/core/component.h" +#include "esphome/core/helpers.h" + +namespace esphome { +namespace nfc { + +class NfcTagBinarySensor : public binary_sensor::BinarySensor, + public Component, + public NfcTagListener, + public Parented { + public: + void setup() override; + void dump_config() override; + + void set_ndef_match_string(const std::string &str); + void set_tag_name(const std::string &str); + void set_uid(const std::vector &uid); + + bool tag_match_ndef_string(const std::shared_ptr &msg); + bool tag_match_tag_name(const std::shared_ptr &msg); + bool tag_match_uid(const std::vector &data); + + void tag_off(NfcTag &tag) override; + void tag_on(NfcTag &tag) override; + + protected: + bool match_tag_name_{false}; + std::string match_string_; + std::vector uid_; +}; + +} // namespace nfc +} // namespace esphome diff --git a/esphome/components/nfc/nfc.h b/esphome/components/nfc/nfc.h index d4d66f970f..23bfdd8ef0 100644 --- a/esphome/components/nfc/nfc.h +++ b/esphome/components/nfc/nfc.h @@ -66,5 +66,19 @@ bool mifare_classic_is_trailer_block(uint8_t block_num); uint32_t get_mifare_ultralight_buffer_size(uint32_t message_length); +class NfcTagListener { + public: + virtual void tag_off(NfcTag &tag) {} + virtual void tag_on(NfcTag &tag) {} +}; + +class Nfcc { + public: + void register_listener(NfcTagListener *listener) { this->tag_listeners_.push_back(listener); } + + protected: + std::vector tag_listeners_; +}; + } // namespace nfc } // namespace esphome diff --git a/esphome/components/pn7150/__init__.py b/esphome/components/pn7150/__init__.py index 3b80b574e9..a136028011 100644 --- a/esphome/components/pn7150/__init__.py +++ b/esphome/components/pn7150/__init__.py @@ -34,7 +34,7 @@ CONF_TAG_TTL = "tag_ttl" CONF_VEN_PIN = "ven_pin" pn7150_ns = cg.esphome_ns.namespace("pn7150") -PN7150 = pn7150_ns.class_("PN7150", cg.Component) +PN7150 = pn7150_ns.class_("PN7150", nfc.Nfcc, cg.Component) EmulationOffAction = pn7150_ns.class_("EmulationOffAction", automation.Action) EmulationOnAction = pn7150_ns.class_("EmulationOnAction", automation.Action) diff --git a/esphome/components/pn7150/pn7150.cpp b/esphome/components/pn7150/pn7150.cpp index 6703ab6a12..be4d6c1bb7 100644 --- a/esphome/components/pn7150/pn7150.cpp +++ b/esphome/components/pn7150/pn7150.cpp @@ -566,6 +566,9 @@ void PN7150::erase_tag_(const uint8_t tag_index) { for (auto *trigger : this->triggers_ontagremoved_) { trigger->process(this->discovered_endpoint_[tag_index].tag); } + for (auto *listener : this->tag_listeners_) { + listener->tag_off(*this->discovered_endpoint_[tag_index].tag); + } ESP_LOGI(TAG, "Tag %s removed", nfc::format_uid(this->discovered_endpoint_[tag_index].tag->get_uid()).c_str()); this->discovered_endpoint_.erase(this->discovered_endpoint_.begin() + tag_index); } @@ -881,6 +884,9 @@ void PN7150::process_rf_intf_activated_oid_(nfc::NciMessage &rx) { // an endpoi for (auto *trigger : this->triggers_ontag_) { trigger->process(working_endpoint.tag); } + for (auto *listener : this->tag_listeners_) { + listener->tag_on(*working_endpoint.tag); + } working_endpoint.trig_called = true; break; } diff --git a/esphome/components/pn7150/pn7150.h b/esphome/components/pn7150/pn7150.h index 4aad4e1720..54038f5085 100644 --- a/esphome/components/pn7150/pn7150.h +++ b/esphome/components/pn7150/pn7150.h @@ -142,7 +142,7 @@ struct DiscoveredEndpoint { bool trig_called; }; -class PN7150 : public Component { +class PN7150 : public nfc::Nfcc, public Component { public: void setup() override; void dump_config() override; diff --git a/esphome/components/pn7160/__init__.py b/esphome/components/pn7160/__init__.py index c91ca78b03..1639041b9e 100644 --- a/esphome/components/pn7160/__init__.py +++ b/esphome/components/pn7160/__init__.py @@ -36,7 +36,7 @@ CONF_VEN_PIN = "ven_pin" CONF_WKUP_REQ_PIN = "wkup_req_pin" pn7160_ns = cg.esphome_ns.namespace("pn7160") -PN7160 = pn7160_ns.class_("PN7160", cg.Component) +PN7160 = pn7160_ns.class_("PN7160", nfc.Nfcc, cg.Component) EmulationOffAction = pn7160_ns.class_("EmulationOffAction", automation.Action) EmulationOnAction = pn7160_ns.class_("EmulationOnAction", automation.Action) diff --git a/esphome/components/pn7160/pn7160.cpp b/esphome/components/pn7160/pn7160.cpp index ce5374d1d1..a7d3b38fb7 100644 --- a/esphome/components/pn7160/pn7160.cpp +++ b/esphome/components/pn7160/pn7160.cpp @@ -591,6 +591,9 @@ void PN7160::erase_tag_(const uint8_t tag_index) { for (auto *trigger : this->triggers_ontagremoved_) { trigger->process(this->discovered_endpoint_[tag_index].tag); } + for (auto *listener : this->tag_listeners_) { + listener->tag_off(*this->discovered_endpoint_[tag_index].tag); + } ESP_LOGI(TAG, "Tag %s removed", nfc::format_uid(this->discovered_endpoint_[tag_index].tag->get_uid()).c_str()); this->discovered_endpoint_.erase(this->discovered_endpoint_.begin() + tag_index); } @@ -905,6 +908,9 @@ void PN7160::process_rf_intf_activated_oid_(nfc::NciMessage &rx) { // an endpoi for (auto *trigger : this->triggers_ontag_) { trigger->process(working_endpoint.tag); } + for (auto *listener : this->tag_listeners_) { + listener->tag_on(*working_endpoint.tag); + } working_endpoint.trig_called = true; break; } diff --git a/esphome/components/pn7160/pn7160.h b/esphome/components/pn7160/pn7160.h index 2b3cb99453..f2e05ea1d0 100644 --- a/esphome/components/pn7160/pn7160.h +++ b/esphome/components/pn7160/pn7160.h @@ -157,7 +157,7 @@ struct DiscoveredEndpoint { bool trig_called; }; -class PN7160 : public Component { +class PN7160 : public nfc::Nfcc, public Component { public: void setup() override; void dump_config() override; diff --git a/tests/test1.yaml b/tests/test1.yaml index 3ca6faca8a..471e2a71a5 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -2028,6 +2028,18 @@ binary_sensor: - platform: dfrobot_sen0395 id: mmwave_detected_uart dfrobot_sen0395_id: mmwave + - platform: nfc + nfcc_id: nfcc_pn7160_i2c + ndef_contains: pulse + name: MFC Tag 1 + - platform: nfc + nfcc_id: nfcc_pn7160_i2c + tag_id: pulse + name: MFC Tag 2 + - platform: nfc + nfcc_id: nfcc_pn7160_i2c + uid: 59-FC-AB-15 + name: MFC Tag 3 pca9685: frequency: 500 @@ -3453,6 +3465,7 @@ pn532_i2c: i2c_id: i2c_bus pn7150_i2c: + id: nfcc_pn7150_i2c i2c_id: i2c_bus irq_pin: allow_other_uses: true From ea03058ace0d46de52c020b10daa39524bb5670c Mon Sep 17 00:00:00 2001 From: Piotr Majkrzak Date: Tue, 16 Jan 2024 09:10:44 +0100 Subject: [PATCH 19/24] Fix RMT timing clock base (#6101) --- esphome/components/esp32_rmt_led_strip/led_strip.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/esphome/components/esp32_rmt_led_strip/led_strip.cpp b/esphome/components/esp32_rmt_led_strip/led_strip.cpp index df6ee2ce2f..3df4077c96 100644 --- a/esphome/components/esp32_rmt_led_strip/led_strip.cpp +++ b/esphome/components/esp32_rmt_led_strip/led_strip.cpp @@ -13,6 +13,8 @@ namespace esp32_rmt_led_strip { static const char *const TAG = "esp32_rmt_led_strip"; +static const uint32_t RMT_CLK_FREQ = 80000000; + static const uint8_t RMT_CLK_DIV = 2; void ESP32RMTLEDStripLightOutput::setup() { @@ -65,7 +67,7 @@ void ESP32RMTLEDStripLightOutput::setup() { void ESP32RMTLEDStripLightOutput::set_led_params(uint32_t bit0_high, uint32_t bit0_low, uint32_t bit1_high, uint32_t bit1_low) { - float ratio = (float) APB_CLK_FREQ / RMT_CLK_DIV / 1e09f; + float ratio = (float) RMT_CLK_FREQ / RMT_CLK_DIV / 1e09f; // 0-bit this->bit0_.duration0 = (uint32_t) (ratio * bit0_high); From 21337ffc67096d9256b3dd34428953b47c8283bf Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 16 Jan 2024 21:37:57 +1300 Subject: [PATCH 20/24] Create RingBuffer for VoiceAssistant (#6102) --- .../voice_assistant/voice_assistant.cpp | 32 ++++-------- .../voice_assistant/voice_assistant.h | 4 +- esphome/core/ring_buffer.cpp | 49 +++++++++++++++++++ esphome/core/ring_buffer.h | 34 +++++++++++++ 4 files changed, 95 insertions(+), 24 deletions(-) create mode 100644 esphome/core/ring_buffer.cpp create mode 100644 esphome/core/ring_buffer.h diff --git a/esphome/components/voice_assistant/voice_assistant.cpp b/esphome/components/voice_assistant/voice_assistant.cpp index 29fc664342..299e624f5f 100644 --- a/esphome/components/voice_assistant/voice_assistant.cpp +++ b/esphome/components/voice_assistant/voice_assistant.cpp @@ -86,14 +86,14 @@ void VoiceAssistant::setup() { #ifdef USE_ESP_ADF this->vad_instance_ = vad_create(VAD_MODE_4); +#endif - this->ring_buffer_ = rb_create(BUFFER_SIZE, sizeof(int16_t)); + this->ring_buffer_ = RingBuffer::create(BUFFER_SIZE * sizeof(int16_t)); if (this->ring_buffer_ == nullptr) { ESP_LOGW(TAG, "Could not allocate ring buffer"); this->mark_failed(); return; } -#endif ExternalRAMAllocator send_allocator(ExternalRAMAllocator::ALLOW_FAILURE); this->send_buffer_ = send_allocator.allocate(SEND_BUFFER_SIZE); @@ -112,14 +112,8 @@ int VoiceAssistant::read_microphone_() { memset(this->input_buffer_, 0, INPUT_BUFFER_SIZE * sizeof(int16_t)); return 0; } -#ifdef USE_ESP_ADF // Write audio into ring buffer - int available = rb_bytes_available(this->ring_buffer_); - if (available < bytes_read) { - rb_read(this->ring_buffer_, nullptr, bytes_read - available, 0); - } - rb_write(this->ring_buffer_, (char *) this->input_buffer_, bytes_read, 0); -#endif + this->ring_buffer_->write((void *) this->input_buffer_, bytes_read); } else { ESP_LOGD(TAG, "microphone not running"); } @@ -141,9 +135,9 @@ void VoiceAssistant::loop() { switch (this->state_) { case State::IDLE: { if (this->continuous_ && this->desired_state_ == State::IDLE) { + this->ring_buffer_->reset(); #ifdef USE_ESP_ADF if (this->use_wake_word_) { - rb_reset(this->ring_buffer_); this->set_state_(State::START_MICROPHONE, State::WAIT_FOR_VAD); } else #endif @@ -236,19 +230,13 @@ void VoiceAssistant::loop() { break; // State changed when udp server port received } case State::STREAMING_MICROPHONE: { - size_t bytes_read = this->read_microphone_(); -#ifdef USE_ESP_ADF - if (rb_bytes_filled(this->ring_buffer_) >= SEND_BUFFER_SIZE) { - rb_read(this->ring_buffer_, (char *) this->send_buffer_, SEND_BUFFER_SIZE, 0); + this->read_microphone_(); + if (this->ring_buffer_->available() >= SEND_BUFFER_SIZE) { + this->ring_buffer_->read((void *) this->send_buffer_, SEND_BUFFER_SIZE, 0); this->socket_->sendto(this->send_buffer_, SEND_BUFFER_SIZE, 0, (struct sockaddr *) &this->dest_addr_, sizeof(this->dest_addr_)); } -#else - if (bytes_read > 0) { - this->socket_->sendto(this->input_buffer_, bytes_read, 0, (struct sockaddr *) &this->dest_addr_, - sizeof(this->dest_addr_)); - } -#endif + break; } case State::STOP_MICROPHONE: { @@ -473,9 +461,9 @@ void VoiceAssistant::request_start(bool continuous, bool silence_detection) { if (this->state_ == State::IDLE) { this->continuous_ = continuous; this->silence_detection_ = silence_detection; + this->ring_buffer_->reset(); #ifdef USE_ESP_ADF if (this->use_wake_word_) { - rb_reset(this->ring_buffer_); this->set_state_(State::START_MICROPHONE, State::WAIT_FOR_VAD); } else #endif @@ -618,9 +606,9 @@ void VoiceAssistant::on_event(const api::VoiceAssistantEventResponse &msg) { case api::enums::VOICE_ASSISTANT_RUN_END: { ESP_LOGD(TAG, "Assist Pipeline ended"); if (this->state_ == State::STREAMING_MICROPHONE) { + this->ring_buffer_->reset(); #ifdef USE_ESP_ADF if (this->use_wake_word_) { - rb_reset(this->ring_buffer_); // No need to stop the microphone since we didn't use the speaker this->set_state_(State::WAIT_FOR_VAD, State::WAITING_FOR_VAD); } else diff --git a/esphome/components/voice_assistant/voice_assistant.h b/esphome/components/voice_assistant/voice_assistant.h index f9325dff54..d996efe08e 100644 --- a/esphome/components/voice_assistant/voice_assistant.h +++ b/esphome/components/voice_assistant/voice_assistant.h @@ -7,6 +7,7 @@ #include "esphome/core/automation.h" #include "esphome/core/component.h" #include "esphome/core/helpers.h" +#include "esphome/core/ring_buffer.h" #include "esphome/components/api/api_connection.h" #include "esphome/components/api/api_pb2.h" @@ -21,7 +22,6 @@ #ifdef USE_ESP_ADF #include -#include #endif namespace esphome { @@ -177,10 +177,10 @@ class VoiceAssistant : public Component { #ifdef USE_ESP_ADF vad_handle_t vad_instance_; - ringbuf_handle_t ring_buffer_; uint8_t vad_threshold_{5}; uint8_t vad_counter_{0}; #endif + std::unique_ptr ring_buffer_; bool use_wake_word_; uint8_t noise_suppression_level_; diff --git a/esphome/core/ring_buffer.cpp b/esphome/core/ring_buffer.cpp new file mode 100644 index 0000000000..d9c56d84c5 --- /dev/null +++ b/esphome/core/ring_buffer.cpp @@ -0,0 +1,49 @@ +#include "ring_buffer.h" + +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" + +#ifdef USE_ESP32 + +#include "helpers.h" + +namespace esphome { + +static const char *const TAG = "ring_buffer"; + +std::unique_ptr RingBuffer::create(size_t len) { + std::unique_ptr rb = make_unique(); + + ExternalRAMAllocator allocator(ExternalRAMAllocator::ALLOW_FAILURE); + rb->storage_ = allocator.allocate(len); + if (rb->storage_ == nullptr) { + return nullptr; + } + + rb->handle_ = xStreamBufferCreateStatic(len, 0, rb->storage_, &rb->structure_); + return rb; +} + +size_t RingBuffer::read(void *data, size_t size, TickType_t ticks_to_wait) { + return xStreamBufferReceive(this->handle_, data, size, ticks_to_wait); +} + +size_t RingBuffer::write(void *data, size_t len) { + size_t free = this->free(); + if (free < len) { + size_t needed = len - free; + uint8_t discard[needed]; + xStreamBufferReceive(this->handle_, discard, needed, 0); + } + return xStreamBufferSend(this->handle_, data, len, 0); +} + +size_t RingBuffer::available() const { return xStreamBufferBytesAvailable(this->handle_); } + +size_t RingBuffer::free() const { return xStreamBufferSpacesAvailable(this->handle_); } + +BaseType_t RingBuffer::reset() { return xStreamBufferReset(this->handle_); } + +} // namespace esphome + +#endif diff --git a/esphome/core/ring_buffer.h b/esphome/core/ring_buffer.h new file mode 100644 index 0000000000..6c6d04117a --- /dev/null +++ b/esphome/core/ring_buffer.h @@ -0,0 +1,34 @@ +#pragma once + +#ifdef USE_ESP32 + +#include +#include + +#include +#include + +namespace esphome { + +class RingBuffer { + public: + size_t read(void *data, size_t size, TickType_t ticks_to_wait = 0); + + size_t write(void *data, size_t len); + + size_t available() const; + size_t free() const; + + BaseType_t reset(); + + static std::unique_ptr create(size_t len); + + protected: + StreamBufferHandle_t handle_; + StaticStreamBuffer_t structure_; + uint8_t *storage_; +}; + +} // namespace esphome + +#endif From 385420303758e02a2acc9dd883c1561a51eaaf91 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Wed, 17 Jan 2024 10:12:31 +1100 Subject: [PATCH 21/24] Socket: Add recvfrom method to receive UDP with source address. (#6103) --- esphome/components/socket/bsd_sockets_impl.cpp | 7 +++++++ esphome/components/socket/socket.h | 3 +++ 2 files changed, 10 insertions(+) diff --git a/esphome/components/socket/bsd_sockets_impl.cpp b/esphome/components/socket/bsd_sockets_impl.cpp index 5d44cd7689..6c356106f3 100644 --- a/esphome/components/socket/bsd_sockets_impl.cpp +++ b/esphome/components/socket/bsd_sockets_impl.cpp @@ -86,6 +86,13 @@ class BSDSocketImpl : public Socket { } int listen(int backlog) override { return ::listen(fd_, backlog); } ssize_t read(void *buf, size_t len) override { return ::read(fd_, buf, len); } + ssize_t recvfrom(void *buf, size_t len, sockaddr *addr, socklen_t *addr_len) override { +#if defined(USE_ESP32) + return ::recvfrom(this->fd_, buf, len, 0, addr, addr_len); +#else + return ::lwip_recvfrom(this->fd_, buf, len, 0, addr, addr_len); +#endif + } ssize_t readv(const struct iovec *iov, int iovcnt) override { #if defined(USE_ESP32) return ::lwip_readv(fd_, iov, iovcnt); diff --git a/esphome/components/socket/socket.h b/esphome/components/socket/socket.h index c9b8be88a0..5c12210d15 100644 --- a/esphome/components/socket/socket.h +++ b/esphome/components/socket/socket.h @@ -31,6 +31,9 @@ class Socket { virtual int setsockopt(int level, int optname, const void *optval, socklen_t optlen) = 0; virtual int listen(int backlog) = 0; virtual ssize_t read(void *buf, size_t len) = 0; +#ifdef USE_SOCKET_IMPL_BSD_SOCKETS + virtual ssize_t recvfrom(void *buf, size_t len, sockaddr *addr, socklen_t *addr_len) = 0; +#endif virtual ssize_t readv(const struct iovec *iov, int iovcnt) = 0; virtual ssize_t write(const void *buf, size_t len) = 0; virtual ssize_t writev(const struct iovec *iov, int iovcnt) = 0; From 596943b683d45bf99b474e92f6dbc97e65a3ae3b Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Wed, 17 Jan 2024 10:23:36 +1100 Subject: [PATCH 22/24] Inkplate6: Fix crash with initial set of greyscale (#6038) --- esphome/components/inkplate6/inkplate.cpp | 3 +++ esphome/components/inkplate6/inkplate.h | 3 ++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/esphome/components/inkplate6/inkplate.cpp b/esphome/components/inkplate6/inkplate.cpp index 92a226de87..f4d0fedf83 100644 --- a/esphome/components/inkplate6/inkplate.cpp +++ b/esphome/components/inkplate6/inkplate.cpp @@ -55,6 +55,9 @@ void Inkplate6::setup() { this->wakeup_pin_->digital_write(false); } +/** + * Allocate buffers. May be called after setup to re-initialise if e.g. greyscale is changed. + */ void Inkplate6::initialize_() { ExternalRAMAllocator allocator(ExternalRAMAllocator::ALLOW_FAILURE); ExternalRAMAllocator allocator32(ExternalRAMAllocator::ALLOW_FAILURE); diff --git a/esphome/components/inkplate6/inkplate.h b/esphome/components/inkplate6/inkplate.h index 307d9671e6..2946c89e1c 100644 --- a/esphome/components/inkplate6/inkplate.h +++ b/esphome/components/inkplate6/inkplate.h @@ -68,8 +68,9 @@ class Inkplate6 : public display::DisplayBuffer, public i2c::I2CDevice { void set_greyscale(bool greyscale) { this->greyscale_ = greyscale; - this->initialize_(); this->block_partial_ = true; + if (this->is_ready()) + this->initialize_(); } void set_partial_updating(bool partial_updating) { this->partial_updating_ = partial_updating; } void set_full_update_every(uint32_t full_update_every) { this->full_update_every_ = full_update_every; } From 0cd232cdf516a04574787843b6af20012f23350a Mon Sep 17 00:00:00 2001 From: Keith Burzinski Date: Wed, 17 Jan 2024 00:50:53 -0600 Subject: [PATCH 23/24] Add support for VEML3235 lux sensor (#5959) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- CODEOWNERS | 1 + esphome/components/veml3235/__init__.py | 0 esphome/components/veml3235/sensor.py | 84 +++++++++ esphome/components/veml3235/veml3235.cpp | 230 +++++++++++++++++++++++ esphome/components/veml3235/veml3235.h | 109 +++++++++++ tests/test1.yaml | 10 + 6 files changed, 434 insertions(+) create mode 100644 esphome/components/veml3235/__init__.py create mode 100644 esphome/components/veml3235/sensor.py create mode 100644 esphome/components/veml3235/veml3235.cpp create mode 100644 esphome/components/veml3235/veml3235.h diff --git a/CODEOWNERS b/CODEOWNERS index c497a82eab..fea1e6215c 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -362,6 +362,7 @@ esphome/components/ufire_ec/* @pvizeli esphome/components/ufire_ise/* @pvizeli esphome/components/ultrasonic/* @OttoWinter esphome/components/vbus/* @ssieb +esphome/components/veml3235/* @kbx81 esphome/components/version/* @esphome/core esphome/components/voice_assistant/* @jesserockz esphome/components/wake_on_lan/* @willwill2will54 diff --git a/esphome/components/veml3235/__init__.py b/esphome/components/veml3235/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esphome/components/veml3235/sensor.py b/esphome/components/veml3235/sensor.py new file mode 100644 index 0000000000..79ba510e41 --- /dev/null +++ b/esphome/components/veml3235/sensor.py @@ -0,0 +1,84 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import i2c, sensor +from esphome.const import ( + CONF_GAIN, + CONF_INTEGRATION_TIME, + DEVICE_CLASS_ILLUMINANCE, + STATE_CLASS_MEASUREMENT, + UNIT_LUX, +) + +CODEOWNERS = ["@kbx81"] +DEPENDENCIES = ["i2c"] + +CONF_AUTO_GAIN = "auto_gain" +CONF_AUTO_GAIN_THRESHOLD_HIGH = "auto_gain_threshold_high" +CONF_AUTO_GAIN_THRESHOLD_LOW = "auto_gain_threshold_low" +CONF_DIGITAL_GAIN = "digital_gain" + +veml3235_ns = cg.esphome_ns.namespace("veml3235") + +VEML3235Sensor = veml3235_ns.class_( + "VEML3235Sensor", sensor.Sensor, cg.PollingComponent, i2c.I2CDevice +) +VEML3235IntegrationTime = veml3235_ns.enum("VEML3235IntegrationTime") +VEML3235_INTEGRATION_TIMES = { + "50ms": VEML3235IntegrationTime.VEML3235_INTEGRATION_TIME_50MS, + "100ms": VEML3235IntegrationTime.VEML3235_INTEGRATION_TIME_100MS, + "200ms": VEML3235IntegrationTime.VEML3235_INTEGRATION_TIME_200MS, + "400ms": VEML3235IntegrationTime.VEML3235_INTEGRATION_TIME_400MS, + "800ms": VEML3235IntegrationTime.VEML3235_INTEGRATION_TIME_800MS, +} +VEML3235ComponentDigitalGain = veml3235_ns.enum("VEML3235ComponentDigitalGain") +DIGITAL_GAINS = { + "1X": VEML3235ComponentDigitalGain.VEML3235_DIGITAL_GAIN_1X, + "2X": VEML3235ComponentDigitalGain.VEML3235_DIGITAL_GAIN_2X, +} +VEML3235ComponentGain = veml3235_ns.enum("VEML3235ComponentGain") +GAINS = { + "1X": VEML3235ComponentGain.VEML3235_GAIN_1X, + "2X": VEML3235ComponentGain.VEML3235_GAIN_2X, + "4X": VEML3235ComponentGain.VEML3235_GAIN_4X, + "AUTO": VEML3235ComponentGain.VEML3235_GAIN_AUTO, +} + +CONFIG_SCHEMA = ( + sensor.sensor_schema( + VEML3235Sensor, + unit_of_measurement=UNIT_LUX, + accuracy_decimals=1, + device_class=DEVICE_CLASS_ILLUMINANCE, + state_class=STATE_CLASS_MEASUREMENT, + ) + .extend( + { + cv.Optional(CONF_DIGITAL_GAIN, default="1X"): cv.enum( + DIGITAL_GAINS, upper=True + ), + cv.Optional(CONF_AUTO_GAIN, default=True): cv.boolean, + cv.Optional(CONF_AUTO_GAIN_THRESHOLD_HIGH, default="90%"): cv.percentage, + cv.Optional(CONF_AUTO_GAIN_THRESHOLD_LOW, default="20%"): cv.percentage, + cv.Optional(CONF_GAIN, default="1X"): cv.enum(GAINS, upper=True), + cv.Optional(CONF_INTEGRATION_TIME, default="50ms"): cv.All( + cv.positive_time_period_milliseconds, + cv.enum(VEML3235_INTEGRATION_TIMES, lower=True), + ), + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x10)) +) + + +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_auto_gain(config[CONF_AUTO_GAIN])) + cg.add(var.set_auto_gain_threshold_high(config[CONF_AUTO_GAIN_THRESHOLD_HIGH])) + cg.add(var.set_auto_gain_threshold_low(config[CONF_AUTO_GAIN_THRESHOLD_LOW])) + cg.add(var.set_digital_gain(DIGITAL_GAINS[config[CONF_DIGITAL_GAIN]])) + cg.add(var.set_gain(GAINS[config[CONF_GAIN]])) + cg.add(var.set_integration_time(config[CONF_INTEGRATION_TIME])) diff --git a/esphome/components/veml3235/veml3235.cpp b/esphome/components/veml3235/veml3235.cpp new file mode 100644 index 0000000000..2410bfdda2 --- /dev/null +++ b/esphome/components/veml3235/veml3235.cpp @@ -0,0 +1,230 @@ +#include "veml3235.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace veml3235 { + +static const char *const TAG = "veml3235.sensor"; + +void VEML3235Sensor::setup() { + uint8_t device_id[] = {0, 0}; + + ESP_LOGCONFIG(TAG, "Setting up VEML3235 '%s'...", this->name_.c_str()); + + if (!this->refresh_config_reg()) { + ESP_LOGE(TAG, "Unable to write configuration"); + this->mark_failed(); + return; + } + if ((this->write(&ID_REG, 1, false) != i2c::ERROR_OK) || !this->read_bytes_raw(device_id, 2)) { + ESP_LOGE(TAG, "Unable to read ID"); + this->mark_failed(); + return; + } else if (device_id[0] != DEVICE_ID) { + ESP_LOGE(TAG, "Incorrect device ID - expected 0x%.2x, read 0x%.2x", DEVICE_ID, device_id[0]); + this->mark_failed(); + return; + } +} + +bool VEML3235Sensor::refresh_config_reg(bool force_on) { + uint16_t data = this->power_on_ || force_on ? 0 : SHUTDOWN_BITS; + + data |= (uint16_t(this->integration_time_ << CONFIG_REG_IT_BIT)); + data |= (uint16_t(this->digital_gain_ << CONFIG_REG_DG_BIT)); + data |= (uint16_t(this->gain_ << CONFIG_REG_G_BIT)); + data |= 0x1; // mandatory 1 here per RM + + ESP_LOGVV(TAG, "Writing 0x%.4x to register 0x%.2x", data, CONFIG_REG); + return this->write_byte_16(CONFIG_REG, data); +} + +float VEML3235Sensor::read_lx_() { + if (!this->power_on_) { // if off, turn on + if (!this->refresh_config_reg(true)) { + ESP_LOGW(TAG, "Turning on failed"); + this->status_set_warning(); + return NAN; + } + delay(4); // from RM: a wait time of 4 ms should be observed before the first measurement is picked up, to allow + // for a correct start of the signal processor and oscillator + } + + uint8_t als_regs[] = {0, 0}; + if ((this->write(&ALS_REG, 1, false) != i2c::ERROR_OK) || !this->read_bytes_raw(als_regs, 2)) { + this->status_set_warning(); + return NAN; + } + + this->status_clear_warning(); + + float als_raw_value_multiplier = LUX_MULTIPLIER_BASE; + uint16_t als_raw_value = encode_uint16(als_regs[1], als_regs[0]); + // determine multiplier value based on gains and integration time + if (this->digital_gain_ == VEML3235_DIGITAL_GAIN_1X) { + als_raw_value_multiplier *= 2; + } + switch (this->gain_) { + case VEML3235_GAIN_1X: + als_raw_value_multiplier *= 4; + break; + case VEML3235_GAIN_2X: + als_raw_value_multiplier *= 2; + break; + default: + break; + } + switch (this->integration_time_) { + case VEML3235_INTEGRATION_TIME_50MS: + als_raw_value_multiplier *= 16; + break; + case VEML3235_INTEGRATION_TIME_100MS: + als_raw_value_multiplier *= 8; + break; + case VEML3235_INTEGRATION_TIME_200MS: + als_raw_value_multiplier *= 4; + break; + case VEML3235_INTEGRATION_TIME_400MS: + als_raw_value_multiplier *= 2; + break; + default: + break; + } + // finally, determine and return the actual lux value + float lx = float(als_raw_value) * als_raw_value_multiplier; + ESP_LOGVV(TAG, "'%s': ALS raw = %u, multiplier = %.5f", this->get_name().c_str(), als_raw_value, + als_raw_value_multiplier); + ESP_LOGD(TAG, "'%s': Illuminance = %.4flx", this->get_name().c_str(), lx); + + if (!this->power_on_) { // turn off if required + if (!this->refresh_config_reg()) { + ESP_LOGW(TAG, "Turning off failed"); + this->status_set_warning(); + } + } + + if (this->auto_gain_) { + this->adjust_gain_(als_raw_value); + } + + return lx; +} + +void VEML3235Sensor::adjust_gain_(const uint16_t als_raw_value) { + if ((als_raw_value > UINT16_MAX * this->auto_gain_threshold_low_) && + (als_raw_value < UINT16_MAX * this->auto_gain_threshold_high_)) { + return; + } + + if (als_raw_value >= UINT16_MAX * 0.9) { // over-saturated, reset all gains and start over + this->digital_gain_ = VEML3235_DIGITAL_GAIN_1X; + this->gain_ = VEML3235_GAIN_1X; + this->integration_time_ = VEML3235_INTEGRATION_TIME_50MS; + this->refresh_config_reg(); + return; + } + + if (this->gain_ != VEML3235_GAIN_4X) { // increase gain if possible + switch (this->gain_) { + case VEML3235_GAIN_1X: + this->gain_ = VEML3235_GAIN_2X; + break; + case VEML3235_GAIN_2X: + this->gain_ = VEML3235_GAIN_4X; + break; + default: + break; + } + this->refresh_config_reg(); + return; + } + // gain is maxed out; reset it and try to increase digital gain + if (this->digital_gain_ != VEML3235_DIGITAL_GAIN_2X) { // increase digital gain if possible + this->digital_gain_ = VEML3235_DIGITAL_GAIN_2X; + this->gain_ = VEML3235_GAIN_1X; + this->refresh_config_reg(); + return; + } + // digital gain is maxed out; reset it and try to increase integration time + if (this->integration_time_ != VEML3235_INTEGRATION_TIME_800MS) { // increase integration time if possible + switch (this->integration_time_) { + case VEML3235_INTEGRATION_TIME_50MS: + this->integration_time_ = VEML3235_INTEGRATION_TIME_100MS; + break; + case VEML3235_INTEGRATION_TIME_100MS: + this->integration_time_ = VEML3235_INTEGRATION_TIME_200MS; + break; + case VEML3235_INTEGRATION_TIME_200MS: + this->integration_time_ = VEML3235_INTEGRATION_TIME_400MS; + break; + case VEML3235_INTEGRATION_TIME_400MS: + this->integration_time_ = VEML3235_INTEGRATION_TIME_800MS; + break; + default: + break; + } + this->digital_gain_ = VEML3235_DIGITAL_GAIN_1X; + this->gain_ = VEML3235_GAIN_1X; + this->refresh_config_reg(); + return; + } +} + +void VEML3235Sensor::dump_config() { + uint8_t digital_gain = 1; + uint8_t gain = 1; + uint16_t integration_time = 0; + + if (this->digital_gain_ == VEML3235_DIGITAL_GAIN_2X) { + digital_gain = 2; + } + switch (this->gain_) { + case VEML3235_GAIN_2X: + gain = 2; + break; + case VEML3235_GAIN_4X: + gain = 4; + break; + default: + break; + } + switch (this->integration_time_) { + case VEML3235_INTEGRATION_TIME_50MS: + integration_time = 50; + break; + case VEML3235_INTEGRATION_TIME_100MS: + integration_time = 100; + break; + case VEML3235_INTEGRATION_TIME_200MS: + integration_time = 200; + break; + case VEML3235_INTEGRATION_TIME_400MS: + integration_time = 400; + break; + case VEML3235_INTEGRATION_TIME_800MS: + integration_time = 800; + break; + default: + break; + } + + LOG_SENSOR("", "VEML3235", this); + LOG_I2C_DEVICE(this); + if (this->is_failed()) { + ESP_LOGE(TAG, "Communication failed"); + } + LOG_UPDATE_INTERVAL(this); + ESP_LOGCONFIG(TAG, " Auto-gain enabled: %s", YESNO(this->auto_gain_)); + if (this->auto_gain_) { + ESP_LOGCONFIG(TAG, " Auto-gain upper threshold: %f%%", this->auto_gain_threshold_high_ * 100.0); + ESP_LOGCONFIG(TAG, " Auto-gain lower threshold: %f%%", this->auto_gain_threshold_low_ * 100.0); + ESP_LOGCONFIG(TAG, " Values below will be used as initial values only"); + } + ESP_LOGCONFIG(TAG, " Digital gain: %uX", digital_gain); + ESP_LOGCONFIG(TAG, " Gain: %uX", gain); + ESP_LOGCONFIG(TAG, " Integration time: %ums", integration_time); +} + +} // namespace veml3235 +} // namespace esphome diff --git a/esphome/components/veml3235/veml3235.h b/esphome/components/veml3235/veml3235.h new file mode 100644 index 0000000000..2b0d6b23ea --- /dev/null +++ b/esphome/components/veml3235/veml3235.h @@ -0,0 +1,109 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/hal.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/i2c/i2c.h" + +namespace esphome { +namespace veml3235 { + +// Register IDs/locations +// +static const uint8_t CONFIG_REG = 0x00; +static const uint8_t W_REG = 0x04; +static const uint8_t ALS_REG = 0x05; +static const uint8_t ID_REG = 0x09; + +// Bit offsets within CONFIG_REG +// +static const uint8_t CONFIG_REG_IT_BIT = 12; +static const uint8_t CONFIG_REG_DG_BIT = 5; +static const uint8_t CONFIG_REG_G_BIT = 3; + +// Other important constants +// +static const uint8_t DEVICE_ID = 0x35; +static const uint16_t SHUTDOWN_BITS = 0x0018; + +// Base multiplier value for lux computation +// +static const float LUX_MULTIPLIER_BASE = 0.00213; + +// Enum for conversion/integration time settings for the VEML3235. +// +// Specific values of the enum constants are register values taken from the VEML3235 datasheet. +// Longer times mean more accurate results, but will take more energy/more time. +// +enum VEML3235ComponentIntegrationTime { + VEML3235_INTEGRATION_TIME_50MS = 0b000, + VEML3235_INTEGRATION_TIME_100MS = 0b001, + VEML3235_INTEGRATION_TIME_200MS = 0b010, + VEML3235_INTEGRATION_TIME_400MS = 0b011, + VEML3235_INTEGRATION_TIME_800MS = 0b100, +}; + +// Enum for digital gain settings for the VEML3235. +// Higher values are better for low light situations, but can increase noise. +// +enum VEML3235ComponentDigitalGain { + VEML3235_DIGITAL_GAIN_1X = 0b0, + VEML3235_DIGITAL_GAIN_2X = 0b1, +}; + +// Enum for gain settings for the VEML3235. +// Higher values are better for low light situations, but can increase noise. +// +enum VEML3235ComponentGain { + VEML3235_GAIN_1X = 0b00, + VEML3235_GAIN_2X = 0b01, + VEML3235_GAIN_4X = 0b11, +}; + +class VEML3235Sensor : public sensor::Sensor, public PollingComponent, public i2c::I2CDevice { + public: + void setup() override; + void dump_config() override; + void update() override { this->publish_state(this->read_lx_()); } + float get_setup_priority() const override { return setup_priority::DATA; } + + // Used by ESPHome framework. Does NOT actually set the value on the device. + void set_auto_gain(bool auto_gain) { this->auto_gain_ = auto_gain; } + void set_auto_gain_threshold_high(float auto_gain_threshold_high) { + this->auto_gain_threshold_high_ = auto_gain_threshold_high; + } + void set_auto_gain_threshold_low(float auto_gain_threshold_low) { + this->auto_gain_threshold_low_ = auto_gain_threshold_low; + } + void set_power_on(bool power_on) { this->power_on_ = power_on; } + void set_digital_gain(VEML3235ComponentDigitalGain digital_gain) { this->digital_gain_ = digital_gain; } + void set_gain(VEML3235ComponentGain gain) { this->gain_ = gain; } + void set_integration_time(VEML3235ComponentIntegrationTime integration_time) { + this->integration_time_ = integration_time; + } + + bool auto_gain() { return this->auto_gain_; } + float auto_gain_threshold_high() { return this->auto_gain_threshold_high_; } + float auto_gain_threshold_low() { return this->auto_gain_threshold_low_; } + VEML3235ComponentDigitalGain digital_gain() { return this->digital_gain_; } + VEML3235ComponentGain gain() { return this->gain_; } + VEML3235ComponentIntegrationTime integration_time() { return this->integration_time_; } + + // Updates the configuration register on the device + bool refresh_config_reg(bool force_on = false); + + protected: + float read_lx_(); + void adjust_gain_(uint16_t als_raw_value); + + bool auto_gain_{true}; + bool power_on_{true}; + float auto_gain_threshold_high_{0.9}; + float auto_gain_threshold_low_{0.2}; + VEML3235ComponentDigitalGain digital_gain_{VEML3235_DIGITAL_GAIN_1X}; + VEML3235ComponentGain gain_{VEML3235_GAIN_1X}; + VEML3235ComponentIntegrationTime integration_time_{VEML3235_INTEGRATION_TIME_50MS}; +}; + +} // namespace veml3235 +} // namespace esphome diff --git a/tests/test1.yaml b/tests/test1.yaml index 471e2a71a5..afd199d264 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -1370,6 +1370,16 @@ sensor: name: tsl2591 calculated_lux id: tsl2591_cl i2c_id: i2c_bus + - platform: veml3235 + id: veml3235_sensor + name: VEML3235 Light Sensor + i2c_id: i2c_bus + auto_gain: true + auto_gain_threshold_high: 90% + auto_gain_threshold_low: 15% + digital_gain: 1X + gain: 1X + integration_time: 50ms - platform: tee501 name: Office Temperature 3 address: 0x48 From b606e976e1e90eaaa2637f3eb95592a3a888c04a Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 17 Jan 2024 20:28:46 +1300 Subject: [PATCH 24/24] CV: tidy up Schema wrapper (#6105) --- esphome/config_validation.py | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/esphome/config_validation.py b/esphome/config_validation.py index 8f2e080b46..fa1170fb93 100644 --- a/esphome/config_validation.py +++ b/esphome/config_validation.py @@ -2004,15 +2004,19 @@ def suppress_invalid(): pass -GIT_SCHEMA = { - Required(CONF_URL): url, - Optional(CONF_REF): git_ref, - Optional(CONF_USERNAME): string, - Optional(CONF_PASSWORD): string, -} -LOCAL_SCHEMA = { - Required(CONF_PATH): directory, -} +GIT_SCHEMA = Schema( + { + Required(CONF_URL): url, + Optional(CONF_REF): git_ref, + Optional(CONF_USERNAME): string, + Optional(CONF_PASSWORD): string, + } +) +LOCAL_SCHEMA = Schema( + { + Required(CONF_PATH): directory, + } +) def validate_source_shorthand(value): @@ -2053,8 +2057,8 @@ SOURCE_SCHEMA = Any( validate_source_shorthand, typed_schema( { - TYPE_GIT: Schema(GIT_SCHEMA), - TYPE_LOCAL: Schema(LOCAL_SCHEMA), + TYPE_GIT: GIT_SCHEMA, + TYPE_LOCAL: LOCAL_SCHEMA, } ), )