From 023cb4937e2ee3abac2c7c46cc54f9705c46a795 Mon Sep 17 00:00:00 2001 From: zry98 Date: Mon, 30 Sep 2024 03:22:27 +0200 Subject: [PATCH 1/3] Add support for Sharp GP2Y1010AU0F PM2.5 sensor (#6007) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- CODEOWNERS | 1 + esphome/components/gp2y1010au0f/__init__.py | 0 .../components/gp2y1010au0f/gp2y1010au0f.cpp | 67 +++++++++++++++++++ .../components/gp2y1010au0f/gp2y1010au0f.h | 52 ++++++++++++++ esphome/components/gp2y1010au0f/sensor.py | 61 +++++++++++++++++ .../gp2y1010au0f/test.esp32-idf.yaml | 16 +++++ 6 files changed, 197 insertions(+) create mode 100644 esphome/components/gp2y1010au0f/__init__.py create mode 100644 esphome/components/gp2y1010au0f/gp2y1010au0f.cpp create mode 100644 esphome/components/gp2y1010au0f/gp2y1010au0f.h create mode 100644 esphome/components/gp2y1010au0f/sensor.py create mode 100644 tests/components/gp2y1010au0f/test.esp32-idf.yaml diff --git a/CODEOWNERS b/CODEOWNERS index 1eb13a534b..3f5ff46c02 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -152,6 +152,7 @@ esphome/components/ft63x6/* @gpambrozio esphome/components/gcja5/* @gcormier esphome/components/gdk101/* @Szewcson esphome/components/globals/* @esphome/core +esphome/components/gp2y1010au0f/* @zry98 esphome/components/gp8403/* @jesserockz esphome/components/gpio/* @esphome/core esphome/components/gpio/one_wire/* @ssieb diff --git a/esphome/components/gp2y1010au0f/__init__.py b/esphome/components/gp2y1010au0f/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esphome/components/gp2y1010au0f/gp2y1010au0f.cpp b/esphome/components/gp2y1010au0f/gp2y1010au0f.cpp new file mode 100644 index 0000000000..95b7653e51 --- /dev/null +++ b/esphome/components/gp2y1010au0f/gp2y1010au0f.cpp @@ -0,0 +1,67 @@ +#include "gp2y1010au0f.h" +#include "esphome/core/hal.h" +#include "esphome/core/log.h" + +#include + +namespace esphome { +namespace gp2y1010au0f { + +static const char *const TAG = "gp2y1010au0f"; +static const float MIN_VOLTAGE = 0.0f; +static const float MAX_VOLTAGE = 4.0f; + +void GP2Y1010AU0FSensor::dump_config() { + LOG_SENSOR("", "Sharp GP2Y1010AU0F PM2.5 Sensor", this); + ESP_LOGCONFIG(TAG, " Sampling duration: %" PRId32 " ms", this->sample_duration_); + ESP_LOGCONFIG(TAG, " ADC voltage multiplier: %.3f", this->voltage_multiplier_); + LOG_UPDATE_INTERVAL(this); +} + +void GP2Y1010AU0FSensor::update() { + is_sampling_ = true; + + this->set_timeout("read", this->sample_duration_, [this]() { + this->is_sampling_ = false; + if (this->num_samples_ == 0) + return; + + float mean = this->sample_sum_ / float(this->num_samples_); + ESP_LOGD(TAG, "ADC read voltage: %.3f V (mean from %" PRId32 " samples)", mean, this->num_samples_); + + // PM2.5 calculation + // ref: https://www.howmuchsnow.com/arduino/airquality/ + int16_t pm_2_5_value = 170 * mean; + this->publish_state(pm_2_5_value); + }); + + // reset readings + this->num_samples_ = 0; + this->sample_sum_ = 0.0f; +} + +void GP2Y1010AU0FSensor::loop() { + if (!this->is_sampling_) + return; + + // enable the internal IR LED + this->led_output_->turn_on(); + // wait for the sensor to stabilize + delayMicroseconds(this->sample_wait_before_); + // perform a single sample + float read_voltage = this->source_->sample(); + // disable the internal IR LED + this->led_output_->turn_off(); + + if (std::isnan(read_voltage)) + return; + read_voltage = read_voltage * this->voltage_multiplier_ - this->voltage_offset_; + if (read_voltage < MIN_VOLTAGE || read_voltage > MAX_VOLTAGE) + return; + + this->num_samples_++; + this->sample_sum_ += read_voltage; +} + +} // namespace gp2y1010au0f +} // namespace esphome diff --git a/esphome/components/gp2y1010au0f/gp2y1010au0f.h b/esphome/components/gp2y1010au0f/gp2y1010au0f.h new file mode 100644 index 0000000000..5ee58e68d2 --- /dev/null +++ b/esphome/components/gp2y1010au0f/gp2y1010au0f.h @@ -0,0 +1,52 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/voltage_sampler/voltage_sampler.h" +#include "esphome/components/output/binary_output.h" + +namespace esphome { +namespace gp2y1010au0f { + +class GP2Y1010AU0FSensor : public sensor::Sensor, public PollingComponent { + public: + void update() override; + void loop() override; + void dump_config() override; + float get_setup_priority() const override { + // after the base sensor has been initialized + return setup_priority::DATA - 1.0f; + } + + void set_adc_source(voltage_sampler::VoltageSampler *source) { source_ = source; } + void set_voltage_refs(float offset, float multiplier) { + this->voltage_offset_ = offset; + this->voltage_multiplier_ = multiplier; + } + void set_led_output(output::BinaryOutput *output) { led_output_ = output; } + + protected: + // duration in ms of the sampling phase + uint32_t sample_duration_ = 100; + // duration in us of the wait before sampling + // ref: https://global.sharp/products/device/lineup/data/pdf/datasheet/gp2y1010au_appl_e.pdf + uint32_t sample_wait_before_ = 280; + // duration in us of the wait after sampling + // it seems no need to delay on purpose since one ADC sampling takes longer than that (300-400 us on ESP8266) + // uint32_t sample_wait_after_ = 40; + // the sampling source to read voltage from + voltage_sampler::VoltageSampler *source_; + // ADC voltage reading offset + float voltage_offset_ = 0.0f; + // ADC voltage reading multiplier + float voltage_multiplier_ = 1.0f; + // the binary output to control the sampling LED + output::BinaryOutput *led_output_; + + float sample_sum_ = 0.0f; + uint32_t num_samples_ = 0; + bool is_sampling_ = false; +}; + +} // namespace gp2y1010au0f +} // namespace esphome diff --git a/esphome/components/gp2y1010au0f/sensor.py b/esphome/components/gp2y1010au0f/sensor.py new file mode 100644 index 0000000000..7e1bd277a6 --- /dev/null +++ b/esphome/components/gp2y1010au0f/sensor.py @@ -0,0 +1,61 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import sensor, voltage_sampler, output +from esphome.const import ( + CONF_SENSOR, + CONF_OUTPUT, + DEVICE_CLASS_PM25, + STATE_CLASS_MEASUREMENT, + UNIT_MICROGRAMS_PER_CUBIC_METER, + ICON_CHEMICAL_WEAPON, +) + +DEPENDENCIES = ["output"] +AUTO_LOAD = ["voltage_sampler"] +CODEOWNERS = ["@zry98"] + +CONF_ADC_VOLTAGE_OFFSET = "adc_voltage_offset" +CONF_ADC_VOLTAGE_MULTIPLIER = "adc_voltage_multiplier" + +gp2y1010au0f_ns = cg.esphome_ns.namespace("gp2y1010au0f") +GP2Y1010AU0FSensor = gp2y1010au0f_ns.class_( + "GP2Y1010AU0FSensor", sensor.Sensor, cg.PollingComponent +) + +CONFIG_SCHEMA = ( + sensor.sensor_schema( + GP2Y1010AU0FSensor, + unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER, + accuracy_decimals=0, + device_class=DEVICE_CLASS_PM25, + state_class=STATE_CLASS_MEASUREMENT, + icon=ICON_CHEMICAL_WEAPON, + ) + .extend( + { + cv.Required(CONF_SENSOR): cv.use_id(voltage_sampler.VoltageSampler), + cv.Optional(CONF_ADC_VOLTAGE_OFFSET, default=0.0): cv.float_, + cv.Optional(CONF_ADC_VOLTAGE_MULTIPLIER, default=1.0): cv.float_, + cv.Required(CONF_OUTPUT): cv.use_id(output.BinaryOutput), + } + ) + .extend(cv.polling_component_schema("60s")) +) + + +async def to_code(config): + var = await sensor.new_sensor(config) + await cg.register_component(var, config) + + # the ADC sensor to read voltage from + adc_sensor = await cg.get_variable(config[CONF_SENSOR]) + cg.add(var.set_adc_source(adc_sensor)) + cg.add( + var.set_voltage_refs( + config[CONF_ADC_VOLTAGE_OFFSET], config[CONF_ADC_VOLTAGE_MULTIPLIER] + ) + ) + + # the binary output to control the module's internal IR LED + led_output = await cg.get_variable(config[CONF_OUTPUT]) + cg.add(var.set_led_output(led_output)) diff --git a/tests/components/gp2y1010au0f/test.esp32-idf.yaml b/tests/components/gp2y1010au0f/test.esp32-idf.yaml new file mode 100644 index 0000000000..eb5ad0ea67 --- /dev/null +++ b/tests/components/gp2y1010au0f/test.esp32-idf.yaml @@ -0,0 +1,16 @@ +sensor: + - platform: adc + pin: GPIO36 + id: adc_sensor + + - platform: gp2y1010au0f + sensor: adc_sensor + name: Dust Sensor + adc_voltage_offset: 0.2 + adc_voltage_multiplier: 3.3 + output: dust_sensor_led + +output: + - platform: gpio + id: dust_sensor_led + pin: GPIO32 From 49a3d385eb9f7b61457b97d87fe599ff4653f647 Mon Sep 17 00:00:00 2001 From: Nick Kinnan Date: Sun, 29 Sep 2024 18:59:12 -0700 Subject: [PATCH 2/3] Prevent rp2040 randomly breaking the build (#7507) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/wizard.py | 2 +- platformio.ini | 2 +- .../test_build_components/build_components_base.rp2040-ard.yaml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/esphome/wizard.py b/esphome/wizard.py index 319fb31938..b1057189fd 100644 --- a/esphome/wizard.py +++ b/esphome/wizard.py @@ -92,7 +92,7 @@ rp2040: board: {board} framework: # Required until https://github.com/platformio/platform-raspberrypi/pull/36 is merged - platform_version: https://github.com/maxgerhardt/platform-raspberrypi.git + platform_version: https://github.com/maxgerhardt/platform-raspberrypi.git#5e87ae34ca025274df25b3303e9e9cb6c120123c """ BK72XX_CONFIG = """ diff --git a/platformio.ini b/platformio.ini index e3593bf43f..fc38923f65 100644 --- a/platformio.ini +++ b/platformio.ini @@ -165,7 +165,7 @@ platform_packages = extends = common:arduino board_build.filesystem_size = 0.5m -platform = https://github.com/maxgerhardt/platform-raspberrypi.git +platform = https://github.com/maxgerhardt/platform-raspberrypi.git#5e87ae34ca025274df25b3303e9e9cb6c120123c platform_packages = ; earlephilhower/framework-arduinopico@~1.20602.0 ; Cannot use the platformio package until old releases stop getting deleted earlephilhower/framework-arduinopico@https://github.com/earlephilhower/arduino-pico/releases/download/3.9.4/rp2040-3.9.4.zip diff --git a/tests/test_build_components/build_components_base.rp2040-ard.yaml b/tests/test_build_components/build_components_base.rp2040-ard.yaml index 6c6a27e0a7..f9815578c5 100644 --- a/tests/test_build_components/build_components_base.rp2040-ard.yaml +++ b/tests/test_build_components/build_components_base.rp2040-ard.yaml @@ -6,7 +6,7 @@ rp2040: board: rpipicow framework: # Waiting for https://github.com/platformio/platform-raspberrypi/pull/36 - platform_version: https://github.com/maxgerhardt/platform-raspberrypi.git + platform_version: https://github.com/maxgerhardt/platform-raspberrypi.git#5e87ae34ca025274df25b3303e9e9cb6c120123c logger: level: VERY_VERBOSE From 20cb2e147fd6bae669facaeeaf967c588f4b55d1 Mon Sep 17 00:00:00 2001 From: Nick Kinnan Date: Sun, 29 Sep 2024 20:27:22 -0700 Subject: [PATCH 3/3] Make time dependency optional (#7425) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/datetime/__init__.py | 28 ++++++++++++------- esphome/components/datetime/datetime_base.h | 9 +++++- .../components/datetime/datetime_entity.cpp | 2 ++ esphome/components/datetime/datetime_entity.h | 2 ++ esphome/components/datetime/time_entity.cpp | 2 ++ esphome/components/datetime/time_entity.h | 2 ++ 6 files changed, 34 insertions(+), 11 deletions(-) diff --git a/esphome/components/datetime/__init__.py b/esphome/components/datetime/__init__.py index 5429121d56..55066006d3 100644 --- a/esphome/components/datetime/__init__.py +++ b/esphome/components/datetime/__init__.py @@ -26,7 +26,6 @@ from esphome.cpp_generator import MockObjClass from esphome.cpp_helpers import setup_entity CODEOWNERS = ["@rfdarter", "@jesserockz"] -DEPENDENCIES = ["time"] IS_PLATFORM_COMPONENT = True @@ -62,20 +61,28 @@ DATETIME_MODES = [ ] -_DATETIME_SCHEMA = ( - cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA) - .extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA) - .extend( +def _validate_time_present(config): + config = config.copy() + if CONF_ON_TIME in config and CONF_TIME_ID not in config: + time_id = cv.use_id(time.RealTimeClock)(None) + config[CONF_TIME_ID] = time_id + return config + + +_DATETIME_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend( + web_server.WEBSERVER_SORTING_SCHEMA, + cv.MQTT_COMMAND_COMPONENT_SCHEMA, + cv.Schema( { cv.Optional(CONF_ON_VALUE): automation.validate_automation( { cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(DateTimeStateTrigger), } ), - cv.GenerateID(CONF_TIME_ID): cv.use_id(time.RealTimeClock), + cv.Optional(CONF_TIME_ID): cv.use_id(time.RealTimeClock), } - ) -) + ), +).add_extra(_validate_time_present) def date_schema(class_: MockObjClass) -> cv.Schema: @@ -138,8 +145,9 @@ async def setup_datetime_core_(var, config): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) await automation.build_automation(trigger, [(cg.ESPTime, "x")], conf) - rtc = await cg.get_variable(config[CONF_TIME_ID]) - cg.add(var.set_rtc(rtc)) + if CONF_TIME_ID in config: + rtc = await cg.get_variable(config[CONF_TIME_ID]) + cg.add(var.set_rtc(rtc)) for conf in config.get(CONF_ON_TIME, []): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID]) diff --git a/esphome/components/datetime/datetime_base.h b/esphome/components/datetime/datetime_base.h index c8240390e3..dea34e6110 100644 --- a/esphome/components/datetime/datetime_base.h +++ b/esphome/components/datetime/datetime_base.h @@ -4,8 +4,9 @@ #include "esphome/core/component.h" #include "esphome/core/entity_base.h" #include "esphome/core/time.h" - +#ifdef USE_TIME #include "esphome/components/time/real_time_clock.h" +#endif namespace esphome { namespace datetime { @@ -19,23 +20,29 @@ class DateTimeBase : public EntityBase { void add_on_state_callback(std::function &&callback) { this->state_callback_.add(std::move(callback)); } +#ifdef USE_TIME void set_rtc(time::RealTimeClock *rtc) { this->rtc_ = rtc; } time::RealTimeClock *get_rtc() const { return this->rtc_; } +#endif protected: CallbackManager state_callback_; +#ifdef USE_TIME time::RealTimeClock *rtc_; +#endif bool has_state_{false}; }; +#ifdef USE_TIME class DateTimeStateTrigger : public Trigger { public: explicit DateTimeStateTrigger(DateTimeBase *parent) { parent->add_on_state_callback([this, parent]() { this->trigger(parent->state_as_esptime()); }); } }; +#endif } // namespace datetime } // namespace esphome diff --git a/esphome/components/datetime/datetime_entity.cpp b/esphome/components/datetime/datetime_entity.cpp index 9a61d341e4..f215b7acb5 100644 --- a/esphome/components/datetime/datetime_entity.cpp +++ b/esphome/components/datetime/datetime_entity.cpp @@ -192,6 +192,7 @@ void DateTimeEntityRestoreState::apply(DateTimeEntity *time) { time->publish_state(); } +#ifdef USE_TIME static const int MAX_TIMESTAMP_DRIFT = 900; // how far can the clock drift before we consider // there has been a drastic time synchronization @@ -245,6 +246,7 @@ bool OnDateTimeTrigger::matches_(const ESPTime &time) const { time.day_of_month == this->parent_->day && time.hour == this->parent_->hour && time.minute == this->parent_->minute && time.second == this->parent_->second; } +#endif } // namespace datetime } // namespace esphome diff --git a/esphome/components/datetime/datetime_entity.h b/esphome/components/datetime/datetime_entity.h index d541fa96b1..27db84cf7e 100644 --- a/esphome/components/datetime/datetime_entity.h +++ b/esphome/components/datetime/datetime_entity.h @@ -134,6 +134,7 @@ template class DateTimeSetAction : public Action, public } }; +#ifdef USE_TIME class OnDateTimeTrigger : public Trigger<>, public Component, public Parented { public: void loop() override; @@ -143,6 +144,7 @@ class OnDateTimeTrigger : public Trigger<>, public Component, public Parented last_check_; }; +#endif } // namespace datetime } // namespace esphome diff --git a/esphome/components/datetime/time_entity.cpp b/esphome/components/datetime/time_entity.cpp index ea5e6684d0..db0094ae01 100644 --- a/esphome/components/datetime/time_entity.cpp +++ b/esphome/components/datetime/time_entity.cpp @@ -94,6 +94,7 @@ void TimeEntityRestoreState::apply(TimeEntity *time) { time->publish_state(); } +#ifdef USE_TIME static const int MAX_TIMESTAMP_DRIFT = 900; // how far can the clock drift before we consider // there has been a drastic time synchronization @@ -145,6 +146,7 @@ bool OnTimeTrigger::matches_(const ESPTime &time) const { return time.is_valid() && time.hour == this->parent_->hour && time.minute == this->parent_->minute && time.second == this->parent_->second; } +#endif } // namespace datetime } // namespace esphome diff --git a/esphome/components/datetime/time_entity.h b/esphome/components/datetime/time_entity.h index 62e593d28a..f7e0a7ddd9 100644 --- a/esphome/components/datetime/time_entity.h +++ b/esphome/components/datetime/time_entity.h @@ -113,6 +113,7 @@ template class TimeSetAction : public Action, public Pare } }; +#ifdef USE_TIME class OnTimeTrigger : public Trigger<>, public Component, public Parented { public: void loop() override; @@ -122,6 +123,7 @@ class OnTimeTrigger : public Trigger<>, public Component, public Parented last_check_; }; +#endif } // namespace datetime } // namespace esphome