mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-30 22:53:59 +00:00 
			
		
		
		
	Merge branch 'dev' into bump-2023.3.0b1
This commit is contained in:
		
							
								
								
									
										5
									
								
								.github/workflows/ci-docker.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										5
									
								
								.github/workflows/ci-docker.yml
									
									
									
									
										vendored
									
									
								
							| @@ -23,6 +23,11 @@ permissions: | ||||
|   contents: read | ||||
|   packages: read | ||||
|  | ||||
| concurrency: | ||||
|   # yamllint disable-line rule:line-length | ||||
|   group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} | ||||
|   cancel-in-progress: true | ||||
|  | ||||
| jobs: | ||||
|   check-docker: | ||||
|     name: Build docker containers | ||||
|   | ||||
							
								
								
									
										16
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										16
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							| @@ -7,6 +7,7 @@ on: | ||||
|     branches: [dev, beta, release] | ||||
|  | ||||
|   pull_request: | ||||
|   merge_group: | ||||
|  | ||||
| permissions: | ||||
|   contents: read | ||||
| @@ -181,9 +182,22 @@ jobs: | ||||
|  | ||||
|       - name: Run yamllint | ||||
|         if: matrix.id == 'yamllint' | ||||
|         uses: frenck/action-yamllint@v1.3.1 | ||||
|         uses: frenck/action-yamllint@v1.4.0 | ||||
|  | ||||
|       - name: Suggested changes | ||||
|         run: script/ci-suggest-changes | ||||
|         # yamllint disable-line rule:line-length | ||||
|         if: always() && (matrix.id == 'clang-tidy' || matrix.id == 'clang-format' || matrix.id == 'lint-python') | ||||
|  | ||||
|   ci-status: | ||||
|     name: CI Status | ||||
|     runs-on: ubuntu-latest | ||||
|     needs: [ci] | ||||
|     if: always() | ||||
|     steps: | ||||
|       - name: Successful deploy | ||||
|         if: ${{ !(contains(needs.*.result, 'failure')) }} | ||||
|         run: exit 0 | ||||
|       - name: Failing deploy | ||||
|         if: ${{ contains(needs.*.result, 'failure') }} | ||||
|         run: exit 1 | ||||
|   | ||||
| @@ -3,7 +3,7 @@ | ||||
| # See https://pre-commit.com/hooks.html for more hooks | ||||
| repos: | ||||
|   - repo: https://github.com/ambv/black | ||||
|     rev: 22.12.0 | ||||
|     rev: 23.1.0 | ||||
|     hooks: | ||||
|       - id: black | ||||
|         args: | ||||
| @@ -27,7 +27,7 @@ repos: | ||||
|           - --branch=release | ||||
|           - --branch=beta | ||||
|   - repo: https://github.com/asottile/pyupgrade | ||||
|     rev: v3.3.0 | ||||
|     rev: v3.3.1 | ||||
|     hooks: | ||||
|       - id: pyupgrade | ||||
|         args: [--py39-plus] | ||||
|   | ||||
							
								
								
									
										10
									
								
								CODEOWNERS
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								CODEOWNERS
									
									
									
									
									
								
							| @@ -11,6 +11,7 @@ esphome/*.py @esphome/core | ||||
| esphome/core/* @esphome/core | ||||
|  | ||||
| # Integrations | ||||
| esphome/components/absolute_humidity/* @DAVe3283 | ||||
| esphome/components/ac_dimmer/* @glmnet | ||||
| esphome/components/adc/* @esphome/core | ||||
| esphome/components/adc128s102/* @DeerMaximum | ||||
| @@ -24,6 +25,7 @@ esphome/components/analog_threshold/* @ianchi | ||||
| esphome/components/animation/* @syndlex | ||||
| esphome/components/anova/* @buxtronix | ||||
| esphome/components/api/* @OttoWinter | ||||
| esphome/components/as7341/* @mrgnr | ||||
| esphome/components/async_tcp/* @OttoWinter | ||||
| esphome/components/atc_mithermometer/* @ahpohl | ||||
| esphome/components/b_parasite/* @rbaron | ||||
| @@ -90,11 +92,13 @@ esphome/components/factory_reset/* @anatoly-savchenkov | ||||
| esphome/components/fastled_base/* @OttoWinter | ||||
| esphome/components/feedback/* @ianchi | ||||
| esphome/components/fingerprint_grow/* @OnFreund @loongyh | ||||
| esphome/components/fs3000/* @kahrendt | ||||
| esphome/components/globals/* @esphome/core | ||||
| esphome/components/gpio/* @esphome/core | ||||
| esphome/components/gps/* @coogle | ||||
| esphome/components/graph/* @synco | ||||
| esphome/components/growatt_solar/* @leeuwte | ||||
| esphome/components/haier/* @Yarikx | ||||
| esphome/components/havells_solar/* @sourabhjaiswal | ||||
| esphome/components/hbridge/fan/* @WeekendWarrior | ||||
| esphome/components/hbridge/light/* @DotNetDann | ||||
| @@ -113,11 +117,13 @@ esphome/components/ina260/* @MrEditor97 | ||||
| esphome/components/inkbird_ibsth1_mini/* @fkirill | ||||
| esphome/components/inkplate6/* @jesserockz | ||||
| esphome/components/integration/* @OttoWinter | ||||
| esphome/components/internal_temperature/* @Mat931 | ||||
| esphome/components/interval/* @esphome/core | ||||
| esphome/components/json/* @OttoWinter | ||||
| esphome/components/kalman_combinator/* @Cat-Ion | ||||
| esphome/components/key_collector/* @ssieb | ||||
| esphome/components/key_provider/* @ssieb | ||||
| esphome/components/kuntze/* @ssieb | ||||
| esphome/components/lcd_menu/* @numo68 | ||||
| esphome/components/ld2410/* @sebcaps | ||||
| esphome/components/ledc/* @OttoWinter | ||||
| @@ -160,8 +166,9 @@ esphome/components/modbus_controller/select/* @martgras @stegm | ||||
| esphome/components/modbus_controller/sensor/* @martgras | ||||
| esphome/components/modbus_controller/switch/* @martgras | ||||
| esphome/components/modbus_controller/text_sensor/* @martgras | ||||
| esphome/components/mopeka_ble/* @spbrogan | ||||
| esphome/components/mopeka_ble/* @Fabian-Schmidt @spbrogan | ||||
| esphome/components/mopeka_pro_check/* @spbrogan | ||||
| esphome/components/mopeka_std_check/* @Fabian-Schmidt | ||||
| esphome/components/mpl3115a2/* @kbickar | ||||
| esphome/components/mpu6886/* @fabaff | ||||
| esphome/components/network/* @esphome/core | ||||
| @@ -208,6 +215,7 @@ esphome/components/sdm_meter/* @jesserockz @polyfaces | ||||
| esphome/components/sdp3x/* @Azimath | ||||
| esphome/components/selec_meter/* @sourabhjaiswal | ||||
| esphome/components/select/* @esphome/core | ||||
| esphome/components/sen21231/* @shreyaskarnik | ||||
| esphome/components/sen5x/* @martgras | ||||
| esphome/components/sensirion_common/* @martgras | ||||
| esphome/components/sensor/* @esphome/core | ||||
|   | ||||
| @@ -1,8 +1,6 @@ | ||||
| include LICENSE | ||||
| include README.md | ||||
| include requirements.txt | ||||
| include esphome/dashboard/templates/*.html | ||||
| recursive-include esphome/dashboard/static *.ico *.js *.css *.woff* LICENSE | ||||
| recursive-include esphome *.cpp *.h *.tcc | ||||
| recursive-include esphome *.cpp *.h *.tcc *.c | ||||
| recursive-include esphome *.py.script | ||||
| recursive-include esphome LICENSE.txt | ||||
|   | ||||
| @@ -6,9 +6,9 @@ | ||||
| ARG BASEIMGTYPE=docker | ||||
|  | ||||
| # https://github.com/hassio-addons/addon-debian-base/releases | ||||
| FROM ghcr.io/hassio-addons/debian-base:6.2.0 AS base-hassio | ||||
| FROM ghcr.io/hassio-addons/debian-base:6.2.3 AS base-hassio | ||||
| # https://hub.docker.com/_/debian?tab=tags&page=1&name=bullseye | ||||
| FROM debian:bullseye-20221024-slim AS base-docker | ||||
| FROM debian:bullseye-20230208-slim AS base-docker | ||||
|  | ||||
| FROM base-${BASEIMGTYPE} AS base | ||||
|  | ||||
| @@ -26,7 +26,7 @@ RUN \ | ||||
|         python3-cryptography=3.3.2-1 \ | ||||
|         iputils-ping=3:20210202-1 \ | ||||
|         git=1:2.30.2-1 \ | ||||
|         curl=7.74.0-1.3+deb11u5 \ | ||||
|         curl=7.74.0-1.3+deb11u7 \ | ||||
|         openssh-client=1:8.4p1-5+deb11u1 \ | ||||
|     && rm -rf \ | ||||
|         /tmp/* \ | ||||
| @@ -51,7 +51,7 @@ RUN \ | ||||
|     # Ubuntu python3-pip is missing wheel | ||||
|     pip3 install --no-cache-dir \ | ||||
|         wheel==0.37.1 \ | ||||
|         platformio==6.1.5 \ | ||||
|         platformio==6.1.6 \ | ||||
|     # Change some platformio settings | ||||
|     && platformio settings set enable_telemetry No \ | ||||
|     && platformio settings set check_platformio_interval 1000000 \ | ||||
|   | ||||
| @@ -254,7 +254,11 @@ async def repeat_action_to_code(config, action_id, template_arg, args): | ||||
|     var = cg.new_Pvariable(action_id, template_arg) | ||||
|     count_template = await cg.templatable(config[CONF_COUNT], args, cg.uint32) | ||||
|     cg.add(var.set_count(count_template)) | ||||
|     actions = await build_action_list(config[CONF_THEN], template_arg, args) | ||||
|     actions = await build_action_list( | ||||
|         config[CONF_THEN], | ||||
|         cg.TemplateArguments(cg.uint32, *template_arg.args), | ||||
|         [(cg.uint32, "iteration"), *args], | ||||
|     ) | ||||
|     cg.add(var.add_then(actions)) | ||||
|     return var | ||||
|  | ||||
|   | ||||
| @@ -47,6 +47,7 @@ from esphome.cpp_helpers import (  # noqa | ||||
|     build_registry_list, | ||||
|     extract_registry_entry_config, | ||||
|     register_parented, | ||||
|     past_safe_mode, | ||||
| ) | ||||
| from esphome.cpp_types import (  # noqa | ||||
|     global_ns, | ||||
| @@ -63,6 +64,7 @@ from esphome.cpp_types import (  # noqa | ||||
|     uint16, | ||||
|     uint32, | ||||
|     uint64, | ||||
|     int16, | ||||
|     int32, | ||||
|     int64, | ||||
|     size_t, | ||||
|   | ||||
							
								
								
									
										1
									
								
								esphome/components/absolute_humidity/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								esphome/components/absolute_humidity/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| CODEOWNERS = ["@DAVe3283"] | ||||
							
								
								
									
										182
									
								
								esphome/components/absolute_humidity/absolute_humidity.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										182
									
								
								esphome/components/absolute_humidity/absolute_humidity.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,182 @@ | ||||
| #include "esphome/core/log.h" | ||||
| #include "absolute_humidity.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace absolute_humidity { | ||||
|  | ||||
| static const char *const TAG = "absolute_humidity.sensor"; | ||||
|  | ||||
| void AbsoluteHumidityComponent::setup() { | ||||
|   ESP_LOGCONFIG(TAG, "Setting up absolute humidity '%s'...", this->get_name().c_str()); | ||||
|  | ||||
|   ESP_LOGD(TAG, "  Added callback for temperature '%s'", this->temperature_sensor_->get_name().c_str()); | ||||
|   this->temperature_sensor_->add_on_state_callback([this](float state) { this->temperature_callback_(state); }); | ||||
|   if (this->temperature_sensor_->has_state()) { | ||||
|     this->temperature_callback_(this->temperature_sensor_->get_state()); | ||||
|   } | ||||
|  | ||||
|   ESP_LOGD(TAG, "  Added callback for relative humidity '%s'", this->humidity_sensor_->get_name().c_str()); | ||||
|   this->humidity_sensor_->add_on_state_callback([this](float state) { this->humidity_callback_(state); }); | ||||
|   if (this->humidity_sensor_->has_state()) { | ||||
|     this->humidity_callback_(this->humidity_sensor_->get_state()); | ||||
|   } | ||||
| } | ||||
|  | ||||
| void AbsoluteHumidityComponent::dump_config() { | ||||
|   LOG_SENSOR("", "Absolute Humidity", this); | ||||
|  | ||||
|   switch (this->equation_) { | ||||
|     case BUCK: | ||||
|       ESP_LOGCONFIG(TAG, "Saturation Vapor Pressure Equation: Buck"); | ||||
|       break; | ||||
|     case TETENS: | ||||
|       ESP_LOGCONFIG(TAG, "Saturation Vapor Pressure Equation: Tetens"); | ||||
|       break; | ||||
|     case WOBUS: | ||||
|       ESP_LOGCONFIG(TAG, "Saturation Vapor Pressure Equation: Wobus"); | ||||
|       break; | ||||
|     default: | ||||
|       ESP_LOGE(TAG, "Invalid saturation vapor pressure equation selection!"); | ||||
|       break; | ||||
|   } | ||||
|  | ||||
|   ESP_LOGCONFIG(TAG, "Sources"); | ||||
|   ESP_LOGCONFIG(TAG, "  Temperature: '%s'", this->temperature_sensor_->get_name().c_str()); | ||||
|   ESP_LOGCONFIG(TAG, "  Relative Humidity: '%s'", this->humidity_sensor_->get_name().c_str()); | ||||
| } | ||||
|  | ||||
| float AbsoluteHumidityComponent::get_setup_priority() const { return setup_priority::DATA; } | ||||
|  | ||||
| void AbsoluteHumidityComponent::loop() { | ||||
|   if (!this->next_update_) { | ||||
|     return; | ||||
|   } | ||||
|   this->next_update_ = false; | ||||
|  | ||||
|   // Ensure we have source data | ||||
|   const bool no_temperature = std::isnan(this->temperature_); | ||||
|   const bool no_humidity = std::isnan(this->humidity_); | ||||
|   if (no_temperature || no_humidity) { | ||||
|     if (no_temperature) { | ||||
|       ESP_LOGW(TAG, "No valid state from temperature sensor!"); | ||||
|     } | ||||
|     if (no_humidity) { | ||||
|       ESP_LOGW(TAG, "No valid state from temperature sensor!"); | ||||
|     } | ||||
|     ESP_LOGW(TAG, "Unable to calculate absolute humidity."); | ||||
|     this->publish_state(NAN); | ||||
|     this->status_set_warning(); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   // Convert to desired units | ||||
|   const float temperature_c = this->temperature_; | ||||
|   const float temperature_k = temperature_c + 273.15; | ||||
|   const float hr = this->humidity_ / 100; | ||||
|  | ||||
|   // Calculate saturation vapor pressure | ||||
|   float es; | ||||
|   switch (this->equation_) { | ||||
|     case BUCK: | ||||
|       es = es_buck(temperature_c); | ||||
|       break; | ||||
|     case TETENS: | ||||
|       es = es_tetens(temperature_c); | ||||
|       break; | ||||
|     case WOBUS: | ||||
|       es = es_wobus(temperature_c); | ||||
|       break; | ||||
|     default: | ||||
|       ESP_LOGE(TAG, "Invalid saturation vapor pressure equation selection!"); | ||||
|       this->publish_state(NAN); | ||||
|       this->status_set_error(); | ||||
|       return; | ||||
|   } | ||||
|   ESP_LOGD(TAG, "Saturation vapor pressure %f kPa", es); | ||||
|  | ||||
|   // Calculate absolute humidity | ||||
|   const float absolute_humidity = vapor_density(es, hr, temperature_k); | ||||
|  | ||||
|   // Publish absolute humidity | ||||
|   ESP_LOGD(TAG, "Publishing absolute humidity %f g/m³", absolute_humidity); | ||||
|   this->status_clear_warning(); | ||||
|   this->publish_state(absolute_humidity); | ||||
| } | ||||
|  | ||||
| // Buck equation (https://en.wikipedia.org/wiki/Arden_Buck_equation) | ||||
| // More accurate than Tetens in normal meteorologic conditions | ||||
| float AbsoluteHumidityComponent::es_buck(float temperature_c) { | ||||
|   float a, b, c, d; | ||||
|   if (temperature_c >= 0) { | ||||
|     a = 0.61121; | ||||
|     b = 18.678; | ||||
|     c = 234.5; | ||||
|     d = 257.14; | ||||
|   } else { | ||||
|     a = 0.61115; | ||||
|     b = 18.678; | ||||
|     c = 233.7; | ||||
|     d = 279.82; | ||||
|   } | ||||
|   return a * expf((b - (temperature_c / c)) * (temperature_c / (d + temperature_c))); | ||||
| } | ||||
|  | ||||
| // Tetens equation (https://en.wikipedia.org/wiki/Tetens_equation) | ||||
| float AbsoluteHumidityComponent::es_tetens(float temperature_c) { | ||||
|   float a, b; | ||||
|   if (temperature_c >= 0) { | ||||
|     a = 17.27; | ||||
|     b = 237.3; | ||||
|   } else { | ||||
|     a = 21.875; | ||||
|     b = 265.5; | ||||
|   } | ||||
|   return 0.61078 * expf((a * temperature_c) / (temperature_c + b)); | ||||
| } | ||||
|  | ||||
| // Wobus equation | ||||
| // https://wahiduddin.net/calc/density_altitude.htm | ||||
| // https://wahiduddin.net/calc/density_algorithms.htm | ||||
| // Calculate the saturation vapor pressure (kPa) | ||||
| float AbsoluteHumidityComponent::es_wobus(float t) { | ||||
|   // THIS FUNCTION RETURNS THE SATURATION VAPOR PRESSURE ESW (MILLIBARS) | ||||
|   // OVER LIQUID WATER GIVEN THE TEMPERATURE T (CELSIUS). THE POLYNOMIAL | ||||
|   // APPROXIMATION BELOW IS DUE TO HERMAN WOBUS, A MATHEMATICIAN WHO | ||||
|   // WORKED AT THE NAVY WEATHER RESEARCH FACILITY, NORFOLK, VIRGINIA, | ||||
|   // BUT WHO IS NOW RETIRED. THE COEFFICIENTS OF THE POLYNOMIAL WERE | ||||
|   // CHOSEN TO FIT THE VALUES IN TABLE 94 ON PP. 351-353 OF THE SMITH- | ||||
|   // SONIAN METEOROLOGICAL TABLES BY ROLAND LIST (6TH EDITION). THE | ||||
|   // APPROXIMATION IS VALID FOR -50 < T < 100C. | ||||
|   // | ||||
|   //     Baker, Schlatter  17-MAY-1982     Original version. | ||||
|  | ||||
|   const float c0 = +0.99999683e00; | ||||
|   const float c1 = -0.90826951e-02; | ||||
|   const float c2 = +0.78736169e-04; | ||||
|   const float c3 = -0.61117958e-06; | ||||
|   const float c4 = +0.43884187e-08; | ||||
|   const float c5 = -0.29883885e-10; | ||||
|   const float c6 = +0.21874425e-12; | ||||
|   const float c7 = -0.17892321e-14; | ||||
|   const float c8 = +0.11112018e-16; | ||||
|   const float c9 = -0.30994571e-19; | ||||
|   const float p = c0 + t * (c1 + t * (c2 + t * (c3 + t * (c4 + t * (c5 + t * (c6 + t * (c7 + t * (c8 + t * (c9))))))))); | ||||
|   return 0.61078 / pow(p, 8); | ||||
| } | ||||
|  | ||||
| // From https://www.environmentalbiophysics.org/chalk-talk-how-to-calculate-absolute-humidity/ | ||||
| // H/T to https://esphome.io/cookbook/bme280_environment.html | ||||
| // H/T to https://carnotcycle.wordpress.com/2012/08/04/how-to-convert-relative-humidity-to-absolute-humidity/ | ||||
| float AbsoluteHumidityComponent::vapor_density(float es, float hr, float ta) { | ||||
|   // es = saturated vapor pressure (kPa) | ||||
|   // hr = relative humidity [0-1] | ||||
|   // ta = absolute temperature (K) | ||||
|  | ||||
|   const float ea = hr * es * 1000;   // vapor pressure of the air (Pa) | ||||
|   const float mw = 18.01528;         // molar mass of water (g⋅mol⁻¹) | ||||
|   const float r = 8.31446261815324;  // molar gas constant (J⋅K⁻¹) | ||||
|   return (ea * mw) / (r * ta); | ||||
| } | ||||
|  | ||||
| }  // namespace absolute_humidity | ||||
| }  // namespace esphome | ||||
							
								
								
									
										76
									
								
								esphome/components/absolute_humidity/absolute_humidity.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								esphome/components/absolute_humidity/absolute_humidity.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,76 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/components/sensor/sensor.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace absolute_humidity { | ||||
|  | ||||
| /// Enum listing all implemented saturation vapor pressure equations. | ||||
| enum SaturationVaporPressureEquation { | ||||
|   BUCK, | ||||
|   TETENS, | ||||
|   WOBUS, | ||||
| }; | ||||
|  | ||||
| /// This class implements calculation of absolute humidity from temperature and relative humidity. | ||||
| class AbsoluteHumidityComponent : public sensor::Sensor, public Component { | ||||
|  public: | ||||
|   AbsoluteHumidityComponent() = default; | ||||
|  | ||||
|   void set_temperature_sensor(sensor::Sensor *temperature_sensor) { this->temperature_sensor_ = temperature_sensor; } | ||||
|   void set_humidity_sensor(sensor::Sensor *humidity_sensor) { this->humidity_sensor_ = humidity_sensor; } | ||||
|   void set_equation(SaturationVaporPressureEquation equation) { this->equation_ = equation; } | ||||
|  | ||||
|   void setup() override; | ||||
|   void dump_config() override; | ||||
|   float get_setup_priority() const override; | ||||
|   void loop() override; | ||||
|  | ||||
|  protected: | ||||
|   void temperature_callback_(float state) { | ||||
|     this->next_update_ = true; | ||||
|     this->temperature_ = state; | ||||
|   } | ||||
|   void humidity_callback_(float state) { | ||||
|     this->next_update_ = true; | ||||
|     this->humidity_ = state; | ||||
|   } | ||||
|  | ||||
|   /** Buck equation for saturation vapor pressure in kPa. | ||||
|    * | ||||
|    * @param temperature_c Air temperature in °C. | ||||
|    */ | ||||
|   static float es_buck(float temperature_c); | ||||
|   /** Tetens equation for saturation vapor pressure in kPa. | ||||
|    * | ||||
|    * @param temperature_c Air temperature in °C. | ||||
|    */ | ||||
|   static float es_tetens(float temperature_c); | ||||
|   /** Wobus equation for saturation vapor pressure in kPa. | ||||
|    * | ||||
|    * @param temperature_c Air temperature in °C. | ||||
|    */ | ||||
|   static float es_wobus(float temperature_c); | ||||
|  | ||||
|   /** Calculate vapor density (absolute humidity) in g/m³. | ||||
|    * | ||||
|    * @param es Saturation vapor pressure in kPa. | ||||
|    * @param hr Relative humidity 0 to 1. | ||||
|    * @param ta Absolute temperature in K. | ||||
|    * @param heater_duration The duration in ms that the heater should turn on for when measuring. | ||||
|    */ | ||||
|   static float vapor_density(float es, float hr, float ta); | ||||
|  | ||||
|   sensor::Sensor *temperature_sensor_{nullptr}; | ||||
|   sensor::Sensor *humidity_sensor_{nullptr}; | ||||
|  | ||||
|   bool next_update_{false}; | ||||
|  | ||||
|   float temperature_{NAN}; | ||||
|   float humidity_{NAN}; | ||||
|   SaturationVaporPressureEquation equation_; | ||||
| }; | ||||
|  | ||||
| }  // namespace absolute_humidity | ||||
| }  // namespace esphome | ||||
							
								
								
									
										56
									
								
								esphome/components/absolute_humidity/sensor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								esphome/components/absolute_humidity/sensor.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,56 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome.components import sensor | ||||
| from esphome.const import ( | ||||
|     CONF_HUMIDITY, | ||||
|     CONF_TEMPERATURE, | ||||
|     STATE_CLASS_MEASUREMENT, | ||||
|     CONF_EQUATION, | ||||
|     ICON_WATER, | ||||
|     UNIT_GRAMS_PER_CUBIC_METER, | ||||
| ) | ||||
|  | ||||
| absolute_humidity_ns = cg.esphome_ns.namespace("absolute_humidity") | ||||
| AbsoluteHumidityComponent = absolute_humidity_ns.class_( | ||||
|     "AbsoluteHumidityComponent", sensor.Sensor, cg.Component | ||||
| ) | ||||
|  | ||||
| SaturationVaporPressureEquation = absolute_humidity_ns.enum( | ||||
|     "SaturationVaporPressureEquation" | ||||
| ) | ||||
| EQUATION = { | ||||
|     "BUCK": SaturationVaporPressureEquation.BUCK, | ||||
|     "TETENS": SaturationVaporPressureEquation.TETENS, | ||||
|     "WOBUS": SaturationVaporPressureEquation.WOBUS, | ||||
| } | ||||
|  | ||||
| CONFIG_SCHEMA = ( | ||||
|     sensor.sensor_schema( | ||||
|         unit_of_measurement=UNIT_GRAMS_PER_CUBIC_METER, | ||||
|         icon=ICON_WATER, | ||||
|         accuracy_decimals=2, | ||||
|         state_class=STATE_CLASS_MEASUREMENT, | ||||
|     ) | ||||
|     .extend( | ||||
|         { | ||||
|             cv.GenerateID(): cv.declare_id(AbsoluteHumidityComponent), | ||||
|             cv.Required(CONF_TEMPERATURE): cv.use_id(sensor.Sensor), | ||||
|             cv.Required(CONF_HUMIDITY): cv.use_id(sensor.Sensor), | ||||
|             cv.Optional(CONF_EQUATION, default="WOBUS"): cv.enum(EQUATION, upper=True), | ||||
|         } | ||||
|     ) | ||||
|     .extend(cv.COMPONENT_SCHEMA) | ||||
| ) | ||||
|  | ||||
|  | ||||
| async def to_code(config): | ||||
|     var = await sensor.new_sensor(config) | ||||
|     await cg.register_component(var, config) | ||||
|  | ||||
|     temperature_sensor = await cg.get_variable(config[CONF_TEMPERATURE]) | ||||
|     cg.add(var.set_temperature_sensor(temperature_sensor)) | ||||
|  | ||||
|     humidity_sensor = await cg.get_variable(config[CONF_HUMIDITY]) | ||||
|     cg.add(var.set_humidity_sensor(humidity_sensor)) | ||||
|  | ||||
|     cg.add(var.set_equation(config[CONF_EQUATION])) | ||||
| @@ -16,13 +16,16 @@ ADC128S102Sensor = adc128s102_ns.class_( | ||||
| ) | ||||
| CONF_ADC128S102_ID = "adc128s102_id" | ||||
|  | ||||
| CONFIG_SCHEMA = sensor.SENSOR_SCHEMA.extend( | ||||
|     { | ||||
|         cv.GenerateID(): cv.declare_id(ADC128S102Sensor), | ||||
|         cv.GenerateID(CONF_ADC128S102_ID): cv.use_id(ADC128S102), | ||||
|         cv.Required(CONF_CHANNEL): cv.int_range(min=0, max=7), | ||||
|     } | ||||
| ).extend(cv.polling_component_schema("60s")) | ||||
| CONFIG_SCHEMA = ( | ||||
|     sensor.sensor_schema(ADC128S102Sensor) | ||||
|     .extend( | ||||
|         { | ||||
|             cv.GenerateID(CONF_ADC128S102_ID): cv.use_id(ADC128S102), | ||||
|             cv.Required(CONF_CHANNEL): cv.int_range(min=0, max=7), | ||||
|         } | ||||
|     ) | ||||
|     .extend(cv.polling_component_schema("60s")) | ||||
| ) | ||||
|  | ||||
|  | ||||
| async def to_code(config): | ||||
|   | ||||
| @@ -15,18 +15,24 @@ AnalogThresholdBinarySensor = analog_threshold_ns.class_( | ||||
| CONF_UPPER = "upper" | ||||
| CONF_LOWER = "lower" | ||||
|  | ||||
| CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend( | ||||
|     { | ||||
|         cv.GenerateID(): cv.declare_id(AnalogThresholdBinarySensor), | ||||
|         cv.Required(CONF_SENSOR_ID): cv.use_id(sensor.Sensor), | ||||
|         cv.Required(CONF_THRESHOLD): cv.Any( | ||||
|             cv.float_, | ||||
|             cv.Schema( | ||||
|                 {cv.Required(CONF_UPPER): cv.float_, cv.Required(CONF_LOWER): cv.float_} | ||||
| CONFIG_SCHEMA = ( | ||||
|     binary_sensor.binary_sensor_schema(AnalogThresholdBinarySensor) | ||||
|     .extend( | ||||
|         { | ||||
|             cv.Required(CONF_SENSOR_ID): cv.use_id(sensor.Sensor), | ||||
|             cv.Required(CONF_THRESHOLD): cv.Any( | ||||
|                 cv.float_, | ||||
|                 cv.Schema( | ||||
|                     { | ||||
|                         cv.Required(CONF_UPPER): cv.float_, | ||||
|                         cv.Required(CONF_LOWER): cv.float_, | ||||
|                     } | ||||
|                 ), | ||||
|             ), | ||||
|         ), | ||||
|     } | ||||
| ).extend(cv.COMPONENT_SCHEMA) | ||||
|         } | ||||
|     ) | ||||
|     .extend(cv.COMPONENT_SCHEMA) | ||||
| ) | ||||
|  | ||||
|  | ||||
| async def to_code(config): | ||||
|   | ||||
| @@ -829,7 +829,7 @@ message ListEntitiesClimateResponse { | ||||
|   repeated ClimateMode supported_modes = 7; | ||||
|   float visual_min_temperature = 8; | ||||
|   float visual_max_temperature = 9; | ||||
|   float visual_temperature_step = 10; | ||||
|   float visual_target_temperature_step = 10; | ||||
|   // for older peer versions - in new system this | ||||
|   // is if CLIMATE_PRESET_AWAY exists is supported_presets | ||||
|   bool legacy_supports_away = 11; | ||||
| @@ -842,6 +842,7 @@ message ListEntitiesClimateResponse { | ||||
|   bool disabled_by_default = 18; | ||||
|   string icon = 19; | ||||
|   EntityCategory entity_category = 20; | ||||
|   float visual_current_temperature_step = 21; | ||||
| } | ||||
| message ClimateStateResponse { | ||||
|   option (id) = 47; | ||||
| @@ -1338,3 +1339,23 @@ message BluetoothGATTNotifyResponse { | ||||
|   uint64 address = 1; | ||||
|   uint32 handle = 2; | ||||
| } | ||||
|  | ||||
| message BluetoothDevicePairingResponse { | ||||
|   option (id) = 85; | ||||
|   option (source) = SOURCE_SERVER; | ||||
|   option (ifdef) = "USE_BLUETOOTH_PROXY"; | ||||
|  | ||||
|   uint64 address = 1; | ||||
|   bool paired = 2; | ||||
|   int32 error = 3; | ||||
| } | ||||
|  | ||||
| message BluetoothDeviceUnpairingResponse { | ||||
|   option (id) = 86; | ||||
|   option (source) = SOURCE_SERVER; | ||||
|   option (ifdef) = "USE_BLUETOOTH_PROXY"; | ||||
|  | ||||
|   uint64 address = 1; | ||||
|   bool success = 2; | ||||
|   int32 error = 3; | ||||
| } | ||||
|   | ||||
| @@ -548,7 +548,9 @@ bool APIConnection::send_climate_info(climate::Climate *climate) { | ||||
|  | ||||
|   msg.visual_min_temperature = traits.get_visual_min_temperature(); | ||||
|   msg.visual_max_temperature = traits.get_visual_max_temperature(); | ||||
|   msg.visual_temperature_step = traits.get_visual_temperature_step(); | ||||
|   msg.visual_target_temperature_step = traits.get_visual_target_temperature_step(); | ||||
|   msg.visual_current_temperature_step = traits.get_visual_current_temperature_step(); | ||||
|  | ||||
|   msg.legacy_supports_away = traits.supports_preset(climate::CLIMATE_PRESET_AWAY); | ||||
|   msg.supports_action = traits.get_supports_action(); | ||||
|  | ||||
| @@ -951,7 +953,7 @@ DeviceInfoResponse APIConnection::device_info(const DeviceInfoRequest &msg) { | ||||
|   resp.webserver_port = USE_WEBSERVER_PORT; | ||||
| #endif | ||||
| #ifdef USE_BLUETOOTH_PROXY | ||||
|   resp.bluetooth_proxy_version = bluetooth_proxy::global_bluetooth_proxy->has_active() ? 3 : 1; | ||||
|   resp.bluetooth_proxy_version = bluetooth_proxy::global_bluetooth_proxy->has_active() ? 4 : 1; | ||||
| #endif | ||||
|   return resp; | ||||
| } | ||||
|   | ||||
| @@ -3451,7 +3451,11 @@ bool ListEntitiesClimateResponse::decode_32bit(uint32_t field_id, Proto32Bit val | ||||
|       return true; | ||||
|     } | ||||
|     case 10: { | ||||
|       this->visual_temperature_step = value.as_float(); | ||||
|       this->visual_target_temperature_step = value.as_float(); | ||||
|       return true; | ||||
|     } | ||||
|     case 21: { | ||||
|       this->visual_current_temperature_step = value.as_float(); | ||||
|       return true; | ||||
|     } | ||||
|     default: | ||||
| @@ -3470,7 +3474,7 @@ void ListEntitiesClimateResponse::encode(ProtoWriteBuffer buffer) const { | ||||
|   } | ||||
|   buffer.encode_float(8, this->visual_min_temperature); | ||||
|   buffer.encode_float(9, this->visual_max_temperature); | ||||
|   buffer.encode_float(10, this->visual_temperature_step); | ||||
|   buffer.encode_float(10, this->visual_target_temperature_step); | ||||
|   buffer.encode_bool(11, this->legacy_supports_away); | ||||
|   buffer.encode_bool(12, this->supports_action); | ||||
|   for (auto &it : this->supported_fan_modes) { | ||||
| @@ -3491,6 +3495,7 @@ void ListEntitiesClimateResponse::encode(ProtoWriteBuffer buffer) const { | ||||
|   buffer.encode_bool(18, this->disabled_by_default); | ||||
|   buffer.encode_string(19, this->icon); | ||||
|   buffer.encode_enum<enums::EntityCategory>(20, this->entity_category); | ||||
|   buffer.encode_float(21, this->visual_current_temperature_step); | ||||
| } | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
| void ListEntitiesClimateResponse::dump_to(std::string &out) const { | ||||
| @@ -3537,8 +3542,8 @@ void ListEntitiesClimateResponse::dump_to(std::string &out) const { | ||||
|   out.append(buffer); | ||||
|   out.append("\n"); | ||||
|  | ||||
|   out.append("  visual_temperature_step: "); | ||||
|   sprintf(buffer, "%g", this->visual_temperature_step); | ||||
|   out.append("  visual_target_temperature_step: "); | ||||
|   sprintf(buffer, "%g", this->visual_target_temperature_step); | ||||
|   out.append(buffer); | ||||
|   out.append("\n"); | ||||
|  | ||||
| @@ -3591,6 +3596,11 @@ void ListEntitiesClimateResponse::dump_to(std::string &out) const { | ||||
|   out.append("  entity_category: "); | ||||
|   out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category)); | ||||
|   out.append("\n"); | ||||
|  | ||||
|   out.append("  visual_current_temperature_step: "); | ||||
|   sprintf(buffer, "%g", this->visual_current_temperature_step); | ||||
|   out.append(buffer); | ||||
|   out.append("\n"); | ||||
|   out.append("}"); | ||||
| } | ||||
| #endif | ||||
| @@ -5964,6 +5974,92 @@ void BluetoothGATTNotifyResponse::dump_to(std::string &out) const { | ||||
|   out.append("}"); | ||||
| } | ||||
| #endif | ||||
| bool BluetoothDevicePairingResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { | ||||
|   switch (field_id) { | ||||
|     case 1: { | ||||
|       this->address = value.as_uint64(); | ||||
|       return true; | ||||
|     } | ||||
|     case 2: { | ||||
|       this->paired = value.as_bool(); | ||||
|       return true; | ||||
|     } | ||||
|     case 3: { | ||||
|       this->error = value.as_int32(); | ||||
|       return true; | ||||
|     } | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| } | ||||
| void BluetoothDevicePairingResponse::encode(ProtoWriteBuffer buffer) const { | ||||
|   buffer.encode_uint64(1, this->address); | ||||
|   buffer.encode_bool(2, this->paired); | ||||
|   buffer.encode_int32(3, this->error); | ||||
| } | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
| void BluetoothDevicePairingResponse::dump_to(std::string &out) const { | ||||
|   __attribute__((unused)) char buffer[64]; | ||||
|   out.append("BluetoothDevicePairingResponse {\n"); | ||||
|   out.append("  address: "); | ||||
|   sprintf(buffer, "%llu", this->address); | ||||
|   out.append(buffer); | ||||
|   out.append("\n"); | ||||
|  | ||||
|   out.append("  paired: "); | ||||
|   out.append(YESNO(this->paired)); | ||||
|   out.append("\n"); | ||||
|  | ||||
|   out.append("  error: "); | ||||
|   sprintf(buffer, "%d", this->error); | ||||
|   out.append(buffer); | ||||
|   out.append("\n"); | ||||
|   out.append("}"); | ||||
| } | ||||
| #endif | ||||
| bool BluetoothDeviceUnpairingResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { | ||||
|   switch (field_id) { | ||||
|     case 1: { | ||||
|       this->address = value.as_uint64(); | ||||
|       return true; | ||||
|     } | ||||
|     case 2: { | ||||
|       this->success = value.as_bool(); | ||||
|       return true; | ||||
|     } | ||||
|     case 3: { | ||||
|       this->error = value.as_int32(); | ||||
|       return true; | ||||
|     } | ||||
|     default: | ||||
|       return false; | ||||
|   } | ||||
| } | ||||
| void BluetoothDeviceUnpairingResponse::encode(ProtoWriteBuffer buffer) const { | ||||
|   buffer.encode_uint64(1, this->address); | ||||
|   buffer.encode_bool(2, this->success); | ||||
|   buffer.encode_int32(3, this->error); | ||||
| } | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
| void BluetoothDeviceUnpairingResponse::dump_to(std::string &out) const { | ||||
|   __attribute__((unused)) char buffer[64]; | ||||
|   out.append("BluetoothDeviceUnpairingResponse {\n"); | ||||
|   out.append("  address: "); | ||||
|   sprintf(buffer, "%llu", this->address); | ||||
|   out.append(buffer); | ||||
|   out.append("\n"); | ||||
|  | ||||
|   out.append("  success: "); | ||||
|   out.append(YESNO(this->success)); | ||||
|   out.append("\n"); | ||||
|  | ||||
|   out.append("  error: "); | ||||
|   sprintf(buffer, "%d", this->error); | ||||
|   out.append(buffer); | ||||
|   out.append("\n"); | ||||
|   out.append("}"); | ||||
| } | ||||
| #endif | ||||
|  | ||||
| }  // namespace api | ||||
| }  // namespace esphome | ||||
|   | ||||
| @@ -915,7 +915,7 @@ class ListEntitiesClimateResponse : public ProtoMessage { | ||||
|   std::vector<enums::ClimateMode> supported_modes{}; | ||||
|   float visual_min_temperature{0.0f}; | ||||
|   float visual_max_temperature{0.0f}; | ||||
|   float visual_temperature_step{0.0f}; | ||||
|   float visual_target_temperature_step{0.0f}; | ||||
|   bool legacy_supports_away{false}; | ||||
|   bool supports_action{false}; | ||||
|   std::vector<enums::ClimateFanMode> supported_fan_modes{}; | ||||
| @@ -926,6 +926,7 @@ class ListEntitiesClimateResponse : public ProtoMessage { | ||||
|   bool disabled_by_default{false}; | ||||
|   std::string icon{}; | ||||
|   enums::EntityCategory entity_category{}; | ||||
|   float visual_current_temperature_step{0.0f}; | ||||
|   void encode(ProtoWriteBuffer buffer) const override; | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|   void dump_to(std::string &out) const override; | ||||
| @@ -1527,6 +1528,32 @@ class BluetoothGATTNotifyResponse : public ProtoMessage { | ||||
|  protected: | ||||
|   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; | ||||
| }; | ||||
| class BluetoothDevicePairingResponse : public ProtoMessage { | ||||
|  public: | ||||
|   uint64_t address{0}; | ||||
|   bool paired{false}; | ||||
|   int32_t error{0}; | ||||
|   void encode(ProtoWriteBuffer buffer) const override; | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|   void dump_to(std::string &out) const override; | ||||
| #endif | ||||
|  | ||||
|  protected: | ||||
|   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; | ||||
| }; | ||||
| class BluetoothDeviceUnpairingResponse : public ProtoMessage { | ||||
|  public: | ||||
|   uint64_t address{0}; | ||||
|   bool success{false}; | ||||
|   int32_t error{0}; | ||||
|   void encode(ProtoWriteBuffer buffer) const override; | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|   void dump_to(std::string &out) const override; | ||||
| #endif | ||||
|  | ||||
|  protected: | ||||
|   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; | ||||
| }; | ||||
|  | ||||
| }  // namespace api | ||||
| }  // namespace esphome | ||||
|   | ||||
| @@ -425,6 +425,22 @@ bool APIServerConnectionBase::send_bluetooth_gatt_notify_response(const Bluetoot | ||||
|   return this->send_message_<BluetoothGATTNotifyResponse>(msg, 84); | ||||
| } | ||||
| #endif | ||||
| #ifdef USE_BLUETOOTH_PROXY | ||||
| bool APIServerConnectionBase::send_bluetooth_device_pairing_response(const BluetoothDevicePairingResponse &msg) { | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|   ESP_LOGVV(TAG, "send_bluetooth_device_pairing_response: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|   return this->send_message_<BluetoothDevicePairingResponse>(msg, 85); | ||||
| } | ||||
| #endif | ||||
| #ifdef USE_BLUETOOTH_PROXY | ||||
| bool APIServerConnectionBase::send_bluetooth_device_unpairing_response(const BluetoothDeviceUnpairingResponse &msg) { | ||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | ||||
|   ESP_LOGVV(TAG, "send_bluetooth_device_unpairing_response: %s", msg.dump().c_str()); | ||||
| #endif | ||||
|   return this->send_message_<BluetoothDeviceUnpairingResponse>(msg, 86); | ||||
| } | ||||
| #endif | ||||
| bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) { | ||||
|   switch (msg_type) { | ||||
|     case 1: { | ||||
|   | ||||
| @@ -209,6 +209,12 @@ class APIServerConnectionBase : public ProtoService { | ||||
| #endif | ||||
| #ifdef USE_BLUETOOTH_PROXY | ||||
|   bool send_bluetooth_gatt_notify_response(const BluetoothGATTNotifyResponse &msg); | ||||
| #endif | ||||
| #ifdef USE_BLUETOOTH_PROXY | ||||
|   bool send_bluetooth_device_pairing_response(const BluetoothDevicePairingResponse &msg); | ||||
| #endif | ||||
| #ifdef USE_BLUETOOTH_PROXY | ||||
|   bool send_bluetooth_device_unpairing_response(const BluetoothDeviceUnpairingResponse &msg); | ||||
| #endif | ||||
|  protected: | ||||
|   bool read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override; | ||||
|   | ||||
| @@ -309,6 +309,28 @@ void APIServer::send_bluetooth_device_connection(uint64_t address, bool connecte | ||||
|   } | ||||
| } | ||||
|  | ||||
| void APIServer::send_bluetooth_device_pairing(uint64_t address, bool paired, esp_err_t error) { | ||||
|   BluetoothDevicePairingResponse call; | ||||
|   call.address = address; | ||||
|   call.paired = paired; | ||||
|   call.error = error; | ||||
|  | ||||
|   for (auto &client : this->clients_) { | ||||
|     client->send_bluetooth_device_pairing_response(call); | ||||
|   } | ||||
| } | ||||
|  | ||||
| void APIServer::send_bluetooth_device_unpairing(uint64_t address, bool success, esp_err_t error) { | ||||
|   BluetoothDeviceUnpairingResponse call; | ||||
|   call.address = address; | ||||
|   call.success = success; | ||||
|   call.error = error; | ||||
|  | ||||
|   for (auto &client : this->clients_) { | ||||
|     client->send_bluetooth_device_unpairing_response(call); | ||||
|   } | ||||
| } | ||||
|  | ||||
| void APIServer::send_bluetooth_connections_free(uint8_t free, uint8_t limit) { | ||||
|   BluetoothConnectionsFreeResponse call; | ||||
|   call.free = free; | ||||
|   | ||||
| @@ -78,6 +78,8 @@ class APIServer : public Component, public Controller { | ||||
| #ifdef USE_BLUETOOTH_PROXY | ||||
|   void send_bluetooth_le_advertisement(const BluetoothLEAdvertisementResponse &call); | ||||
|   void send_bluetooth_device_connection(uint64_t address, bool connected, uint16_t mtu = 0, esp_err_t error = ESP_OK); | ||||
|   void send_bluetooth_device_pairing(uint64_t address, bool paired, esp_err_t error = ESP_OK); | ||||
|   void send_bluetooth_device_unpairing(uint64_t address, bool success, esp_err_t error = ESP_OK); | ||||
|   void send_bluetooth_connections_free(uint8_t free, uint8_t limit); | ||||
|   void send_bluetooth_gatt_read_response(const BluetoothGATTReadResponse &call); | ||||
|   void send_bluetooth_gatt_write_response(const BluetoothGATTWriteResponse &call); | ||||
|   | ||||
							
								
								
									
										0
									
								
								esphome/components/as7341/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								esphome/components/as7341/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										271
									
								
								esphome/components/as7341/as7341.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										271
									
								
								esphome/components/as7341/as7341.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,271 @@ | ||||
| #include "as7341.h" | ||||
| #include "esphome/core/log.h" | ||||
| #include "esphome/core/hal.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace as7341 { | ||||
|  | ||||
| static const char *const TAG = "as7341"; | ||||
|  | ||||
| void AS7341Component::setup() { | ||||
|   ESP_LOGCONFIG(TAG, "Setting up AS7341..."); | ||||
|   LOG_I2C_DEVICE(this); | ||||
|  | ||||
|   // Verify device ID | ||||
|   uint8_t id; | ||||
|   this->read_byte(AS7341_ID, &id); | ||||
|   ESP_LOGCONFIG(TAG, "  Read ID: 0x%X", id); | ||||
|   if ((id & 0xFC) != (AS7341_CHIP_ID << 2)) { | ||||
|     this->mark_failed(); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   // Power on (enter IDLE state) | ||||
|   if (!this->enable_power(true)) { | ||||
|     ESP_LOGE(TAG, "  Power on failed!"); | ||||
|     this->mark_failed(); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   // Set configuration | ||||
|   this->write_byte(AS7341_CONFIG, 0x00); | ||||
|   this->setup_atime(this->atime_); | ||||
|   this->setup_astep(this->astep_); | ||||
|   this->setup_gain(this->gain_); | ||||
| } | ||||
|  | ||||
| void AS7341Component::dump_config() { | ||||
|   ESP_LOGCONFIG(TAG, "AS7341:"); | ||||
|   LOG_I2C_DEVICE(this); | ||||
|   if (this->is_failed()) { | ||||
|     ESP_LOGE(TAG, "Communication with AS7341 failed!"); | ||||
|   } | ||||
|   LOG_UPDATE_INTERVAL(this); | ||||
|   ESP_LOGCONFIG(TAG, "  Gain: %u", get_gain()); | ||||
|   ESP_LOGCONFIG(TAG, "  ATIME: %u", get_atime()); | ||||
|   ESP_LOGCONFIG(TAG, "  ASTEP: %u", get_astep()); | ||||
|  | ||||
|   LOG_SENSOR("  ", "F1", this->f1_); | ||||
|   LOG_SENSOR("  ", "F2", this->f2_); | ||||
|   LOG_SENSOR("  ", "F3", this->f3_); | ||||
|   LOG_SENSOR("  ", "F4", this->f4_); | ||||
|   LOG_SENSOR("  ", "F5", this->f5_); | ||||
|   LOG_SENSOR("  ", "F6", this->f6_); | ||||
|   LOG_SENSOR("  ", "F7", this->f7_); | ||||
|   LOG_SENSOR("  ", "F8", this->f8_); | ||||
|   LOG_SENSOR("  ", "Clear", this->clear_); | ||||
|   LOG_SENSOR("  ", "NIR", this->nir_); | ||||
| } | ||||
|  | ||||
| float AS7341Component::get_setup_priority() const { return setup_priority::DATA; } | ||||
|  | ||||
| void AS7341Component::update() { | ||||
|   this->read_channels(this->channel_readings_); | ||||
|  | ||||
|   if (this->f1_ != nullptr) { | ||||
|     this->f1_->publish_state(this->channel_readings_[0]); | ||||
|   } | ||||
|   if (this->f2_ != nullptr) { | ||||
|     this->f2_->publish_state(this->channel_readings_[1]); | ||||
|   } | ||||
|   if (this->f3_ != nullptr) { | ||||
|     this->f3_->publish_state(this->channel_readings_[2]); | ||||
|   } | ||||
|   if (this->f4_ != nullptr) { | ||||
|     this->f4_->publish_state(this->channel_readings_[3]); | ||||
|   } | ||||
|   if (this->f5_ != nullptr) { | ||||
|     this->f5_->publish_state(this->channel_readings_[6]); | ||||
|   } | ||||
|   if (this->f6_ != nullptr) { | ||||
|     this->f6_->publish_state(this->channel_readings_[7]); | ||||
|   } | ||||
|   if (this->f7_ != nullptr) { | ||||
|     this->f7_->publish_state(this->channel_readings_[8]); | ||||
|   } | ||||
|   if (this->f8_ != nullptr) { | ||||
|     this->f8_->publish_state(this->channel_readings_[9]); | ||||
|   } | ||||
|   if (this->clear_ != nullptr) { | ||||
|     this->clear_->publish_state(this->channel_readings_[10]); | ||||
|   } | ||||
|   if (this->nir_ != nullptr) { | ||||
|     this->nir_->publish_state(this->channel_readings_[11]); | ||||
|   } | ||||
| } | ||||
|  | ||||
| AS7341Gain AS7341Component::get_gain() { | ||||
|   uint8_t data; | ||||
|   this->read_byte(AS7341_CFG1, &data); | ||||
|   return (AS7341Gain) data; | ||||
| } | ||||
|  | ||||
| uint8_t AS7341Component::get_atime() { | ||||
|   uint8_t data; | ||||
|   this->read_byte(AS7341_ATIME, &data); | ||||
|   return data; | ||||
| } | ||||
|  | ||||
| uint16_t AS7341Component::get_astep() { | ||||
|   uint16_t data; | ||||
|   this->read_byte_16(AS7341_ASTEP, &data); | ||||
|   return this->swap_bytes(data); | ||||
| } | ||||
|  | ||||
| bool AS7341Component::setup_gain(AS7341Gain gain) { return this->write_byte(AS7341_CFG1, gain); } | ||||
|  | ||||
| bool AS7341Component::setup_atime(uint8_t atime) { return this->write_byte(AS7341_ATIME, atime); } | ||||
|  | ||||
| bool AS7341Component::setup_astep(uint16_t astep) { return this->write_byte_16(AS7341_ASTEP, swap_bytes(astep)); } | ||||
|  | ||||
| bool AS7341Component::read_channels(uint16_t *data) { | ||||
|   this->set_smux_low_channels(true); | ||||
|   this->enable_spectral_measurement(true); | ||||
|   this->wait_for_data(); | ||||
|   bool low_success = this->read_bytes_16(AS7341_CH0_DATA_L, data, 6); | ||||
|  | ||||
|   this->set_smux_low_channels(false); | ||||
|   this->enable_spectral_measurement(true); | ||||
|   this->wait_for_data(); | ||||
|   bool high_sucess = this->read_bytes_16(AS7341_CH0_DATA_L, &data[6], 6); | ||||
|  | ||||
|   return low_success && high_sucess; | ||||
| } | ||||
|  | ||||
| void AS7341Component::set_smux_low_channels(bool enable) { | ||||
|   this->enable_spectral_measurement(false); | ||||
|   this->set_smux_command(AS7341_SMUX_CMD_WRITE); | ||||
|  | ||||
|   if (enable) { | ||||
|     this->configure_smux_low_channels(); | ||||
|  | ||||
|   } else { | ||||
|     this->configure_smux_high_channels(); | ||||
|   } | ||||
|   this->enable_smux(); | ||||
| } | ||||
|  | ||||
| bool AS7341Component::set_smux_command(AS7341SmuxCommand command) { | ||||
|   uint8_t data = command << 3;  // Write to bits 4:3 of the register | ||||
|   return this->write_byte(AS7341_CFG6, data); | ||||
| } | ||||
|  | ||||
| void AS7341Component::configure_smux_low_channels() { | ||||
|   // SMUX Config for F1,F2,F3,F4,NIR,Clear | ||||
|   this->write_byte(0x00, 0x30);  // F3 left set to ADC2 | ||||
|   this->write_byte(0x01, 0x01);  // F1 left set to ADC0 | ||||
|   this->write_byte(0x02, 0x00);  // Reserved or disabled | ||||
|   this->write_byte(0x03, 0x00);  // F8 left disabled | ||||
|   this->write_byte(0x04, 0x00);  // F6 left disabled | ||||
|   this->write_byte(0x05, 0x42);  // F4 left connected to ADC3/f2 left connected to ADC1 | ||||
|   this->write_byte(0x06, 0x00);  // F5 left disbled | ||||
|   this->write_byte(0x07, 0x00);  // F7 left disbled | ||||
|   this->write_byte(0x08, 0x50);  // CLEAR connected to ADC4 | ||||
|   this->write_byte(0x09, 0x00);  // F5 right disabled | ||||
|   this->write_byte(0x0A, 0x00);  // F7 right disabled | ||||
|   this->write_byte(0x0B, 0x00);  // Reserved or disabled | ||||
|   this->write_byte(0x0C, 0x20);  // F2 right connected to ADC1 | ||||
|   this->write_byte(0x0D, 0x04);  // F4 right connected to ADC3 | ||||
|   this->write_byte(0x0E, 0x00);  // F6/F8 right disabled | ||||
|   this->write_byte(0x0F, 0x30);  // F3 right connected to AD2 | ||||
|   this->write_byte(0x10, 0x01);  // F1 right connected to AD0 | ||||
|   this->write_byte(0x11, 0x50);  // CLEAR right connected to AD4 | ||||
|   this->write_byte(0x12, 0x00);  // Reserved or disabled | ||||
|   this->write_byte(0x13, 0x06);  // NIR connected to ADC5 | ||||
| } | ||||
|  | ||||
| void AS7341Component::configure_smux_high_channels() { | ||||
|   // SMUX Config for F5,F6,F7,F8,NIR,Clear | ||||
|   this->write_byte(0x00, 0x00);  // F3 left disable | ||||
|   this->write_byte(0x01, 0x00);  // F1 left disable | ||||
|   this->write_byte(0x02, 0x00);  // reserved/disable | ||||
|   this->write_byte(0x03, 0x40);  // F8 left connected to ADC3 | ||||
|   this->write_byte(0x04, 0x02);  // F6 left connected to ADC1 | ||||
|   this->write_byte(0x05, 0x00);  // F4/ F2 disabled | ||||
|   this->write_byte(0x06, 0x10);  // F5 left connected to ADC0 | ||||
|   this->write_byte(0x07, 0x03);  // F7 left connected to ADC2 | ||||
|   this->write_byte(0x08, 0x50);  // CLEAR Connected to ADC4 | ||||
|   this->write_byte(0x09, 0x10);  // F5 right connected to ADC0 | ||||
|   this->write_byte(0x0A, 0x03);  // F7 right connected to ADC2 | ||||
|   this->write_byte(0x0B, 0x00);  // Reserved or disabled | ||||
|   this->write_byte(0x0C, 0x00);  // F2 right disabled | ||||
|   this->write_byte(0x0D, 0x00);  // F4 right disabled | ||||
|   this->write_byte(0x0E, 0x24);  // F8 right connected to ADC2/ F6 right connected to ADC1 | ||||
|   this->write_byte(0x0F, 0x00);  // F3 right disabled | ||||
|   this->write_byte(0x10, 0x00);  // F1 right disabled | ||||
|   this->write_byte(0x11, 0x50);  // CLEAR right connected to AD4 | ||||
|   this->write_byte(0x12, 0x00);  // Reserved or disabled | ||||
|   this->write_byte(0x13, 0x06);  // NIR connected to ADC5 | ||||
| } | ||||
|  | ||||
| bool AS7341Component::enable_smux() { | ||||
|   this->set_register_bit(AS7341_ENABLE, 4); | ||||
|  | ||||
|   uint16_t timeout = 1000; | ||||
|   for (uint16_t time = 0; time < timeout; time++) { | ||||
|     // The SMUXEN bit is cleared once the SMUX operation is finished | ||||
|     bool smuxen = this->read_register_bit(AS7341_ENABLE, 4); | ||||
|     if (!smuxen) { | ||||
|       return true; | ||||
|     } | ||||
|  | ||||
|     delay(1); | ||||
|   } | ||||
|  | ||||
|   return false; | ||||
| } | ||||
|  | ||||
| bool AS7341Component::wait_for_data() { | ||||
|   uint16_t timeout = 1000; | ||||
|   for (uint16_t time = 0; time < timeout; time++) { | ||||
|     if (this->is_data_ready()) { | ||||
|       return true; | ||||
|     } | ||||
|  | ||||
|     delay(1); | ||||
|   } | ||||
|  | ||||
|   return false; | ||||
| } | ||||
|  | ||||
| bool AS7341Component::is_data_ready() { return this->read_register_bit(AS7341_STATUS2, 6); } | ||||
|  | ||||
| bool AS7341Component::enable_power(bool enable) { return this->write_register_bit(AS7341_ENABLE, enable, 0); } | ||||
|  | ||||
| bool AS7341Component::enable_spectral_measurement(bool enable) { | ||||
|   return this->write_register_bit(AS7341_ENABLE, enable, 1); | ||||
| } | ||||
|  | ||||
| bool AS7341Component::read_register_bit(uint8_t address, uint8_t bit_position) { | ||||
|   uint8_t data; | ||||
|   this->read_byte(address, &data); | ||||
|   bool bit = (data & (1 << bit_position)) > 0; | ||||
|   return bit; | ||||
| } | ||||
|  | ||||
| bool AS7341Component::write_register_bit(uint8_t address, bool value, uint8_t bit_position) { | ||||
|   if (value) { | ||||
|     return this->set_register_bit(address, bit_position); | ||||
|   } | ||||
|  | ||||
|   return this->clear_register_bit(address, bit_position); | ||||
| } | ||||
|  | ||||
| bool AS7341Component::set_register_bit(uint8_t address, uint8_t bit_position) { | ||||
|   uint8_t data; | ||||
|   this->read_byte(address, &data); | ||||
|   data |= (1 << bit_position); | ||||
|   return this->write_byte(address, data); | ||||
| } | ||||
|  | ||||
| bool AS7341Component::clear_register_bit(uint8_t address, uint8_t bit_position) { | ||||
|   uint8_t data; | ||||
|   this->read_byte(address, &data); | ||||
|   data &= ~(1 << bit_position); | ||||
|   return this->write_byte(address, data); | ||||
| } | ||||
|  | ||||
| uint16_t AS7341Component::swap_bytes(uint16_t data) { return (data >> 8) | (data << 8); } | ||||
|  | ||||
| }  // namespace as7341 | ||||
| }  // namespace esphome | ||||
							
								
								
									
										144
									
								
								esphome/components/as7341/as7341.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										144
									
								
								esphome/components/as7341/as7341.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,144 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/components/sensor/sensor.h" | ||||
| #include "esphome/components/i2c/i2c.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace as7341 { | ||||
|  | ||||
| static const uint8_t AS7341_CHIP_ID = 0X09; | ||||
|  | ||||
| static const uint8_t AS7341_CONFIG = 0x70; | ||||
| static const uint8_t AS7341_LED = 0x74; | ||||
|  | ||||
| static const uint8_t AS7341_ENABLE = 0x80; | ||||
| static const uint8_t AS7341_ATIME = 0x81; | ||||
|  | ||||
| static const uint8_t AS7341_WTIME = 0x83; | ||||
|  | ||||
| static const uint8_t AS7341_AUXID = 0x90; | ||||
| static const uint8_t AS7341_REVID = 0x91; | ||||
| static const uint8_t AS7341_ID = 0x92; | ||||
| static const uint8_t AS7341_STATUS = 0x93; | ||||
|  | ||||
| static const uint8_t AS7341_CH0_DATA_L = 0x95; | ||||
| static const uint8_t AS7341_CH0_DATA_H = 0x96; | ||||
| static const uint8_t AS7341_CH1_DATA_L = 0x97; | ||||
| static const uint8_t AS7341_CH1_DATA_H = 0x98; | ||||
| static const uint8_t AS7341_CH2_DATA_L = 0x99; | ||||
| static const uint8_t AS7341_CH2_DATA_H = 0x9A; | ||||
| static const uint8_t AS7341_CH3_DATA_L = 0x9B; | ||||
| static const uint8_t AS7341_CH3_DATA_H = 0x9C; | ||||
| static const uint8_t AS7341_CH4_DATA_L = 0x9D; | ||||
| static const uint8_t AS7341_CH4_DATA_H = 0x9E; | ||||
| static const uint8_t AS7341_CH5_DATA_L = 0x9F; | ||||
| static const uint8_t AS7341_CH5_DATA_H = 0xA0; | ||||
|  | ||||
| static const uint8_t AS7341_STATUS2 = 0xA3; | ||||
|  | ||||
| static const uint8_t AS7341_CFG1 = 0xAA;  ///< Controls ADC Gain | ||||
|  | ||||
| static const uint8_t AS7341_CFG6 = 0xAF;  // Stores SMUX command | ||||
| static const uint8_t AS7341_CFG9 = 0xB2;  // Config for system interrupts (SMUX, Flicker detection) | ||||
|  | ||||
| static const uint8_t AS7341_ASTEP = 0xCA;      // LSB | ||||
| static const uint8_t AS7341_ASTEP_MSB = 0xCB;  // MSB | ||||
|  | ||||
| enum AS7341AdcChannel { | ||||
|   AS7341_ADC_CHANNEL_0, | ||||
|   AS7341_ADC_CHANNEL_1, | ||||
|   AS7341_ADC_CHANNEL_2, | ||||
|   AS7341_ADC_CHANNEL_3, | ||||
|   AS7341_ADC_CHANNEL_4, | ||||
|   AS7341_ADC_CHANNEL_5, | ||||
| }; | ||||
|  | ||||
| enum AS7341SmuxCommand { | ||||
|   AS7341_SMUX_CMD_ROM_RESET,  ///< ROM code initialization of SMUX | ||||
|   AS7341_SMUX_CMD_READ,       ///< Read SMUX configuration to RAM from SMUX chain | ||||
|   AS7341_SMUX_CMD_WRITE,      ///< Write SMUX configuration from RAM to SMUX chain | ||||
| }; | ||||
|  | ||||
| enum AS7341Gain { | ||||
|   AS7341_GAIN_0_5X, | ||||
|   AS7341_GAIN_1X, | ||||
|   AS7341_GAIN_2X, | ||||
|   AS7341_GAIN_4X, | ||||
|   AS7341_GAIN_8X, | ||||
|   AS7341_GAIN_16X, | ||||
|   AS7341_GAIN_32X, | ||||
|   AS7341_GAIN_64X, | ||||
|   AS7341_GAIN_128X, | ||||
|   AS7341_GAIN_256X, | ||||
|   AS7341_GAIN_512X, | ||||
| }; | ||||
|  | ||||
| class AS7341Component : public PollingComponent, public i2c::I2CDevice { | ||||
|  public: | ||||
|   void setup() override; | ||||
|   void dump_config() override; | ||||
|   float get_setup_priority() const override; | ||||
|   void update() override; | ||||
|  | ||||
|   void set_f1_sensor(sensor::Sensor *f1_sensor) { this->f1_ = f1_sensor; } | ||||
|   void set_f2_sensor(sensor::Sensor *f2_sensor) { f2_ = f2_sensor; } | ||||
|   void set_f3_sensor(sensor::Sensor *f3_sensor) { f3_ = f3_sensor; } | ||||
|   void set_f4_sensor(sensor::Sensor *f4_sensor) { f4_ = f4_sensor; } | ||||
|   void set_f5_sensor(sensor::Sensor *f5_sensor) { f5_ = f5_sensor; } | ||||
|   void set_f6_sensor(sensor::Sensor *f6_sensor) { f6_ = f6_sensor; } | ||||
|   void set_f7_sensor(sensor::Sensor *f7_sensor) { f7_ = f7_sensor; } | ||||
|   void set_f8_sensor(sensor::Sensor *f8_sensor) { f8_ = f8_sensor; } | ||||
|   void set_clear_sensor(sensor::Sensor *clear_sensor) { clear_ = clear_sensor; } | ||||
|   void set_nir_sensor(sensor::Sensor *nir_sensor) { nir_ = nir_sensor; } | ||||
|  | ||||
|   void set_gain(AS7341Gain gain) { gain_ = gain; } | ||||
|   void set_atime(uint8_t atime) { atime_ = atime; } | ||||
|   void set_astep(uint16_t astep) { astep_ = astep; } | ||||
|  | ||||
|   AS7341Gain get_gain(); | ||||
|   uint8_t get_atime(); | ||||
|   uint16_t get_astep(); | ||||
|   bool setup_gain(AS7341Gain gain); | ||||
|   bool setup_atime(uint8_t atime); | ||||
|   bool setup_astep(uint16_t astep); | ||||
|  | ||||
|   uint16_t read_channel(AS7341AdcChannel channel); | ||||
|   bool read_channels(uint16_t *data); | ||||
|   void set_smux_low_channels(bool enable); | ||||
|   bool set_smux_command(AS7341SmuxCommand command); | ||||
|   void configure_smux_low_channels(); | ||||
|   void configure_smux_high_channels(); | ||||
|   bool enable_smux(); | ||||
|  | ||||
|   bool wait_for_data(); | ||||
|   bool is_data_ready(); | ||||
|   bool enable_power(bool enable); | ||||
|   bool enable_spectral_measurement(bool enable); | ||||
|  | ||||
|   bool read_register_bit(uint8_t address, uint8_t bit_position); | ||||
|   bool write_register_bit(uint8_t address, bool value, uint8_t bit_position); | ||||
|   bool set_register_bit(uint8_t address, uint8_t bit_position); | ||||
|   bool clear_register_bit(uint8_t address, uint8_t bit_position); | ||||
|   uint16_t swap_bytes(uint16_t data); | ||||
|  | ||||
|  protected: | ||||
|   sensor::Sensor *f1_{nullptr}; | ||||
|   sensor::Sensor *f2_{nullptr}; | ||||
|   sensor::Sensor *f3_{nullptr}; | ||||
|   sensor::Sensor *f4_{nullptr}; | ||||
|   sensor::Sensor *f5_{nullptr}; | ||||
|   sensor::Sensor *f6_{nullptr}; | ||||
|   sensor::Sensor *f7_{nullptr}; | ||||
|   sensor::Sensor *f8_{nullptr}; | ||||
|   sensor::Sensor *clear_{nullptr}; | ||||
|   sensor::Sensor *nir_{nullptr}; | ||||
|  | ||||
|   uint16_t astep_; | ||||
|   AS7341Gain gain_; | ||||
|   uint8_t atime_; | ||||
|   uint16_t channel_readings_[12]; | ||||
| }; | ||||
|  | ||||
| }  // namespace as7341 | ||||
| }  // namespace esphome | ||||
							
								
								
									
										112
									
								
								esphome/components/as7341/sensor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										112
									
								
								esphome/components/as7341/sensor.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,112 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome.components import i2c, sensor | ||||
| from esphome.const import ( | ||||
|     CONF_GAIN, | ||||
|     CONF_ID, | ||||
|     DEVICE_CLASS_ILLUMINANCE, | ||||
|     ICON_BRIGHTNESS_5, | ||||
|     STATE_CLASS_MEASUREMENT, | ||||
| ) | ||||
|  | ||||
|  | ||||
| CODEOWNERS = ["@mrgnr"] | ||||
| DEPENDENCIES = ["i2c"] | ||||
|  | ||||
| as7341_ns = cg.esphome_ns.namespace("as7341") | ||||
|  | ||||
| AS7341Component = as7341_ns.class_( | ||||
|     "AS7341Component", cg.PollingComponent, i2c.I2CDevice | ||||
| ) | ||||
|  | ||||
| CONF_ATIME = "atime" | ||||
| CONF_ASTEP = "astep" | ||||
|  | ||||
| CONF_F1 = "f1" | ||||
| CONF_F2 = "f2" | ||||
| CONF_F3 = "f3" | ||||
| CONF_F4 = "f4" | ||||
| CONF_F5 = "f5" | ||||
| CONF_F6 = "f6" | ||||
| CONF_F7 = "f7" | ||||
| CONF_F8 = "f8" | ||||
| CONF_CLEAR = "clear" | ||||
| CONF_NIR = "nir" | ||||
|  | ||||
| UNIT_COUNTS = "#" | ||||
|  | ||||
| AS7341_GAIN = as7341_ns.enum("AS7341Gain") | ||||
| GAIN_OPTIONS = { | ||||
|     "X0.5": AS7341_GAIN.AS7341_GAIN_0_5X, | ||||
|     "X1": AS7341_GAIN.AS7341_GAIN_1X, | ||||
|     "X2": AS7341_GAIN.AS7341_GAIN_2X, | ||||
|     "X4": AS7341_GAIN.AS7341_GAIN_4X, | ||||
|     "X8": AS7341_GAIN.AS7341_GAIN_8X, | ||||
|     "X16": AS7341_GAIN.AS7341_GAIN_16X, | ||||
|     "X32": AS7341_GAIN.AS7341_GAIN_32X, | ||||
|     "X64": AS7341_GAIN.AS7341_GAIN_64X, | ||||
|     "X128": AS7341_GAIN.AS7341_GAIN_128X, | ||||
|     "X256": AS7341_GAIN.AS7341_GAIN_256X, | ||||
|     "X512": AS7341_GAIN.AS7341_GAIN_512X, | ||||
| } | ||||
|  | ||||
|  | ||||
| SENSOR_SCHEMA = sensor.sensor_schema( | ||||
|     unit_of_measurement=UNIT_COUNTS, | ||||
|     icon=ICON_BRIGHTNESS_5, | ||||
|     accuracy_decimals=0, | ||||
|     device_class=DEVICE_CLASS_ILLUMINANCE, | ||||
|     state_class=STATE_CLASS_MEASUREMENT, | ||||
| ) | ||||
|  | ||||
|  | ||||
| CONFIG_SCHEMA = ( | ||||
|     cv.Schema( | ||||
|         { | ||||
|             cv.GenerateID(): cv.declare_id(AS7341Component), | ||||
|             cv.Optional(CONF_F1): SENSOR_SCHEMA, | ||||
|             cv.Optional(CONF_F2): SENSOR_SCHEMA, | ||||
|             cv.Optional(CONF_F3): SENSOR_SCHEMA, | ||||
|             cv.Optional(CONF_F4): SENSOR_SCHEMA, | ||||
|             cv.Optional(CONF_F5): SENSOR_SCHEMA, | ||||
|             cv.Optional(CONF_F6): SENSOR_SCHEMA, | ||||
|             cv.Optional(CONF_F7): SENSOR_SCHEMA, | ||||
|             cv.Optional(CONF_F8): SENSOR_SCHEMA, | ||||
|             cv.Optional(CONF_CLEAR): SENSOR_SCHEMA, | ||||
|             cv.Optional(CONF_NIR): SENSOR_SCHEMA, | ||||
|             cv.Optional(CONF_GAIN, default="X8"): cv.enum(GAIN_OPTIONS), | ||||
|             cv.Optional(CONF_ATIME, default=29): cv.int_range(min=0, max=255), | ||||
|             cv.Optional(CONF_ASTEP, default=599): cv.int_range(min=0, max=65534), | ||||
|         } | ||||
|     ) | ||||
|     .extend(cv.polling_component_schema("60s")) | ||||
|     .extend(i2c.i2c_device_schema(0x39)) | ||||
| ) | ||||
|  | ||||
| SENSORS = { | ||||
|     CONF_F1: "set_f1_sensor", | ||||
|     CONF_F2: "set_f2_sensor", | ||||
|     CONF_F3: "set_f3_sensor", | ||||
|     CONF_F4: "set_f4_sensor", | ||||
|     CONF_F5: "set_f5_sensor", | ||||
|     CONF_F6: "set_f6_sensor", | ||||
|     CONF_F7: "set_f7_sensor", | ||||
|     CONF_F8: "set_f8_sensor", | ||||
|     CONF_CLEAR: "set_clear_sensor", | ||||
|     CONF_NIR: "set_nir_sensor", | ||||
| } | ||||
|  | ||||
|  | ||||
| async def to_code(config): | ||||
|     var = cg.new_Pvariable(config[CONF_ID]) | ||||
|     await cg.register_component(var, config) | ||||
|     await i2c.register_i2c_device(var, config) | ||||
|  | ||||
|     cg.add(var.set_gain(config[CONF_GAIN])) | ||||
|     cg.add(var.set_atime(config[CONF_ATIME])) | ||||
|     cg.add(var.set_astep(config[CONF_ASTEP])) | ||||
|  | ||||
|     for conf_id, set_sensor_func in SENSORS.items(): | ||||
|         if conf_id in config: | ||||
|             sens = await sensor.new_sensor(config[conf_id]) | ||||
|             cg.add(getattr(var, set_sensor_func)(sens)) | ||||
| @@ -80,7 +80,7 @@ async def to_code(config): | ||||
|  | ||||
|     cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex)) | ||||
|  | ||||
|     for (config_key, setter) in [ | ||||
|     for config_key, setter in [ | ||||
|         (CONF_TEMPERATURE, var.set_temperature), | ||||
|         (CONF_HUMIDITY, var.set_humidity), | ||||
|         (CONF_BATTERY_VOLTAGE, var.set_battery_voltage), | ||||
|   | ||||
| @@ -1,3 +1,5 @@ | ||||
| #ifdef USE_ESP32 | ||||
|  | ||||
| #include "bedjet_hub.h" | ||||
| #include "bedjet_child.h" | ||||
| #include "bedjet_const.h" | ||||
| @@ -541,3 +543,5 @@ void BedJetHub::register_child(BedJetClient *obj) { | ||||
|  | ||||
| }  // namespace bedjet | ||||
| }  // namespace esphome | ||||
|  | ||||
| #endif | ||||
|   | ||||
| @@ -1,4 +1,5 @@ | ||||
| #pragma once | ||||
| #ifdef USE_ESP32 | ||||
|  | ||||
| #include "esphome/components/ble_client/ble_client.h" | ||||
| #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" | ||||
| @@ -14,8 +15,6 @@ | ||||
| #include "esphome/components/time/real_time_clock.h" | ||||
| #endif | ||||
|  | ||||
| #ifdef USE_ESP32 | ||||
|  | ||||
| #include <esp_gattc_api.h> | ||||
|  | ||||
| namespace esphome { | ||||
|   | ||||
| @@ -27,13 +27,13 @@ from esphome.const import ( | ||||
|     CONF_TIMING, | ||||
|     CONF_TRIGGER_ID, | ||||
|     CONF_MQTT_ID, | ||||
|     DEVICE_CLASS_EMPTY, | ||||
|     DEVICE_CLASS_BATTERY, | ||||
|     DEVICE_CLASS_BATTERY_CHARGING, | ||||
|     DEVICE_CLASS_CARBON_MONOXIDE, | ||||
|     DEVICE_CLASS_COLD, | ||||
|     DEVICE_CLASS_CONNECTIVITY, | ||||
|     DEVICE_CLASS_DOOR, | ||||
|     DEVICE_CLASS_EMPTY, | ||||
|     DEVICE_CLASS_GARAGE_DOOR, | ||||
|     DEVICE_CLASS_GAS, | ||||
|     DEVICE_CLASS_HEAT, | ||||
| @@ -62,13 +62,13 @@ from esphome.util import Registry | ||||
|  | ||||
| CODEOWNERS = ["@esphome/core"] | ||||
| DEVICE_CLASSES = [ | ||||
|     DEVICE_CLASS_EMPTY, | ||||
|     DEVICE_CLASS_BATTERY, | ||||
|     DEVICE_CLASS_BATTERY_CHARGING, | ||||
|     DEVICE_CLASS_CARBON_MONOXIDE, | ||||
|     DEVICE_CLASS_COLD, | ||||
|     DEVICE_CLASS_CONNECTIVITY, | ||||
|     DEVICE_CLASS_DOOR, | ||||
|     DEVICE_CLASS_EMPTY, | ||||
|     DEVICE_CLASS_GARAGE_DOOR, | ||||
|     DEVICE_CLASS_GAS, | ||||
|     DEVICE_CLASS_HEAT, | ||||
| @@ -393,28 +393,21 @@ def binary_sensor_schema( | ||||
|     entity_category: str = _UNDEF, | ||||
|     device_class: str = _UNDEF, | ||||
| ) -> cv.Schema: | ||||
|     schema = BINARY_SENSOR_SCHEMA | ||||
|     schema = {} | ||||
|  | ||||
|     if class_ is not _UNDEF: | ||||
|         schema = schema.extend({cv.GenerateID(): cv.declare_id(class_)}) | ||||
|     if icon is not _UNDEF: | ||||
|         schema = schema.extend({cv.Optional(CONF_ICON, default=icon): cv.icon}) | ||||
|     if entity_category is not _UNDEF: | ||||
|         schema = schema.extend( | ||||
|             { | ||||
|                 cv.Optional( | ||||
|                     CONF_ENTITY_CATEGORY, default=entity_category | ||||
|                 ): cv.entity_category | ||||
|             } | ||||
|         ) | ||||
|     if device_class is not _UNDEF: | ||||
|         schema = schema.extend( | ||||
|             { | ||||
|                 cv.Optional( | ||||
|                     CONF_DEVICE_CLASS, default=device_class | ||||
|                 ): validate_device_class | ||||
|             } | ||||
|         ) | ||||
|     return schema | ||||
|         # Not cv.optional | ||||
|         schema[cv.GenerateID()] = cv.declare_id(class_) | ||||
|  | ||||
|     for key, default, validator in [ | ||||
|         (CONF_ICON, icon, cv.icon), | ||||
|         (CONF_ENTITY_CATEGORY, entity_category, cv.entity_category), | ||||
|         (CONF_DEVICE_CLASS, device_class, validate_device_class), | ||||
|     ]: | ||||
|         if default is not _UNDEF: | ||||
|             schema[cv.Optional(key, default=default)] = validator | ||||
|  | ||||
|     return BINARY_SENSOR_SCHEMA.extend(schema) | ||||
|  | ||||
|  | ||||
| async def setup_binary_sensor_core_(var, config): | ||||
|   | ||||
| @@ -19,6 +19,15 @@ namespace binary_sensor { | ||||
|     } \ | ||||
|   } | ||||
|  | ||||
| #define SUB_BINARY_SENSOR(name) \ | ||||
|  protected: \ | ||||
|   binary_sensor::BinarySensor *name##_binary_sensor_{nullptr}; \ | ||||
| \ | ||||
|  public: \ | ||||
|   void set_##name##_binary_sensor(binary_sensor::BinarySensor *binary_sensor) { \ | ||||
|     this->name##_binary_sensor_ = binary_sensor; \ | ||||
|   } | ||||
|  | ||||
| /** Base class for all binary_sensor-type classes. | ||||
|  * | ||||
|  * This class includes a callback that components such as MQTT can subscribe to for state changes. | ||||
|   | ||||
| @@ -9,6 +9,7 @@ from esphome.const import ( | ||||
|     DEVICE_CLASS_POWER, | ||||
|     DEVICE_CLASS_VOLTAGE, | ||||
|     STATE_CLASS_MEASUREMENT, | ||||
|     STATE_CLASS_TOTAL_INCREASING, | ||||
|     UNIT_AMPERE, | ||||
|     UNIT_KILOWATT_HOURS, | ||||
|     UNIT_VOLT, | ||||
| @@ -66,16 +67,19 @@ CONFIG_SCHEMA = ( | ||||
|                 unit_of_measurement=UNIT_KILOWATT_HOURS, | ||||
|                 accuracy_decimals=3, | ||||
|                 device_class=DEVICE_CLASS_ENERGY, | ||||
|                 state_class=STATE_CLASS_TOTAL_INCREASING, | ||||
|             ), | ||||
|             cv.Optional(CONF_ENERGY_2): sensor.sensor_schema( | ||||
|                 unit_of_measurement=UNIT_KILOWATT_HOURS, | ||||
|                 accuracy_decimals=3, | ||||
|                 device_class=DEVICE_CLASS_ENERGY, | ||||
|                 state_class=STATE_CLASS_TOTAL_INCREASING, | ||||
|             ), | ||||
|             cv.Optional(CONF_ENERGY_TOTAL): sensor.sensor_schema( | ||||
|                 unit_of_measurement=UNIT_KILOWATT_HOURS, | ||||
|                 accuracy_decimals=3, | ||||
|                 device_class=DEVICE_CLASS_ENERGY, | ||||
|                 state_class=STATE_CLASS_TOTAL_INCREASING, | ||||
|             ), | ||||
|         } | ||||
|     ) | ||||
|   | ||||
| @@ -1,3 +1,5 @@ | ||||
| #ifdef USE_ESP32 | ||||
|  | ||||
| #include "automation.h" | ||||
|  | ||||
| #include <esp_bt_defs.h> | ||||
| @@ -73,3 +75,5 @@ void BLEWriterClientNode::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga | ||||
|  | ||||
| }  // namespace ble_client | ||||
| }  // namespace esphome | ||||
|  | ||||
| #endif | ||||
|   | ||||
| @@ -1,13 +1,13 @@ | ||||
| #pragma once | ||||
|  | ||||
| #ifdef USE_ESP32 | ||||
|  | ||||
| #include <utility> | ||||
| #include <vector> | ||||
|  | ||||
| #include "esphome/core/automation.h" | ||||
| #include "esphome/components/ble_client/ble_client.h" | ||||
|  | ||||
| #ifdef USE_ESP32 | ||||
|  | ||||
| namespace esphome { | ||||
| namespace ble_client { | ||||
| class BLEClientConnectTrigger : public Trigger<>, public BLEClientNode { | ||||
|   | ||||
| @@ -158,6 +158,25 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga | ||||
|   return true; | ||||
| } | ||||
|  | ||||
| void BluetoothConnection::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) { | ||||
|   BLEClientBase::gap_event_handler(event, param); | ||||
|  | ||||
|   switch (event) { | ||||
|     case ESP_GAP_BLE_AUTH_CMPL_EVT: | ||||
|       if (memcmp(param->ble_security.auth_cmpl.bd_addr, this->remote_bda_, 6) != 0) | ||||
|         break; | ||||
|       if (param->ble_security.auth_cmpl.success) { | ||||
|         api::global_api_server->send_bluetooth_device_pairing(this->address_, true); | ||||
|       } else { | ||||
|         api::global_api_server->send_bluetooth_device_pairing(this->address_, false, | ||||
|                                                               param->ble_security.auth_cmpl.fail_reason); | ||||
|       } | ||||
|       break; | ||||
|     default: | ||||
|       break; | ||||
|   } | ||||
| } | ||||
|  | ||||
| esp_err_t BluetoothConnection::read_characteristic(uint16_t handle) { | ||||
|   if (!this->connected()) { | ||||
|     ESP_LOGW(TAG, "[%d] [%s] Cannot read GATT characteristic, not connected.", this->connection_index_, | ||||
|   | ||||
| @@ -13,6 +13,7 @@ class BluetoothConnection : public esp32_ble_client::BLEClientBase { | ||||
|  public: | ||||
|   bool gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, | ||||
|                            esp_ble_gattc_cb_param_t *param) override; | ||||
|   void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override; | ||||
|  | ||||
|   esp_err_t read_characteristic(uint16_t handle); | ||||
|   esp_err_t write_characteristic(uint16_t handle, const std::string &data, bool response); | ||||
|   | ||||
| @@ -257,12 +257,7 @@ void BluetoothProxy::bluetooth_device_request(const api::BluetoothDeviceRequest | ||||
|         ESP_LOGI(TAG, "[%d] [%s] Connecting v1", connection->get_connection_index(), connection->address_str().c_str()); | ||||
|       } | ||||
|       if (msg.has_address_type) { | ||||
|         connection->remote_bda_[0] = (msg.address >> 40) & 0xFF; | ||||
|         connection->remote_bda_[1] = (msg.address >> 32) & 0xFF; | ||||
|         connection->remote_bda_[2] = (msg.address >> 24) & 0xFF; | ||||
|         connection->remote_bda_[3] = (msg.address >> 16) & 0xFF; | ||||
|         connection->remote_bda_[4] = (msg.address >> 8) & 0xFF; | ||||
|         connection->remote_bda_[5] = (msg.address >> 0) & 0xFF; | ||||
|         uint64_to_bd_addr(msg.address, connection->remote_bda_); | ||||
|         connection->set_remote_addr_type(static_cast<esp_ble_addr_type_t>(msg.address_type)); | ||||
|         connection->set_state(espbt::ClientState::DISCOVERED); | ||||
|       } else { | ||||
| @@ -290,9 +285,27 @@ void BluetoothProxy::bluetooth_device_request(const api::BluetoothDeviceRequest | ||||
|       } | ||||
|       break; | ||||
|     } | ||||
|     case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_PAIR: | ||||
|     case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_UNPAIR: | ||||
|     case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_PAIR: { | ||||
|       auto *connection = this->get_connection_(msg.address, false); | ||||
|       if (connection != nullptr) { | ||||
|         if (!connection->is_paired()) { | ||||
|           auto err = connection->pair(); | ||||
|           if (err != ESP_OK) { | ||||
|             api::global_api_server->send_bluetooth_device_pairing(msg.address, false, err); | ||||
|           } | ||||
|         } else { | ||||
|           api::global_api_server->send_bluetooth_device_pairing(msg.address, true); | ||||
|         } | ||||
|       } | ||||
|       break; | ||||
|     } | ||||
|     case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_UNPAIR: { | ||||
|       esp_bd_addr_t address; | ||||
|       uint64_to_bd_addr(msg.address, address); | ||||
|       esp_err_t ret = esp_ble_remove_bond_device(address); | ||||
|       api::global_api_server->send_bluetooth_device_unpairing(msg.address, ret == ESP_OK, ret); | ||||
|       break; | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -44,6 +44,15 @@ class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Com | ||||
|   int get_bluetooth_connections_free(); | ||||
|   int get_bluetooth_connections_limit() { return this->connections_.size(); } | ||||
|  | ||||
|   static void uint64_to_bd_addr(uint64_t address, esp_bd_addr_t bd_addr) { | ||||
|     bd_addr[0] = (address >> 40) & 0xff; | ||||
|     bd_addr[1] = (address >> 32) & 0xff; | ||||
|     bd_addr[2] = (address >> 24) & 0xff; | ||||
|     bd_addr[3] = (address >> 16) & 0xff; | ||||
|     bd_addr[4] = (address >> 8) & 0xff; | ||||
|     bd_addr[5] = (address >> 0) & 0xff; | ||||
|   } | ||||
|  | ||||
|   void set_active(bool active) { this->active_ = active; } | ||||
|   bool has_active() { return this->active_; } | ||||
|  | ||||
|   | ||||
| @@ -6,6 +6,7 @@ from esphome.const import CONF_ID | ||||
| CODEOWNERS = ["@trvrnrth"] | ||||
| DEPENDENCIES = ["i2c"] | ||||
| AUTO_LOAD = ["sensor", "text_sensor"] | ||||
| MULTI_CONF = True | ||||
|  | ||||
| CONF_BME680_BSEC_ID = "bme680_bsec_id" | ||||
| CONF_TEMPERATURE_OFFSET = "temperature_offset" | ||||
| @@ -54,6 +55,7 @@ async def to_code(config): | ||||
|     await cg.register_component(var, config) | ||||
|     await i2c.register_i2c_device(var, config) | ||||
|  | ||||
|     cg.add(var.set_device_id(str(config[CONF_ID]))) | ||||
|     cg.add(var.set_temperature_offset(config[CONF_TEMPERATURE_OFFSET])) | ||||
|     cg.add(var.set_iaq_mode(config[CONF_IAQ_MODE])) | ||||
|     cg.add(var.set_sample_rate(config[CONF_SAMPLE_RATE])) | ||||
|   | ||||
| @@ -10,19 +10,24 @@ static const char *const TAG = "bme680_bsec.sensor"; | ||||
|  | ||||
| static const std::string IAQ_ACCURACY_STATES[4] = {"Stabilizing", "Uncertain", "Calibrating", "Calibrated"}; | ||||
|  | ||||
| BME680BSECComponent *BME680BSECComponent::instance;  // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) | ||||
| std::vector<BME680BSECComponent *> | ||||
|     BME680BSECComponent::instances;  // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) | ||||
| uint8_t BME680BSECComponent::work_buffer_[BSEC_MAX_WORKBUFFER_SIZE] = {0}; | ||||
|  | ||||
| void BME680BSECComponent::setup() { | ||||
|   ESP_LOGCONFIG(TAG, "Setting up BME680 via BSEC..."); | ||||
|   BME680BSECComponent::instance = this; | ||||
|   ESP_LOGCONFIG(TAG, "Setting up BME680(%s) via BSEC...", this->device_id_.c_str()); | ||||
|  | ||||
|   this->bsec_status_ = bsec_init(); | ||||
|   if (this->bsec_status_ != BSEC_OK) { | ||||
|     this->mark_failed(); | ||||
|     return; | ||||
|   } | ||||
|   uint8_t new_idx = BME680BSECComponent::instances.size(); | ||||
|   BME680BSECComponent::instances.push_back(this); | ||||
|  | ||||
|   this->bme680_.dev_id = this->address_; | ||||
|   this->bsec_state_data_valid_ = false; | ||||
|  | ||||
|   // Initialize the bme680_ structure (passed-in to the bme680_* functions) and the BME680 device | ||||
|   this->bme680_.dev_id = | ||||
|       new_idx;  // This is a "Place holder to store the id of the device structure" (see bme680_defs.h). | ||||
|                 // This will be passed-in as first parameter to the next "read" and "write" function pointers. | ||||
|                 // We currently use the index of the object in the BME680BSECComponent::instances vector to identify | ||||
|                 // the different devices in the system. | ||||
|   this->bme680_.intf = BME680_I2C_INTF; | ||||
|   this->bme680_.read = BME680BSECComponent::read_bytes_wrapper; | ||||
|   this->bme680_.write = BME680BSECComponent::write_bytes_wrapper; | ||||
| @@ -35,29 +40,30 @@ void BME680BSECComponent::setup() { | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   if (this->sample_rate_ == SAMPLE_RATE_ULP) { | ||||
|     const uint8_t bsec_config[] = { | ||||
| #include "config/generic_33v_300s_28d/bsec_iaq.txt" | ||||
|     }; | ||||
|     this->set_config_(bsec_config); | ||||
|   } else { | ||||
|     const uint8_t bsec_config[] = { | ||||
| #include "config/generic_33v_3s_28d/bsec_iaq.txt" | ||||
|     }; | ||||
|     this->set_config_(bsec_config); | ||||
|   } | ||||
|   this->update_subscription_(); | ||||
|   if (this->bsec_status_ != BSEC_OK) { | ||||
|   // Initialize the BSEC library | ||||
|   if (this->reinit_bsec_lib_() != 0) { | ||||
|     this->mark_failed(); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   // Load the BSEC library state from storage | ||||
|   this->load_state_(); | ||||
| } | ||||
|  | ||||
| void BME680BSECComponent::set_config_(const uint8_t *config) { | ||||
|   uint8_t work_buffer[BSEC_MAX_WORKBUFFER_SIZE]; | ||||
|   this->bsec_status_ = bsec_set_configuration(config, BSEC_MAX_PROPERTY_BLOB_SIZE, work_buffer, sizeof(work_buffer)); | ||||
| void BME680BSECComponent::set_config_() { | ||||
|   if (this->sample_rate_ == SAMPLE_RATE_ULP) { | ||||
|     const uint8_t config[] = { | ||||
| #include "config/generic_33v_300s_28d/bsec_iaq.txt" | ||||
|     }; | ||||
|     this->bsec_status_ = | ||||
|         bsec_set_configuration(config, BSEC_MAX_PROPERTY_BLOB_SIZE, this->work_buffer_, sizeof(this->work_buffer_)); | ||||
|   } else { | ||||
|     const uint8_t config[] = { | ||||
| #include "config/generic_33v_3s_28d/bsec_iaq.txt" | ||||
|     }; | ||||
|     this->bsec_status_ = | ||||
|         bsec_set_configuration(config, BSEC_MAX_PROPERTY_BLOB_SIZE, this->work_buffer_, sizeof(this->work_buffer_)); | ||||
|   } | ||||
| } | ||||
|  | ||||
| float BME680BSECComponent::calc_sensor_sample_rate_(SampleRate sample_rate) { | ||||
| @@ -118,10 +124,12 @@ void BME680BSECComponent::update_subscription_() { | ||||
|   uint8_t num_sensor_settings = BSEC_MAX_PHYSICAL_SENSOR; | ||||
|   this->bsec_status_ = | ||||
|       bsec_update_subscription(virtual_sensors, num_virtual_sensors, sensor_settings, &num_sensor_settings); | ||||
|   ESP_LOGV(TAG, "%s: updating subscription for %d virtual sensors (out=%d sensors)", this->device_id_.c_str(), | ||||
|            num_virtual_sensors, num_sensor_settings); | ||||
| } | ||||
|  | ||||
| void BME680BSECComponent::dump_config() { | ||||
|   ESP_LOGCONFIG(TAG, "BME680 via BSEC:"); | ||||
|   ESP_LOGCONFIG(TAG, "%s via BSEC:", this->device_id_.c_str()); | ||||
|  | ||||
|   bsec_version_t version; | ||||
|   bsec_get_version(&version); | ||||
| @@ -185,23 +193,31 @@ void BME680BSECComponent::run_() { | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   ESP_LOGV(TAG, "Performing sensor run"); | ||||
|   ESP_LOGV(TAG, "%s: Performing sensor run", this->device_id_.c_str()); | ||||
|  | ||||
|   bsec_bme_settings_t bme680_settings; | ||||
|   this->bsec_status_ = bsec_sensor_control(curr_time_ns, &bme680_settings); | ||||
|   // Restore BSEC library state | ||||
|   // The reinit_bsec_lib_ method is computationally expensive: it takes 1200÷2900 microseconds on a ESP32. | ||||
|   // It can be skipped entirely when there is only one device (since the BSEC library won't be shared) | ||||
|   if (BME680BSECComponent::instances.size() > 1) { | ||||
|     int res = this->reinit_bsec_lib_(); | ||||
|     if (res != 0) | ||||
|       return; | ||||
|   } | ||||
|  | ||||
|   this->bsec_status_ = bsec_sensor_control(curr_time_ns, &this->bme680_settings_); | ||||
|   if (this->bsec_status_ < BSEC_OK) { | ||||
|     ESP_LOGW(TAG, "Failed to fetch sensor control settings (BSEC Error Code %d)", this->bsec_status_); | ||||
|     return; | ||||
|   } | ||||
|   this->next_call_ns_ = bme680_settings.next_call; | ||||
|   this->next_call_ns_ = this->bme680_settings_.next_call; | ||||
|  | ||||
|   if (bme680_settings.trigger_measurement) { | ||||
|     this->bme680_.tph_sett.os_temp = bme680_settings.temperature_oversampling; | ||||
|     this->bme680_.tph_sett.os_pres = bme680_settings.pressure_oversampling; | ||||
|     this->bme680_.tph_sett.os_hum = bme680_settings.humidity_oversampling; | ||||
|     this->bme680_.gas_sett.run_gas = bme680_settings.run_gas; | ||||
|     this->bme680_.gas_sett.heatr_temp = bme680_settings.heater_temperature; | ||||
|     this->bme680_.gas_sett.heatr_dur = bme680_settings.heating_duration; | ||||
|   if (this->bme680_settings_.trigger_measurement) { | ||||
|     this->bme680_.tph_sett.os_temp = this->bme680_settings_.temperature_oversampling; | ||||
|     this->bme680_.tph_sett.os_pres = this->bme680_settings_.pressure_oversampling; | ||||
|     this->bme680_.tph_sett.os_hum = this->bme680_settings_.humidity_oversampling; | ||||
|     this->bme680_.gas_sett.run_gas = this->bme680_settings_.run_gas; | ||||
|     this->bme680_.gas_sett.heatr_temp = this->bme680_settings_.heater_temperature; | ||||
|     this->bme680_.gas_sett.heatr_dur = this->bme680_settings_.heating_duration; | ||||
|     this->bme680_.power_mode = BME680_FORCED_MODE; | ||||
|     uint16_t desired_settings = BME680_OST_SEL | BME680_OSP_SEL | BME680_OSH_SEL | BME680_GAS_SENSOR_SEL; | ||||
|     this->bme680_status_ = bme680_set_sensor_settings(desired_settings, &this->bme680_); | ||||
| @@ -218,19 +234,26 @@ void BME680BSECComponent::run_() { | ||||
|  | ||||
|     uint16_t meas_dur = 0; | ||||
|     bme680_get_profile_dur(&meas_dur, &this->bme680_); | ||||
|  | ||||
|     // Since we are about to go "out of scope" in the loop, take a snapshot of the state now so we can restore it later | ||||
|     // TODO: it would be interesting to see if this is really needed here, or if it's needed only after each | ||||
|     // bsec_do_steps() call | ||||
|     if (BME680BSECComponent::instances.size() > 1) | ||||
|       this->snapshot_state_(); | ||||
|  | ||||
|     ESP_LOGV(TAG, "Queueing read in %ums", meas_dur); | ||||
|     this->set_timeout("read", meas_dur, | ||||
|                       [this, curr_time_ns, bme680_settings]() { this->read_(curr_time_ns, bme680_settings); }); | ||||
|     this->set_timeout("read", meas_dur, [this]() { this->read_(); }); | ||||
|   } else { | ||||
|     ESP_LOGV(TAG, "Measurement not required"); | ||||
|     this->read_(curr_time_ns, bme680_settings); | ||||
|     this->read_(); | ||||
|   } | ||||
| } | ||||
|  | ||||
| void BME680BSECComponent::read_(int64_t trigger_time_ns, bsec_bme_settings_t bme680_settings) { | ||||
|   ESP_LOGV(TAG, "Reading data"); | ||||
| void BME680BSECComponent::read_() { | ||||
|   ESP_LOGV(TAG, "%s: Reading data", this->device_id_.c_str()); | ||||
|   int64_t curr_time_ns = this->get_time_ns_(); | ||||
|  | ||||
|   if (bme680_settings.trigger_measurement) { | ||||
|   if (this->bme680_settings_.trigger_measurement) { | ||||
|     while (this->bme680_.power_mode != BME680_SLEEP_MODE) { | ||||
|       this->bme680_status_ = bme680_get_sensor_mode(&this->bme680_); | ||||
|       if (this->bme680_status_ != BME680_OK) { | ||||
| @@ -239,7 +262,7 @@ void BME680BSECComponent::read_(int64_t trigger_time_ns, bsec_bme_settings_t bme | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   if (!bme680_settings.process_data) { | ||||
|   if (!this->bme680_settings_.process_data) { | ||||
|     ESP_LOGV(TAG, "Data processing not required"); | ||||
|     return; | ||||
|   } | ||||
| @@ -259,35 +282,35 @@ void BME680BSECComponent::read_(int64_t trigger_time_ns, bsec_bme_settings_t bme | ||||
|   bsec_input_t inputs[BSEC_MAX_PHYSICAL_SENSOR];  // Temperature, Pressure, Humidity & Gas Resistance | ||||
|   uint8_t num_inputs = 0; | ||||
|  | ||||
|   if (bme680_settings.process_data & BSEC_PROCESS_TEMPERATURE) { | ||||
|   if (this->bme680_settings_.process_data & BSEC_PROCESS_TEMPERATURE) { | ||||
|     inputs[num_inputs].sensor_id = BSEC_INPUT_TEMPERATURE; | ||||
|     inputs[num_inputs].signal = data.temperature / 100.0f; | ||||
|     inputs[num_inputs].time_stamp = trigger_time_ns; | ||||
|     inputs[num_inputs].time_stamp = curr_time_ns; | ||||
|     num_inputs++; | ||||
|  | ||||
|     // Temperature offset from the real temperature due to external heat sources | ||||
|     inputs[num_inputs].sensor_id = BSEC_INPUT_HEATSOURCE; | ||||
|     inputs[num_inputs].signal = this->temperature_offset_; | ||||
|     inputs[num_inputs].time_stamp = trigger_time_ns; | ||||
|     inputs[num_inputs].time_stamp = curr_time_ns; | ||||
|     num_inputs++; | ||||
|   } | ||||
|   if (bme680_settings.process_data & BSEC_PROCESS_HUMIDITY) { | ||||
|   if (this->bme680_settings_.process_data & BSEC_PROCESS_HUMIDITY) { | ||||
|     inputs[num_inputs].sensor_id = BSEC_INPUT_HUMIDITY; | ||||
|     inputs[num_inputs].signal = data.humidity / 1000.0f; | ||||
|     inputs[num_inputs].time_stamp = trigger_time_ns; | ||||
|     inputs[num_inputs].time_stamp = curr_time_ns; | ||||
|     num_inputs++; | ||||
|   } | ||||
|   if (bme680_settings.process_data & BSEC_PROCESS_PRESSURE) { | ||||
|   if (this->bme680_settings_.process_data & BSEC_PROCESS_PRESSURE) { | ||||
|     inputs[num_inputs].sensor_id = BSEC_INPUT_PRESSURE; | ||||
|     inputs[num_inputs].signal = data.pressure; | ||||
|     inputs[num_inputs].time_stamp = trigger_time_ns; | ||||
|     inputs[num_inputs].time_stamp = curr_time_ns; | ||||
|     num_inputs++; | ||||
|   } | ||||
|   if (bme680_settings.process_data & BSEC_PROCESS_GAS) { | ||||
|   if (this->bme680_settings_.process_data & BSEC_PROCESS_GAS) { | ||||
|     if (data.status & BME680_GASM_VALID_MSK) { | ||||
|       inputs[num_inputs].sensor_id = BSEC_INPUT_GASRESISTOR; | ||||
|       inputs[num_inputs].signal = data.gas_resistance; | ||||
|       inputs[num_inputs].time_stamp = trigger_time_ns; | ||||
|       inputs[num_inputs].time_stamp = curr_time_ns; | ||||
|       num_inputs++; | ||||
|     } else { | ||||
|       ESP_LOGD(TAG, "BME680 did not report gas data"); | ||||
| @@ -298,6 +321,22 @@ void BME680BSECComponent::read_(int64_t trigger_time_ns, bsec_bme_settings_t bme | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   // Restore BSEC library state | ||||
|   // The reinit_bsec_lib_ method is computationally expensive: it takes 1200÷2900 microseconds on a ESP32. | ||||
|   // It can be skipped entirely when there is only one device (since the BSEC library won't be shared) | ||||
|   if (BME680BSECComponent::instances.size() > 1) { | ||||
|     int res = this->reinit_bsec_lib_(); | ||||
|     if (res != 0) | ||||
|       return; | ||||
|     // Now that the BSEC library has been re-initialized, bsec_sensor_control *NEEDS* to be called in order to support | ||||
|     // multiple devices with a different set of enabled sensors (even if the bme680_settings_ data is not used) | ||||
|     this->bsec_status_ = bsec_sensor_control(curr_time_ns, &this->bme680_settings_); | ||||
|     if (this->bsec_status_ < BSEC_OK) { | ||||
|       ESP_LOGW(TAG, "Failed to fetch sensor control settings (BSEC Error Code %d)", this->bsec_status_); | ||||
|       return; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   bsec_output_t outputs[BSEC_NUMBER_OUTPUTS]; | ||||
|   uint8_t num_outputs = BSEC_NUMBER_OUTPUTS; | ||||
|   this->bsec_status_ = bsec_do_steps(inputs, num_inputs, outputs, &num_outputs); | ||||
| @@ -305,6 +344,13 @@ void BME680BSECComponent::read_(int64_t trigger_time_ns, bsec_bme_settings_t bme | ||||
|     ESP_LOGW(TAG, "BSEC failed to process signals (BSEC Error Code %d)", this->bsec_status_); | ||||
|     return; | ||||
|   } | ||||
|   ESP_LOGV(TAG, "%s: after bsec_do_steps: num_inputs=%d num_outputs=%d", this->device_id_.c_str(), num_inputs, | ||||
|            num_outputs); | ||||
|  | ||||
|   // Since we are about to go "out of scope" in the loop, take a snapshot of the state now so we can restore it later | ||||
|   if (BME680BSECComponent::instances.size() > 1) | ||||
|     this->snapshot_state_(); | ||||
|  | ||||
|   if (num_outputs < 1) { | ||||
|     ESP_LOGD(TAG, "No signal outputs provided by BSEC"); | ||||
|     return; | ||||
| @@ -314,7 +360,7 @@ void BME680BSECComponent::read_(int64_t trigger_time_ns, bsec_bme_settings_t bme | ||||
| } | ||||
|  | ||||
| void BME680BSECComponent::publish_(const bsec_output_t *outputs, uint8_t num_outputs) { | ||||
|   ESP_LOGV(TAG, "Queuing sensor state publish actions"); | ||||
|   ESP_LOGV(TAG, "%s: Queuing sensor state publish actions", this->device_id_.c_str()); | ||||
|   for (uint8_t i = 0; i < num_outputs; i++) { | ||||
|     float signal = outputs[i].signal; | ||||
|     switch (outputs[i].sensor_id) { | ||||
| @@ -376,12 +422,20 @@ void BME680BSECComponent::publish_sensor_(text_sensor::TextSensor *sensor, const | ||||
|   sensor->publish_state(value); | ||||
| } | ||||
|  | ||||
| int8_t BME680BSECComponent::read_bytes_wrapper(uint8_t address, uint8_t a_register, uint8_t *data, uint16_t len) { | ||||
|   return BME680BSECComponent::instance->read_bytes(a_register, data, len) ? 0 : -1; | ||||
| // Communication function - read | ||||
| // First parameter is the "dev_id" member of our "bme680_" object, which is passed-back here as-is | ||||
| int8_t BME680BSECComponent::read_bytes_wrapper(uint8_t devid, uint8_t a_register, uint8_t *data, uint16_t len) { | ||||
|   BME680BSECComponent *inst = instances[devid]; | ||||
|   // Use the I2CDevice::read_bytes method to perform the actual I2C register read | ||||
|   return inst->read_bytes(a_register, data, len) ? 0 : -1; | ||||
| } | ||||
|  | ||||
| int8_t BME680BSECComponent::write_bytes_wrapper(uint8_t address, uint8_t a_register, uint8_t *data, uint16_t len) { | ||||
|   return BME680BSECComponent::instance->write_bytes(a_register, data, len) ? 0 : -1; | ||||
| // Communication function - write | ||||
| // First parameter is the "dev_id" member of our "bme680_" object, which is passed-back here as-is | ||||
| int8_t BME680BSECComponent::write_bytes_wrapper(uint8_t devid, uint8_t a_register, uint8_t *data, uint16_t len) { | ||||
|   BME680BSECComponent *inst = instances[devid]; | ||||
|   // Use the I2CDevice::write_bytes method to perform the actual I2C register write | ||||
|   return inst->write_bytes(a_register, data, len) ? 0 : -1; | ||||
| } | ||||
|  | ||||
| void BME680BSECComponent::delay_ms(uint32_t period) { | ||||
| @@ -389,41 +443,97 @@ void BME680BSECComponent::delay_ms(uint32_t period) { | ||||
|   delay(period); | ||||
| } | ||||
|  | ||||
| // Fetch the BSEC library state and save it in the bsec_state_data_ member (volatile memory) | ||||
| // Used to share the library when using more than one sensor | ||||
| void BME680BSECComponent::snapshot_state_() { | ||||
|   uint32_t num_serialized_state = BSEC_MAX_STATE_BLOB_SIZE; | ||||
|   this->bsec_status_ = bsec_get_state(0, this->bsec_state_data_, BSEC_MAX_STATE_BLOB_SIZE, this->work_buffer_, | ||||
|                                       sizeof(this->work_buffer_), &num_serialized_state); | ||||
|   if (this->bsec_status_ != BSEC_OK) { | ||||
|     ESP_LOGW(TAG, "%s: Failed to fetch BSEC library state for snapshot (BSEC Error Code %d)", this->device_id_.c_str(), | ||||
|              this->bsec_status_); | ||||
|     return; | ||||
|   } | ||||
|   this->bsec_state_data_valid_ = true; | ||||
| } | ||||
|  | ||||
| // Restores the BSEC library state from a snapshot in memory | ||||
| // Used to share the library when using more than one sensor | ||||
| void BME680BSECComponent::restore_state_() { | ||||
|   if (!this->bsec_state_data_valid_) { | ||||
|     ESP_LOGV(TAG, "%s: BSEC state data NOT valid, aborting restore_state_()", this->device_id_.c_str()); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   this->bsec_status_ = | ||||
|       bsec_set_state(this->bsec_state_data_, BSEC_MAX_STATE_BLOB_SIZE, this->work_buffer_, sizeof(this->work_buffer_)); | ||||
|   if (this->bsec_status_ != BSEC_OK) { | ||||
|     ESP_LOGW(TAG, "Failed to restore BSEC library state (BSEC Error Code %d)", this->bsec_status_); | ||||
|     return; | ||||
|   } | ||||
| } | ||||
|  | ||||
| int BME680BSECComponent::reinit_bsec_lib_() { | ||||
|   this->bsec_status_ = bsec_init(); | ||||
|   if (this->bsec_status_ != BSEC_OK) { | ||||
|     this->mark_failed(); | ||||
|     return -1; | ||||
|   } | ||||
|  | ||||
|   this->set_config_(); | ||||
|   if (this->bsec_status_ != BSEC_OK) { | ||||
|     this->mark_failed(); | ||||
|     return -2; | ||||
|   } | ||||
|  | ||||
|   this->restore_state_(); | ||||
|  | ||||
|   this->update_subscription_(); | ||||
|   if (this->bsec_status_ != BSEC_OK) { | ||||
|     this->mark_failed(); | ||||
|     return -3; | ||||
|   } | ||||
|  | ||||
|   return 0; | ||||
| } | ||||
|  | ||||
| void BME680BSECComponent::load_state_() { | ||||
|   uint32_t hash = fnv1_hash("bme680_bsec_state_" + to_string(this->address_)); | ||||
|   uint32_t hash = fnv1_hash("bme680_bsec_state_" + this->device_id_); | ||||
|   this->bsec_state_ = global_preferences->make_preference<uint8_t[BSEC_MAX_STATE_BLOB_SIZE]>(hash, true); | ||||
|  | ||||
|   uint8_t state[BSEC_MAX_STATE_BLOB_SIZE]; | ||||
|   if (this->bsec_state_.load(&state)) { | ||||
|     ESP_LOGV(TAG, "Loading state"); | ||||
|     uint8_t work_buffer[BSEC_MAX_WORKBUFFER_SIZE]; | ||||
|     this->bsec_status_ = bsec_set_state(state, BSEC_MAX_STATE_BLOB_SIZE, work_buffer, sizeof(work_buffer)); | ||||
|     if (this->bsec_status_ != BSEC_OK) { | ||||
|       ESP_LOGW(TAG, "Failed to load state (BSEC Error Code %d)", this->bsec_status_); | ||||
|     } | ||||
|     ESP_LOGI(TAG, "Loaded state"); | ||||
|   if (!this->bsec_state_.load(&this->bsec_state_data_)) { | ||||
|     // No saved BSEC library state available | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   ESP_LOGV(TAG, "%s: Loading BSEC library state", this->device_id_.c_str()); | ||||
|   this->bsec_status_ = | ||||
|       bsec_set_state(this->bsec_state_data_, BSEC_MAX_STATE_BLOB_SIZE, this->work_buffer_, sizeof(this->work_buffer_)); | ||||
|   if (this->bsec_status_ != BSEC_OK) { | ||||
|     ESP_LOGW(TAG, "%s: Failed to load BSEC library state (BSEC Error Code %d)", this->device_id_.c_str(), | ||||
|              this->bsec_status_); | ||||
|     return; | ||||
|   } | ||||
|   // All OK: set the BSEC state data as valid | ||||
|   this->bsec_state_data_valid_ = true; | ||||
|   ESP_LOGI(TAG, "%s: Loaded BSEC library state", this->device_id_.c_str()); | ||||
| } | ||||
|  | ||||
| void BME680BSECComponent::save_state_(uint8_t accuracy) { | ||||
|   if (accuracy < 3 || (millis() - this->last_state_save_ms_ < this->state_save_interval_ms_)) { | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   ESP_LOGV(TAG, "Saving state"); | ||||
|  | ||||
|   uint8_t state[BSEC_MAX_STATE_BLOB_SIZE]; | ||||
|   uint8_t work_buffer[BSEC_MAX_STATE_BLOB_SIZE]; | ||||
|   uint32_t num_serialized_state = BSEC_MAX_STATE_BLOB_SIZE; | ||||
|  | ||||
|   this->bsec_status_ = | ||||
|       bsec_get_state(0, state, BSEC_MAX_STATE_BLOB_SIZE, work_buffer, BSEC_MAX_STATE_BLOB_SIZE, &num_serialized_state); | ||||
|   if (this->bsec_status_ != BSEC_OK) { | ||||
|     ESP_LOGW(TAG, "Failed fetch state for save (BSEC Error Code %d)", this->bsec_status_); | ||||
|     return; | ||||
|   if (BME680BSECComponent::instances.size() <= 1) { | ||||
|     // When a single device is in use, no snapshot is taken regularly so one is taken now | ||||
|     // On multiple devices, a snapshot is taken at every loop, so there is no need to take one here | ||||
|     this->snapshot_state_(); | ||||
|   } | ||||
|   if (!this->bsec_state_data_valid_) | ||||
|     return; | ||||
|  | ||||
|   if (!this->bsec_state_.save(&state)) { | ||||
|   ESP_LOGV(TAG, "%s: Saving state", this->device_id_.c_str()); | ||||
|  | ||||
|   if (!this->bsec_state_.save(&this->bsec_state_data_)) { | ||||
|     ESP_LOGW(TAG, "Failed to save state"); | ||||
|     return; | ||||
|   } | ||||
|   | ||||
| @@ -31,6 +31,7 @@ enum SampleRate { | ||||
|  | ||||
| class BME680BSECComponent : public Component, public i2c::I2CDevice { | ||||
|  public: | ||||
|   void set_device_id(const std::string &devid) { this->device_id_.assign(devid); } | ||||
|   void set_temperature_offset(float offset) { this->temperature_offset_ = offset; } | ||||
|   void set_iaq_mode(IAQMode iaq_mode) { this->iaq_mode_ = iaq_mode; } | ||||
|   void set_state_save_interval(uint32_t interval) { this->state_save_interval_ms_ = interval; } | ||||
| @@ -50,9 +51,9 @@ class BME680BSECComponent : public Component, public i2c::I2CDevice { | ||||
|   void set_co2_equivalent_sensor(sensor::Sensor *sensor) { this->co2_equivalent_sensor_ = sensor; } | ||||
|   void set_breath_voc_equivalent_sensor(sensor::Sensor *sensor) { this->breath_voc_equivalent_sensor_ = sensor; } | ||||
|  | ||||
|   static BME680BSECComponent *instance; | ||||
|   static int8_t read_bytes_wrapper(uint8_t address, uint8_t a_register, uint8_t *data, uint16_t len); | ||||
|   static int8_t write_bytes_wrapper(uint8_t address, uint8_t a_register, uint8_t *data, uint16_t len); | ||||
|   static std::vector<BME680BSECComponent *> instances; | ||||
|   static int8_t read_bytes_wrapper(uint8_t devid, uint8_t a_register, uint8_t *data, uint16_t len); | ||||
|   static int8_t write_bytes_wrapper(uint8_t devid, uint8_t a_register, uint8_t *data, uint16_t len); | ||||
|   static void delay_ms(uint32_t period); | ||||
|  | ||||
|   void setup() override; | ||||
| @@ -61,23 +62,33 @@ class BME680BSECComponent : public Component, public i2c::I2CDevice { | ||||
|   void loop() override; | ||||
|  | ||||
|  protected: | ||||
|   void set_config_(const uint8_t *config); | ||||
|   void set_config_(); | ||||
|   float calc_sensor_sample_rate_(SampleRate sample_rate); | ||||
|   void update_subscription_(); | ||||
|  | ||||
|   void run_(); | ||||
|   void read_(int64_t trigger_time_ns, bsec_bme_settings_t bme680_settings); | ||||
|   void read_(); | ||||
|   void publish_(const bsec_output_t *outputs, uint8_t num_outputs); | ||||
|   int64_t get_time_ns_(); | ||||
|  | ||||
|   void publish_sensor_(sensor::Sensor *sensor, float value, bool change_only = false); | ||||
|   void publish_sensor_(text_sensor::TextSensor *sensor, const std::string &value); | ||||
|  | ||||
|   void load_state_(); | ||||
|   void save_state_(uint8_t accuracy); | ||||
|   void snapshot_state_();  // Fetch the current BSEC library state and save it in the bsec_state_data_ member (volatile | ||||
|                            // memory) | ||||
|   void restore_state_();   // Push the state contained in the bsec_state_data_ member (volatile memory) to the BSEC | ||||
|                            // library | ||||
|   int reinit_bsec_lib_();  // Prepare the BSEC library to be used again after this object returns active | ||||
|                            // (as the library may have been used by other objects) | ||||
|   void load_state_();      // Initialize the ESP preferences object; retrieve the BSEC library state from the ESP | ||||
|                            // preferences (storage); then save it in the bsec_state_data_ member (volatile memory) and | ||||
|                            // push it to the BSEC library | ||||
|   void save_state_( | ||||
|       uint8_t accuracy);  // Save the bsec_state_data_ member (volatile memory) to the ESP preferences (storage) | ||||
|  | ||||
|   void queue_push_(std::function<void()> &&f) { this->queue_.push(std::move(f)); } | ||||
|  | ||||
|   static uint8_t work_buffer_[BSEC_MAX_WORKBUFFER_SIZE]; | ||||
|   struct bme680_dev bme680_; | ||||
|   bsec_library_return_t bsec_status_{BSEC_OK}; | ||||
|   int8_t bme680_status_{BME680_OK}; | ||||
| @@ -88,10 +99,14 @@ class BME680BSECComponent : public Component, public i2c::I2CDevice { | ||||
|  | ||||
|   std::queue<std::function<void()>> queue_; | ||||
|  | ||||
|   bool bsec_state_data_valid_; | ||||
|   uint8_t bsec_state_data_[BSEC_MAX_STATE_BLOB_SIZE];  // This is the current snapshot of the BSEC library state | ||||
|   ESPPreferenceObject bsec_state_; | ||||
|   uint32_t state_save_interval_ms_{21600000};  // 6 hours - 4 times a day | ||||
|   uint32_t last_state_save_ms_ = 0; | ||||
|   bsec_bme_settings_t bme680_settings_; | ||||
|  | ||||
|   std::string device_id_; | ||||
|   float temperature_offset_{0}; | ||||
|   IAQMode iaq_mode_{IAQ_MODE_STATIC}; | ||||
|  | ||||
|   | ||||
| @@ -11,16 +11,19 @@ from esphome.const import ( | ||||
|     CONF_ON_PRESS, | ||||
|     CONF_TRIGGER_ID, | ||||
|     CONF_MQTT_ID, | ||||
|     DEVICE_CLASS_EMPTY, | ||||
|     DEVICE_CLASS_RESTART, | ||||
|     DEVICE_CLASS_UPDATE, | ||||
| ) | ||||
| from esphome.core import CORE, coroutine_with_priority | ||||
| from esphome.cpp_helpers import setup_entity | ||||
| from esphome.cpp_generator import MockObjClass | ||||
|  | ||||
| CODEOWNERS = ["@esphome/core"] | ||||
| IS_PLATFORM_COMPONENT = True | ||||
|  | ||||
| DEVICE_CLASSES = [ | ||||
|     DEVICE_CLASS_EMPTY, | ||||
|     DEVICE_CLASS_RESTART, | ||||
|     DEVICE_CLASS_UPDATE, | ||||
| ] | ||||
| @@ -54,30 +57,23 @@ _UNDEF = object() | ||||
|  | ||||
|  | ||||
| def button_schema( | ||||
|     class_: MockObjClass, | ||||
|     *, | ||||
|     icon: str = _UNDEF, | ||||
|     entity_category: str = _UNDEF, | ||||
|     device_class: str = _UNDEF, | ||||
| ) -> cv.Schema: | ||||
|     schema = BUTTON_SCHEMA | ||||
|     if icon is not _UNDEF: | ||||
|         schema = schema.extend({cv.Optional(CONF_ICON, default=icon): cv.icon}) | ||||
|     if entity_category is not _UNDEF: | ||||
|         schema = schema.extend( | ||||
|             { | ||||
|                 cv.Optional( | ||||
|                     CONF_ENTITY_CATEGORY, default=entity_category | ||||
|                 ): cv.entity_category | ||||
|             } | ||||
|         ) | ||||
|     if device_class is not _UNDEF: | ||||
|         schema = schema.extend( | ||||
|             { | ||||
|                 cv.Optional( | ||||
|                     CONF_DEVICE_CLASS, default=device_class | ||||
|                 ): validate_device_class | ||||
|             } | ||||
|         ) | ||||
|     return schema | ||||
|     schema = {cv.GenerateID(): cv.declare_id(class_)} | ||||
|  | ||||
|     for key, default, validator in [ | ||||
|         (CONF_ICON, icon, cv.icon), | ||||
|         (CONF_ENTITY_CATEGORY, entity_category, cv.entity_category), | ||||
|         (CONF_DEVICE_CLASS, device_class, validate_device_class), | ||||
|     ]: | ||||
|         if default is not _UNDEF: | ||||
|             schema[cv.Optional(key, default=default)] = validator | ||||
|  | ||||
|     return BUTTON_SCHEMA.extend(schema) | ||||
|  | ||||
|  | ||||
| async def setup_button_core_(var, config): | ||||
|   | ||||
| @@ -15,6 +15,13 @@ namespace button { | ||||
|     } \ | ||||
|   } | ||||
|  | ||||
| #define SUB_BUTTON(name) \ | ||||
|  protected: \ | ||||
|   button::Button *name##_button_{nullptr}; \ | ||||
| \ | ||||
|  public: \ | ||||
|   void set_##name##_button(button::Button *button) { this->name##_button_ = button; } | ||||
|  | ||||
| /** Base class for all buttons. | ||||
|  * | ||||
|  * A button is just a momentary switch that does not have a state, only a trigger. | ||||
|   | ||||
| @@ -20,6 +20,7 @@ from esphome.const import ( | ||||
|     CONF_MODE, | ||||
|     CONF_MODE_COMMAND_TOPIC, | ||||
|     CONF_MODE_STATE_TOPIC, | ||||
|     CONF_ON_CONTROL, | ||||
|     CONF_ON_STATE, | ||||
|     CONF_PRESET, | ||||
|     CONF_PRESET_COMMAND_TOPIC, | ||||
| @@ -104,9 +105,40 @@ CLIMATE_SWING_MODES = { | ||||
|  | ||||
| validate_climate_swing_mode = cv.enum(CLIMATE_SWING_MODES, upper=True) | ||||
|  | ||||
| CONF_CURRENT_TEMPERATURE = "current_temperature" | ||||
|  | ||||
| visual_temperature = cv.float_with_unit( | ||||
|     "visual_temperature", "(°C|° C|°|C|° K|° K|K|°F|° F|F)?" | ||||
| ) | ||||
|  | ||||
|  | ||||
| def single_visual_temperature(value): | ||||
|     if isinstance(value, dict): | ||||
|         return value | ||||
|  | ||||
|     value = visual_temperature(value) | ||||
|     return VISUAL_TEMPERATURE_STEP_SCHEMA( | ||||
|         { | ||||
|             CONF_TARGET_TEMPERATURE: value, | ||||
|             CONF_CURRENT_TEMPERATURE: value, | ||||
|         } | ||||
|     ) | ||||
|  | ||||
|  | ||||
| # Actions | ||||
| ControlAction = climate_ns.class_("ControlAction", automation.Action) | ||||
| StateTrigger = climate_ns.class_("StateTrigger", automation.Trigger.template()) | ||||
| ControlTrigger = climate_ns.class_("ControlTrigger", automation.Trigger.template()) | ||||
|  | ||||
| VISUAL_TEMPERATURE_STEP_SCHEMA = cv.Any( | ||||
|     single_visual_temperature, | ||||
|     cv.Schema( | ||||
|         { | ||||
|             cv.Required(CONF_TARGET_TEMPERATURE): visual_temperature, | ||||
|             cv.Required(CONF_CURRENT_TEMPERATURE): visual_temperature, | ||||
|         } | ||||
|     ), | ||||
| ) | ||||
|  | ||||
| CLIMATE_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend( | ||||
|     { | ||||
| @@ -116,9 +148,7 @@ CLIMATE_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA). | ||||
|             { | ||||
|                 cv.Optional(CONF_MIN_TEMPERATURE): cv.temperature, | ||||
|                 cv.Optional(CONF_MAX_TEMPERATURE): cv.temperature, | ||||
|                 cv.Optional(CONF_TEMPERATURE_STEP): cv.float_with_unit( | ||||
|                     "visual_temperature", "(°C|° C|°|C|° K|° K|K|°F|° F|F)?" | ||||
|                 ), | ||||
|                 cv.Optional(CONF_TEMPERATURE_STEP): VISUAL_TEMPERATURE_STEP_SCHEMA, | ||||
|             } | ||||
|         ), | ||||
|         cv.Optional(CONF_ACTION_STATE_TOPIC): cv.All( | ||||
| @@ -175,6 +205,11 @@ CLIMATE_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA). | ||||
|         cv.Optional(CONF_TARGET_TEMPERATURE_LOW_STATE_TOPIC): cv.All( | ||||
|             cv.requires_component("mqtt"), cv.publish_topic | ||||
|         ), | ||||
|         cv.Optional(CONF_ON_CONTROL): automation.validate_automation( | ||||
|             { | ||||
|                 cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ControlTrigger), | ||||
|             } | ||||
|         ), | ||||
|         cv.Optional(CONF_ON_STATE): automation.validate_automation( | ||||
|             { | ||||
|                 cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StateTrigger), | ||||
| @@ -193,7 +228,12 @@ async def setup_climate_core_(var, config): | ||||
|     if CONF_MAX_TEMPERATURE in visual: | ||||
|         cg.add(var.set_visual_max_temperature_override(visual[CONF_MAX_TEMPERATURE])) | ||||
|     if CONF_TEMPERATURE_STEP in visual: | ||||
|         cg.add(var.set_visual_temperature_step_override(visual[CONF_TEMPERATURE_STEP])) | ||||
|         cg.add( | ||||
|             var.set_visual_temperature_step_override( | ||||
|                 visual[CONF_TEMPERATURE_STEP][CONF_TARGET_TEMPERATURE], | ||||
|                 visual[CONF_TEMPERATURE_STEP][CONF_CURRENT_TEMPERATURE], | ||||
|             ) | ||||
|         ) | ||||
|  | ||||
|     if CONF_MQTT_ID in config: | ||||
|         mqtt_ = cg.new_Pvariable(config[CONF_MQTT_ID], var) | ||||
|   | ||||
| @@ -42,6 +42,13 @@ template<typename... Ts> class ControlAction : public Action<Ts...> { | ||||
|   Climate *climate_; | ||||
| }; | ||||
|  | ||||
| class ControlTrigger : public Trigger<> { | ||||
|  public: | ||||
|   ControlTrigger(Climate *climate) { | ||||
|     climate->add_on_control_callback([this]() { this->trigger(); }); | ||||
|   } | ||||
| }; | ||||
|  | ||||
| class StateTrigger : public Trigger<> { | ||||
|  public: | ||||
|   StateTrigger(Climate *climate) { | ||||
|   | ||||
| @@ -44,6 +44,7 @@ void ClimateCall::perform() { | ||||
|   if (this->target_temperature_high_.has_value()) { | ||||
|     ESP_LOGD(TAG, "  Target Temperature High: %.2f", *this->target_temperature_high_); | ||||
|   } | ||||
|   this->parent_->control_callback_.call(); | ||||
|   this->parent_->control(*this); | ||||
| } | ||||
| void ClimateCall::validate_() { | ||||
| @@ -317,6 +318,10 @@ void Climate::add_on_state_callback(std::function<void()> &&callback) { | ||||
|   this->state_callback_.add(std::move(callback)); | ||||
| } | ||||
|  | ||||
| void Climate::add_on_control_callback(std::function<void()> &&callback) { | ||||
|   this->control_callback_.add(std::move(callback)); | ||||
| } | ||||
|  | ||||
| // Random 32bit value; If this changes existing restore preferences are invalidated | ||||
| static const uint32_t RESTORE_STATE_VERSION = 0x848EA6ADUL; | ||||
|  | ||||
| @@ -430,9 +435,11 @@ ClimateTraits Climate::get_traits() { | ||||
|   if (this->visual_max_temperature_override_.has_value()) { | ||||
|     traits.set_visual_max_temperature(*this->visual_max_temperature_override_); | ||||
|   } | ||||
|   if (this->visual_temperature_step_override_.has_value()) { | ||||
|     traits.set_visual_temperature_step(*this->visual_temperature_step_override_); | ||||
|   if (this->visual_target_temperature_step_override_.has_value()) { | ||||
|     traits.set_visual_target_temperature_step(*this->visual_target_temperature_step_override_); | ||||
|     traits.set_visual_current_temperature_step(*this->visual_current_temperature_step_override_); | ||||
|   } | ||||
|  | ||||
|   return traits; | ||||
| } | ||||
|  | ||||
| @@ -442,8 +449,9 @@ void Climate::set_visual_min_temperature_override(float visual_min_temperature_o | ||||
| void Climate::set_visual_max_temperature_override(float visual_max_temperature_override) { | ||||
|   this->visual_max_temperature_override_ = visual_max_temperature_override; | ||||
| } | ||||
| void Climate::set_visual_temperature_step_override(float visual_temperature_step_override) { | ||||
|   this->visual_temperature_step_override_ = visual_temperature_step_override; | ||||
| void Climate::set_visual_temperature_step_override(float target, float current) { | ||||
|   this->visual_target_temperature_step_override_ = target; | ||||
|   this->visual_current_temperature_step_override_ = current; | ||||
| } | ||||
| #pragma GCC diagnostic push | ||||
| #pragma GCC diagnostic ignored "-Wdeprecated-declarations" | ||||
| @@ -541,7 +549,9 @@ void Climate::dump_traits_(const char *tag) { | ||||
|   ESP_LOGCONFIG(tag, "  [x] Visual settings:"); | ||||
|   ESP_LOGCONFIG(tag, "      - Min: %.1f", traits.get_visual_min_temperature()); | ||||
|   ESP_LOGCONFIG(tag, "      - Max: %.1f", traits.get_visual_max_temperature()); | ||||
|   ESP_LOGCONFIG(tag, "      - Step: %.1f", traits.get_visual_temperature_step()); | ||||
|   ESP_LOGCONFIG(tag, "      - Step:"); | ||||
|   ESP_LOGCONFIG(tag, "          Target: %.1f", traits.get_visual_target_temperature_step()); | ||||
|   ESP_LOGCONFIG(tag, "          Current: %.1f", traits.get_visual_current_temperature_step()); | ||||
|   if (traits.get_supports_current_temperature()) { | ||||
|     ESP_LOGCONFIG(tag, "  [x] Supports current temperature"); | ||||
|   } | ||||
|   | ||||
| @@ -219,6 +219,14 @@ class Climate : public EntityBase { | ||||
|    */ | ||||
|   void add_on_state_callback(std::function<void()> &&callback); | ||||
|  | ||||
|   /** | ||||
|    * Add a callback for the climate device configuration; each time the configuration parameters of a climate device | ||||
|    * is updated (using perform() of a ClimateCall), this callback will be called, before any on_state callback. | ||||
|    * | ||||
|    * @param callback The callback to call. | ||||
|    */ | ||||
|   void add_on_control_callback(std::function<void()> &&callback); | ||||
|  | ||||
|   /** Make a climate device control call, this is used to control the climate device, see the ClimateCall description | ||||
|    * for more info. | ||||
|    * @return A new ClimateCall instance targeting this climate device. | ||||
| @@ -241,7 +249,7 @@ class Climate : public EntityBase { | ||||
|  | ||||
|   void set_visual_min_temperature_override(float visual_min_temperature_override); | ||||
|   void set_visual_max_temperature_override(float visual_max_temperature_override); | ||||
|   void set_visual_temperature_step_override(float visual_temperature_step_override); | ||||
|   void set_visual_temperature_step_override(float target, float current); | ||||
|  | ||||
|  protected: | ||||
|   friend ClimateCall; | ||||
| @@ -285,10 +293,12 @@ class Climate : public EntityBase { | ||||
|   void dump_traits_(const char *tag); | ||||
|  | ||||
|   CallbackManager<void()> state_callback_{}; | ||||
|   CallbackManager<void()> control_callback_{}; | ||||
|   ESPPreferenceObject rtc_; | ||||
|   optional<float> visual_min_temperature_override_{}; | ||||
|   optional<float> visual_max_temperature_override_{}; | ||||
|   optional<float> visual_temperature_step_override_{}; | ||||
|   optional<float> visual_target_temperature_step_override_{}; | ||||
|   optional<float> visual_current_temperature_step_override_{}; | ||||
| }; | ||||
|  | ||||
| }  // namespace climate | ||||
|   | ||||
| @@ -3,8 +3,12 @@ | ||||
| namespace esphome { | ||||
| namespace climate { | ||||
|  | ||||
| int8_t ClimateTraits::get_temperature_accuracy_decimals() const { | ||||
|   return step_to_accuracy_decimals(this->visual_temperature_step_); | ||||
| int8_t ClimateTraits::get_target_temperature_accuracy_decimals() const { | ||||
|   return step_to_accuracy_decimals(this->visual_target_temperature_step_); | ||||
| } | ||||
|  | ||||
| int8_t ClimateTraits::get_current_temperature_accuracy_decimals() const { | ||||
|   return step_to_accuracy_decimals(this->visual_current_temperature_step_); | ||||
| } | ||||
|  | ||||
| }  // namespace climate | ||||
|   | ||||
| @@ -147,9 +147,20 @@ class ClimateTraits { | ||||
|   void set_visual_min_temperature(float visual_min_temperature) { visual_min_temperature_ = visual_min_temperature; } | ||||
|   float get_visual_max_temperature() const { return visual_max_temperature_; } | ||||
|   void set_visual_max_temperature(float visual_max_temperature) { visual_max_temperature_ = visual_max_temperature; } | ||||
|   float get_visual_temperature_step() const { return visual_temperature_step_; } | ||||
|   int8_t get_temperature_accuracy_decimals() const; | ||||
|   void set_visual_temperature_step(float temperature_step) { visual_temperature_step_ = temperature_step; } | ||||
|   float get_visual_target_temperature_step() const { return visual_target_temperature_step_; } | ||||
|   float get_visual_current_temperature_step() const { return visual_current_temperature_step_; } | ||||
|   void set_visual_target_temperature_step(float temperature_step) { | ||||
|     visual_target_temperature_step_ = temperature_step; | ||||
|   } | ||||
|   void set_visual_current_temperature_step(float temperature_step) { | ||||
|     visual_current_temperature_step_ = temperature_step; | ||||
|   } | ||||
|   void set_visual_temperature_step(float temperature_step) { | ||||
|     visual_target_temperature_step_ = temperature_step; | ||||
|     visual_current_temperature_step_ = temperature_step; | ||||
|   } | ||||
|   int8_t get_target_temperature_accuracy_decimals() const; | ||||
|   int8_t get_current_temperature_accuracy_decimals() const; | ||||
|  | ||||
|  protected: | ||||
|   void set_mode_support_(climate::ClimateMode mode, bool supported) { | ||||
| @@ -186,7 +197,8 @@ class ClimateTraits { | ||||
|  | ||||
|   float visual_min_temperature_{10}; | ||||
|   float visual_max_temperature_{30}; | ||||
|   float visual_temperature_step_{0.1}; | ||||
|   float visual_target_temperature_step_{0.1}; | ||||
|   float visual_current_temperature_step_{0.1}; | ||||
| }; | ||||
|  | ||||
| }  // namespace climate | ||||
|   | ||||
| @@ -16,10 +16,9 @@ CopyButton = copy_ns.class_("CopyButton", button.Button, cg.Component) | ||||
|  | ||||
|  | ||||
| CONFIG_SCHEMA = ( | ||||
|     button.button_schema() | ||||
|     button.button_schema(CopyButton) | ||||
|     .extend( | ||||
|         { | ||||
|             cv.GenerateID(): cv.declare_id(CopyButton), | ||||
|             cv.Required(CONF_SOURCE_ID): cv.use_id(button.Button), | ||||
|         } | ||||
|     ) | ||||
|   | ||||
| @@ -15,12 +15,15 @@ from .. import copy_ns | ||||
| CopyNumber = copy_ns.class_("CopyNumber", number.Number, cg.Component) | ||||
|  | ||||
|  | ||||
| CONFIG_SCHEMA = number.NUMBER_SCHEMA.extend( | ||||
|     { | ||||
|         cv.GenerateID(): cv.declare_id(CopyNumber), | ||||
|         cv.Required(CONF_SOURCE_ID): cv.use_id(number.Number), | ||||
|     } | ||||
| ).extend(cv.COMPONENT_SCHEMA) | ||||
| CONFIG_SCHEMA = ( | ||||
|     number.number_schema(CopyNumber) | ||||
|     .extend( | ||||
|         { | ||||
|             cv.Required(CONF_SOURCE_ID): cv.use_id(number.Number), | ||||
|         } | ||||
|     ) | ||||
|     .extend(cv.COMPONENT_SCHEMA) | ||||
| ) | ||||
|  | ||||
| FINAL_VALIDATE_SCHEMA = cv.All( | ||||
|     inherit_property_from(CONF_ICON, CONF_SOURCE_ID), | ||||
|   | ||||
| @@ -17,6 +17,17 @@ from esphome.const import ( | ||||
|     CONF_STOP, | ||||
|     CONF_MQTT_ID, | ||||
|     CONF_TRIGGER_ID, | ||||
|     DEVICE_CLASS_AWNING, | ||||
|     DEVICE_CLASS_BLIND, | ||||
|     DEVICE_CLASS_CURTAIN, | ||||
|     DEVICE_CLASS_DAMPER, | ||||
|     DEVICE_CLASS_DOOR, | ||||
|     DEVICE_CLASS_EMPTY, | ||||
|     DEVICE_CLASS_GARAGE, | ||||
|     DEVICE_CLASS_GATE, | ||||
|     DEVICE_CLASS_SHADE, | ||||
|     DEVICE_CLASS_SHUTTER, | ||||
|     DEVICE_CLASS_WINDOW, | ||||
| ) | ||||
| from esphome.core import CORE, coroutine_with_priority | ||||
| from esphome.cpp_helpers import setup_entity | ||||
| @@ -25,17 +36,17 @@ IS_PLATFORM_COMPONENT = True | ||||
|  | ||||
| CODEOWNERS = ["@esphome/core"] | ||||
| DEVICE_CLASSES = [ | ||||
|     "", | ||||
|     "awning", | ||||
|     "blind", | ||||
|     "curtain", | ||||
|     "damper", | ||||
|     "door", | ||||
|     "garage", | ||||
|     "gate", | ||||
|     "shade", | ||||
|     "shutter", | ||||
|     "window", | ||||
|     DEVICE_CLASS_AWNING, | ||||
|     DEVICE_CLASS_BLIND, | ||||
|     DEVICE_CLASS_CURTAIN, | ||||
|     DEVICE_CLASS_DAMPER, | ||||
|     DEVICE_CLASS_DOOR, | ||||
|     DEVICE_CLASS_EMPTY, | ||||
|     DEVICE_CLASS_GARAGE, | ||||
|     DEVICE_CLASS_GATE, | ||||
|     DEVICE_CLASS_SHADE, | ||||
|     DEVICE_CLASS_SHUTTER, | ||||
|     DEVICE_CLASS_WINDOW, | ||||
| ] | ||||
|  | ||||
| cover_ns = cg.esphome_ns.namespace("cover") | ||||
|   | ||||
| @@ -10,7 +10,7 @@ CONFIG_SCHEMA = cv.Schema( | ||||
|     { | ||||
|         cv.GenerateID(): cv.declare_id(CustomSensorConstructor), | ||||
|         cv.Required(CONF_LAMBDA): cv.returning_lambda, | ||||
|         cv.Required(CONF_SENSORS): cv.ensure_list(sensor.SENSOR_SCHEMA), | ||||
|         cv.Required(CONF_SENSORS): cv.ensure_list(sensor.sensor_schema()), | ||||
|     } | ||||
| ) | ||||
|  | ||||
|   | ||||
| @@ -83,11 +83,30 @@ def import_config( | ||||
|         raise FileExistsError | ||||
|  | ||||
|     if project_name == "esphome.web": | ||||
|         if "esp32c3" in import_url: | ||||
|             board = "esp32-c3-devkitm-1" | ||||
|             platform = "ESP32" | ||||
|         elif "esp32s2" in import_url: | ||||
|             board = "esp32-s2-saola-1" | ||||
|             platform = "ESP32" | ||||
|         elif "esp32s3" in import_url: | ||||
|             board = "esp32-s3-devkitc-1" | ||||
|             platform = "ESP32" | ||||
|         elif "esp32" in import_url: | ||||
|             board = "esp32dev" | ||||
|             platform = "ESP32" | ||||
|         elif "esp8266" in import_url: | ||||
|             board = "esp01_1m" | ||||
|             platform = "ESP8266" | ||||
|         elif "pico-w" in import_url: | ||||
|             board = "pico-w" | ||||
|             platform = "RP2040" | ||||
|  | ||||
|         kwargs = { | ||||
|             "name": name, | ||||
|             "friendly_name": friendly_name, | ||||
|             "platform": "ESP32" if "esp32" in import_url else "ESP8266", | ||||
|             "board": "esp32dev" if "esp32" in import_url else "esp01_1m", | ||||
|             "platform": platform, | ||||
|             "board": board, | ||||
|             "ssid": "!secret wifi_ssid", | ||||
|             "psk": "!secret wifi_password", | ||||
|         } | ||||
|   | ||||
| @@ -21,6 +21,7 @@ from esphome.components.esp32.const import ( | ||||
|     VARIANT_ESP32, | ||||
|     VARIANT_ESP32C3, | ||||
|     VARIANT_ESP32S2, | ||||
|     VARIANT_ESP32S3, | ||||
| ) | ||||
|  | ||||
| WAKEUP_PINS = { | ||||
| @@ -69,6 +70,30 @@ WAKEUP_PINS = { | ||||
|         20, | ||||
|         21, | ||||
|     ], | ||||
|     VARIANT_ESP32S3: [ | ||||
|         0, | ||||
|         1, | ||||
|         2, | ||||
|         3, | ||||
|         4, | ||||
|         5, | ||||
|         6, | ||||
|         7, | ||||
|         8, | ||||
|         9, | ||||
|         10, | ||||
|         11, | ||||
|         12, | ||||
|         13, | ||||
|         14, | ||||
|         15, | ||||
|         16, | ||||
|         17, | ||||
|         18, | ||||
|         19, | ||||
|         20, | ||||
|         21, | ||||
|     ], | ||||
| } | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -284,9 +284,10 @@ CONFIG_SCHEMA = cv.Schema( | ||||
|                 }, | ||||
|             ], | ||||
|         ): [ | ||||
|             number.NUMBER_SCHEMA.extend(cv.COMPONENT_SCHEMA).extend( | ||||
|             number.number_schema(DemoNumber) | ||||
|             .extend(cv.COMPONENT_SCHEMA) | ||||
|             .extend( | ||||
|                 { | ||||
|                     cv.GenerateID(): cv.declare_id(DemoNumber), | ||||
|                     cv.Required(CONF_TYPE): cv.enum(NUMBER_TYPES, int=True), | ||||
|                     cv.Required(CONF_MIN_VALUE): cv.float_, | ||||
|                     cv.Required(CONF_MAX_VALUE): cv.float_, | ||||
|   | ||||
| @@ -256,7 +256,7 @@ void DisplayBuffer::print(int x, int y, Font *font, Color color, TextAlign align | ||||
|     if (glyph_n < 0) { | ||||
|       // Unknown char, skip | ||||
|       ESP_LOGW(TAG, "Encountered character without representation in font: '%c'", text[i]); | ||||
|       if (!font->get_glyphs().empty()) { | ||||
|       if (font->get_glyphs_size() > 0) { | ||||
|         uint8_t glyph_width = font->get_glyphs()[0].glyph_data_->width; | ||||
|         for (int glyph_x = 0; glyph_x < glyph_width; glyph_x++) { | ||||
|           for (int glyph_y = 0; glyph_y < height; glyph_y++) | ||||
| @@ -557,7 +557,7 @@ void Glyph::scan_area(int *x1, int *y1, int *width, int *height) const { | ||||
| } | ||||
| int Font::match_next_glyph(const char *str, int *match_length) { | ||||
|   int lo = 0; | ||||
|   int hi = this->glyphs_.size() - 1; | ||||
|   int hi = this->glyphs_size_ - 1; | ||||
|   while (lo != hi) { | ||||
|     int mid = (lo + hi + 1) / 2; | ||||
|     if (this->glyphs_[mid].compare_to(str)) { | ||||
| @@ -583,7 +583,7 @@ void Font::measure(const char *str, int *width, int *x_offset, int *baseline, in | ||||
|     int glyph_n = this->match_next_glyph(str + i, &match_length); | ||||
|     if (glyph_n < 0) { | ||||
|       // Unknown char, skip | ||||
|       if (!this->get_glyphs().empty()) | ||||
|       if (this->glyphs_size_ > 0) | ||||
|         x += this->get_glyphs()[0].glyph_data_->width; | ||||
|       i++; | ||||
|       continue; | ||||
| @@ -603,10 +603,17 @@ void Font::measure(const char *str, int *width, int *x_offset, int *baseline, in | ||||
|   *x_offset = min_x; | ||||
|   *width = x - min_x; | ||||
| } | ||||
| const std::vector<Glyph> &Font::get_glyphs() const { return this->glyphs_; } | ||||
| Font::Font(const GlyphData *data, int data_nr, int baseline, int height) : baseline_(baseline), height_(height) { | ||||
|   for (int i = 0; i < data_nr; ++i) | ||||
|     glyphs_.emplace_back(data + i); | ||||
|   ExternalRAMAllocator<Glyph> allocator(ExternalRAMAllocator<Glyph>::ALLOW_FAILURE); | ||||
|   this->glyphs_ = allocator.allocate(data_nr); | ||||
|   if (this->glyphs_ == nullptr) { | ||||
|     ESP_LOGE(TAG, "Could not allocate buffer for Glyphs!"); | ||||
|     return; | ||||
|   } | ||||
|   for (int i = 0; i < data_nr; ++i) { | ||||
|     this->glyphs_[i] = Glyph(data + i); | ||||
|   } | ||||
|   this->glyphs_size_ = data_nr; | ||||
| } | ||||
|  | ||||
| bool Image::get_pixel(int x, int y) const { | ||||
|   | ||||
| @@ -526,10 +526,12 @@ class Font { | ||||
|   inline int get_baseline() { return this->baseline_; } | ||||
|   inline int get_height() { return this->height_; } | ||||
|  | ||||
|   const std::vector<Glyph> &get_glyphs() const; | ||||
|   Glyph *&get_glyphs() { return this->glyphs_; } | ||||
|   const u_int16_t &get_glyphs_size() const { return this->glyphs_size_; } | ||||
|  | ||||
|  protected: | ||||
|   std::vector<Glyph> glyphs_; | ||||
|   Glyph *glyphs_{nullptr}; | ||||
|   u_int16_t glyphs_size_; | ||||
|   int baseline_; | ||||
|   int height_; | ||||
| }; | ||||
|   | ||||
| @@ -4,29 +4,43 @@ from pathlib import Path | ||||
| import logging | ||||
| import os | ||||
|  | ||||
| from esphome.helpers import copy_file_if_changed, write_file_if_changed | ||||
| from esphome.helpers import copy_file_if_changed, write_file_if_changed, mkdir_p | ||||
| from esphome.const import ( | ||||
|     CONF_BOARD, | ||||
|     CONF_COMPONENTS, | ||||
|     CONF_FRAMEWORK, | ||||
|     CONF_NAME, | ||||
|     CONF_SOURCE, | ||||
|     CONF_TYPE, | ||||
|     CONF_VARIANT, | ||||
|     CONF_VERSION, | ||||
|     CONF_ADVANCED, | ||||
|     CONF_REFRESH, | ||||
|     CONF_PATH, | ||||
|     CONF_URL, | ||||
|     CONF_REF, | ||||
|     CONF_IGNORE_EFUSE_MAC_CRC, | ||||
|     KEY_CORE, | ||||
|     KEY_FRAMEWORK_VERSION, | ||||
|     KEY_TARGET_FRAMEWORK, | ||||
|     KEY_TARGET_PLATFORM, | ||||
|     TYPE_GIT, | ||||
|     TYPE_LOCAL, | ||||
|     __version__, | ||||
| ) | ||||
| from esphome.core import CORE, HexInt | ||||
| from esphome.core import CORE, HexInt, TimePeriod | ||||
| import esphome.config_validation as cv | ||||
| import esphome.codegen as cg | ||||
| from esphome import git | ||||
|  | ||||
| from .const import (  # noqa | ||||
|     KEY_BOARD, | ||||
|     KEY_COMPONENTS, | ||||
|     KEY_ESP32, | ||||
|     KEY_PATH, | ||||
|     KEY_REF, | ||||
|     KEY_REFRESH, | ||||
|     KEY_REPO, | ||||
|     KEY_SDKCONFIG_OPTIONS, | ||||
|     KEY_VARIANT, | ||||
|     VARIANT_ESP32C3, | ||||
| @@ -51,6 +65,7 @@ def set_core_data(config): | ||||
|     if conf[CONF_TYPE] == FRAMEWORK_ESP_IDF: | ||||
|         CORE.data[KEY_CORE][KEY_TARGET_FRAMEWORK] = "esp-idf" | ||||
|         CORE.data[KEY_ESP32][KEY_SDKCONFIG_OPTIONS] = {} | ||||
|         CORE.data[KEY_ESP32][KEY_COMPONENTS] = {} | ||||
|     elif conf[CONF_TYPE] == FRAMEWORK_ARDUINO: | ||||
|         CORE.data[KEY_CORE][KEY_TARGET_FRAMEWORK] = "arduino" | ||||
|     CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] = cv.Version.parse( | ||||
| @@ -104,6 +119,21 @@ def add_idf_sdkconfig_option(name: str, value: SdkconfigValueType): | ||||
|     CORE.data[KEY_ESP32][KEY_SDKCONFIG_OPTIONS][name] = value | ||||
|  | ||||
|  | ||||
| def add_idf_component( | ||||
|     name: str, repo: str, ref: str = None, path: str = None, refresh: TimePeriod = None | ||||
| ): | ||||
|     """Add an esp-idf component to the project.""" | ||||
|     if not CORE.using_esp_idf: | ||||
|         raise ValueError("Not an esp-idf project") | ||||
|     if name not in CORE.data[KEY_ESP32][KEY_COMPONENTS]: | ||||
|         CORE.data[KEY_ESP32][KEY_COMPONENTS][name] = { | ||||
|             KEY_REPO: repo, | ||||
|             KEY_REF: ref, | ||||
|             KEY_PATH: path, | ||||
|             KEY_REFRESH: refresh, | ||||
|         } | ||||
|  | ||||
|  | ||||
| def _format_framework_arduino_version(ver: cv.Version) -> str: | ||||
|     # format the given arduino (https://github.com/espressif/arduino-esp32/releases) version to | ||||
|     # a PIO platformio/framework-arduinoespressif32 value | ||||
| @@ -138,18 +168,18 @@ ARDUINO_PLATFORM_VERSION = cv.Version(5, 2, 0) | ||||
| # The default/recommended esp-idf framework version | ||||
| #  - https://github.com/espressif/esp-idf/releases | ||||
| #  - https://api.registry.platformio.org/v3/packages/platformio/tool/framework-espidf | ||||
| RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION = cv.Version(4, 4, 2) | ||||
| RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION = cv.Version(4, 4, 4) | ||||
| # The platformio/espressif32 version to use for esp-idf frameworks | ||||
| #  - https://github.com/platformio/platform-espressif32/releases | ||||
| #  - https://api.registry.platformio.org/v3/packages/platformio/platform/espressif32 | ||||
| ESP_IDF_PLATFORM_VERSION = cv.Version(5, 2, 0) | ||||
| ESP_IDF_PLATFORM_VERSION = cv.Version(5, 3, 0) | ||||
|  | ||||
|  | ||||
| def _arduino_check_versions(value): | ||||
|     value = value.copy() | ||||
|     lookups = { | ||||
|         "dev": (cv.Version(2, 0, 5), "https://github.com/espressif/arduino-esp32.git"), | ||||
|         "latest": (cv.Version(2, 0, 5), None), | ||||
|         "dev": (cv.Version(2, 1, 0), "https://github.com/espressif/arduino-esp32.git"), | ||||
|         "latest": (cv.Version(2, 0, 7), None), | ||||
|         "recommended": (RECOMMENDED_ARDUINO_FRAMEWORK_VERSION, None), | ||||
|     } | ||||
|  | ||||
| @@ -183,8 +213,8 @@ def _arduino_check_versions(value): | ||||
| def _esp_idf_check_versions(value): | ||||
|     value = value.copy() | ||||
|     lookups = { | ||||
|         "dev": (cv.Version(5, 0, 0), "https://github.com/espressif/esp-idf.git"), | ||||
|         "latest": (cv.Version(4, 4, 2), None), | ||||
|         "dev": (cv.Version(5, 1, 0), "https://github.com/espressif/esp-idf.git"), | ||||
|         "latest": (cv.Version(5, 0, 1), None), | ||||
|         "recommended": (RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION, None), | ||||
|     } | ||||
|  | ||||
| @@ -270,6 +300,18 @@ ESP_IDF_FRAMEWORK_SCHEMA = cv.All( | ||||
|                     cv.Optional(CONF_IGNORE_EFUSE_MAC_CRC, default=False): cv.boolean, | ||||
|                 } | ||||
|             ), | ||||
|             cv.Optional(CONF_COMPONENTS, default=[]): cv.ensure_list( | ||||
|                 cv.Schema( | ||||
|                     { | ||||
|                         cv.Required(CONF_NAME): cv.string_strict, | ||||
|                         cv.Required(CONF_SOURCE): cv.SOURCE_SCHEMA, | ||||
|                         cv.Optional(CONF_PATH): cv.string, | ||||
|                         cv.Optional(CONF_REFRESH, default="1d"): cv.All( | ||||
|                             cv.string, cv.source_refresh | ||||
|                         ), | ||||
|                     } | ||||
|                 ) | ||||
|             ), | ||||
|         } | ||||
|     ), | ||||
|     _esp_idf_check_versions, | ||||
| @@ -372,6 +414,19 @@ async def to_code(config): | ||||
|             ), | ||||
|         ) | ||||
|  | ||||
|         for component in conf[CONF_COMPONENTS]: | ||||
|             source = component[CONF_SOURCE] | ||||
|             if source[CONF_TYPE] == TYPE_GIT: | ||||
|                 add_idf_component( | ||||
|                     name=component[CONF_NAME], | ||||
|                     repo=source[CONF_URL], | ||||
|                     ref=source.get(CONF_REF), | ||||
|                     path=component.get(CONF_PATH), | ||||
|                     refresh=component[CONF_REFRESH], | ||||
|                 ) | ||||
|             elif source[CONF_TYPE] == TYPE_LOCAL: | ||||
|                 _LOGGER.warning("Local components are not implemented yet.") | ||||
|  | ||||
|     elif conf[CONF_TYPE] == FRAMEWORK_ARDUINO: | ||||
|         cg.add_platformio_option("framework", "arduino") | ||||
|         cg.add_build_flag("-DUSE_ARDUINO") | ||||
| @@ -468,6 +523,32 @@ def copy_files(): | ||||
|             __version__, | ||||
|         ) | ||||
|  | ||||
|         import shutil | ||||
|  | ||||
|         shutil.rmtree(CORE.relative_build_path("components"), ignore_errors=True) | ||||
|  | ||||
|         if CORE.data[KEY_ESP32][KEY_COMPONENTS]: | ||||
|             components: dict = CORE.data[KEY_ESP32][KEY_COMPONENTS] | ||||
|  | ||||
|             for name, component in components.items(): | ||||
|                 repo_dir, _ = git.clone_or_update( | ||||
|                     url=component[KEY_REPO], | ||||
|                     ref=component[KEY_REF], | ||||
|                     refresh=component[KEY_REFRESH], | ||||
|                     domain="idf_components", | ||||
|                 ) | ||||
|                 mkdir_p(CORE.relative_build_path("components")) | ||||
|                 component_dir = repo_dir | ||||
|                 if component[KEY_PATH] is not None: | ||||
|                     component_dir = component_dir / component[KEY_PATH] | ||||
|  | ||||
|                 shutil.copytree( | ||||
|                     component_dir, | ||||
|                     CORE.relative_build_path(f"components/{name}"), | ||||
|                     dirs_exist_ok=True, | ||||
|                     ignore=shutil.ignore_patterns(".git", ".github"), | ||||
|                 ) | ||||
|  | ||||
|     dir = os.path.dirname(__file__) | ||||
|     post_build_file = os.path.join(dir, "post_build.py.script") | ||||
|     copy_file_if_changed( | ||||
|   | ||||
| @@ -4,6 +4,11 @@ KEY_ESP32 = "esp32" | ||||
| KEY_BOARD = "board" | ||||
| KEY_VARIANT = "variant" | ||||
| KEY_SDKCONFIG_OPTIONS = "sdkconfig_options" | ||||
| KEY_COMPONENTS = "components" | ||||
| KEY_REPO = "repo" | ||||
| KEY_REF = "ref" | ||||
| KEY_REFRESH = "refresh" | ||||
| KEY_PATH = "path" | ||||
|  | ||||
| VARIANT_ESP32 = "ESP32" | ||||
| VARIANT_ESP32S2 = "ESP32S2" | ||||
|   | ||||
| @@ -1,15 +1,25 @@ | ||||
| # Source https://github.com/letscontrolit/ESPEasy/pull/3845#issuecomment-1005864664 | ||||
|  | ||||
| import os | ||||
| if os.environ.get("ESPHOME_USE_SUBPROCESS") is None: | ||||
|     import esptool | ||||
| else: | ||||
|     import subprocess | ||||
| from SCons.Script import ARGUMENTS | ||||
|  | ||||
| # pylint: disable=E0602 | ||||
| Import("env")  # noqa | ||||
|  | ||||
| import os | ||||
| import shutil | ||||
|  | ||||
| if os.environ.get("ESPHOME_USE_SUBPROCESS") is None: | ||||
|     try: | ||||
|         import esptool | ||||
|     except ImportError: | ||||
|         env.Execute("$PYTHONEXE -m pip install esptool") | ||||
| else: | ||||
|     import subprocess | ||||
| from SCons.Script import ARGUMENTS | ||||
|  | ||||
| # Copy over the default sdkconfig. | ||||
| from os import path | ||||
| if path.exists("./sdkconfig.defaults"): | ||||
|     os.makedirs(".temp", exist_ok=True) | ||||
|     shutil.copy("./sdkconfig.defaults", "./.temp/sdkconfig-esp32-idf") | ||||
|  | ||||
| def esp32_create_combined_bin(source, target, env): | ||||
|     verbose = bool(int(ARGUMENTS.get("PIOVERBOSE", "0"))) | ||||
|   | ||||
| @@ -62,6 +62,7 @@ bool BLEClientBase::parse_device(const espbt::ESPBTDevice &device) { | ||||
| void BLEClientBase::connect() { | ||||
|   ESP_LOGI(TAG, "[%d] [%s] 0x%02x Attempting BLE connection", this->connection_index_, this->address_str_.c_str(), | ||||
|            this->remote_addr_type_); | ||||
|   this->paired_ = false; | ||||
|   auto ret = esp_ble_gattc_open(this->gattc_if_, this->remote_bda_, this->remote_addr_type_, true); | ||||
|   if (ret) { | ||||
|     ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_open error, status=%d", this->connection_index_, this->address_str_.c_str(), | ||||
| @@ -72,6 +73,8 @@ void BLEClientBase::connect() { | ||||
|   } | ||||
| } | ||||
|  | ||||
| esp_err_t BLEClientBase::pair() { return esp_ble_set_encryption(this->remote_bda_, ESP_BLE_SEC_ENCRYPT); } | ||||
|  | ||||
| void BLEClientBase::disconnect() { | ||||
|   if (this->state_ == espbt::ClientState::IDLE || this->state_ == espbt::ClientState::DISCONNECTING) | ||||
|     return; | ||||
| @@ -247,11 +250,15 @@ void BLEClientBase::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_ | ||||
|   switch (event) { | ||||
|     // This event is sent by the server when it requests security | ||||
|     case ESP_GAP_BLE_SEC_REQ_EVT: | ||||
|       if (memcmp(param->ble_security.auth_cmpl.bd_addr, this->remote_bda_, 6) != 0) | ||||
|         break; | ||||
|       ESP_LOGV(TAG, "[%d] [%s] ESP_GAP_BLE_SEC_REQ_EVT %x", this->connection_index_, this->address_str_.c_str(), event); | ||||
|       esp_ble_gap_security_rsp(param->ble_security.ble_req.bd_addr, true); | ||||
|       break; | ||||
|     // This event is sent once authentication has completed | ||||
|     case ESP_GAP_BLE_AUTH_CMPL_EVT: | ||||
|       if (memcmp(param->ble_security.auth_cmpl.bd_addr, this->remote_bda_, 6) != 0) | ||||
|         break; | ||||
|       esp_bd_addr_t bd_addr; | ||||
|       memcpy(bd_addr, param->ble_security.auth_cmpl.bd_addr, sizeof(esp_bd_addr_t)); | ||||
|       ESP_LOGI(TAG, "[%d] [%s] auth complete. remote BD_ADDR: %s", this->connection_index_, this->address_str_.c_str(), | ||||
| @@ -260,6 +267,7 @@ void BLEClientBase::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_ | ||||
|         ESP_LOGE(TAG, "[%d] [%s] auth fail reason = 0x%x", this->connection_index_, this->address_str_.c_str(), | ||||
|                  param->ble_security.auth_cmpl.fail_reason); | ||||
|       } else { | ||||
|         this->paired_ = true; | ||||
|         ESP_LOGV(TAG, "[%d] [%s] auth success. address type = %d auth mode = %d", this->connection_index_, | ||||
|                  this->address_str_.c_str(), param->ble_security.auth_cmpl.addr_type, | ||||
|                  param->ble_security.auth_cmpl.auth_mode); | ||||
|   | ||||
| @@ -33,6 +33,7 @@ class BLEClientBase : public espbt::ESPBTClient, public Component { | ||||
|                            esp_ble_gattc_cb_param_t *param) override; | ||||
|   void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override; | ||||
|   void connect() override; | ||||
|   esp_err_t pair(); | ||||
|   void disconnect(); | ||||
|   void release_services(); | ||||
|  | ||||
| @@ -71,6 +72,7 @@ class BLEClientBase : public espbt::ESPBTClient, public Component { | ||||
|   void set_remote_addr_type(esp_ble_addr_type_t address_type) { this->remote_addr_type_ = address_type; } | ||||
|   uint16_t get_conn_id() const { return this->conn_id_; } | ||||
|   uint64_t get_address() const { return this->address_; } | ||||
|   bool is_paired() const { return this->paired_; } | ||||
|  | ||||
|   uint8_t get_connection_index() const { return this->connection_index_; } | ||||
|  | ||||
| @@ -86,6 +88,7 @@ class BLEClientBase : public espbt::ESPBTClient, public Component { | ||||
|   uint8_t connection_index_; | ||||
|   int16_t service_count_{0}; | ||||
|   uint16_t mtu_{23}; | ||||
|   bool paired_{false}; | ||||
|   espbt::ConnectionType connection_type_{espbt::ConnectionType::V1}; | ||||
|  | ||||
|   std::vector<BLEService *> services_; | ||||
|   | ||||
| @@ -53,6 +53,14 @@ void ESP32BLETracker::setup() { | ||||
|     ESP_LOGE(TAG, "BLE Tracker was marked failed by ESP32BLE"); | ||||
|     return; | ||||
|   } | ||||
|   ExternalRAMAllocator<esp_ble_gap_cb_param_t::ble_scan_result_evt_param> allocator( | ||||
|       ExternalRAMAllocator<esp_ble_gap_cb_param_t::ble_scan_result_evt_param>::ALLOW_FAILURE); | ||||
|   this->scan_result_buffer_ = allocator.allocate(ESP32BLETracker::SCAN_RESULT_BUFFER_SIZE); | ||||
|  | ||||
|   if (this->scan_result_buffer_ == nullptr) { | ||||
|     ESP_LOGE(TAG, "Could not allocate buffer for BLE Tracker!"); | ||||
|     this->mark_failed(); | ||||
|   } | ||||
|  | ||||
|   global_esp32_ble_tracker = this; | ||||
|   this->scan_result_lock_ = xSemaphoreCreateMutex(); | ||||
| @@ -107,7 +115,7 @@ void ESP32BLETracker::loop() { | ||||
|         xSemaphoreTake(this->scan_result_lock_, 5L / portTICK_PERIOD_MS)) { | ||||
|       uint32_t index = this->scan_result_index_; | ||||
|       if (index) { | ||||
|         if (index >= 16) { | ||||
|         if (index >= ESP32BLETracker::SCAN_RESULT_BUFFER_SIZE) { | ||||
|           ESP_LOGW(TAG, "Too many BLE events to process. Some devices may not show up."); | ||||
|         } | ||||
|         for (size_t i = 0; i < index; i++) { | ||||
| @@ -322,7 +330,7 @@ void ESP32BLETracker::gap_scan_stop_complete_(const esp_ble_gap_cb_param_t::ble_ | ||||
| void ESP32BLETracker::gap_scan_result_(const esp_ble_gap_cb_param_t::ble_scan_result_evt_param ¶m) { | ||||
|   if (param.search_evt == ESP_GAP_SEARCH_INQ_RES_EVT) { | ||||
|     if (xSemaphoreTake(this->scan_result_lock_, 0L)) { | ||||
|       if (this->scan_result_index_ < 16) { | ||||
|       if (this->scan_result_index_ < ESP32BLETracker::SCAN_RESULT_BUFFER_SIZE) { | ||||
|         this->scan_result_buffer_[this->scan_result_index_++] = param; | ||||
|       } | ||||
|       xSemaphoreGive(this->scan_result_lock_); | ||||
|   | ||||
| @@ -101,7 +101,7 @@ class ESPBTDevice { | ||||
|   std::vector<int8_t> tx_powers_{}; | ||||
|   optional<uint16_t> appearance_{}; | ||||
|   optional<uint8_t> ad_flag_{}; | ||||
|   std::vector<ESPBTUUID> service_uuids_; | ||||
|   std::vector<ESPBTUUID> service_uuids_{}; | ||||
|   std::vector<ServiceData> manufacturer_datas_{}; | ||||
|   std::vector<ServiceData> service_datas_{}; | ||||
|   esp_ble_gap_cb_param_t::ble_scan_result_evt_param scan_result_{}; | ||||
| @@ -231,7 +231,12 @@ class ESP32BLETracker : public Component, public GAPEventHandler, public GATTcEv | ||||
|   SemaphoreHandle_t scan_result_lock_; | ||||
|   SemaphoreHandle_t scan_end_lock_; | ||||
|   size_t scan_result_index_{0}; | ||||
|   esp_ble_gap_cb_param_t::ble_scan_result_evt_param scan_result_buffer_[16]; | ||||
| #if CONFIG_SPIRAM | ||||
|   const static u_int8_t SCAN_RESULT_BUFFER_SIZE = 32; | ||||
| #else | ||||
|   const static u_int8_t SCAN_RESULT_BUFFER_SIZE = 16; | ||||
| #endif  // CONFIG_SPIRAM | ||||
|   esp_ble_gap_cb_param_t::ble_scan_result_evt_param *scan_result_buffer_; | ||||
|   esp_bt_status_t scan_start_failed_{ESP_BT_STATUS_SUCCESS}; | ||||
|   esp_bt_status_t scan_set_param_failed_{ESP_BT_STATUS_SUCCESS}; | ||||
| }; | ||||
|   | ||||
| @@ -240,7 +240,6 @@ async def to_code(config): | ||||
|  | ||||
| # Called by writer.py | ||||
| def copy_files(): | ||||
|  | ||||
|     dir = os.path.dirname(__file__) | ||||
|     post_build_file = os.path.join(dir, "post_build.py.script") | ||||
|     copy_file_if_changed( | ||||
|   | ||||
| @@ -43,8 +43,7 @@ void EthernetComponent::setup() { | ||||
|   eth_phy_config_t phy_config = ETH_PHY_DEFAULT_CONFIG(); | ||||
|  | ||||
|   phy_config.phy_addr = this->phy_addr_; | ||||
|   if (this->power_pin_ != -1) | ||||
|     phy_config.reset_gpio_num = this->power_pin_; | ||||
|   phy_config.reset_gpio_num = this->power_pin_; | ||||
|  | ||||
|   mac_config.smi_mdc_gpio_num = this->mdc_pin_; | ||||
|   mac_config.smi_mdio_gpio_num = this->mdio_pin_; | ||||
|   | ||||
| @@ -1,90 +1,32 @@ | ||||
| import re | ||||
| import logging | ||||
| from pathlib import Path | ||||
|  | ||||
| import esphome.config_validation as cv | ||||
| from esphome import git, loader | ||||
| from esphome.const import ( | ||||
|     CONF_COMPONENTS, | ||||
|     CONF_EXTERNAL_COMPONENTS, | ||||
|     CONF_PASSWORD, | ||||
|     CONF_PATH, | ||||
|     CONF_REF, | ||||
|     CONF_REFRESH, | ||||
|     CONF_SOURCE, | ||||
|     CONF_URL, | ||||
|     CONF_TYPE, | ||||
|     CONF_EXTERNAL_COMPONENTS, | ||||
|     CONF_PATH, | ||||
|     CONF_URL, | ||||
|     CONF_USERNAME, | ||||
|     CONF_PASSWORD, | ||||
|     TYPE_GIT, | ||||
|     TYPE_LOCAL, | ||||
| ) | ||||
| from esphome.core import CORE | ||||
| from esphome import git, loader | ||||
|  | ||||
| _LOGGER = logging.getLogger(__name__) | ||||
|  | ||||
| DOMAIN = CONF_EXTERNAL_COMPONENTS | ||||
|  | ||||
| TYPE_GIT = "git" | ||||
| TYPE_LOCAL = "local" | ||||
|  | ||||
|  | ||||
| GIT_SCHEMA = { | ||||
|     cv.Required(CONF_URL): cv.url, | ||||
|     cv.Optional(CONF_REF): cv.git_ref, | ||||
|     cv.Optional(CONF_USERNAME): cv.string, | ||||
|     cv.Optional(CONF_PASSWORD): cv.string, | ||||
| } | ||||
| LOCAL_SCHEMA = { | ||||
|     cv.Required(CONF_PATH): cv.directory, | ||||
| } | ||||
|  | ||||
|  | ||||
| def validate_source_shorthand(value): | ||||
|     if not isinstance(value, str): | ||||
|         raise cv.Invalid("Shorthand only for strings") | ||||
|     try: | ||||
|         return SOURCE_SCHEMA({CONF_TYPE: TYPE_LOCAL, CONF_PATH: value}) | ||||
|     except cv.Invalid: | ||||
|         pass | ||||
|     # Regex for GitHub repo name with optional branch/tag | ||||
|     # Note: git allows other branch/tag names as well, but never seen them used before | ||||
|     m = re.match( | ||||
|         r"github://(?:([a-zA-Z0-9\-]+)/([a-zA-Z0-9\-\._]+)(?:@([a-zA-Z0-9\-_.\./]+))?|pr#([0-9]+))", | ||||
|         value, | ||||
|     ) | ||||
|     if m is None: | ||||
|         raise cv.Invalid( | ||||
|             "Source is not a file system path, in expected github://username/name[@branch-or-tag] or github://pr#1234 format!" | ||||
|         ) | ||||
|     if m.group(4): | ||||
|         conf = { | ||||
|             CONF_TYPE: TYPE_GIT, | ||||
|             CONF_URL: "https://github.com/esphome/esphome.git", | ||||
|             CONF_REF: f"pull/{m.group(4)}/head", | ||||
|         } | ||||
|     else: | ||||
|         conf = { | ||||
|             CONF_TYPE: TYPE_GIT, | ||||
|             CONF_URL: f"https://github.com/{m.group(1)}/{m.group(2)}.git", | ||||
|         } | ||||
|         if m.group(3): | ||||
|             conf[CONF_REF] = m.group(3) | ||||
|  | ||||
|     return SOURCE_SCHEMA(conf) | ||||
|  | ||||
|  | ||||
| SOURCE_SCHEMA = cv.Any( | ||||
|     validate_source_shorthand, | ||||
|     cv.typed_schema( | ||||
|         { | ||||
|             TYPE_GIT: cv.Schema(GIT_SCHEMA), | ||||
|             TYPE_LOCAL: cv.Schema(LOCAL_SCHEMA), | ||||
|         } | ||||
|     ), | ||||
| ) | ||||
|  | ||||
|  | ||||
| CONFIG_SCHEMA = cv.ensure_list( | ||||
|     { | ||||
|         cv.Required(CONF_SOURCE): SOURCE_SCHEMA, | ||||
|         cv.Required(CONF_SOURCE): cv.SOURCE_SCHEMA, | ||||
|         cv.Optional(CONF_REFRESH, default="1d"): cv.All(cv.string, cv.source_refresh), | ||||
|         cv.Optional(CONF_COMPONENTS, default="all"): cv.Any( | ||||
|             "all", cv.ensure_list(cv.string) | ||||
|   | ||||
| @@ -41,9 +41,9 @@ DeviceInformationTrigger = ezo_ns.class_( | ||||
| LedTrigger = ezo_ns.class_("LedTrigger", automation.Trigger.template(cg.bool_)) | ||||
|  | ||||
| CONFIG_SCHEMA = ( | ||||
|     sensor.SENSOR_SCHEMA.extend( | ||||
|     sensor.sensor_schema(EZOSensor) | ||||
|     .extend( | ||||
|         { | ||||
|             cv.GenerateID(): cv.declare_id(EZOSensor), | ||||
|             cv.Optional(CONF_ON_CUSTOM): automation.validate_automation( | ||||
|                 { | ||||
|                     cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(CustomTrigger), | ||||
|   | ||||
| @@ -13,15 +13,12 @@ FactoryResetButton = factory_reset_ns.class_( | ||||
|     "FactoryResetButton", button.Button, cg.Component | ||||
| ) | ||||
|  | ||||
| CONFIG_SCHEMA = ( | ||||
|     button.button_schema( | ||||
|         device_class=DEVICE_CLASS_RESTART, | ||||
|         entity_category=ENTITY_CATEGORY_CONFIG, | ||||
|         icon=ICON_RESTART_ALERT, | ||||
|     ) | ||||
|     .extend({cv.GenerateID(): cv.declare_id(FactoryResetButton)}) | ||||
|     .extend(cv.COMPONENT_SCHEMA) | ||||
| ) | ||||
| CONFIG_SCHEMA = button.button_schema( | ||||
|     FactoryResetButton, | ||||
|     device_class=DEVICE_CLASS_RESTART, | ||||
|     entity_category=ENTITY_CATEGORY_CONFIG, | ||||
|     icon=ICON_RESTART_ALERT, | ||||
| ).extend(cv.COMPONENT_SCHEMA) | ||||
|  | ||||
|  | ||||
| async def to_code(config): | ||||
|   | ||||
							
								
								
									
										0
									
								
								esphome/components/fs3000/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								esphome/components/fs3000/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										107
									
								
								esphome/components/fs3000/fs3000.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								esphome/components/fs3000/fs3000.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,107 @@ | ||||
| #include "fs3000.h" | ||||
| #include "esphome/core/log.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace fs3000 { | ||||
|  | ||||
| static const char *const TAG = "fs3000"; | ||||
|  | ||||
| void FS3000Component::setup() { | ||||
|   ESP_LOGCONFIG(TAG, "Setting up FS3000..."); | ||||
|  | ||||
|   if (model_ == FIVE) { | ||||
|     // datasheet gives 9 points to interpolate from for the 1005 model | ||||
|     static const uint16_t RAW_DATA_POINTS_1005[9] = {409, 915, 1522, 2066, 2523, 2908, 3256, 3572, 3686}; | ||||
|     static const float MPS_DATA_POINTS_1005[9] = {0.0, 1.07, 2.01, 3.0, 3.97, 4.96, 5.98, 6.99, 7.23}; | ||||
|  | ||||
|     std::copy(RAW_DATA_POINTS_1005, RAW_DATA_POINTS_1005 + 9, this->raw_data_points_); | ||||
|     std::copy(MPS_DATA_POINTS_1005, MPS_DATA_POINTS_1005 + 9, this->mps_data_points_); | ||||
|   } else if (model_ == FIFTEEN) { | ||||
|     // datasheet gives 13 points to extrapolate from for the 1015 model | ||||
|     static const uint16_t RAW_DATA_POINTS_1015[13] = {409,  1203, 1597, 1908, 2187, 2400, 2629, | ||||
|                                                       2801, 3006, 3178, 3309, 3563, 3686}; | ||||
|     static const float MPS_DATA_POINTS_1015[13] = {0.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 13.0, 15.0}; | ||||
|  | ||||
|     std::copy(RAW_DATA_POINTS_1015, RAW_DATA_POINTS_1015 + 13, this->raw_data_points_); | ||||
|     std::copy(MPS_DATA_POINTS_1015, MPS_DATA_POINTS_1015 + 13, this->mps_data_points_); | ||||
|   } | ||||
| } | ||||
|  | ||||
| void FS3000Component::update() { | ||||
|   // 5 bytes of data read from fs3000 sensor | ||||
|   //  byte 1 - checksum | ||||
|   //  byte 2 - (lower 4 bits) high byte of sensor reading | ||||
|   //  byte 3 - (8 bits) low byte of sensor reading | ||||
|   //  byte 4 - generic checksum data | ||||
|   //  byte 5 - generic checksum data | ||||
|  | ||||
|   uint8_t data[5]; | ||||
|  | ||||
|   if (!this->read_bytes_raw(data, 5)) { | ||||
|     this->status_set_warning(); | ||||
|     ESP_LOGW(TAG, "Error reading data from FS3000"); | ||||
|     this->publish_state(NAN); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   // checksum passes if the modulo 256 sum of the five bytes is 0 | ||||
|   uint8_t checksum = 0; | ||||
|   for (uint8_t i : data) { | ||||
|     checksum += i; | ||||
|   } | ||||
|  | ||||
|   if (checksum != 0) { | ||||
|     this->status_set_warning(); | ||||
|     ESP_LOGW(TAG, "Checksum failure when reading from FS3000"); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   // raw value information is 12 bits | ||||
|   uint16_t raw_value = (data[1] << 8) | data[2]; | ||||
|   ESP_LOGV(TAG, "Got raw reading=%i", raw_value); | ||||
|  | ||||
|   // convert and publish the raw value into m/s using the table of data points in the datasheet | ||||
|   this->publish_state(fit_raw_(raw_value)); | ||||
|  | ||||
|   this->status_clear_warning(); | ||||
| } | ||||
|  | ||||
| void FS3000Component::dump_config() { | ||||
|   ESP_LOGCONFIG(TAG, "FS3000:"); | ||||
|   LOG_I2C_DEVICE(this); | ||||
|   LOG_UPDATE_INTERVAL(this); | ||||
|   LOG_SENSOR("  ", "Air Velocity", this); | ||||
| } | ||||
|  | ||||
| float FS3000Component::fit_raw_(uint16_t raw_value) { | ||||
|   // converts a raw value read from the FS3000 into a speed in m/s based on the | ||||
|   // reference data points given in the datasheet | ||||
|   // fits raw reading using a linear interpolation between each data point | ||||
|  | ||||
|   uint8_t end = 8;  // assume model 1005, which has 9 data points | ||||
|   if (this->model_ == FIFTEEN) | ||||
|     end = 12;  // model 1015 has 13 data points | ||||
|  | ||||
|   if (raw_value <= this->raw_data_points_[0]) {  // less than smallest data point returns first data point | ||||
|     return this->mps_data_points_[0]; | ||||
|   } else if (raw_value >= this->raw_data_points_[end]) {  // greater than largest data point returns max speed | ||||
|     return this->mps_data_points_[end]; | ||||
|   } else { | ||||
|     uint8_t i = 0; | ||||
|  | ||||
|     // determine between which data points does the reading fall, i-1 and i | ||||
|     while (raw_value > this->raw_data_points_[i]) { | ||||
|       ++i; | ||||
|     } | ||||
|  | ||||
|     // calculate the slope of the secant line between the two data points that surrounds the reading | ||||
|     float slope = (this->mps_data_points_[i] - this->mps_data_points_[i - 1]) / | ||||
|                   (this->raw_data_points_[i] - this->raw_data_points_[i - 1]); | ||||
|  | ||||
|     // return the interpolated value for the reading | ||||
|     return (float(raw_value - this->raw_data_points_[i - 1])) * slope + this->mps_data_points_[i - 1]; | ||||
|   } | ||||
| } | ||||
|  | ||||
| }  // namespace fs3000 | ||||
| }  // namespace esphome | ||||
							
								
								
									
										35
									
								
								esphome/components/fs3000/fs3000.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								esphome/components/fs3000/fs3000.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,35 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/components/sensor/sensor.h" | ||||
| #include "esphome/components/i2c/i2c.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace fs3000 { | ||||
|  | ||||
| // FS3000 has two models, 1005 and 1015 | ||||
| //  1005 has a max speed detection of 7.23 m/s | ||||
| //  1015 has a max speed detection of 15 m/s | ||||
| enum FS3000Model { FIVE, FIFTEEN }; | ||||
|  | ||||
| class FS3000Component : public PollingComponent, public i2c::I2CDevice, public sensor::Sensor { | ||||
|  public: | ||||
|   void setup() override; | ||||
|   void update() override; | ||||
|  | ||||
|   void dump_config() override; | ||||
|   float get_setup_priority() const override { return setup_priority::DATA; } | ||||
|  | ||||
|   void set_model(FS3000Model model) { this->model_ = model; } | ||||
|  | ||||
|  protected: | ||||
|   FS3000Model model_{}; | ||||
|  | ||||
|   uint16_t raw_data_points_[13]; | ||||
|   float mps_data_points_[13]; | ||||
|  | ||||
|   float fit_raw_(uint16_t raw_value); | ||||
| }; | ||||
|  | ||||
| }  // namespace fs3000 | ||||
| }  // namespace esphome | ||||
							
								
								
									
										50
									
								
								esphome/components/fs3000/sensor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								esphome/components/fs3000/sensor.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,50 @@ | ||||
| # initially based off of TMP117 component | ||||
|  | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome.components import i2c, sensor | ||||
| from esphome.const import ( | ||||
|     CONF_MODEL, | ||||
|     DEVICE_CLASS_WIND_SPEED, | ||||
|     STATE_CLASS_MEASUREMENT, | ||||
| ) | ||||
|  | ||||
| DEPENDENCIES = ["i2c"] | ||||
| CODEOWNERS = ["@kahrendt"] | ||||
|  | ||||
| fs3000_ns = cg.esphome_ns.namespace("fs3000") | ||||
|  | ||||
| FS3000Model = fs3000_ns.enum("MODEL") | ||||
| FS3000_MODEL_OPTIONS = { | ||||
|     "1005": FS3000Model.FIVE, | ||||
|     "1015": FS3000Model.FIFTEEN, | ||||
| } | ||||
|  | ||||
| FS3000Component = fs3000_ns.class_( | ||||
|     "FS3000Component", cg.PollingComponent, i2c.I2CDevice, sensor.Sensor | ||||
| ) | ||||
|  | ||||
| CONFIG_SCHEMA = ( | ||||
|     sensor.sensor_schema( | ||||
|         FS3000Component, | ||||
|         unit_of_measurement="m/s", | ||||
|         accuracy_decimals=2, | ||||
|         device_class=DEVICE_CLASS_WIND_SPEED, | ||||
|         state_class=STATE_CLASS_MEASUREMENT, | ||||
|     ) | ||||
|     .extend( | ||||
|         { | ||||
|             cv.Required(CONF_MODEL): cv.enum(FS3000_MODEL_OPTIONS, lower=True), | ||||
|         } | ||||
|     ) | ||||
|     .extend(cv.polling_component_schema("60s")) | ||||
|     .extend(i2c.i2c_device_schema(0x28)) | ||||
| ) | ||||
|  | ||||
|  | ||||
| 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_model(config[CONF_MODEL])) | ||||
							
								
								
									
										1
									
								
								esphome/components/haier/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								esphome/components/haier/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| CODEOWNERS = ["@Yarikx"] | ||||
							
								
								
									
										43
									
								
								esphome/components/haier/climate.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								esphome/components/haier/climate.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,43 @@ | ||||
| from esphome.components import climate | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome.components import uart | ||||
| from esphome.components.climate import ClimateSwingMode | ||||
| from esphome.const import CONF_ID, CONF_SUPPORTED_SWING_MODES | ||||
|  | ||||
| DEPENDENCIES = ["uart"] | ||||
|  | ||||
| haier_ns = cg.esphome_ns.namespace("haier") | ||||
| HaierClimate = haier_ns.class_( | ||||
|     "HaierClimate", climate.Climate, cg.PollingComponent, uart.UARTDevice | ||||
| ) | ||||
|  | ||||
| ALLOWED_CLIMATE_SWING_MODES = { | ||||
|     "BOTH": ClimateSwingMode.CLIMATE_SWING_BOTH, | ||||
|     "VERTICAL": ClimateSwingMode.CLIMATE_SWING_VERTICAL, | ||||
|     "HORIZONTAL": ClimateSwingMode.CLIMATE_SWING_HORIZONTAL, | ||||
| } | ||||
|  | ||||
| validate_swing_modes = cv.enum(ALLOWED_CLIMATE_SWING_MODES, upper=True) | ||||
|  | ||||
| CONFIG_SCHEMA = cv.All( | ||||
|     climate.CLIMATE_SCHEMA.extend( | ||||
|         { | ||||
|             cv.GenerateID(): cv.declare_id(HaierClimate), | ||||
|             cv.Optional(CONF_SUPPORTED_SWING_MODES): cv.ensure_list( | ||||
|                 validate_swing_modes | ||||
|             ), | ||||
|         } | ||||
|     ) | ||||
|     .extend(cv.polling_component_schema("5s")) | ||||
|     .extend(uart.UART_DEVICE_SCHEMA), | ||||
| ) | ||||
|  | ||||
|  | ||||
| async def to_code(config): | ||||
|     var = cg.new_Pvariable(config[CONF_ID]) | ||||
|     await cg.register_component(var, config) | ||||
|     await climate.register_climate(var, config) | ||||
|     await uart.register_uart_device(var, config) | ||||
|     if CONF_SUPPORTED_SWING_MODES in config: | ||||
|         cg.add(var.set_supported_swing_modes(config[CONF_SUPPORTED_SWING_MODES])) | ||||
							
								
								
									
										302
									
								
								esphome/components/haier/haier.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										302
									
								
								esphome/components/haier/haier.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,302 @@ | ||||
| #include <cmath> | ||||
| #include "haier.h" | ||||
| #include "esphome/core/macros.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace haier { | ||||
|  | ||||
| static const char *const TAG = "haier"; | ||||
|  | ||||
| static const uint8_t TEMPERATURE = 13; | ||||
| static const uint8_t HUMIDITY = 15; | ||||
|  | ||||
| static const uint8_t MODE = 23; | ||||
|  | ||||
| static const uint8_t FAN_SPEED = 25; | ||||
|  | ||||
| static const uint8_t SWING = 27; | ||||
|  | ||||
| static const uint8_t POWER = 29; | ||||
| static const uint8_t POWER_MASK = 1; | ||||
|  | ||||
| static const uint8_t SET_TEMPERATURE = 35; | ||||
| static const uint8_t DECIMAL_MASK = (1 << 5); | ||||
|  | ||||
| static const uint8_t CRC = 36; | ||||
|  | ||||
| static const uint8_t COMFORT_PRESET_MASK = (1 << 3); | ||||
|  | ||||
| static const uint8_t MIN_VALID_TEMPERATURE = 16; | ||||
| static const uint8_t MAX_VALID_TEMPERATURE = 50; | ||||
| static const float TEMPERATURE_STEP = 0.5f; | ||||
|  | ||||
| static const uint8_t POLL_REQ[13] = {255, 255, 10, 0, 0, 0, 0, 0, 1, 1, 77, 1, 90}; | ||||
| static const uint8_t OFF_REQ[13] = {255, 255, 10, 0, 0, 0, 0, 0, 1, 1, 77, 3, 92}; | ||||
|  | ||||
| void HaierClimate::dump_config() { | ||||
|   ESP_LOGCONFIG(TAG, "Haier:"); | ||||
|   ESP_LOGCONFIG(TAG, "  Update interval: %u", this->get_update_interval()); | ||||
|   this->dump_traits_(TAG); | ||||
|   this->check_uart_settings(9600); | ||||
| } | ||||
|  | ||||
| void HaierClimate::loop() { | ||||
|   if (this->available() >= sizeof(this->data_)) { | ||||
|     this->read_array(this->data_, sizeof(this->data_)); | ||||
|     if (this->data_[0] != 255 || this->data_[1] != 255) | ||||
|       return; | ||||
|  | ||||
|     read_state_(this->data_, sizeof(this->data_)); | ||||
|   } | ||||
| } | ||||
|  | ||||
| void HaierClimate::update() { | ||||
|   this->write_array(POLL_REQ, sizeof(POLL_REQ)); | ||||
|   dump_message_("Poll sent", POLL_REQ, sizeof(POLL_REQ)); | ||||
| } | ||||
|  | ||||
| climate::ClimateTraits HaierClimate::traits() { | ||||
|   auto traits = climate::ClimateTraits(); | ||||
|  | ||||
|   traits.set_visual_min_temperature(MIN_VALID_TEMPERATURE); | ||||
|   traits.set_visual_max_temperature(MAX_VALID_TEMPERATURE); | ||||
|   traits.set_visual_temperature_step(TEMPERATURE_STEP); | ||||
|  | ||||
|   traits.set_supported_modes({climate::CLIMATE_MODE_OFF, climate::CLIMATE_MODE_HEAT_COOL, climate::CLIMATE_MODE_COOL, | ||||
|                               climate::CLIMATE_MODE_HEAT, climate::CLIMATE_MODE_FAN_ONLY, climate::CLIMATE_MODE_DRY}); | ||||
|  | ||||
|   traits.set_supported_fan_modes({ | ||||
|       climate::CLIMATE_FAN_AUTO, | ||||
|       climate::CLIMATE_FAN_LOW, | ||||
|       climate::CLIMATE_FAN_MEDIUM, | ||||
|       climate::CLIMATE_FAN_HIGH, | ||||
|   }); | ||||
|  | ||||
|   traits.set_supported_swing_modes(this->supported_swing_modes_); | ||||
|   traits.set_supports_current_temperature(true); | ||||
|   traits.set_supports_two_point_target_temperature(false); | ||||
|  | ||||
|   traits.add_supported_preset(climate::CLIMATE_PRESET_NONE); | ||||
|   traits.add_supported_preset(climate::CLIMATE_PRESET_COMFORT); | ||||
|  | ||||
|   return traits; | ||||
| } | ||||
|  | ||||
| void HaierClimate::read_state_(const uint8_t *data, uint8_t size) { | ||||
|   dump_message_("Received state", data, size); | ||||
|  | ||||
|   uint8_t check = data[CRC]; | ||||
|  | ||||
|   uint8_t crc = get_checksum_(data, size); | ||||
|  | ||||
|   if (check != crc) { | ||||
|     ESP_LOGW(TAG, "Invalid checksum"); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   this->current_temperature = data[TEMPERATURE]; | ||||
|  | ||||
|   this->target_temperature = data[SET_TEMPERATURE] + MIN_VALID_TEMPERATURE; | ||||
|  | ||||
|   if (data[POWER] & DECIMAL_MASK) { | ||||
|     this->target_temperature += 0.5f; | ||||
|   } | ||||
|  | ||||
|   switch (data[MODE]) { | ||||
|     case MODE_SMART: | ||||
|       this->mode = climate::CLIMATE_MODE_HEAT_COOL; | ||||
|       break; | ||||
|     case MODE_COOL: | ||||
|       this->mode = climate::CLIMATE_MODE_COOL; | ||||
|       break; | ||||
|     case MODE_HEAT: | ||||
|       this->mode = climate::CLIMATE_MODE_HEAT; | ||||
|       break; | ||||
|     case MODE_ONLY_FAN: | ||||
|       this->mode = climate::CLIMATE_MODE_FAN_ONLY; | ||||
|       break; | ||||
|     case MODE_DRY: | ||||
|       this->mode = climate::CLIMATE_MODE_DRY; | ||||
|       break; | ||||
|     default:  // other modes are unsupported | ||||
|       this->mode = climate::CLIMATE_MODE_HEAT_COOL; | ||||
|   } | ||||
|  | ||||
|   switch (data[FAN_SPEED]) { | ||||
|     case FAN_AUTO: | ||||
|       this->fan_mode = climate::CLIMATE_FAN_AUTO; | ||||
|       break; | ||||
|  | ||||
|     case FAN_MIN: | ||||
|       this->fan_mode = climate::CLIMATE_FAN_LOW; | ||||
|       break; | ||||
|  | ||||
|     case FAN_MIDDLE: | ||||
|       this->fan_mode = climate::CLIMATE_FAN_MEDIUM; | ||||
|       break; | ||||
|  | ||||
|     case FAN_MAX: | ||||
|       this->fan_mode = climate::CLIMATE_FAN_HIGH; | ||||
|       break; | ||||
|   } | ||||
|  | ||||
|   switch (data[SWING]) { | ||||
|     case SWING_OFF: | ||||
|       this->swing_mode = climate::CLIMATE_SWING_OFF; | ||||
|       break; | ||||
|  | ||||
|     case SWING_VERTICAL: | ||||
|       this->swing_mode = climate::CLIMATE_SWING_VERTICAL; | ||||
|       break; | ||||
|  | ||||
|     case SWING_HORIZONTAL: | ||||
|       this->swing_mode = climate::CLIMATE_SWING_HORIZONTAL; | ||||
|       break; | ||||
|  | ||||
|     case SWING_BOTH: | ||||
|       this->swing_mode = climate::CLIMATE_SWING_BOTH; | ||||
|       break; | ||||
|   } | ||||
|  | ||||
|   if (data[POWER] & COMFORT_PRESET_MASK) { | ||||
|     this->preset = climate::CLIMATE_PRESET_COMFORT; | ||||
|   } else { | ||||
|     this->preset = climate::CLIMATE_PRESET_NONE; | ||||
|   } | ||||
|  | ||||
|   if ((data[POWER] & POWER_MASK) == 0) { | ||||
|     this->mode = climate::CLIMATE_MODE_OFF; | ||||
|   } | ||||
|  | ||||
|   this->publish_state(); | ||||
| } | ||||
|  | ||||
| void HaierClimate::control(const climate::ClimateCall &call) { | ||||
|   if (call.get_mode().has_value()) { | ||||
|     switch (call.get_mode().value()) { | ||||
|       case climate::CLIMATE_MODE_OFF: | ||||
|         send_data_(OFF_REQ, sizeof(OFF_REQ)); | ||||
|         break; | ||||
|  | ||||
|       case climate::CLIMATE_MODE_HEAT_COOL: | ||||
|       case climate::CLIMATE_MODE_AUTO: | ||||
|         data_[POWER] |= POWER_MASK; | ||||
|         data_[MODE] = MODE_SMART; | ||||
|         break; | ||||
|       case climate::CLIMATE_MODE_HEAT: | ||||
|         data_[POWER] |= POWER_MASK; | ||||
|         data_[MODE] = MODE_HEAT; | ||||
|         break; | ||||
|       case climate::CLIMATE_MODE_COOL: | ||||
|         data_[POWER] |= POWER_MASK; | ||||
|         data_[MODE] = MODE_COOL; | ||||
|         break; | ||||
|  | ||||
|       case climate::CLIMATE_MODE_FAN_ONLY: | ||||
|         data_[POWER] |= POWER_MASK; | ||||
|         data_[MODE] = MODE_ONLY_FAN; | ||||
|         break; | ||||
|  | ||||
|       case climate::CLIMATE_MODE_DRY: | ||||
|         data_[POWER] |= POWER_MASK; | ||||
|         data_[MODE] = MODE_DRY; | ||||
|         break; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   if (call.get_preset().has_value()) { | ||||
|     if (call.get_preset().value() == climate::CLIMATE_PRESET_COMFORT) { | ||||
|       data_[POWER] |= COMFORT_PRESET_MASK; | ||||
|     } else { | ||||
|       data_[POWER] &= ~COMFORT_PRESET_MASK; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   if (call.get_target_temperature().has_value()) { | ||||
|     float target = call.get_target_temperature().value() - MIN_VALID_TEMPERATURE; | ||||
|  | ||||
|     data_[SET_TEMPERATURE] = (uint8_t) target; | ||||
|  | ||||
|     if ((int) target == std::lroundf(target)) { | ||||
|       data_[POWER] &= ~DECIMAL_MASK; | ||||
|     } else { | ||||
|       data_[POWER] |= DECIMAL_MASK; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   if (call.get_fan_mode().has_value()) { | ||||
|     switch (call.get_fan_mode().value()) { | ||||
|       case climate::CLIMATE_FAN_AUTO: | ||||
|         data_[FAN_SPEED] = FAN_AUTO; | ||||
|         break; | ||||
|       case climate::CLIMATE_FAN_LOW: | ||||
|         data_[FAN_SPEED] = FAN_MIN; | ||||
|         break; | ||||
|       case climate::CLIMATE_FAN_MEDIUM: | ||||
|         data_[FAN_SPEED] = FAN_MIDDLE; | ||||
|         break; | ||||
|       case climate::CLIMATE_FAN_HIGH: | ||||
|         data_[FAN_SPEED] = FAN_MAX; | ||||
|         break; | ||||
|  | ||||
|       default:  // other modes are unsupported | ||||
|         break; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   if (call.get_swing_mode().has_value()) { | ||||
|     switch (call.get_swing_mode().value()) { | ||||
|       case climate::CLIMATE_SWING_OFF: | ||||
|         data_[SWING] = SWING_OFF; | ||||
|         break; | ||||
|       case climate::CLIMATE_SWING_VERTICAL: | ||||
|         data_[SWING] = SWING_VERTICAL; | ||||
|         break; | ||||
|       case climate::CLIMATE_SWING_HORIZONTAL: | ||||
|         data_[SWING] = SWING_HORIZONTAL; | ||||
|         break; | ||||
|       case climate::CLIMATE_SWING_BOTH: | ||||
|         data_[SWING] = SWING_BOTH; | ||||
|         break; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   // Parts of the message that must have specific values for "send" command. | ||||
|   // The meaning of those values is unknown at the moment. | ||||
|   data_[9] = 1; | ||||
|   data_[10] = 77; | ||||
|   data_[11] = 95; | ||||
|   data_[17] = 0; | ||||
|  | ||||
|   // Compute checksum | ||||
|   uint8_t crc = get_checksum_(data_, sizeof(data_)); | ||||
|   data_[CRC] = crc; | ||||
|  | ||||
|   send_data_(data_, sizeof(data_)); | ||||
| } | ||||
|  | ||||
| void HaierClimate::send_data_(const uint8_t *message, uint8_t size) { | ||||
|   this->write_array(message, size); | ||||
|  | ||||
|   dump_message_("Sent message", message, size); | ||||
| } | ||||
|  | ||||
| void HaierClimate::dump_message_(const char *title, const uint8_t *message, uint8_t size) { | ||||
|   ESP_LOGV(TAG, "%s:", title); | ||||
|   for (int i = 0; i < size; i++) { | ||||
|     ESP_LOGV(TAG, "  byte %02d - %d", i, message[i]); | ||||
|   } | ||||
| } | ||||
|  | ||||
| uint8_t HaierClimate::get_checksum_(const uint8_t *message, size_t size) { | ||||
|   uint8_t position = size - 1; | ||||
|   uint8_t crc = 0; | ||||
|  | ||||
|   for (int i = 2; i < position; i++) | ||||
|     crc += message[i]; | ||||
|  | ||||
|   return crc; | ||||
| } | ||||
|  | ||||
| }  // namespace haier | ||||
| }  // namespace esphome | ||||
							
								
								
									
										37
									
								
								esphome/components/haier/haier.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								esphome/components/haier/haier.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,37 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/components/climate/climate.h" | ||||
| #include "esphome/components/uart/uart.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace haier { | ||||
|  | ||||
| enum Mode : uint8_t { MODE_SMART = 0, MODE_COOL = 1, MODE_HEAT = 2, MODE_ONLY_FAN = 3, MODE_DRY = 4 }; | ||||
| enum FanSpeed : uint8_t { FAN_MAX = 0, FAN_MIDDLE = 1, FAN_MIN = 2, FAN_AUTO = 3 }; | ||||
| enum SwingMode : uint8_t { SWING_OFF = 0, SWING_VERTICAL = 1, SWING_HORIZONTAL = 2, SWING_BOTH = 3 }; | ||||
|  | ||||
| class HaierClimate : public climate::Climate, public uart::UARTDevice, public PollingComponent { | ||||
|  public: | ||||
|   void loop() override; | ||||
|   void update() override; | ||||
|   void dump_config() override; | ||||
|   void control(const climate::ClimateCall &call) override; | ||||
|   void set_supported_swing_modes(const std::set<climate::ClimateSwingMode> &modes) { | ||||
|     this->supported_swing_modes_ = modes; | ||||
|   } | ||||
|  | ||||
|  protected: | ||||
|   climate::ClimateTraits traits() override; | ||||
|   void read_state_(const uint8_t *data, uint8_t size); | ||||
|   void send_data_(const uint8_t *message, uint8_t size); | ||||
|   void dump_message_(const char *title, const uint8_t *message, uint8_t size); | ||||
|   uint8_t get_checksum_(const uint8_t *message, size_t size); | ||||
|  | ||||
|  private: | ||||
|   uint8_t data_[37]; | ||||
|   std::set<climate::ClimateSwingMode> supported_swing_modes_{}; | ||||
| }; | ||||
|  | ||||
| }  // namespace haier | ||||
| }  // namespace esphome | ||||
| @@ -52,7 +52,6 @@ CONFIG_SCHEMA = ( | ||||
|  | ||||
|  | ||||
| async def to_code(config): | ||||
|  | ||||
|     var = cg.new_Pvariable(config[CONF_ID]) | ||||
|     await cg.register_component(var, config) | ||||
|     await spi.register_spi_device(var, config) | ||||
|   | ||||
| @@ -48,20 +48,23 @@ def inherit_accuracy_decimals(decimals, config): | ||||
|     return decimals + 2 | ||||
|  | ||||
|  | ||||
| CONFIG_SCHEMA = sensor.SENSOR_SCHEMA.extend( | ||||
|     { | ||||
|         cv.GenerateID(): cv.declare_id(IntegrationSensor), | ||||
|         cv.Required(CONF_SENSOR): cv.use_id(sensor.Sensor), | ||||
|         cv.Required(CONF_TIME_UNIT): cv.enum(INTEGRATION_TIMES, lower=True), | ||||
|         cv.Optional(CONF_INTEGRATION_METHOD, default="trapezoid"): cv.enum( | ||||
|             INTEGRATION_METHODS, lower=True | ||||
|         ), | ||||
|         cv.Optional(CONF_RESTORE, default=False): cv.boolean, | ||||
|         cv.Optional("min_save_interval"): cv.invalid( | ||||
|             "min_save_interval was removed in 2022.8.0. Please use the `preferences` -> `flash_write_interval` to adjust." | ||||
|         ), | ||||
|     } | ||||
| ).extend(cv.COMPONENT_SCHEMA) | ||||
| CONFIG_SCHEMA = ( | ||||
|     sensor.sensor_schema(IntegrationSensor) | ||||
|     .extend( | ||||
|         { | ||||
|             cv.Required(CONF_SENSOR): cv.use_id(sensor.Sensor), | ||||
|             cv.Required(CONF_TIME_UNIT): cv.enum(INTEGRATION_TIMES, lower=True), | ||||
|             cv.Optional(CONF_INTEGRATION_METHOD, default="trapezoid"): cv.enum( | ||||
|                 INTEGRATION_METHODS, lower=True | ||||
|             ), | ||||
|             cv.Optional(CONF_RESTORE, default=False): cv.boolean, | ||||
|             cv.Optional("min_save_interval"): cv.invalid( | ||||
|                 "min_save_interval was removed in 2022.8.0. Please use the `preferences` -> `flash_write_interval` to adjust." | ||||
|             ), | ||||
|         } | ||||
|     ) | ||||
|     .extend(cv.COMPONENT_SCHEMA) | ||||
| ) | ||||
|  | ||||
|  | ||||
| FINAL_VALIDATE_SCHEMA = cv.All( | ||||
|   | ||||
							
								
								
									
										1
									
								
								esphome/components/internal_temperature/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								esphome/components/internal_temperature/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| CODEOWNERS = ["@Mat931"] | ||||
| @@ -0,0 +1,58 @@ | ||||
| #include "internal_temperature.h" | ||||
| #include "esphome/core/log.h" | ||||
|  | ||||
| #ifdef USE_ESP32 | ||||
| #if defined(USE_ESP32_VARIANT_ESP32) | ||||
| // there is no official API available on the original ESP32 | ||||
| extern "C" { | ||||
| uint8_t temprature_sens_read(); | ||||
| } | ||||
| #elif defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) | ||||
| #include "driver/temp_sensor.h" | ||||
| #endif  // USE_ESP32_VARIANT | ||||
| #endif  // USE_ESP32 | ||||
| #ifdef USE_RP2040 | ||||
| #include "Arduino.h" | ||||
| #endif  // USE_RP2040 | ||||
|  | ||||
| namespace esphome { | ||||
| namespace internal_temperature { | ||||
|  | ||||
| static const char *const TAG = "internal_temperature"; | ||||
|  | ||||
| void InternalTemperatureSensor::update() { | ||||
|   float temperature = NAN; | ||||
|   bool success = false; | ||||
| #ifdef USE_ESP32 | ||||
| #if defined(USE_ESP32_VARIANT_ESP32) | ||||
|   uint8_t raw = temprature_sens_read(); | ||||
|   ESP_LOGV(TAG, "Raw temperature value: %d", raw); | ||||
|   temperature = (raw - 32) / 1.8f; | ||||
|   success = (raw != 128); | ||||
| #elif defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) | ||||
|   temp_sensor_config_t tsens = TSENS_CONFIG_DEFAULT(); | ||||
|   temp_sensor_set_config(tsens); | ||||
|   temp_sensor_start(); | ||||
|   esp_err_t result = temp_sensor_read_celsius(&temperature); | ||||
|   temp_sensor_stop(); | ||||
|   success = (result == ESP_OK); | ||||
| #endif  // USE_ESP32_VARIANT | ||||
| #endif  // USE_ESP32 | ||||
| #ifdef USE_RP2040 | ||||
|   temperature = analogReadTemp(); | ||||
|   success = (temperature != 0.0f); | ||||
| #endif  // USE_RP2040 | ||||
|   if (success && std::isfinite(temperature)) { | ||||
|     this->publish_state(temperature); | ||||
|   } else { | ||||
|     ESP_LOGD(TAG, "Ignoring invalid temperature (success=%d, value=%.1f)", success, temperature); | ||||
|     if (!this->has_state()) { | ||||
|       this->publish_state(NAN); | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| void InternalTemperatureSensor::dump_config() { LOG_SENSOR("", "Internal Temperature Sensor", this); } | ||||
|  | ||||
| }  // namespace internal_temperature | ||||
| }  // namespace esphome | ||||
| @@ -0,0 +1,17 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/components/sensor/sensor.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace internal_temperature { | ||||
|  | ||||
| class InternalTemperatureSensor : public sensor::Sensor, public PollingComponent { | ||||
|  public: | ||||
|   void dump_config() override; | ||||
|  | ||||
|   void update() override; | ||||
| }; | ||||
|  | ||||
| }  // namespace internal_temperature | ||||
| }  // namespace esphome | ||||
							
								
								
									
										31
									
								
								esphome/components/internal_temperature/sensor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								esphome/components/internal_temperature/sensor.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,31 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome.components import sensor | ||||
| from esphome.const import ( | ||||
|     STATE_CLASS_MEASUREMENT, | ||||
|     UNIT_CELSIUS, | ||||
|     DEVICE_CLASS_TEMPERATURE, | ||||
|     ENTITY_CATEGORY_DIAGNOSTIC, | ||||
| ) | ||||
|  | ||||
| internal_temperature_ns = cg.esphome_ns.namespace("internal_temperature") | ||||
| InternalTemperatureSensor = internal_temperature_ns.class_( | ||||
|     "InternalTemperatureSensor", sensor.Sensor, cg.PollingComponent | ||||
| ) | ||||
|  | ||||
| CONFIG_SCHEMA = cv.All( | ||||
|     sensor.sensor_schema( | ||||
|         InternalTemperatureSensor, | ||||
|         unit_of_measurement=UNIT_CELSIUS, | ||||
|         accuracy_decimals=1, | ||||
|         device_class=DEVICE_CLASS_TEMPERATURE, | ||||
|         state_class=STATE_CLASS_MEASUREMENT, | ||||
|         entity_category=ENTITY_CATEGORY_DIAGNOSTIC, | ||||
|     ).extend(cv.polling_component_schema("60s")), | ||||
|     cv.only_on(["esp32", "rp2040"]), | ||||
| ) | ||||
|  | ||||
|  | ||||
| async def to_code(config): | ||||
|     var = await sensor.new_sensor(config) | ||||
|     await cg.register_component(var, config) | ||||
| @@ -23,20 +23,23 @@ CONF_PROCESS_STD_DEV = "process_std_dev" | ||||
| CONF_STD_DEV = "std_dev" | ||||
|  | ||||
|  | ||||
| CONFIG_SCHEMA = sensor.SENSOR_SCHEMA.extend(cv.COMPONENT_SCHEMA).extend( | ||||
|     { | ||||
|         cv.GenerateID(): cv.declare_id(KalmanCombinatorComponent), | ||||
|         cv.Required(CONF_PROCESS_STD_DEV): cv.positive_float, | ||||
|         cv.Required(CONF_SOURCES): cv.ensure_list( | ||||
|             cv.Schema( | ||||
|                 { | ||||
|                     cv.Required(CONF_SOURCE): cv.use_id(sensor.Sensor), | ||||
|                     cv.Required(CONF_ERROR): cv.templatable(cv.positive_float), | ||||
|                 } | ||||
| CONFIG_SCHEMA = ( | ||||
|     sensor.sensor_schema(KalmanCombinatorComponent) | ||||
|     .extend(cv.COMPONENT_SCHEMA) | ||||
|     .extend( | ||||
|         { | ||||
|             cv.Required(CONF_PROCESS_STD_DEV): cv.positive_float, | ||||
|             cv.Required(CONF_SOURCES): cv.ensure_list( | ||||
|                 cv.Schema( | ||||
|                     { | ||||
|                         cv.Required(CONF_SOURCE): cv.use_id(sensor.Sensor), | ||||
|                         cv.Required(CONF_ERROR): cv.templatable(cv.positive_float), | ||||
|                     } | ||||
|                 ), | ||||
|             ), | ||||
|         ), | ||||
|         cv.Optional(CONF_STD_DEV): sensor.SENSOR_SCHEMA, | ||||
|     } | ||||
|             cv.Optional(CONF_STD_DEV): sensor.sensor_schema(), | ||||
|         } | ||||
|     ) | ||||
| ) | ||||
|  | ||||
| # Inherit some sensor values from the first source, for both the state and the error value | ||||
|   | ||||
							
								
								
									
										0
									
								
								esphome/components/kuntze/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								esphome/components/kuntze/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										91
									
								
								esphome/components/kuntze/kuntze.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										91
									
								
								esphome/components/kuntze/kuntze.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,91 @@ | ||||
| #include "kuntze.h" | ||||
| #include "esphome/core/log.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace kuntze { | ||||
|  | ||||
| static const char *const TAG = "kuntze"; | ||||
|  | ||||
| static const uint8_t CMD_READ_REG = 0x03; | ||||
| static const uint16_t REGISTER[] = {4136, 4160, 4680, 6000, 4688, 4728, 5832}; | ||||
|  | ||||
| void Kuntze::on_modbus_data(const std::vector<uint8_t> &data) { | ||||
|   auto get_16bit = [&](int i) -> uint16_t { return (uint16_t(data[i * 2]) << 8) | uint16_t(data[i * 2 + 1]); }; | ||||
|  | ||||
|   this->waiting_ = false; | ||||
|   ESP_LOGV(TAG, "Data: %s", hexencode(data).c_str()); | ||||
|  | ||||
|   float value = (float) get_16bit(0); | ||||
|   for (int i = 0; i < data[3]; i++) | ||||
|     value /= 10.0; | ||||
|   switch (this->state_) { | ||||
|     case 1: | ||||
|       ESP_LOGD(TAG, "pH=%.1f", value); | ||||
|       if (this->ph_sensor_ != nullptr) | ||||
|         this->ph_sensor_->publish_state(value); | ||||
|       break; | ||||
|     case 2: | ||||
|       ESP_LOGD(TAG, "temperature=%.1f", value); | ||||
|       if (this->temperature_sensor_ != nullptr) | ||||
|         this->temperature_sensor_->publish_state(value); | ||||
|       break; | ||||
|     case 3: | ||||
|       ESP_LOGD(TAG, "DIS1=%.1f", value); | ||||
|       if (this->dis1_sensor_ != nullptr) | ||||
|         this->dis1_sensor_->publish_state(value); | ||||
|       break; | ||||
|     case 4: | ||||
|       ESP_LOGD(TAG, "DIS2=%.1f", value); | ||||
|       if (this->dis2_sensor_ != nullptr) | ||||
|         this->dis2_sensor_->publish_state(value); | ||||
|       break; | ||||
|     case 5: | ||||
|       ESP_LOGD(TAG, "REDOX=%.1f", value); | ||||
|       if (this->redox_sensor_ != nullptr) | ||||
|         this->redox_sensor_->publish_state(value); | ||||
|       break; | ||||
|     case 6: | ||||
|       ESP_LOGD(TAG, "EC=%.1f", value); | ||||
|       if (this->ec_sensor_ != nullptr) | ||||
|         this->ec_sensor_->publish_state(value); | ||||
|       break; | ||||
|     case 7: | ||||
|       ESP_LOGD(TAG, "OCI=%.1f", value); | ||||
|       if (this->oci_sensor_ != nullptr) | ||||
|         this->oci_sensor_->publish_state(value); | ||||
|       break; | ||||
|   } | ||||
|   if (++this->state_ > 7) | ||||
|     this->state_ = 0; | ||||
| } | ||||
|  | ||||
| void Kuntze::loop() { | ||||
|   uint32_t now = millis(); | ||||
|   // timeout after 15 seconds | ||||
|   if (this->waiting_ && (now - this->last_send_ > 15000)) { | ||||
|     ESP_LOGW(TAG, "timed out waiting for response"); | ||||
|     this->waiting_ = false; | ||||
|   } | ||||
|   if (this->waiting_ || (this->state_ == 0)) | ||||
|     return; | ||||
|   this->last_send_ = now; | ||||
|   send(CMD_READ_REG, REGISTER[this->state_ - 1], 2); | ||||
|   this->waiting_ = true; | ||||
| } | ||||
|  | ||||
| void Kuntze::update() { this->state_ = 1; } | ||||
|  | ||||
| void Kuntze::dump_config() { | ||||
|   ESP_LOGCONFIG(TAG, "Kuntze:"); | ||||
|   ESP_LOGCONFIG(TAG, "  Address: 0x%02X", this->address_); | ||||
|   LOG_SENSOR("", "pH", this->ph_sensor_); | ||||
|   LOG_SENSOR("", "temperature", this->temperature_sensor_); | ||||
|   LOG_SENSOR("", "DIS1", this->dis1_sensor_); | ||||
|   LOG_SENSOR("", "DIS2", this->dis2_sensor_); | ||||
|   LOG_SENSOR("", "REDOX", this->redox_sensor_); | ||||
|   LOG_SENSOR("", "EC", this->ec_sensor_); | ||||
|   LOG_SENSOR("", "OCI", this->oci_sensor_); | ||||
| } | ||||
|  | ||||
| }  // namespace kuntze | ||||
| }  // namespace esphome | ||||
							
								
								
									
										42
									
								
								esphome/components/kuntze/kuntze.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								esphome/components/kuntze/kuntze.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,42 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/components/sensor/sensor.h" | ||||
| #include "esphome/components/modbus/modbus.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace kuntze { | ||||
|  | ||||
| class Kuntze : public PollingComponent, public modbus::ModbusDevice { | ||||
|  public: | ||||
|   void set_ph_sensor(sensor::Sensor *ph_sensor) { ph_sensor_ = ph_sensor; } | ||||
|   void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; } | ||||
|   void set_dis1_sensor(sensor::Sensor *dis1_sensor) { dis1_sensor_ = dis1_sensor; } | ||||
|   void set_dis2_sensor(sensor::Sensor *dis2_sensor) { dis2_sensor_ = dis2_sensor; } | ||||
|   void set_redox_sensor(sensor::Sensor *redox_sensor) { redox_sensor_ = redox_sensor; } | ||||
|   void set_ec_sensor(sensor::Sensor *ec_sensor) { ec_sensor_ = ec_sensor; } | ||||
|   void set_oci_sensor(sensor::Sensor *oci_sensor) { oci_sensor_ = oci_sensor; } | ||||
|  | ||||
|   void loop() override; | ||||
|   void update() override; | ||||
|  | ||||
|   void on_modbus_data(const std::vector<uint8_t> &data) override; | ||||
|  | ||||
|   void dump_config() override; | ||||
|  | ||||
|  protected: | ||||
|   int state_{0}; | ||||
|   bool waiting_{false}; | ||||
|   uint32_t last_send_{0}; | ||||
|  | ||||
|   sensor::Sensor *ph_sensor_{nullptr}; | ||||
|   sensor::Sensor *temperature_sensor_{nullptr}; | ||||
|   sensor::Sensor *dis1_sensor_{nullptr}; | ||||
|   sensor::Sensor *dis2_sensor_{nullptr}; | ||||
|   sensor::Sensor *redox_sensor_{nullptr}; | ||||
|   sensor::Sensor *ec_sensor_{nullptr}; | ||||
|   sensor::Sensor *oci_sensor_{nullptr}; | ||||
| }; | ||||
|  | ||||
| }  // namespace kuntze | ||||
| }  // namespace esphome | ||||
							
								
								
									
										123
									
								
								esphome/components/kuntze/sensor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										123
									
								
								esphome/components/kuntze/sensor.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,123 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome.components import sensor, modbus | ||||
| from esphome.const import ( | ||||
|     CONF_ID, | ||||
|     CONF_EC, | ||||
|     CONF_PH, | ||||
|     CONF_TEMPERATURE, | ||||
|     ICON_EMPTY, | ||||
|     ICON_THERMOMETER, | ||||
|     UNIT_CELSIUS, | ||||
|     UNIT_EMPTY, | ||||
|     UNIT_PH, | ||||
|     STATE_CLASS_MEASUREMENT, | ||||
|     DEVICE_CLASS_EMPTY, | ||||
|     DEVICE_CLASS_TEMPERATURE, | ||||
| ) | ||||
|  | ||||
| CODEOWNERS = ["@ssieb"] | ||||
|  | ||||
| AUTO_LOAD = ["modbus"] | ||||
|  | ||||
| kuntze_ns = cg.esphome_ns.namespace("kuntze") | ||||
| Kuntze = kuntze_ns.class_("Kuntze", cg.PollingComponent, modbus.ModbusDevice) | ||||
|  | ||||
| CONF_DIS1 = "dis1" | ||||
| CONF_DIS2 = "dis2" | ||||
| CONF_REDOX = "redox" | ||||
| CONF_OCI = "oci" | ||||
|  | ||||
| CONFIG_SCHEMA = ( | ||||
|     cv.Schema( | ||||
|         { | ||||
|             cv.GenerateID(): cv.declare_id(Kuntze), | ||||
|             cv.Optional(CONF_PH): sensor.sensor_schema( | ||||
|                 unit_of_measurement=UNIT_PH, | ||||
|                 icon=ICON_EMPTY, | ||||
|                 accuracy_decimals=1, | ||||
|                 state_class=STATE_CLASS_MEASUREMENT, | ||||
|                 device_class=DEVICE_CLASS_EMPTY, | ||||
|             ), | ||||
|             cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( | ||||
|                 unit_of_measurement=UNIT_CELSIUS, | ||||
|                 icon=ICON_THERMOMETER, | ||||
|                 accuracy_decimals=1, | ||||
|                 state_class=STATE_CLASS_MEASUREMENT, | ||||
|                 device_class=DEVICE_CLASS_TEMPERATURE, | ||||
|             ), | ||||
|             cv.Optional(CONF_DIS1): sensor.sensor_schema( | ||||
|                 unit_of_measurement=UNIT_EMPTY, | ||||
|                 icon=ICON_EMPTY, | ||||
|                 accuracy_decimals=1, | ||||
|                 state_class=STATE_CLASS_MEASUREMENT, | ||||
|                 device_class=DEVICE_CLASS_EMPTY, | ||||
|             ), | ||||
|             cv.Optional(CONF_DIS2): sensor.sensor_schema( | ||||
|                 unit_of_measurement=UNIT_EMPTY, | ||||
|                 icon=ICON_EMPTY, | ||||
|                 accuracy_decimals=1, | ||||
|                 state_class=STATE_CLASS_MEASUREMENT, | ||||
|                 device_class=DEVICE_CLASS_EMPTY, | ||||
|             ), | ||||
|             cv.Optional(CONF_REDOX): sensor.sensor_schema( | ||||
|                 unit_of_measurement=UNIT_EMPTY, | ||||
|                 icon=ICON_EMPTY, | ||||
|                 accuracy_decimals=1, | ||||
|                 state_class=STATE_CLASS_MEASUREMENT, | ||||
|                 device_class=DEVICE_CLASS_EMPTY, | ||||
|             ), | ||||
|             cv.Optional(CONF_EC): sensor.sensor_schema( | ||||
|                 unit_of_measurement=UNIT_EMPTY, | ||||
|                 icon=ICON_EMPTY, | ||||
|                 accuracy_decimals=1, | ||||
|                 state_class=STATE_CLASS_MEASUREMENT, | ||||
|                 device_class=DEVICE_CLASS_EMPTY, | ||||
|             ), | ||||
|             cv.Optional(CONF_OCI): sensor.sensor_schema( | ||||
|                 unit_of_measurement=UNIT_EMPTY, | ||||
|                 icon=ICON_EMPTY, | ||||
|                 accuracy_decimals=1, | ||||
|                 state_class=STATE_CLASS_MEASUREMENT, | ||||
|                 device_class=DEVICE_CLASS_EMPTY, | ||||
|             ), | ||||
|         } | ||||
|     ) | ||||
|     .extend(cv.polling_component_schema("60s")) | ||||
|     .extend(modbus.modbus_device_schema(0x01)) | ||||
| ) | ||||
|  | ||||
|  | ||||
| async def to_code(config): | ||||
|     var = cg.new_Pvariable(config[CONF_ID]) | ||||
|     await cg.register_component(var, config) | ||||
|     await modbus.register_modbus_device(var, config) | ||||
|  | ||||
|     if CONF_PH in config: | ||||
|         conf = config[CONF_PH] | ||||
|         sens = await sensor.new_sensor(conf) | ||||
|         cg.add(var.set_ph_sensor(sens)) | ||||
|     if CONF_TEMPERATURE in config: | ||||
|         conf = config[CONF_TEMPERATURE] | ||||
|         sens = await sensor.new_sensor(conf) | ||||
|         cg.add(var.set_temperature_sensor(sens)) | ||||
|     if CONF_DIS1 in config: | ||||
|         conf = config[CONF_DIS1] | ||||
|         sens = await sensor.new_sensor(conf) | ||||
|         cg.add(var.set_dis1_sensor(sens)) | ||||
|     if CONF_DIS2 in config: | ||||
|         conf = config[CONF_DIS2] | ||||
|         sens = await sensor.new_sensor(conf) | ||||
|         cg.add(var.set_dis2_sensor(sens)) | ||||
|     if CONF_REDOX in config: | ||||
|         conf = config[CONF_REDOX] | ||||
|         sens = await sensor.new_sensor(conf) | ||||
|         cg.add(var.set_redox_sensor(sens)) | ||||
|     if CONF_EC in config: | ||||
|         conf = config[CONF_EC] | ||||
|         sens = await sensor.new_sensor(conf) | ||||
|         cg.add(var.set_ec_sensor(sens)) | ||||
|     if CONF_OCI in config: | ||||
|         conf = config[CONF_OCI] | ||||
|         sens = await sensor.new_sensor(conf) | ||||
|         cg.add(var.set_oci_sensor(sens)) | ||||
| @@ -30,9 +30,8 @@ def check_button(obj): | ||||
|  | ||||
|  | ||||
| CONFIG_SCHEMA = cv.All( | ||||
|     binary_sensor.BINARY_SENSOR_SCHEMA.extend( | ||||
|     binary_sensor.binary_sensor_schema(MatrixKeypadBinarySensor).extend( | ||||
|         { | ||||
|             cv.GenerateID(): cv.declare_id(MatrixKeypadBinarySensor), | ||||
|             cv.GenerateID(CONF_KEYPAD_ID): cv.use_id(MatrixKeypad), | ||||
|             cv.Optional(CONF_ROW): cv.int_, | ||||
|             cv.Optional(CONF_COL): cv.int_, | ||||
|   | ||||
| @@ -14,14 +14,17 @@ MCP3008Sensor = mcp3008_ns.class_( | ||||
| CONF_REFERENCE_VOLTAGE = "reference_voltage" | ||||
| CONF_MCP3008_ID = "mcp3008_id" | ||||
|  | ||||
| CONFIG_SCHEMA = sensor.SENSOR_SCHEMA.extend( | ||||
|     { | ||||
|         cv.GenerateID(): cv.declare_id(MCP3008Sensor), | ||||
|         cv.GenerateID(CONF_MCP3008_ID): cv.use_id(MCP3008), | ||||
|         cv.Required(CONF_NUMBER): cv.int_, | ||||
|         cv.Optional(CONF_REFERENCE_VOLTAGE, default="3.3V"): cv.voltage, | ||||
|     } | ||||
| ).extend(cv.polling_component_schema("1s")) | ||||
| CONFIG_SCHEMA = ( | ||||
|     sensor.sensor_schema(MCP3008Sensor) | ||||
|     .extend( | ||||
|         { | ||||
|             cv.GenerateID(CONF_MCP3008_ID): cv.use_id(MCP3008), | ||||
|             cv.Required(CONF_NUMBER): cv.int_, | ||||
|             cv.Optional(CONF_REFERENCE_VOLTAGE, default="3.3V"): cv.voltage, | ||||
|         } | ||||
|     ) | ||||
|     .extend(cv.polling_component_schema("1s")) | ||||
| ) | ||||
|  | ||||
|  | ||||
| async def to_code(config): | ||||
|   | ||||
| @@ -13,13 +13,16 @@ MCP3204Sensor = mcp3204_ns.class_( | ||||
| ) | ||||
| CONF_MCP3204_ID = "mcp3204_id" | ||||
|  | ||||
| CONFIG_SCHEMA = sensor.SENSOR_SCHEMA.extend( | ||||
|     { | ||||
|         cv.GenerateID(): cv.declare_id(MCP3204Sensor), | ||||
|         cv.GenerateID(CONF_MCP3204_ID): cv.use_id(MCP3204), | ||||
|         cv.Required(CONF_NUMBER): cv.int_range(min=0, max=7), | ||||
|     } | ||||
| ).extend(cv.polling_component_schema("60s")) | ||||
| CONFIG_SCHEMA = ( | ||||
|     sensor.sensor_schema(MCP3204Sensor) | ||||
|     .extend( | ||||
|         { | ||||
|             cv.GenerateID(CONF_MCP3204_ID): cv.use_id(MCP3204), | ||||
|             cv.Required(CONF_NUMBER): cv.int_range(min=0, max=7), | ||||
|         } | ||||
|     ) | ||||
|     .extend(cv.polling_component_schema("60s")) | ||||
| ) | ||||
|  | ||||
|  | ||||
| async def to_code(config): | ||||
|   | ||||
| @@ -12,7 +12,7 @@ namespace modbus_controller { | ||||
| class ModbusBinarySensor : public Component, public binary_sensor::BinarySensor, public SensorItem { | ||||
|  public: | ||||
|   ModbusBinarySensor(ModbusRegisterType register_type, uint16_t start_address, uint8_t offset, uint32_t bitmask, | ||||
|                      uint8_t skip_updates, bool force_new_range) { | ||||
|                      uint16_t skip_updates, bool force_new_range) { | ||||
|     this->register_type = register_type; | ||||
|     this->start_address = start_address; | ||||
|     this->offset = offset; | ||||
|   | ||||
| @@ -246,7 +246,7 @@ class SensorItem { | ||||
|   uint8_t offset; | ||||
|   uint8_t register_count; | ||||
|   uint8_t response_bytes{0}; | ||||
|   uint8_t skip_updates; | ||||
|   uint16_t skip_updates; | ||||
|   std::vector<uint8_t> custom_data{}; | ||||
|   bool force_new_range{false}; | ||||
| }; | ||||
| @@ -288,9 +288,9 @@ struct RegisterRange { | ||||
|   uint16_t start_address; | ||||
|   ModbusRegisterType register_type; | ||||
|   uint8_t register_count; | ||||
|   uint8_t skip_updates;          // the config value | ||||
|   SensorSet sensors;             // all sensors of this range | ||||
|   uint8_t skip_updates_counter;  // the running value | ||||
|   uint16_t skip_updates;          // the config value | ||||
|   SensorSet sensors;              // all sensors of this range | ||||
|   uint16_t skip_updates_counter;  // the running value | ||||
| }; | ||||
|  | ||||
| class ModbusCommandItem { | ||||
|   | ||||
| @@ -60,9 +60,10 @@ def validate_modbus_number(config): | ||||
|  | ||||
|  | ||||
| CONFIG_SCHEMA = cv.All( | ||||
|     number.NUMBER_SCHEMA.extend(ModbusItemBaseSchema).extend( | ||||
|     number.number_schema(ModbusNumber) | ||||
|     .extend(ModbusItemBaseSchema) | ||||
|     .extend( | ||||
|         { | ||||
|             cv.GenerateID(): cv.declare_id(ModbusNumber), | ||||
|             cv.Optional(CONF_REGISTER_TYPE, default="holding"): cv.enum( | ||||
|                 MODBUS_WRITE_REGISTER_TYPE | ||||
|             ), | ||||
|   | ||||
| @@ -14,7 +14,7 @@ using value_to_data_t = std::function<float>(float); | ||||
| class ModbusNumber : public number::Number, public Component, public SensorItem { | ||||
|  public: | ||||
|   ModbusNumber(ModbusRegisterType register_type, uint16_t start_address, uint8_t offset, uint32_t bitmask, | ||||
|                SensorValueType value_type, int register_count, uint8_t skip_updates, bool force_new_range) { | ||||
|                SensorValueType value_type, int register_count, uint16_t skip_updates, bool force_new_range) { | ||||
|     this->register_type = register_type; | ||||
|     this->start_address = start_address; | ||||
|     this->offset = offset; | ||||
|   | ||||
| @@ -12,7 +12,7 @@ namespace modbus_controller { | ||||
|  | ||||
| class ModbusSelect : public Component, public select::Select, public SensorItem { | ||||
|  public: | ||||
|   ModbusSelect(SensorValueType sensor_value_type, uint16_t start_address, uint8_t register_count, uint8_t skip_updates, | ||||
|   ModbusSelect(SensorValueType sensor_value_type, uint16_t start_address, uint8_t register_count, uint16_t skip_updates, | ||||
|                bool force_new_range, std::vector<int64_t> mapping) { | ||||
|     this->register_type = ModbusRegisterType::HOLDING;  // not configurable | ||||
|     this->sensor_value_type = sensor_value_type; | ||||
|   | ||||
| @@ -32,11 +32,11 @@ ModbusSensor = modbus_controller_ns.class_( | ||||
| ) | ||||
|  | ||||
| CONFIG_SCHEMA = cv.All( | ||||
|     sensor.SENSOR_SCHEMA.extend(cv.COMPONENT_SCHEMA) | ||||
|     sensor.sensor_schema(ModbusSensor) | ||||
|     .extend(cv.COMPONENT_SCHEMA) | ||||
|     .extend(ModbusItemBaseSchema) | ||||
|     .extend( | ||||
|         { | ||||
|             cv.GenerateID(): cv.declare_id(ModbusSensor), | ||||
|             cv.Optional(CONF_REGISTER_TYPE): cv.enum(MODBUS_REGISTER_TYPE), | ||||
|             cv.Optional(CONF_VALUE_TYPE, default="U_WORD"): cv.enum(SENSOR_VALUE_TYPE), | ||||
|             cv.Optional(CONF_REGISTER_COUNT, default=0): cv.positive_int, | ||||
|   | ||||
| @@ -12,7 +12,7 @@ namespace modbus_controller { | ||||
| class ModbusSensor : public Component, public sensor::Sensor, public SensorItem { | ||||
|  public: | ||||
|   ModbusSensor(ModbusRegisterType register_type, uint16_t start_address, uint8_t offset, uint32_t bitmask, | ||||
|                SensorValueType value_type, int register_count, uint8_t skip_updates, bool force_new_range) { | ||||
|                SensorValueType value_type, int register_count, uint16_t skip_updates, bool force_new_range) { | ||||
|     this->register_type = register_type; | ||||
|     this->start_address = start_address; | ||||
|     this->offset = offset; | ||||
|   | ||||
| @@ -12,7 +12,7 @@ namespace modbus_controller { | ||||
| class ModbusSwitch : public Component, public switch_::Switch, public SensorItem { | ||||
|  public: | ||||
|   ModbusSwitch(ModbusRegisterType register_type, uint16_t start_address, uint8_t offset, uint32_t bitmask, | ||||
|                uint8_t skip_updates, bool force_new_range) { | ||||
|                uint16_t skip_updates, bool force_new_range) { | ||||
|     this->register_type = register_type; | ||||
|     this->start_address = start_address; | ||||
|     this->offset = offset; | ||||
|   | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user