mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-30 22:53:59 +00:00 
			
		
		
		
	Merge branch 'beta' into bump-2023.3.0
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 |   contents: read | ||||||
|   packages: 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: | jobs: | ||||||
|   check-docker: |   check-docker: | ||||||
|     name: Build docker containers |     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] |     branches: [dev, beta, release] | ||||||
|  |  | ||||||
|   pull_request: |   pull_request: | ||||||
|  |   merge_group: | ||||||
|  |  | ||||||
| permissions: | permissions: | ||||||
|   contents: read |   contents: read | ||||||
| @@ -181,9 +182,22 @@ jobs: | |||||||
|  |  | ||||||
|       - name: Run yamllint |       - name: Run yamllint | ||||||
|         if: matrix.id == 'yamllint' |         if: matrix.id == 'yamllint' | ||||||
|         uses: frenck/action-yamllint@v1.3.1 |         uses: frenck/action-yamllint@v1.4.0 | ||||||
|  |  | ||||||
|       - name: Suggested changes |       - name: Suggested changes | ||||||
|         run: script/ci-suggest-changes |         run: script/ci-suggest-changes | ||||||
|         # yamllint disable-line rule:line-length |         # yamllint disable-line rule:line-length | ||||||
|         if: always() && (matrix.id == 'clang-tidy' || matrix.id == 'clang-format' || matrix.id == 'lint-python') |         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 | # See https://pre-commit.com/hooks.html for more hooks | ||||||
| repos: | repos: | ||||||
|   - repo: https://github.com/ambv/black |   - repo: https://github.com/ambv/black | ||||||
|     rev: 22.12.0 |     rev: 23.1.0 | ||||||
|     hooks: |     hooks: | ||||||
|       - id: black |       - id: black | ||||||
|         args: |         args: | ||||||
| @@ -27,7 +27,7 @@ repos: | |||||||
|           - --branch=release |           - --branch=release | ||||||
|           - --branch=beta |           - --branch=beta | ||||||
|   - repo: https://github.com/asottile/pyupgrade |   - repo: https://github.com/asottile/pyupgrade | ||||||
|     rev: v3.3.0 |     rev: v3.3.1 | ||||||
|     hooks: |     hooks: | ||||||
|       - id: pyupgrade |       - id: pyupgrade | ||||||
|         args: [--py39-plus] |         args: [--py39-plus] | ||||||
|   | |||||||
							
								
								
									
										11
									
								
								CODEOWNERS
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								CODEOWNERS
									
									
									
									
									
								
							| @@ -11,6 +11,7 @@ esphome/*.py @esphome/core | |||||||
| esphome/core/* @esphome/core | esphome/core/* @esphome/core | ||||||
|  |  | ||||||
| # Integrations | # Integrations | ||||||
|  | esphome/components/absolute_humidity/* @DAVe3283 | ||||||
| esphome/components/ac_dimmer/* @glmnet | esphome/components/ac_dimmer/* @glmnet | ||||||
| esphome/components/adc/* @esphome/core | esphome/components/adc/* @esphome/core | ||||||
| esphome/components/adc128s102/* @DeerMaximum | esphome/components/adc128s102/* @DeerMaximum | ||||||
| @@ -24,6 +25,7 @@ esphome/components/analog_threshold/* @ianchi | |||||||
| esphome/components/animation/* @syndlex | esphome/components/animation/* @syndlex | ||||||
| esphome/components/anova/* @buxtronix | esphome/components/anova/* @buxtronix | ||||||
| esphome/components/api/* @OttoWinter | esphome/components/api/* @OttoWinter | ||||||
|  | esphome/components/as7341/* @mrgnr | ||||||
| esphome/components/async_tcp/* @OttoWinter | esphome/components/async_tcp/* @OttoWinter | ||||||
| esphome/components/atc_mithermometer/* @ahpohl | esphome/components/atc_mithermometer/* @ahpohl | ||||||
| esphome/components/b_parasite/* @rbaron | esphome/components/b_parasite/* @rbaron | ||||||
| @@ -90,11 +92,13 @@ esphome/components/factory_reset/* @anatoly-savchenkov | |||||||
| esphome/components/fastled_base/* @OttoWinter | esphome/components/fastled_base/* @OttoWinter | ||||||
| esphome/components/feedback/* @ianchi | esphome/components/feedback/* @ianchi | ||||||
| esphome/components/fingerprint_grow/* @OnFreund @loongyh | esphome/components/fingerprint_grow/* @OnFreund @loongyh | ||||||
|  | esphome/components/fs3000/* @kahrendt | ||||||
| esphome/components/globals/* @esphome/core | esphome/components/globals/* @esphome/core | ||||||
| esphome/components/gpio/* @esphome/core | esphome/components/gpio/* @esphome/core | ||||||
| esphome/components/gps/* @coogle | esphome/components/gps/* @coogle | ||||||
| esphome/components/graph/* @synco | esphome/components/graph/* @synco | ||||||
| esphome/components/growatt_solar/* @leeuwte | esphome/components/growatt_solar/* @leeuwte | ||||||
|  | esphome/components/haier/* @Yarikx | ||||||
| esphome/components/havells_solar/* @sourabhjaiswal | esphome/components/havells_solar/* @sourabhjaiswal | ||||||
| esphome/components/hbridge/fan/* @WeekendWarrior | esphome/components/hbridge/fan/* @WeekendWarrior | ||||||
| esphome/components/hbridge/light/* @DotNetDann | esphome/components/hbridge/light/* @DotNetDann | ||||||
| @@ -107,17 +111,20 @@ esphome/components/hte501/* @Stock-M | |||||||
| esphome/components/hydreon_rgxx/* @functionpointer | esphome/components/hydreon_rgxx/* @functionpointer | ||||||
| esphome/components/i2c/* @esphome/core | esphome/components/i2c/* @esphome/core | ||||||
| esphome/components/i2s_audio/* @jesserockz | esphome/components/i2s_audio/* @jesserockz | ||||||
|  | esphome/components/ili9xxx/* @nielsnl68 | ||||||
| esphome/components/improv_base/* @esphome/core | esphome/components/improv_base/* @esphome/core | ||||||
| esphome/components/improv_serial/* @esphome/core | esphome/components/improv_serial/* @esphome/core | ||||||
| esphome/components/ina260/* @MrEditor97 | esphome/components/ina260/* @MrEditor97 | ||||||
| esphome/components/inkbird_ibsth1_mini/* @fkirill | esphome/components/inkbird_ibsth1_mini/* @fkirill | ||||||
| esphome/components/inkplate6/* @jesserockz | esphome/components/inkplate6/* @jesserockz | ||||||
| esphome/components/integration/* @OttoWinter | esphome/components/integration/* @OttoWinter | ||||||
|  | esphome/components/internal_temperature/* @Mat931 | ||||||
| esphome/components/interval/* @esphome/core | esphome/components/interval/* @esphome/core | ||||||
| esphome/components/json/* @OttoWinter | esphome/components/json/* @OttoWinter | ||||||
| esphome/components/kalman_combinator/* @Cat-Ion | esphome/components/kalman_combinator/* @Cat-Ion | ||||||
| esphome/components/key_collector/* @ssieb | esphome/components/key_collector/* @ssieb | ||||||
| esphome/components/key_provider/* @ssieb | esphome/components/key_provider/* @ssieb | ||||||
|  | esphome/components/kuntze/* @ssieb | ||||||
| esphome/components/lcd_menu/* @numo68 | esphome/components/lcd_menu/* @numo68 | ||||||
| esphome/components/ld2410/* @sebcaps | esphome/components/ld2410/* @sebcaps | ||||||
| esphome/components/ledc/* @OttoWinter | esphome/components/ledc/* @OttoWinter | ||||||
| @@ -160,8 +167,9 @@ esphome/components/modbus_controller/select/* @martgras @stegm | |||||||
| esphome/components/modbus_controller/sensor/* @martgras | esphome/components/modbus_controller/sensor/* @martgras | ||||||
| esphome/components/modbus_controller/switch/* @martgras | esphome/components/modbus_controller/switch/* @martgras | ||||||
| esphome/components/modbus_controller/text_sensor/* @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_pro_check/* @spbrogan | ||||||
|  | esphome/components/mopeka_std_check/* @Fabian-Schmidt | ||||||
| esphome/components/mpl3115a2/* @kbickar | esphome/components/mpl3115a2/* @kbickar | ||||||
| esphome/components/mpu6886/* @fabaff | esphome/components/mpu6886/* @fabaff | ||||||
| esphome/components/network/* @esphome/core | esphome/components/network/* @esphome/core | ||||||
| @@ -208,6 +216,7 @@ esphome/components/sdm_meter/* @jesserockz @polyfaces | |||||||
| esphome/components/sdp3x/* @Azimath | esphome/components/sdp3x/* @Azimath | ||||||
| esphome/components/selec_meter/* @sourabhjaiswal | esphome/components/selec_meter/* @sourabhjaiswal | ||||||
| esphome/components/select/* @esphome/core | esphome/components/select/* @esphome/core | ||||||
|  | esphome/components/sen21231/* @shreyaskarnik | ||||||
| esphome/components/sen5x/* @martgras | esphome/components/sen5x/* @martgras | ||||||
| esphome/components/sensirion_common/* @martgras | esphome/components/sensirion_common/* @martgras | ||||||
| esphome/components/sensor/* @esphome/core | esphome/components/sensor/* @esphome/core | ||||||
|   | |||||||
| @@ -6,9 +6,9 @@ | |||||||
| ARG BASEIMGTYPE=docker | ARG BASEIMGTYPE=docker | ||||||
|  |  | ||||||
| # https://github.com/hassio-addons/addon-debian-base/releases | # 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 | # 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 | FROM base-${BASEIMGTYPE} AS base | ||||||
|  |  | ||||||
| @@ -26,7 +26,7 @@ RUN \ | |||||||
|         python3-cryptography=3.3.2-1 \ |         python3-cryptography=3.3.2-1 \ | ||||||
|         iputils-ping=3:20210202-1 \ |         iputils-ping=3:20210202-1 \ | ||||||
|         git=1:2.30.2-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 \ |         openssh-client=1:8.4p1-5+deb11u1 \ | ||||||
|     && rm -rf \ |     && rm -rf \ | ||||||
|         /tmp/* \ |         /tmp/* \ | ||||||
| @@ -51,7 +51,7 @@ RUN \ | |||||||
|     # Ubuntu python3-pip is missing wheel |     # Ubuntu python3-pip is missing wheel | ||||||
|     pip3 install --no-cache-dir \ |     pip3 install --no-cache-dir \ | ||||||
|         wheel==0.37.1 \ |         wheel==0.37.1 \ | ||||||
|         platformio==6.1.5 \ |         platformio==6.1.6 \ | ||||||
|     # Change some platformio settings |     # Change some platformio settings | ||||||
|     && platformio settings set enable_telemetry No \ |     && platformio settings set enable_telemetry No \ | ||||||
|     && platformio settings set check_platformio_interval 1000000 \ |     && 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) |     var = cg.new_Pvariable(action_id, template_arg) | ||||||
|     count_template = await cg.templatable(config[CONF_COUNT], args, cg.uint32) |     count_template = await cg.templatable(config[CONF_COUNT], args, cg.uint32) | ||||||
|     cg.add(var.set_count(count_template)) |     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)) |     cg.add(var.add_then(actions)) | ||||||
|     return var |     return var | ||||||
|  |  | ||||||
|   | |||||||
| @@ -47,6 +47,7 @@ from esphome.cpp_helpers import (  # noqa | |||||||
|     build_registry_list, |     build_registry_list, | ||||||
|     extract_registry_entry_config, |     extract_registry_entry_config, | ||||||
|     register_parented, |     register_parented, | ||||||
|  |     past_safe_mode, | ||||||
| ) | ) | ||||||
| from esphome.cpp_types import (  # noqa | from esphome.cpp_types import (  # noqa | ||||||
|     global_ns, |     global_ns, | ||||||
| @@ -63,6 +64,7 @@ from esphome.cpp_types import (  # noqa | |||||||
|     uint16, |     uint16, | ||||||
|     uint32, |     uint32, | ||||||
|     uint64, |     uint64, | ||||||
|  |     int16, | ||||||
|     int32, |     int32, | ||||||
|     int64, |     int64, | ||||||
|     size_t, |     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" | CONF_ADC128S102_ID = "adc128s102_id" | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = sensor.SENSOR_SCHEMA.extend( | CONFIG_SCHEMA = ( | ||||||
|  |     sensor.sensor_schema(ADC128S102Sensor) | ||||||
|  |     .extend( | ||||||
|         { |         { | ||||||
|         cv.GenerateID(): cv.declare_id(ADC128S102Sensor), |  | ||||||
|             cv.GenerateID(CONF_ADC128S102_ID): cv.use_id(ADC128S102), |             cv.GenerateID(CONF_ADC128S102_ID): cv.use_id(ADC128S102), | ||||||
|             cv.Required(CONF_CHANNEL): cv.int_range(min=0, max=7), |             cv.Required(CONF_CHANNEL): cv.int_range(min=0, max=7), | ||||||
|         } |         } | ||||||
| ).extend(cv.polling_component_schema("60s")) |     ) | ||||||
|  |     .extend(cv.polling_component_schema("60s")) | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
| async def to_code(config): | async def to_code(config): | ||||||
|   | |||||||
| @@ -15,18 +15,24 @@ AnalogThresholdBinarySensor = analog_threshold_ns.class_( | |||||||
| CONF_UPPER = "upper" | CONF_UPPER = "upper" | ||||||
| CONF_LOWER = "lower" | CONF_LOWER = "lower" | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend( | CONFIG_SCHEMA = ( | ||||||
|  |     binary_sensor.binary_sensor_schema(AnalogThresholdBinarySensor) | ||||||
|  |     .extend( | ||||||
|         { |         { | ||||||
|         cv.GenerateID(): cv.declare_id(AnalogThresholdBinarySensor), |  | ||||||
|             cv.Required(CONF_SENSOR_ID): cv.use_id(sensor.Sensor), |             cv.Required(CONF_SENSOR_ID): cv.use_id(sensor.Sensor), | ||||||
|             cv.Required(CONF_THRESHOLD): cv.Any( |             cv.Required(CONF_THRESHOLD): cv.Any( | ||||||
|                 cv.float_, |                 cv.float_, | ||||||
|                 cv.Schema( |                 cv.Schema( | ||||||
|                 {cv.Required(CONF_UPPER): cv.float_, cv.Required(CONF_LOWER): cv.float_} |                     { | ||||||
|  |                         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): | async def to_code(config): | ||||||
|   | |||||||
| @@ -829,7 +829,7 @@ message ListEntitiesClimateResponse { | |||||||
|   repeated ClimateMode supported_modes = 7; |   repeated ClimateMode supported_modes = 7; | ||||||
|   float visual_min_temperature = 8; |   float visual_min_temperature = 8; | ||||||
|   float visual_max_temperature = 9; |   float visual_max_temperature = 9; | ||||||
|   float visual_temperature_step = 10; |   float visual_target_temperature_step = 10; | ||||||
|   // for older peer versions - in new system this |   // for older peer versions - in new system this | ||||||
|   // is if CLIMATE_PRESET_AWAY exists is supported_presets |   // is if CLIMATE_PRESET_AWAY exists is supported_presets | ||||||
|   bool legacy_supports_away = 11; |   bool legacy_supports_away = 11; | ||||||
| @@ -842,6 +842,7 @@ message ListEntitiesClimateResponse { | |||||||
|   bool disabled_by_default = 18; |   bool disabled_by_default = 18; | ||||||
|   string icon = 19; |   string icon = 19; | ||||||
|   EntityCategory entity_category = 20; |   EntityCategory entity_category = 20; | ||||||
|  |   float visual_current_temperature_step = 21; | ||||||
| } | } | ||||||
| message ClimateStateResponse { | message ClimateStateResponse { | ||||||
|   option (id) = 47; |   option (id) = 47; | ||||||
| @@ -1338,3 +1339,23 @@ message BluetoothGATTNotifyResponse { | |||||||
|   uint64 address = 1; |   uint64 address = 1; | ||||||
|   uint32 handle = 2; |   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_min_temperature = traits.get_visual_min_temperature(); | ||||||
|   msg.visual_max_temperature = traits.get_visual_max_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.legacy_supports_away = traits.supports_preset(climate::CLIMATE_PRESET_AWAY); | ||||||
|   msg.supports_action = traits.get_supports_action(); |   msg.supports_action = traits.get_supports_action(); | ||||||
|  |  | ||||||
| @@ -951,7 +953,7 @@ DeviceInfoResponse APIConnection::device_info(const DeviceInfoRequest &msg) { | |||||||
|   resp.webserver_port = USE_WEBSERVER_PORT; |   resp.webserver_port = USE_WEBSERVER_PORT; | ||||||
| #endif | #endif | ||||||
| #ifdef USE_BLUETOOTH_PROXY | #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 | #endif | ||||||
|   return resp; |   return resp; | ||||||
| } | } | ||||||
|   | |||||||
| @@ -3451,7 +3451,11 @@ bool ListEntitiesClimateResponse::decode_32bit(uint32_t field_id, Proto32Bit val | |||||||
|       return true; |       return true; | ||||||
|     } |     } | ||||||
|     case 10: { |     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; |       return true; | ||||||
|     } |     } | ||||||
|     default: |     default: | ||||||
| @@ -3470,7 +3474,7 @@ void ListEntitiesClimateResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
|   } |   } | ||||||
|   buffer.encode_float(8, this->visual_min_temperature); |   buffer.encode_float(8, this->visual_min_temperature); | ||||||
|   buffer.encode_float(9, this->visual_max_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(11, this->legacy_supports_away); | ||||||
|   buffer.encode_bool(12, this->supports_action); |   buffer.encode_bool(12, this->supports_action); | ||||||
|   for (auto &it : this->supported_fan_modes) { |   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_bool(18, this->disabled_by_default); | ||||||
|   buffer.encode_string(19, this->icon); |   buffer.encode_string(19, this->icon); | ||||||
|   buffer.encode_enum<enums::EntityCategory>(20, this->entity_category); |   buffer.encode_enum<enums::EntityCategory>(20, this->entity_category); | ||||||
|  |   buffer.encode_float(21, this->visual_current_temperature_step); | ||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void ListEntitiesClimateResponse::dump_to(std::string &out) const { | 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(buffer); | ||||||
|   out.append("\n"); |   out.append("\n"); | ||||||
|  |  | ||||||
|   out.append("  visual_temperature_step: "); |   out.append("  visual_target_temperature_step: "); | ||||||
|   sprintf(buffer, "%g", this->visual_temperature_step); |   sprintf(buffer, "%g", this->visual_target_temperature_step); | ||||||
|   out.append(buffer); |   out.append(buffer); | ||||||
|   out.append("\n"); |   out.append("\n"); | ||||||
|  |  | ||||||
| @@ -3591,6 +3596,11 @@ void ListEntitiesClimateResponse::dump_to(std::string &out) const { | |||||||
|   out.append("  entity_category: "); |   out.append("  entity_category: "); | ||||||
|   out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category)); |   out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category)); | ||||||
|   out.append("\n"); |   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("}"); |   out.append("}"); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| @@ -5964,6 +5974,92 @@ void BluetoothGATTNotifyResponse::dump_to(std::string &out) const { | |||||||
|   out.append("}"); |   out.append("}"); | ||||||
| } | } | ||||||
| #endif | #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 api | ||||||
| }  // namespace esphome | }  // namespace esphome | ||||||
|   | |||||||
| @@ -915,7 +915,7 @@ class ListEntitiesClimateResponse : public ProtoMessage { | |||||||
|   std::vector<enums::ClimateMode> supported_modes{}; |   std::vector<enums::ClimateMode> supported_modes{}; | ||||||
|   float visual_min_temperature{0.0f}; |   float visual_min_temperature{0.0f}; | ||||||
|   float visual_max_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 legacy_supports_away{false}; | ||||||
|   bool supports_action{false}; |   bool supports_action{false}; | ||||||
|   std::vector<enums::ClimateFanMode> supported_fan_modes{}; |   std::vector<enums::ClimateFanMode> supported_fan_modes{}; | ||||||
| @@ -926,6 +926,7 @@ class ListEntitiesClimateResponse : public ProtoMessage { | |||||||
|   bool disabled_by_default{false}; |   bool disabled_by_default{false}; | ||||||
|   std::string icon{}; |   std::string icon{}; | ||||||
|   enums::EntityCategory entity_category{}; |   enums::EntityCategory entity_category{}; | ||||||
|  |   float visual_current_temperature_step{0.0f}; | ||||||
|   void encode(ProtoWriteBuffer buffer) const override; |   void encode(ProtoWriteBuffer buffer) const override; | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   void dump_to(std::string &out) const override; |   void dump_to(std::string &out) const override; | ||||||
| @@ -1527,6 +1528,32 @@ class BluetoothGATTNotifyResponse : public ProtoMessage { | |||||||
|  protected: |  protected: | ||||||
|   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; |   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 api | ||||||
| }  // namespace esphome | }  // namespace esphome | ||||||
|   | |||||||
| @@ -425,6 +425,22 @@ bool APIServerConnectionBase::send_bluetooth_gatt_notify_response(const Bluetoot | |||||||
|   return this->send_message_<BluetoothGATTNotifyResponse>(msg, 84); |   return this->send_message_<BluetoothGATTNotifyResponse>(msg, 84); | ||||||
| } | } | ||||||
| #endif | #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) { | bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) { | ||||||
|   switch (msg_type) { |   switch (msg_type) { | ||||||
|     case 1: { |     case 1: { | ||||||
|   | |||||||
| @@ -209,6 +209,12 @@ class APIServerConnectionBase : public ProtoService { | |||||||
| #endif | #endif | ||||||
| #ifdef USE_BLUETOOTH_PROXY | #ifdef USE_BLUETOOTH_PROXY | ||||||
|   bool send_bluetooth_gatt_notify_response(const BluetoothGATTNotifyResponse &msg); |   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 | #endif | ||||||
|  protected: |  protected: | ||||||
|   bool read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override; |   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) { | void APIServer::send_bluetooth_connections_free(uint8_t free, uint8_t limit) { | ||||||
|   BluetoothConnectionsFreeResponse call; |   BluetoothConnectionsFreeResponse call; | ||||||
|   call.free = free; |   call.free = free; | ||||||
|   | |||||||
| @@ -78,6 +78,8 @@ class APIServer : public Component, public Controller { | |||||||
| #ifdef USE_BLUETOOTH_PROXY | #ifdef USE_BLUETOOTH_PROXY | ||||||
|   void send_bluetooth_le_advertisement(const BluetoothLEAdvertisementResponse &call); |   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_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_connections_free(uint8_t free, uint8_t limit); | ||||||
|   void send_bluetooth_gatt_read_response(const BluetoothGATTReadResponse &call); |   void send_bluetooth_gatt_read_response(const BluetoothGATTReadResponse &call); | ||||||
|   void send_bluetooth_gatt_write_response(const BluetoothGATTWriteResponse &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)) |     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_TEMPERATURE, var.set_temperature), | ||||||
|         (CONF_HUMIDITY, var.set_humidity), |         (CONF_HUMIDITY, var.set_humidity), | ||||||
|         (CONF_BATTERY_VOLTAGE, var.set_battery_voltage), |         (CONF_BATTERY_VOLTAGE, var.set_battery_voltage), | ||||||
|   | |||||||
| @@ -1,3 +1,5 @@ | |||||||
|  | #ifdef USE_ESP32 | ||||||
|  |  | ||||||
| #include "bedjet_hub.h" | #include "bedjet_hub.h" | ||||||
| #include "bedjet_child.h" | #include "bedjet_child.h" | ||||||
| #include "bedjet_const.h" | #include "bedjet_const.h" | ||||||
| @@ -541,3 +543,5 @@ void BedJetHub::register_child(BedJetClient *obj) { | |||||||
|  |  | ||||||
| }  // namespace bedjet | }  // namespace bedjet | ||||||
| }  // namespace esphome | }  // namespace esphome | ||||||
|  |  | ||||||
|  | #endif | ||||||
|   | |||||||
| @@ -1,4 +1,5 @@ | |||||||
| #pragma once | #pragma once | ||||||
|  | #ifdef USE_ESP32 | ||||||
|  |  | ||||||
| #include "esphome/components/ble_client/ble_client.h" | #include "esphome/components/ble_client/ble_client.h" | ||||||
| #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" | #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" | ||||||
| @@ -14,8 +15,6 @@ | |||||||
| #include "esphome/components/time/real_time_clock.h" | #include "esphome/components/time/real_time_clock.h" | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
| #ifdef USE_ESP32 |  | ||||||
|  |  | ||||||
| #include <esp_gattc_api.h> | #include <esp_gattc_api.h> | ||||||
|  |  | ||||||
| namespace esphome { | namespace esphome { | ||||||
|   | |||||||
| @@ -27,13 +27,13 @@ from esphome.const import ( | |||||||
|     CONF_TIMING, |     CONF_TIMING, | ||||||
|     CONF_TRIGGER_ID, |     CONF_TRIGGER_ID, | ||||||
|     CONF_MQTT_ID, |     CONF_MQTT_ID, | ||||||
|     DEVICE_CLASS_EMPTY, |  | ||||||
|     DEVICE_CLASS_BATTERY, |     DEVICE_CLASS_BATTERY, | ||||||
|     DEVICE_CLASS_BATTERY_CHARGING, |     DEVICE_CLASS_BATTERY_CHARGING, | ||||||
|     DEVICE_CLASS_CARBON_MONOXIDE, |     DEVICE_CLASS_CARBON_MONOXIDE, | ||||||
|     DEVICE_CLASS_COLD, |     DEVICE_CLASS_COLD, | ||||||
|     DEVICE_CLASS_CONNECTIVITY, |     DEVICE_CLASS_CONNECTIVITY, | ||||||
|     DEVICE_CLASS_DOOR, |     DEVICE_CLASS_DOOR, | ||||||
|  |     DEVICE_CLASS_EMPTY, | ||||||
|     DEVICE_CLASS_GARAGE_DOOR, |     DEVICE_CLASS_GARAGE_DOOR, | ||||||
|     DEVICE_CLASS_GAS, |     DEVICE_CLASS_GAS, | ||||||
|     DEVICE_CLASS_HEAT, |     DEVICE_CLASS_HEAT, | ||||||
| @@ -62,13 +62,13 @@ from esphome.util import Registry | |||||||
|  |  | ||||||
| CODEOWNERS = ["@esphome/core"] | CODEOWNERS = ["@esphome/core"] | ||||||
| DEVICE_CLASSES = [ | DEVICE_CLASSES = [ | ||||||
|     DEVICE_CLASS_EMPTY, |  | ||||||
|     DEVICE_CLASS_BATTERY, |     DEVICE_CLASS_BATTERY, | ||||||
|     DEVICE_CLASS_BATTERY_CHARGING, |     DEVICE_CLASS_BATTERY_CHARGING, | ||||||
|     DEVICE_CLASS_CARBON_MONOXIDE, |     DEVICE_CLASS_CARBON_MONOXIDE, | ||||||
|     DEVICE_CLASS_COLD, |     DEVICE_CLASS_COLD, | ||||||
|     DEVICE_CLASS_CONNECTIVITY, |     DEVICE_CLASS_CONNECTIVITY, | ||||||
|     DEVICE_CLASS_DOOR, |     DEVICE_CLASS_DOOR, | ||||||
|  |     DEVICE_CLASS_EMPTY, | ||||||
|     DEVICE_CLASS_GARAGE_DOOR, |     DEVICE_CLASS_GARAGE_DOOR, | ||||||
|     DEVICE_CLASS_GAS, |     DEVICE_CLASS_GAS, | ||||||
|     DEVICE_CLASS_HEAT, |     DEVICE_CLASS_HEAT, | ||||||
| @@ -393,28 +393,21 @@ def binary_sensor_schema( | |||||||
|     entity_category: str = _UNDEF, |     entity_category: str = _UNDEF, | ||||||
|     device_class: str = _UNDEF, |     device_class: str = _UNDEF, | ||||||
| ) -> cv.Schema: | ) -> cv.Schema: | ||||||
|     schema = BINARY_SENSOR_SCHEMA |     schema = {} | ||||||
|  |  | ||||||
|     if class_ is not _UNDEF: |     if class_ is not _UNDEF: | ||||||
|         schema = schema.extend({cv.GenerateID(): cv.declare_id(class_)}) |         # Not cv.optional | ||||||
|     if icon is not _UNDEF: |         schema[cv.GenerateID()] = cv.declare_id(class_) | ||||||
|         schema = schema.extend({cv.Optional(CONF_ICON, default=icon): cv.icon}) |  | ||||||
|     if entity_category is not _UNDEF: |     for key, default, validator in [ | ||||||
|         schema = schema.extend( |         (CONF_ICON, icon, cv.icon), | ||||||
|             { |         (CONF_ENTITY_CATEGORY, entity_category, cv.entity_category), | ||||||
|                 cv.Optional( |         (CONF_DEVICE_CLASS, device_class, validate_device_class), | ||||||
|                     CONF_ENTITY_CATEGORY, default=entity_category |     ]: | ||||||
|                 ): cv.entity_category |         if default is not _UNDEF: | ||||||
|             } |             schema[cv.Optional(key, default=default)] = validator | ||||||
|         ) |  | ||||||
|     if device_class is not _UNDEF: |     return BINARY_SENSOR_SCHEMA.extend(schema) | ||||||
|         schema = schema.extend( |  | ||||||
|             { |  | ||||||
|                 cv.Optional( |  | ||||||
|                     CONF_DEVICE_CLASS, default=device_class |  | ||||||
|                 ): validate_device_class |  | ||||||
|             } |  | ||||||
|         ) |  | ||||||
|     return schema |  | ||||||
|  |  | ||||||
|  |  | ||||||
| async def setup_binary_sensor_core_(var, config): | 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. | /** Base class for all binary_sensor-type classes. | ||||||
|  * |  * | ||||||
|  * This class includes a callback that components such as MQTT can subscribe to for state changes. |  * This class includes a callback that components such as MQTT can subscribe to for state changes. | ||||||
|   | |||||||
| @@ -1,3 +1,5 @@ | |||||||
|  | #ifdef USE_ESP32 | ||||||
|  |  | ||||||
| #include "automation.h" | #include "automation.h" | ||||||
|  |  | ||||||
| #include <esp_bt_defs.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 ble_client | ||||||
| }  // namespace esphome | }  // namespace esphome | ||||||
|  |  | ||||||
|  | #endif | ||||||
|   | |||||||
| @@ -1,13 +1,13 @@ | |||||||
| #pragma once | #pragma once | ||||||
|  |  | ||||||
|  | #ifdef USE_ESP32 | ||||||
|  |  | ||||||
| #include <utility> | #include <utility> | ||||||
| #include <vector> | #include <vector> | ||||||
|  |  | ||||||
| #include "esphome/core/automation.h" | #include "esphome/core/automation.h" | ||||||
| #include "esphome/components/ble_client/ble_client.h" | #include "esphome/components/ble_client/ble_client.h" | ||||||
|  |  | ||||||
| #ifdef USE_ESP32 |  | ||||||
|  |  | ||||||
| namespace esphome { | namespace esphome { | ||||||
| namespace ble_client { | namespace ble_client { | ||||||
| class BLEClientConnectTrigger : public Trigger<>, public BLEClientNode { | 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; |   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) { | esp_err_t BluetoothConnection::read_characteristic(uint16_t handle) { | ||||||
|   if (!this->connected()) { |   if (!this->connected()) { | ||||||
|     ESP_LOGW(TAG, "[%d] [%s] Cannot read GATT characteristic, not connected.", this->connection_index_, |     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: |  public: | ||||||
|   bool gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, |   bool gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, | ||||||
|                            esp_ble_gattc_cb_param_t *param) override; |                            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 read_characteristic(uint16_t handle); | ||||||
|   esp_err_t write_characteristic(uint16_t handle, const std::string &data, bool response); |   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()); |         ESP_LOGI(TAG, "[%d] [%s] Connecting v1", connection->get_connection_index(), connection->address_str().c_str()); | ||||||
|       } |       } | ||||||
|       if (msg.has_address_type) { |       if (msg.has_address_type) { | ||||||
|         connection->remote_bda_[0] = (msg.address >> 40) & 0xFF; |         uint64_to_bd_addr(msg.address, connection->remote_bda_); | ||||||
|         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; |  | ||||||
|         connection->set_remote_addr_type(static_cast<esp_ble_addr_type_t>(msg.address_type)); |         connection->set_remote_addr_type(static_cast<esp_ble_addr_type_t>(msg.address_type)); | ||||||
|         connection->set_state(espbt::ClientState::DISCOVERED); |         connection->set_state(espbt::ClientState::DISCOVERED); | ||||||
|       } else { |       } else { | ||||||
| @@ -290,10 +285,28 @@ void BluetoothProxy::bluetooth_device_request(const api::BluetoothDeviceRequest | |||||||
|       } |       } | ||||||
|       break; |       break; | ||||||
|     } |     } | ||||||
|     case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_PAIR: |     case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_PAIR: { | ||||||
|     case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_UNPAIR: |       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; |       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; | ||||||
|  |     } | ||||||
|  |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| void BluetoothProxy::bluetooth_gatt_read(const api::BluetoothGATTReadRequest &msg) { | void BluetoothProxy::bluetooth_gatt_read(const api::BluetoothGATTReadRequest &msg) { | ||||||
|   | |||||||
| @@ -44,6 +44,15 @@ class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Com | |||||||
|   int get_bluetooth_connections_free(); |   int get_bluetooth_connections_free(); | ||||||
|   int get_bluetooth_connections_limit() { return this->connections_.size(); } |   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; } |   void set_active(bool active) { this->active_ = active; } | ||||||
|   bool has_active() { return this->active_; } |   bool has_active() { return this->active_; } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -117,18 +117,24 @@ void BME680Component::setup() { | |||||||
|   this->calibration_.gh2 = cal2[12] << 8 | cal2[13]; |   this->calibration_.gh2 = cal2[12] << 8 | cal2[13]; | ||||||
|   this->calibration_.gh3 = cal2[15]; |   this->calibration_.gh3 = cal2[15]; | ||||||
|  |  | ||||||
|   if (!this->read_byte(0x02, &this->calibration_.res_heat_range)) { |   uint8_t temp_var = 0; | ||||||
|  |   if (!this->read_byte(0x02, &temp_var)) { | ||||||
|     this->mark_failed(); |     this->mark_failed(); | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|   if (!this->read_byte(0x00, &this->calibration_.res_heat_val)) { |   this->calibration_.res_heat_range = ((temp_var & 0x30) / 16); | ||||||
|  |  | ||||||
|  |   if (!this->read_byte(0x00, &temp_var)) { | ||||||
|     this->mark_failed(); |     this->mark_failed(); | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|   if (!this->read_byte(0x04, &this->calibration_.range_sw_err)) { |   this->calibration_.res_heat_val = (int8_t) temp_var; | ||||||
|  |  | ||||||
|  |   if (!this->read_byte(0x04, &temp_var)) { | ||||||
|     this->mark_failed(); |     this->mark_failed(); | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|  |   this->calibration_.range_sw_err = ((int8_t) temp_var & (int8_t) 0xf0) / 16; | ||||||
|  |  | ||||||
|   this->calibration_.ambient_temperature = 25;  // prime ambient temperature |   this->calibration_.ambient_temperature = 25;  // prime ambient temperature | ||||||
|  |  | ||||||
| @@ -181,7 +187,7 @@ void BME680Component::setup() { | |||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|   gas0_control &= ~0b00001000; |   gas0_control &= ~0b00001000; | ||||||
|   gas0_control |= heat_off ? 0b100 : 0b000; |   gas0_control |= heat_off << 3; | ||||||
|   if (!this->write_byte(BME680_REGISTER_CONTROL_GAS0, gas0_control)) { |   if (!this->write_byte(BME680_REGISTER_CONTROL_GAS0, gas0_control)) { | ||||||
|     this->mark_failed(); |     this->mark_failed(); | ||||||
|     return; |     return; | ||||||
| @@ -249,12 +255,12 @@ uint8_t BME680Component::calc_heater_resistance_(uint16_t temperature) { | |||||||
|   if (temperature > 400) |   if (temperature > 400) | ||||||
|     temperature = 400; |     temperature = 400; | ||||||
|  |  | ||||||
|   const uint8_t ambient_temperature = this->calibration_.ambient_temperature; |   const int8_t ambient_temperature = this->calibration_.ambient_temperature; | ||||||
|   const int8_t gh1 = this->calibration_.gh1; |   const int8_t gh1 = this->calibration_.gh1; | ||||||
|   const int16_t gh2 = this->calibration_.gh2; |   const int16_t gh2 = this->calibration_.gh2; | ||||||
|   const int8_t gh3 = this->calibration_.gh3; |   const int8_t gh3 = this->calibration_.gh3; | ||||||
|   const uint8_t res_heat_range = this->calibration_.res_heat_range; |   const uint8_t res_heat_range = this->calibration_.res_heat_range; | ||||||
|   const uint8_t res_heat_val = this->calibration_.res_heat_val; |   const int8_t res_heat_val = this->calibration_.res_heat_val; | ||||||
|  |  | ||||||
|   uint8_t heatr_res; |   uint8_t heatr_res; | ||||||
|   int32_t var1; |   int32_t var1; | ||||||
| @@ -293,35 +299,57 @@ uint8_t BME680Component::calc_heater_duration_(uint16_t duration) { | |||||||
| void BME680Component::read_data_() { | void BME680Component::read_data_() { | ||||||
|   uint8_t data[15]; |   uint8_t data[15]; | ||||||
|   if (!this->read_bytes(BME680_REGISTER_FIELD0, data, 15)) { |   if (!this->read_bytes(BME680_REGISTER_FIELD0, data, 15)) { | ||||||
|  |     if (this->temperature_sensor_ != nullptr) | ||||||
|  |       this->temperature_sensor_->publish_state(NAN); | ||||||
|  |     if (this->pressure_sensor_ != nullptr) | ||||||
|  |       this->pressure_sensor_->publish_state(NAN); | ||||||
|  |     if (this->humidity_sensor_ != nullptr) | ||||||
|  |       this->humidity_sensor_->publish_state(NAN); | ||||||
|  |     if (this->gas_resistance_sensor_ != nullptr) | ||||||
|  |       this->gas_resistance_sensor_->publish_state(NAN); | ||||||
|  |     ESP_LOGW(TAG, "Communication with BME680 failed!"); | ||||||
|     this->status_set_warning(); |     this->status_set_warning(); | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|  |   this->status_clear_warning(); | ||||||
|  |  | ||||||
|   uint32_t raw_temperature = (uint32_t(data[5]) << 12) | (uint32_t(data[6]) << 4) | (uint32_t(data[7]) >> 4); |   uint32_t raw_temperature = (uint32_t(data[5]) << 12) | (uint32_t(data[6]) << 4) | (uint32_t(data[7]) >> 4); | ||||||
|   uint32_t raw_pressure = (uint32_t(data[2]) << 12) | (uint32_t(data[3]) << 4) | (uint32_t(data[4]) >> 4); |   uint32_t raw_pressure = (uint32_t(data[2]) << 12) | (uint32_t(data[3]) << 4) | (uint32_t(data[4]) >> 4); | ||||||
|   uint32_t raw_humidity = (uint32_t(data[8]) << 8) | uint32_t(data[9]); |   uint32_t raw_humidity = (uint32_t(data[8]) << 8) | uint32_t(data[9]); | ||||||
|   uint16_t raw_gas = (uint16_t(data[13]) << 2) | (uint16_t(14) >> 6); |   uint16_t raw_gas = (uint16_t)((uint32_t) data[13] * 4 | (((uint32_t) data[14]) / 64)); | ||||||
|   uint8_t gas_range = data[14] & 0x0F; |   uint8_t gas_range = data[14] & 0x0F; | ||||||
|  |  | ||||||
|   float temperature = this->calc_temperature_(raw_temperature); |   float temperature = this->calc_temperature_(raw_temperature); | ||||||
|   float pressure = this->calc_pressure_(raw_pressure); |   float pressure = this->calc_pressure_(raw_pressure); | ||||||
|   float humidity = this->calc_humidity_(raw_humidity); |   float humidity = this->calc_humidity_(raw_humidity); | ||||||
|   float gas_resistance = NAN; |   float gas_resistance = this->calc_gas_resistance_(raw_gas, gas_range); | ||||||
|   if (data[14] & 0x20) { |  | ||||||
|     gas_resistance = this->calc_gas_resistance_(raw_gas, gas_range); |   bool gas_valid = (data[14] >> 5) & 1; | ||||||
|   } |   bool heat_stable = (data[14] >> 4) & 1; | ||||||
|  |   if (this->heater_temperature_ == 0 || this->heater_duration_ == 0) | ||||||
|  |     heat_stable = true;  // Allow reporting gas resistance when heater is disabled | ||||||
|  |  | ||||||
|   ESP_LOGD(TAG, "Got temperature=%.1f°C pressure=%.1fhPa humidity=%.1f%% gas_resistance=%.1fΩ", temperature, pressure, |   ESP_LOGD(TAG, "Got temperature=%.1f°C pressure=%.1fhPa humidity=%.1f%% gas_resistance=%.1fΩ", temperature, pressure, | ||||||
|            humidity, gas_resistance); |            humidity, gas_resistance); | ||||||
|  |   if (!gas_valid) | ||||||
|  |     ESP_LOGW(TAG, "Gas measurement unsuccessful, reading invalid!"); | ||||||
|  |   if (!heat_stable) | ||||||
|  |     ESP_LOGW(TAG, "Heater unstable, reading invalid! (Normal for a few readings after a power cycle)"); | ||||||
|  |  | ||||||
|   if (this->temperature_sensor_ != nullptr) |   if (this->temperature_sensor_ != nullptr) | ||||||
|     this->temperature_sensor_->publish_state(temperature); |     this->temperature_sensor_->publish_state(temperature); | ||||||
|   if (this->pressure_sensor_ != nullptr) |   if (this->pressure_sensor_ != nullptr) | ||||||
|     this->pressure_sensor_->publish_state(pressure); |     this->pressure_sensor_->publish_state(pressure); | ||||||
|   if (this->humidity_sensor_ != nullptr) |   if (this->humidity_sensor_ != nullptr) | ||||||
|     this->humidity_sensor_->publish_state(humidity); |     this->humidity_sensor_->publish_state(humidity); | ||||||
|   if (this->gas_resistance_sensor_ != nullptr) |   if (this->gas_resistance_sensor_ != nullptr) { | ||||||
|  |     if (gas_valid && heat_stable) { | ||||||
|       this->gas_resistance_sensor_->publish_state(gas_resistance); |       this->gas_resistance_sensor_->publish_state(gas_resistance); | ||||||
|   this->status_clear_warning(); |     } else { | ||||||
|  |       this->status_set_warning(); | ||||||
|  |       this->gas_resistance_sensor_->publish_state(NAN); | ||||||
|  |     } | ||||||
|  |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| float BME680Component::calc_temperature_(uint32_t raw_temperature) { | float BME680Component::calc_temperature_(uint32_t raw_temperature) { | ||||||
| @@ -428,20 +456,22 @@ float BME680Component::calc_humidity_(uint16_t raw_humidity) { | |||||||
|  |  | ||||||
|   return calc_hum; |   return calc_hum; | ||||||
| } | } | ||||||
| uint32_t BME680Component::calc_gas_resistance_(uint16_t raw_gas, uint8_t range) { | float BME680Component::calc_gas_resistance_(uint16_t raw_gas, uint8_t range) { | ||||||
|   float calc_gas_res; |   float calc_gas_res; | ||||||
|   float var1 = 0; |   float var1 = 0; | ||||||
|   float var2 = 0; |   float var2 = 0; | ||||||
|   float var3 = 0; |   float var3 = 0; | ||||||
|  |   float raw_gas_f = raw_gas; | ||||||
|  |   float range_f = 1U << range; | ||||||
|   const float range_sw_err = this->calibration_.range_sw_err; |   const float range_sw_err = this->calibration_.range_sw_err; | ||||||
|  |  | ||||||
|   var1 = 1340.0f + (5.0f * range_sw_err); |   var1 = 1340.0f + (5.0f * range_sw_err); | ||||||
|   var2 = var1 * (1.0f + BME680_GAS_LOOKUP_TABLE_1[range] / 100.0f); |   var2 = var1 * (1.0f + BME680_GAS_LOOKUP_TABLE_1[range] / 100.0f); | ||||||
|   var3 = 1.0f + (BME680_GAS_LOOKUP_TABLE_2[range] / 100.0f); |   var3 = 1.0f + (BME680_GAS_LOOKUP_TABLE_2[range] / 100.0f); | ||||||
|  |  | ||||||
|   calc_gas_res = 1.0f / (var3 * 0.000000125f * float(1 << range) * (((float(raw_gas) - 512.0f) / var2) + 1.0f)); |   calc_gas_res = 1.0f / (var3 * 0.000000125f * range_f * (((raw_gas_f - 512.0f) / var2) + 1.0f)); | ||||||
|  |  | ||||||
|   return static_cast<uint32_t>(calc_gas_res); |   return calc_gas_res; | ||||||
| } | } | ||||||
| uint32_t BME680Component::calc_meas_duration_() { | uint32_t BME680Component::calc_meas_duration_() { | ||||||
|   uint32_t tph_dur;  // Calculate in us |   uint32_t tph_dur;  // Calculate in us | ||||||
|   | |||||||
| @@ -59,11 +59,11 @@ struct BME680CalibrationData { | |||||||
|   int8_t gh3; |   int8_t gh3; | ||||||
|  |  | ||||||
|   uint8_t res_heat_range; |   uint8_t res_heat_range; | ||||||
|   uint8_t res_heat_val; |   int8_t res_heat_val; | ||||||
|   uint8_t range_sw_err; |   int8_t range_sw_err; | ||||||
|  |  | ||||||
|   float tfine; |   float tfine; | ||||||
|   uint8_t ambient_temperature; |   int8_t ambient_temperature; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| class BME680Component : public PollingComponent, public i2c::I2CDevice { | class BME680Component : public PollingComponent, public i2c::I2CDevice { | ||||||
| @@ -117,7 +117,7 @@ class BME680Component : public PollingComponent, public i2c::I2CDevice { | |||||||
|   /// Calculate the relative humidity in % using the provided raw ADC value. |   /// Calculate the relative humidity in % using the provided raw ADC value. | ||||||
|   float calc_humidity_(uint16_t raw_humidity); |   float calc_humidity_(uint16_t raw_humidity); | ||||||
|   /// Calculate the gas resistance in Ω using the provided raw ADC value. |   /// Calculate the gas resistance in Ω using the provided raw ADC value. | ||||||
|   uint32_t calc_gas_resistance_(uint16_t raw_gas, uint8_t range); |   float calc_gas_resistance_(uint16_t raw_gas, uint8_t range); | ||||||
|   /// Calculate how long the sensor will take until we can retrieve data. |   /// Calculate how long the sensor will take until we can retrieve data. | ||||||
|   uint32_t calc_meas_duration_(); |   uint32_t calc_meas_duration_(); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -6,6 +6,7 @@ from esphome.const import CONF_ID | |||||||
| CODEOWNERS = ["@trvrnrth"] | CODEOWNERS = ["@trvrnrth"] | ||||||
| DEPENDENCIES = ["i2c"] | DEPENDENCIES = ["i2c"] | ||||||
| AUTO_LOAD = ["sensor", "text_sensor"] | AUTO_LOAD = ["sensor", "text_sensor"] | ||||||
|  | MULTI_CONF = True | ||||||
|  |  | ||||||
| CONF_BME680_BSEC_ID = "bme680_bsec_id" | CONF_BME680_BSEC_ID = "bme680_bsec_id" | ||||||
| CONF_TEMPERATURE_OFFSET = "temperature_offset" | CONF_TEMPERATURE_OFFSET = "temperature_offset" | ||||||
| @@ -54,6 +55,7 @@ async def to_code(config): | |||||||
|     await cg.register_component(var, config) |     await cg.register_component(var, config) | ||||||
|     await i2c.register_i2c_device(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_temperature_offset(config[CONF_TEMPERATURE_OFFSET])) | ||||||
|     cg.add(var.set_iaq_mode(config[CONF_IAQ_MODE])) |     cg.add(var.set_iaq_mode(config[CONF_IAQ_MODE])) | ||||||
|     cg.add(var.set_sample_rate(config[CONF_SAMPLE_RATE])) |     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"}; | 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() { | void BME680BSECComponent::setup() { | ||||||
|   ESP_LOGCONFIG(TAG, "Setting up BME680 via BSEC..."); |   ESP_LOGCONFIG(TAG, "Setting up BME680(%s) via BSEC...", this->device_id_.c_str()); | ||||||
|   BME680BSECComponent::instance = this; |  | ||||||
|  |  | ||||||
|   this->bsec_status_ = bsec_init(); |   uint8_t new_idx = BME680BSECComponent::instances.size(); | ||||||
|   if (this->bsec_status_ != BSEC_OK) { |   BME680BSECComponent::instances.push_back(this); | ||||||
|     this->mark_failed(); |  | ||||||
|     return; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   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_.intf = BME680_I2C_INTF; | ||||||
|   this->bme680_.read = BME680BSECComponent::read_bytes_wrapper; |   this->bme680_.read = BME680BSECComponent::read_bytes_wrapper; | ||||||
|   this->bme680_.write = BME680BSECComponent::write_bytes_wrapper; |   this->bme680_.write = BME680BSECComponent::write_bytes_wrapper; | ||||||
| @@ -35,29 +40,30 @@ void BME680BSECComponent::setup() { | |||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   if (this->sample_rate_ == SAMPLE_RATE_ULP) { |   // Initialize the BSEC library | ||||||
|     const uint8_t bsec_config[] = { |   if (this->reinit_bsec_lib_() != 0) { | ||||||
| #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) { |  | ||||||
|     this->mark_failed(); |     this->mark_failed(); | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   // Load the BSEC library state from storage | ||||||
|   this->load_state_(); |   this->load_state_(); | ||||||
| } | } | ||||||
|  |  | ||||||
| void BME680BSECComponent::set_config_(const uint8_t *config) { | void BME680BSECComponent::set_config_() { | ||||||
|   uint8_t work_buffer[BSEC_MAX_WORKBUFFER_SIZE]; |   if (this->sample_rate_ == SAMPLE_RATE_ULP) { | ||||||
|   this->bsec_status_ = bsec_set_configuration(config, BSEC_MAX_PROPERTY_BLOB_SIZE, work_buffer, sizeof(work_buffer)); |     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) { | 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; |   uint8_t num_sensor_settings = BSEC_MAX_PHYSICAL_SENSOR; | ||||||
|   this->bsec_status_ = |   this->bsec_status_ = | ||||||
|       bsec_update_subscription(virtual_sensors, num_virtual_sensors, sensor_settings, &num_sensor_settings); |       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() { | 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_version_t version; | ||||||
|   bsec_get_version(&version); |   bsec_get_version(&version); | ||||||
| @@ -185,23 +193,31 @@ void BME680BSECComponent::run_() { | |||||||
|     return; |     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; |   // Restore BSEC library state | ||||||
|   this->bsec_status_ = bsec_sensor_control(curr_time_ns, &bme680_settings); |   // 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) { |   if (this->bsec_status_ < BSEC_OK) { | ||||||
|     ESP_LOGW(TAG, "Failed to fetch sensor control settings (BSEC Error Code %d)", this->bsec_status_); |     ESP_LOGW(TAG, "Failed to fetch sensor control settings (BSEC Error Code %d)", this->bsec_status_); | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|   this->next_call_ns_ = bme680_settings.next_call; |   this->next_call_ns_ = this->bme680_settings_.next_call; | ||||||
|  |  | ||||||
|   if (bme680_settings.trigger_measurement) { |   if (this->bme680_settings_.trigger_measurement) { | ||||||
|     this->bme680_.tph_sett.os_temp = bme680_settings.temperature_oversampling; |     this->bme680_.tph_sett.os_temp = this->bme680_settings_.temperature_oversampling; | ||||||
|     this->bme680_.tph_sett.os_pres = bme680_settings.pressure_oversampling; |     this->bme680_.tph_sett.os_pres = this->bme680_settings_.pressure_oversampling; | ||||||
|     this->bme680_.tph_sett.os_hum = bme680_settings.humidity_oversampling; |     this->bme680_.tph_sett.os_hum = this->bme680_settings_.humidity_oversampling; | ||||||
|     this->bme680_.gas_sett.run_gas = bme680_settings.run_gas; |     this->bme680_.gas_sett.run_gas = this->bme680_settings_.run_gas; | ||||||
|     this->bme680_.gas_sett.heatr_temp = bme680_settings.heater_temperature; |     this->bme680_.gas_sett.heatr_temp = this->bme680_settings_.heater_temperature; | ||||||
|     this->bme680_.gas_sett.heatr_dur = bme680_settings.heating_duration; |     this->bme680_.gas_sett.heatr_dur = this->bme680_settings_.heating_duration; | ||||||
|     this->bme680_.power_mode = BME680_FORCED_MODE; |     this->bme680_.power_mode = BME680_FORCED_MODE; | ||||||
|     uint16_t desired_settings = BME680_OST_SEL | BME680_OSP_SEL | BME680_OSH_SEL | BME680_GAS_SENSOR_SEL; |     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_); |     this->bme680_status_ = bme680_set_sensor_settings(desired_settings, &this->bme680_); | ||||||
| @@ -218,19 +234,26 @@ void BME680BSECComponent::run_() { | |||||||
|  |  | ||||||
|     uint16_t meas_dur = 0; |     uint16_t meas_dur = 0; | ||||||
|     bme680_get_profile_dur(&meas_dur, &this->bme680_); |     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); |     ESP_LOGV(TAG, "Queueing read in %ums", meas_dur); | ||||||
|     this->set_timeout("read", meas_dur, |     this->set_timeout("read", meas_dur, [this]() { this->read_(); }); | ||||||
|                       [this, curr_time_ns, bme680_settings]() { this->read_(curr_time_ns, bme680_settings); }); |  | ||||||
|   } else { |   } else { | ||||||
|     ESP_LOGV(TAG, "Measurement not required"); |     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) { | void BME680BSECComponent::read_() { | ||||||
|   ESP_LOGV(TAG, "Reading data"); |   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) { |     while (this->bme680_.power_mode != BME680_SLEEP_MODE) { | ||||||
|       this->bme680_status_ = bme680_get_sensor_mode(&this->bme680_); |       this->bme680_status_ = bme680_get_sensor_mode(&this->bme680_); | ||||||
|       if (this->bme680_status_ != BME680_OK) { |       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"); |     ESP_LOGV(TAG, "Data processing not required"); | ||||||
|     return; |     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 |   bsec_input_t inputs[BSEC_MAX_PHYSICAL_SENSOR];  // Temperature, Pressure, Humidity & Gas Resistance | ||||||
|   uint8_t num_inputs = 0; |   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].sensor_id = BSEC_INPUT_TEMPERATURE; | ||||||
|     inputs[num_inputs].signal = data.temperature / 100.0f; |     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++; |     num_inputs++; | ||||||
|  |  | ||||||
|     // Temperature offset from the real temperature due to external heat sources |     // Temperature offset from the real temperature due to external heat sources | ||||||
|     inputs[num_inputs].sensor_id = BSEC_INPUT_HEATSOURCE; |     inputs[num_inputs].sensor_id = BSEC_INPUT_HEATSOURCE; | ||||||
|     inputs[num_inputs].signal = this->temperature_offset_; |     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++; |     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].sensor_id = BSEC_INPUT_HUMIDITY; | ||||||
|     inputs[num_inputs].signal = data.humidity / 1000.0f; |     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++; |     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].sensor_id = BSEC_INPUT_PRESSURE; | ||||||
|     inputs[num_inputs].signal = data.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++; |     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) { |     if (data.status & BME680_GASM_VALID_MSK) { | ||||||
|       inputs[num_inputs].sensor_id = BSEC_INPUT_GASRESISTOR; |       inputs[num_inputs].sensor_id = BSEC_INPUT_GASRESISTOR; | ||||||
|       inputs[num_inputs].signal = data.gas_resistance; |       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++; |       num_inputs++; | ||||||
|     } else { |     } else { | ||||||
|       ESP_LOGD(TAG, "BME680 did not report gas data"); |       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; |     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]; |   bsec_output_t outputs[BSEC_NUMBER_OUTPUTS]; | ||||||
|   uint8_t num_outputs = BSEC_NUMBER_OUTPUTS; |   uint8_t num_outputs = BSEC_NUMBER_OUTPUTS; | ||||||
|   this->bsec_status_ = bsec_do_steps(inputs, num_inputs, outputs, &num_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_); |     ESP_LOGW(TAG, "BSEC failed to process signals (BSEC Error Code %d)", this->bsec_status_); | ||||||
|     return; |     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) { |   if (num_outputs < 1) { | ||||||
|     ESP_LOGD(TAG, "No signal outputs provided by BSEC"); |     ESP_LOGD(TAG, "No signal outputs provided by BSEC"); | ||||||
|     return; |     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) { | 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++) { |   for (uint8_t i = 0; i < num_outputs; i++) { | ||||||
|     float signal = outputs[i].signal; |     float signal = outputs[i].signal; | ||||||
|     switch (outputs[i].sensor_id) { |     switch (outputs[i].sensor_id) { | ||||||
| @@ -376,12 +422,20 @@ void BME680BSECComponent::publish_sensor_(text_sensor::TextSensor *sensor, const | |||||||
|   sensor->publish_state(value); |   sensor->publish_state(value); | ||||||
| } | } | ||||||
|  |  | ||||||
| int8_t BME680BSECComponent::read_bytes_wrapper(uint8_t address, uint8_t a_register, uint8_t *data, uint16_t len) { | // Communication function - read | ||||||
|   return BME680BSECComponent::instance->read_bytes(a_register, data, len) ? 0 : -1; | // 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) { | // Communication function - write | ||||||
|   return BME680BSECComponent::instance->write_bytes(a_register, data, len) ? 0 : -1; | // 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) { | void BME680BSECComponent::delay_ms(uint32_t period) { | ||||||
| @@ -389,41 +443,97 @@ void BME680BSECComponent::delay_ms(uint32_t period) { | |||||||
|   delay(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_() { | 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); |   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(&this->bsec_state_data_)) { | ||||||
|   if (this->bsec_state_.load(&state)) { |     // No saved BSEC library state available | ||||||
|     ESP_LOGV(TAG, "Loading state"); |     return; | ||||||
|     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)); |  | ||||||
|  |   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) { |   if (this->bsec_status_ != BSEC_OK) { | ||||||
|       ESP_LOGW(TAG, "Failed to load state (BSEC Error Code %d)", this->bsec_status_); |     ESP_LOGW(TAG, "%s: Failed to load BSEC library state (BSEC Error Code %d)", this->device_id_.c_str(), | ||||||
|     } |              this->bsec_status_); | ||||||
|     ESP_LOGI(TAG, "Loaded state"); |     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) { | void BME680BSECComponent::save_state_(uint8_t accuracy) { | ||||||
|   if (accuracy < 3 || (millis() - this->last_state_save_ms_ < this->state_save_interval_ms_)) { |   if (accuracy < 3 || (millis() - this->last_state_save_ms_ < this->state_save_interval_ms_)) { | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|  |   if (BME680BSECComponent::instances.size() <= 1) { | ||||||
|   ESP_LOGV(TAG, "Saving state"); |     // 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 | ||||||
|   uint8_t state[BSEC_MAX_STATE_BLOB_SIZE]; |     this->snapshot_state_(); | ||||||
|   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 (!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"); |     ESP_LOGW(TAG, "Failed to save state"); | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|   | |||||||
| @@ -31,6 +31,7 @@ enum SampleRate { | |||||||
|  |  | ||||||
| class BME680BSECComponent : public Component, public i2c::I2CDevice { | class BME680BSECComponent : public Component, public i2c::I2CDevice { | ||||||
|  public: |  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_temperature_offset(float offset) { this->temperature_offset_ = offset; } | ||||||
|   void set_iaq_mode(IAQMode iaq_mode) { this->iaq_mode_ = iaq_mode; } |   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; } |   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_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; } |   void set_breath_voc_equivalent_sensor(sensor::Sensor *sensor) { this->breath_voc_equivalent_sensor_ = sensor; } | ||||||
|  |  | ||||||
|   static BME680BSECComponent *instance; |   static std::vector<BME680BSECComponent *> instances; | ||||||
|   static int8_t read_bytes_wrapper(uint8_t address, uint8_t a_register, uint8_t *data, uint16_t len); |   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 address, 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); |   static void delay_ms(uint32_t period); | ||||||
|  |  | ||||||
|   void setup() override; |   void setup() override; | ||||||
| @@ -61,23 +62,33 @@ class BME680BSECComponent : public Component, public i2c::I2CDevice { | |||||||
|   void loop() override; |   void loop() override; | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   void set_config_(const uint8_t *config); |   void set_config_(); | ||||||
|   float calc_sensor_sample_rate_(SampleRate sample_rate); |   float calc_sensor_sample_rate_(SampleRate sample_rate); | ||||||
|   void update_subscription_(); |   void update_subscription_(); | ||||||
|  |  | ||||||
|   void run_(); |   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); |   void publish_(const bsec_output_t *outputs, uint8_t num_outputs); | ||||||
|   int64_t get_time_ns_(); |   int64_t get_time_ns_(); | ||||||
|  |  | ||||||
|   void publish_sensor_(sensor::Sensor *sensor, float value, bool change_only = false); |   void publish_sensor_(sensor::Sensor *sensor, float value, bool change_only = false); | ||||||
|   void publish_sensor_(text_sensor::TextSensor *sensor, const std::string &value); |   void publish_sensor_(text_sensor::TextSensor *sensor, const std::string &value); | ||||||
|  |  | ||||||
|   void load_state_(); |   void snapshot_state_();  // Fetch the current BSEC library state and save it in the bsec_state_data_ member (volatile | ||||||
|   void save_state_(uint8_t accuracy); |                            // 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)); } |   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_; |   struct bme680_dev bme680_; | ||||||
|   bsec_library_return_t bsec_status_{BSEC_OK}; |   bsec_library_return_t bsec_status_{BSEC_OK}; | ||||||
|   int8_t bme680_status_{BME680_OK}; |   int8_t bme680_status_{BME680_OK}; | ||||||
| @@ -88,10 +99,14 @@ class BME680BSECComponent : public Component, public i2c::I2CDevice { | |||||||
|  |  | ||||||
|   std::queue<std::function<void()>> queue_; |   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_; |   ESPPreferenceObject bsec_state_; | ||||||
|   uint32_t state_save_interval_ms_{21600000};  // 6 hours - 4 times a day |   uint32_t state_save_interval_ms_{21600000};  // 6 hours - 4 times a day | ||||||
|   uint32_t last_state_save_ms_ = 0; |   uint32_t last_state_save_ms_ = 0; | ||||||
|  |   bsec_bme_settings_t bme680_settings_; | ||||||
|  |  | ||||||
|  |   std::string device_id_; | ||||||
|   float temperature_offset_{0}; |   float temperature_offset_{0}; | ||||||
|   IAQMode iaq_mode_{IAQ_MODE_STATIC}; |   IAQMode iaq_mode_{IAQ_MODE_STATIC}; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -11,16 +11,19 @@ from esphome.const import ( | |||||||
|     CONF_ON_PRESS, |     CONF_ON_PRESS, | ||||||
|     CONF_TRIGGER_ID, |     CONF_TRIGGER_ID, | ||||||
|     CONF_MQTT_ID, |     CONF_MQTT_ID, | ||||||
|  |     DEVICE_CLASS_EMPTY, | ||||||
|     DEVICE_CLASS_RESTART, |     DEVICE_CLASS_RESTART, | ||||||
|     DEVICE_CLASS_UPDATE, |     DEVICE_CLASS_UPDATE, | ||||||
| ) | ) | ||||||
| from esphome.core import CORE, coroutine_with_priority | from esphome.core import CORE, coroutine_with_priority | ||||||
| from esphome.cpp_helpers import setup_entity | from esphome.cpp_helpers import setup_entity | ||||||
|  | from esphome.cpp_generator import MockObjClass | ||||||
|  |  | ||||||
| CODEOWNERS = ["@esphome/core"] | CODEOWNERS = ["@esphome/core"] | ||||||
| IS_PLATFORM_COMPONENT = True | IS_PLATFORM_COMPONENT = True | ||||||
|  |  | ||||||
| DEVICE_CLASSES = [ | DEVICE_CLASSES = [ | ||||||
|  |     DEVICE_CLASS_EMPTY, | ||||||
|     DEVICE_CLASS_RESTART, |     DEVICE_CLASS_RESTART, | ||||||
|     DEVICE_CLASS_UPDATE, |     DEVICE_CLASS_UPDATE, | ||||||
| ] | ] | ||||||
| @@ -54,30 +57,23 @@ _UNDEF = object() | |||||||
|  |  | ||||||
|  |  | ||||||
| def button_schema( | def button_schema( | ||||||
|  |     class_: MockObjClass, | ||||||
|  |     *, | ||||||
|     icon: str = _UNDEF, |     icon: str = _UNDEF, | ||||||
|     entity_category: str = _UNDEF, |     entity_category: str = _UNDEF, | ||||||
|     device_class: str = _UNDEF, |     device_class: str = _UNDEF, | ||||||
| ) -> cv.Schema: | ) -> cv.Schema: | ||||||
|     schema = BUTTON_SCHEMA |     schema = {cv.GenerateID(): cv.declare_id(class_)} | ||||||
|     if icon is not _UNDEF: |  | ||||||
|         schema = schema.extend({cv.Optional(CONF_ICON, default=icon): cv.icon}) |     for key, default, validator in [ | ||||||
|     if entity_category is not _UNDEF: |         (CONF_ICON, icon, cv.icon), | ||||||
|         schema = schema.extend( |         (CONF_ENTITY_CATEGORY, entity_category, cv.entity_category), | ||||||
|             { |         (CONF_DEVICE_CLASS, device_class, validate_device_class), | ||||||
|                 cv.Optional( |     ]: | ||||||
|                     CONF_ENTITY_CATEGORY, default=entity_category |         if default is not _UNDEF: | ||||||
|                 ): cv.entity_category |             schema[cv.Optional(key, default=default)] = validator | ||||||
|             } |  | ||||||
|         ) |     return BUTTON_SCHEMA.extend(schema) | ||||||
|     if device_class is not _UNDEF: |  | ||||||
|         schema = schema.extend( |  | ||||||
|             { |  | ||||||
|                 cv.Optional( |  | ||||||
|                     CONF_DEVICE_CLASS, default=device_class |  | ||||||
|                 ): validate_device_class |  | ||||||
|             } |  | ||||||
|         ) |  | ||||||
|     return schema |  | ||||||
|  |  | ||||||
|  |  | ||||||
| async def setup_button_core_(var, config): | 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. | /** Base class for all buttons. | ||||||
|  * |  * | ||||||
|  * A button is just a momentary switch that does not have a state, only a trigger. |  * 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, | ||||||
|     CONF_MODE_COMMAND_TOPIC, |     CONF_MODE_COMMAND_TOPIC, | ||||||
|     CONF_MODE_STATE_TOPIC, |     CONF_MODE_STATE_TOPIC, | ||||||
|  |     CONF_ON_CONTROL, | ||||||
|     CONF_ON_STATE, |     CONF_ON_STATE, | ||||||
|     CONF_PRESET, |     CONF_PRESET, | ||||||
|     CONF_PRESET_COMMAND_TOPIC, |     CONF_PRESET_COMMAND_TOPIC, | ||||||
| @@ -104,9 +105,40 @@ CLIMATE_SWING_MODES = { | |||||||
|  |  | ||||||
| validate_climate_swing_mode = cv.enum(CLIMATE_SWING_MODES, upper=True) | 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 | # Actions | ||||||
| ControlAction = climate_ns.class_("ControlAction", automation.Action) | ControlAction = climate_ns.class_("ControlAction", automation.Action) | ||||||
| StateTrigger = climate_ns.class_("StateTrigger", automation.Trigger.template()) | 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( | 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_MIN_TEMPERATURE): cv.temperature, | ||||||
|                 cv.Optional(CONF_MAX_TEMPERATURE): cv.temperature, |                 cv.Optional(CONF_MAX_TEMPERATURE): cv.temperature, | ||||||
|                 cv.Optional(CONF_TEMPERATURE_STEP): cv.float_with_unit( |                 cv.Optional(CONF_TEMPERATURE_STEP): VISUAL_TEMPERATURE_STEP_SCHEMA, | ||||||
|                     "visual_temperature", "(°C|° C|°|C|° K|° K|K|°F|° F|F)?" |  | ||||||
|                 ), |  | ||||||
|             } |             } | ||||||
|         ), |         ), | ||||||
|         cv.Optional(CONF_ACTION_STATE_TOPIC): cv.All( |         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.Optional(CONF_TARGET_TEMPERATURE_LOW_STATE_TOPIC): cv.All( | ||||||
|             cv.requires_component("mqtt"), cv.publish_topic |             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.Optional(CONF_ON_STATE): automation.validate_automation( | ||||||
|             { |             { | ||||||
|                 cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StateTrigger), |                 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: |     if CONF_MAX_TEMPERATURE in visual: | ||||||
|         cg.add(var.set_visual_max_temperature_override(visual[CONF_MAX_TEMPERATURE])) |         cg.add(var.set_visual_max_temperature_override(visual[CONF_MAX_TEMPERATURE])) | ||||||
|     if CONF_TEMPERATURE_STEP in visual: |     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: |     if CONF_MQTT_ID in config: | ||||||
|         mqtt_ = cg.new_Pvariable(config[CONF_MQTT_ID], var) |         mqtt_ = cg.new_Pvariable(config[CONF_MQTT_ID], var) | ||||||
|   | |||||||
| @@ -42,6 +42,13 @@ template<typename... Ts> class ControlAction : public Action<Ts...> { | |||||||
|   Climate *climate_; |   Climate *climate_; | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | class ControlTrigger : public Trigger<> { | ||||||
|  |  public: | ||||||
|  |   ControlTrigger(Climate *climate) { | ||||||
|  |     climate->add_on_control_callback([this]() { this->trigger(); }); | ||||||
|  |   } | ||||||
|  | }; | ||||||
|  |  | ||||||
| class StateTrigger : public Trigger<> { | class StateTrigger : public Trigger<> { | ||||||
|  public: |  public: | ||||||
|   StateTrigger(Climate *climate) { |   StateTrigger(Climate *climate) { | ||||||
|   | |||||||
| @@ -44,6 +44,7 @@ void ClimateCall::perform() { | |||||||
|   if (this->target_temperature_high_.has_value()) { |   if (this->target_temperature_high_.has_value()) { | ||||||
|     ESP_LOGD(TAG, "  Target Temperature High: %.2f", *this->target_temperature_high_); |     ESP_LOGD(TAG, "  Target Temperature High: %.2f", *this->target_temperature_high_); | ||||||
|   } |   } | ||||||
|  |   this->parent_->control_callback_.call(); | ||||||
|   this->parent_->control(*this); |   this->parent_->control(*this); | ||||||
| } | } | ||||||
| void ClimateCall::validate_() { | void ClimateCall::validate_() { | ||||||
| @@ -317,6 +318,10 @@ void Climate::add_on_state_callback(std::function<void()> &&callback) { | |||||||
|   this->state_callback_.add(std::move(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 | // Random 32bit value; If this changes existing restore preferences are invalidated | ||||||
| static const uint32_t RESTORE_STATE_VERSION = 0x848EA6ADUL; | static const uint32_t RESTORE_STATE_VERSION = 0x848EA6ADUL; | ||||||
|  |  | ||||||
| @@ -430,9 +435,11 @@ ClimateTraits Climate::get_traits() { | |||||||
|   if (this->visual_max_temperature_override_.has_value()) { |   if (this->visual_max_temperature_override_.has_value()) { | ||||||
|     traits.set_visual_max_temperature(*this->visual_max_temperature_override_); |     traits.set_visual_max_temperature(*this->visual_max_temperature_override_); | ||||||
|   } |   } | ||||||
|   if (this->visual_temperature_step_override_.has_value()) { |   if (this->visual_target_temperature_step_override_.has_value()) { | ||||||
|     traits.set_visual_temperature_step(*this->visual_temperature_step_override_); |     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; |   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) { | void Climate::set_visual_max_temperature_override(float visual_max_temperature_override) { | ||||||
|   this->visual_max_temperature_override_ = 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) { | void Climate::set_visual_temperature_step_override(float target, float current) { | ||||||
|   this->visual_temperature_step_override_ = visual_temperature_step_override; |   this->visual_target_temperature_step_override_ = target; | ||||||
|  |   this->visual_current_temperature_step_override_ = current; | ||||||
| } | } | ||||||
| #pragma GCC diagnostic push | #pragma GCC diagnostic push | ||||||
| #pragma GCC diagnostic ignored "-Wdeprecated-declarations" | #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, "  [x] Visual settings:"); | ||||||
|   ESP_LOGCONFIG(tag, "      - Min: %.1f", traits.get_visual_min_temperature()); |   ESP_LOGCONFIG(tag, "      - Min: %.1f", traits.get_visual_min_temperature()); | ||||||
|   ESP_LOGCONFIG(tag, "      - Max: %.1f", traits.get_visual_max_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()) { |   if (traits.get_supports_current_temperature()) { | ||||||
|     ESP_LOGCONFIG(tag, "  [x] 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); |   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 |   /** Make a climate device control call, this is used to control the climate device, see the ClimateCall description | ||||||
|    * for more info. |    * for more info. | ||||||
|    * @return A new ClimateCall instance targeting this climate device. |    * @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_min_temperature_override(float visual_min_temperature_override); | ||||||
|   void set_visual_max_temperature_override(float visual_max_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: |  protected: | ||||||
|   friend ClimateCall; |   friend ClimateCall; | ||||||
| @@ -285,10 +293,12 @@ class Climate : public EntityBase { | |||||||
|   void dump_traits_(const char *tag); |   void dump_traits_(const char *tag); | ||||||
|  |  | ||||||
|   CallbackManager<void()> state_callback_{}; |   CallbackManager<void()> state_callback_{}; | ||||||
|  |   CallbackManager<void()> control_callback_{}; | ||||||
|   ESPPreferenceObject rtc_; |   ESPPreferenceObject rtc_; | ||||||
|   optional<float> visual_min_temperature_override_{}; |   optional<float> visual_min_temperature_override_{}; | ||||||
|   optional<float> visual_max_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 | }  // namespace climate | ||||||
|   | |||||||
| @@ -3,8 +3,12 @@ | |||||||
| namespace esphome { | namespace esphome { | ||||||
| namespace climate { | namespace climate { | ||||||
|  |  | ||||||
| int8_t ClimateTraits::get_temperature_accuracy_decimals() const { | int8_t ClimateTraits::get_target_temperature_accuracy_decimals() const { | ||||||
|   return step_to_accuracy_decimals(this->visual_temperature_step_); |   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 | }  // namespace climate | ||||||
|   | |||||||
| @@ -147,9 +147,20 @@ class ClimateTraits { | |||||||
|   void set_visual_min_temperature(float visual_min_temperature) { visual_min_temperature_ = visual_min_temperature; } |   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_; } |   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; } |   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_; } |   float get_visual_target_temperature_step() const { return visual_target_temperature_step_; } | ||||||
|   int8_t get_temperature_accuracy_decimals() const; |   float get_visual_current_temperature_step() const { return visual_current_temperature_step_; } | ||||||
|   void set_visual_temperature_step(float temperature_step) { visual_temperature_step_ = 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: |  protected: | ||||||
|   void set_mode_support_(climate::ClimateMode mode, bool supported) { |   void set_mode_support_(climate::ClimateMode mode, bool supported) { | ||||||
| @@ -186,7 +197,8 @@ class ClimateTraits { | |||||||
|  |  | ||||||
|   float visual_min_temperature_{10}; |   float visual_min_temperature_{10}; | ||||||
|   float visual_max_temperature_{30}; |   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 | }  // namespace climate | ||||||
|   | |||||||
| @@ -10,8 +10,20 @@ CONF_RED_INT = "red_int" | |||||||
| CONF_GREEN_INT = "green_int" | CONF_GREEN_INT = "green_int" | ||||||
| CONF_BLUE_INT = "blue_int" | CONF_BLUE_INT = "blue_int" | ||||||
| CONF_WHITE_INT = "white_int" | CONF_WHITE_INT = "white_int" | ||||||
|  | CONF_HEX = "hex" | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = cv.Schema( |  | ||||||
|  | def hex_color(value): | ||||||
|  |     if len(value) != 6: | ||||||
|  |         raise cv.Invalid("Color must have six digits") | ||||||
|  |     try: | ||||||
|  |         return (int(value[0:2], 16), int(value[2:4], 16), int(value[4:6], 16)) | ||||||
|  |     except ValueError as exc: | ||||||
|  |         raise cv.Invalid("Color must be hexadecimal") from exc | ||||||
|  |  | ||||||
|  |  | ||||||
|  | CONFIG_SCHEMA = cv.Any( | ||||||
|  |     cv.Schema( | ||||||
|         { |         { | ||||||
|             cv.Required(CONF_ID): cv.declare_id(ColorStruct), |             cv.Required(CONF_ID): cv.declare_id(ColorStruct), | ||||||
|             cv.Exclusive(CONF_RED, "red"): cv.percentage, |             cv.Exclusive(CONF_RED, "red"): cv.percentage, | ||||||
| @@ -23,10 +35,17 @@ CONFIG_SCHEMA = cv.Schema( | |||||||
|             cv.Exclusive(CONF_WHITE, "white"): cv.percentage, |             cv.Exclusive(CONF_WHITE, "white"): cv.percentage, | ||||||
|             cv.Exclusive(CONF_WHITE_INT, "white"): cv.uint8_t, |             cv.Exclusive(CONF_WHITE_INT, "white"): cv.uint8_t, | ||||||
|         } |         } | ||||||
| ).extend(cv.COMPONENT_SCHEMA) |     ).extend(cv.COMPONENT_SCHEMA), | ||||||
|  |     cv.Schema( | ||||||
|  |         { | ||||||
|  |             cv.Required(CONF_ID): cv.declare_id(ColorStruct), | ||||||
|  |             cv.Required(CONF_HEX): hex_color, | ||||||
|  |         } | ||||||
|  |     ).extend(cv.COMPONENT_SCHEMA), | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
| async def to_code(config): | def from_rgbw(config): | ||||||
|     r = 0 |     r = 0 | ||||||
|     if CONF_RED in config: |     if CONF_RED in config: | ||||||
|         r = int(config[CONF_RED] * 255) |         r = int(config[CONF_RED] * 255) | ||||||
| @@ -51,6 +70,16 @@ async def to_code(config): | |||||||
|     elif CONF_WHITE_INT in config: |     elif CONF_WHITE_INT in config: | ||||||
|         w = config[CONF_WHITE_INT] |         w = config[CONF_WHITE_INT] | ||||||
|  |  | ||||||
|  |     return (r, g, b, w) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | async def to_code(config): | ||||||
|  |     if CONF_HEX in config: | ||||||
|  |         r, g, b = config[CONF_HEX] | ||||||
|  |         w = 0 | ||||||
|  |     else: | ||||||
|  |         r, g, b, w = from_rgbw(config) | ||||||
|  |  | ||||||
|     cg.new_variable( |     cg.new_variable( | ||||||
|         config[CONF_ID], |         config[CONF_ID], | ||||||
|         cg.StructInitializer(ColorStruct, ("r", r), ("g", g), ("b", b), ("w", w)), |         cg.StructInitializer(ColorStruct, ("r", r), ("g", g), ("b", b), ("w", w)), | ||||||
|   | |||||||
| @@ -16,10 +16,9 @@ CopyButton = copy_ns.class_("CopyButton", button.Button, cg.Component) | |||||||
|  |  | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = ( | CONFIG_SCHEMA = ( | ||||||
|     button.button_schema() |     button.button_schema(CopyButton) | ||||||
|     .extend( |     .extend( | ||||||
|         { |         { | ||||||
|             cv.GenerateID(): cv.declare_id(CopyButton), |  | ||||||
|             cv.Required(CONF_SOURCE_ID): cv.use_id(button.Button), |             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) | CopyNumber = copy_ns.class_("CopyNumber", number.Number, cg.Component) | ||||||
|  |  | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = number.NUMBER_SCHEMA.extend( | CONFIG_SCHEMA = ( | ||||||
|  |     number.number_schema(CopyNumber) | ||||||
|  |     .extend( | ||||||
|         { |         { | ||||||
|         cv.GenerateID(): cv.declare_id(CopyNumber), |  | ||||||
|             cv.Required(CONF_SOURCE_ID): cv.use_id(number.Number), |             cv.Required(CONF_SOURCE_ID): cv.use_id(number.Number), | ||||||
|         } |         } | ||||||
| ).extend(cv.COMPONENT_SCHEMA) |     ) | ||||||
|  |     .extend(cv.COMPONENT_SCHEMA) | ||||||
|  | ) | ||||||
|  |  | ||||||
| FINAL_VALIDATE_SCHEMA = cv.All( | FINAL_VALIDATE_SCHEMA = cv.All( | ||||||
|     inherit_property_from(CONF_ICON, CONF_SOURCE_ID), |     inherit_property_from(CONF_ICON, CONF_SOURCE_ID), | ||||||
|   | |||||||
| @@ -17,6 +17,17 @@ from esphome.const import ( | |||||||
|     CONF_STOP, |     CONF_STOP, | ||||||
|     CONF_MQTT_ID, |     CONF_MQTT_ID, | ||||||
|     CONF_TRIGGER_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.core import CORE, coroutine_with_priority | ||||||
| from esphome.cpp_helpers import setup_entity | from esphome.cpp_helpers import setup_entity | ||||||
| @@ -25,17 +36,17 @@ IS_PLATFORM_COMPONENT = True | |||||||
|  |  | ||||||
| CODEOWNERS = ["@esphome/core"] | CODEOWNERS = ["@esphome/core"] | ||||||
| DEVICE_CLASSES = [ | DEVICE_CLASSES = [ | ||||||
|     "", |     DEVICE_CLASS_AWNING, | ||||||
|     "awning", |     DEVICE_CLASS_BLIND, | ||||||
|     "blind", |     DEVICE_CLASS_CURTAIN, | ||||||
|     "curtain", |     DEVICE_CLASS_DAMPER, | ||||||
|     "damper", |     DEVICE_CLASS_DOOR, | ||||||
|     "door", |     DEVICE_CLASS_EMPTY, | ||||||
|     "garage", |     DEVICE_CLASS_GARAGE, | ||||||
|     "gate", |     DEVICE_CLASS_GATE, | ||||||
|     "shade", |     DEVICE_CLASS_SHADE, | ||||||
|     "shutter", |     DEVICE_CLASS_SHUTTER, | ||||||
|     "window", |     DEVICE_CLASS_WINDOW, | ||||||
| ] | ] | ||||||
|  |  | ||||||
| cover_ns = cg.esphome_ns.namespace("cover") | cover_ns = cg.esphome_ns.namespace("cover") | ||||||
|   | |||||||
| @@ -10,7 +10,7 @@ CONFIG_SCHEMA = cv.Schema( | |||||||
|     { |     { | ||||||
|         cv.GenerateID(): cv.declare_id(CustomSensorConstructor), |         cv.GenerateID(): cv.declare_id(CustomSensorConstructor), | ||||||
|         cv.Required(CONF_LAMBDA): cv.returning_lambda, |         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()), | ||||||
|     } |     } | ||||||
| ) | ) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -21,6 +21,7 @@ from esphome.components.esp32.const import ( | |||||||
|     VARIANT_ESP32, |     VARIANT_ESP32, | ||||||
|     VARIANT_ESP32C3, |     VARIANT_ESP32C3, | ||||||
|     VARIANT_ESP32S2, |     VARIANT_ESP32S2, | ||||||
|  |     VARIANT_ESP32S3, | ||||||
| ) | ) | ||||||
|  |  | ||||||
| WAKEUP_PINS = { | WAKEUP_PINS = { | ||||||
| @@ -69,6 +70,30 @@ WAKEUP_PINS = { | |||||||
|         20, |         20, | ||||||
|         21, |         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_TYPE): cv.enum(NUMBER_TYPES, int=True), | ||||||
|                     cv.Required(CONF_MIN_VALUE): cv.float_, |                     cv.Required(CONF_MIN_VALUE): cv.float_, | ||||||
|                     cv.Required(CONF_MAX_VALUE): cv.float_, |                     cv.Required(CONF_MAX_VALUE): cv.float_, | ||||||
|   | |||||||
| @@ -32,9 +32,11 @@ void Rect::extend(Rect rect) { | |||||||
|     this->h = rect.h; |     this->h = rect.h; | ||||||
|   } else { |   } else { | ||||||
|     if (this->x > rect.x) { |     if (this->x > rect.x) { | ||||||
|  |       this->w = this->w + (this->x - rect.x); | ||||||
|       this->x = rect.x; |       this->x = rect.x; | ||||||
|     } |     } | ||||||
|     if (this->y > rect.y) { |     if (this->y > rect.y) { | ||||||
|  |       this->h = this->h + (this->y - rect.y); | ||||||
|       this->y = rect.y; |       this->y = rect.y; | ||||||
|     } |     } | ||||||
|     if (this->x2() < rect.x2()) { |     if (this->x2() < rect.x2()) { | ||||||
| @@ -49,29 +51,35 @@ void Rect::shrink(Rect rect) { | |||||||
|   if (!this->inside(rect)) { |   if (!this->inside(rect)) { | ||||||
|     (*this) = Rect(); |     (*this) = Rect(); | ||||||
|   } else { |   } else { | ||||||
|     if (this->x < rect.x) { |  | ||||||
|       this->x = rect.x; |  | ||||||
|     } |  | ||||||
|     if (this->y < rect.y) { |  | ||||||
|       this->y = rect.y; |  | ||||||
|     } |  | ||||||
|     if (this->x2() > rect.x2()) { |     if (this->x2() > rect.x2()) { | ||||||
|       this->w = rect.x2() - this->x; |       this->w = rect.x2() - this->x; | ||||||
|     } |     } | ||||||
|  |     if (this->x < rect.x) { | ||||||
|  |       this->w = this->w + (this->x - rect.x); | ||||||
|  |       this->x = rect.x; | ||||||
|  |     } | ||||||
|     if (this->y2() > rect.y2()) { |     if (this->y2() > rect.y2()) { | ||||||
|       this->h = rect.y2() - this->y; |       this->h = rect.y2() - this->y; | ||||||
|     } |     } | ||||||
|  |     if (this->y < rect.y) { | ||||||
|  |       this->h = this->h + (this->y - rect.y); | ||||||
|  |       this->y = rect.y; | ||||||
|  |     } | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| bool Rect::inside(int16_t x, int16_t y, bool absolute) {  // NOLINT | bool Rect::equal(Rect rect) { | ||||||
|  |   return (rect.x == this->x) && (rect.w == this->w) && (rect.y == this->y) && (rect.h == this->h); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool Rect::inside(int16_t test_x, int16_t test_y, bool absolute) {  // NOLINT | ||||||
|   if (!this->is_set()) { |   if (!this->is_set()) { | ||||||
|     return true; |     return true; | ||||||
|   } |   } | ||||||
|   if (absolute) { |   if (absolute) { | ||||||
|     return ((x >= 0) && (x <= this->w) && (y >= 0) && (y <= this->h)); |     return ((test_x >= this->x) && (test_x <= this->x2()) && (test_y >= this->y) && (test_y <= this->y2())); | ||||||
|   } else { |   } else { | ||||||
|     return ((x >= this->x) && (x <= this->x2()) && (y >= this->y) && (y <= this->y2())); |     return ((test_x >= 0) && (test_x <= this->w) && (test_y >= 0) && (test_y <= this->h)); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -80,15 +88,16 @@ bool Rect::inside(Rect rect, bool absolute) { | |||||||
|     return true; |     return true; | ||||||
|   } |   } | ||||||
|   if (absolute) { |   if (absolute) { | ||||||
|     return ((rect.x <= this->w) && (rect.w >= 0) && (rect.y <= this->h) && (rect.h >= 0)); |  | ||||||
|   } else { |  | ||||||
|     return ((rect.x <= this->x2()) && (rect.x2() >= this->x) && (rect.y <= this->y2()) && (rect.y2() >= this->y)); |     return ((rect.x <= this->x2()) && (rect.x2() >= this->x) && (rect.y <= this->y2()) && (rect.y2() >= this->y)); | ||||||
|  |   } else { | ||||||
|  |     return ((rect.x <= this->w) && (rect.w >= 0) && (rect.y <= this->h) && (rect.h >= 0)); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| void Rect::info(const std::string &prefix) { | void Rect::info(const std::string &prefix) { | ||||||
|   if (this->is_set()) { |   if (this->is_set()) { | ||||||
|     ESP_LOGI(TAG, "%s [%3d,%3d,%3d,%3d]", prefix.c_str(), this->x, this->y, this->w, this->h); |     ESP_LOGI(TAG, "%s [%3d,%3d,%3d,%3d] (%3d,%3d)", prefix.c_str(), this->x, this->y, this->w, this->h, this->x2(), | ||||||
|  |              this->y2()); | ||||||
|   } else |   } else | ||||||
|     ESP_LOGI(TAG, "%s ** IS NOT SET **", prefix.c_str()); |     ESP_LOGI(TAG, "%s ** IS NOT SET **", prefix.c_str()); | ||||||
| } | } | ||||||
| @@ -603,10 +612,10 @@ void Font::measure(const char *str, int *width, int *x_offset, int *baseline, in | |||||||
|   *x_offset = min_x; |   *x_offset = min_x; | ||||||
|   *width = x - 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) { | Font::Font(const GlyphData *data, int data_nr, int baseline, int height) : baseline_(baseline), height_(height) { | ||||||
|  |   glyphs_.reserve(data_nr); | ||||||
|   for (int i = 0; i < data_nr; ++i) |   for (int i = 0; i < data_nr; ++i) | ||||||
|     glyphs_.emplace_back(data + i); |     glyphs_.emplace_back(&data[i]); | ||||||
| } | } | ||||||
|  |  | ||||||
| bool Image::get_pixel(int x, int y) const { | bool Image::get_pixel(int x, int y) const { | ||||||
|   | |||||||
| @@ -120,8 +120,9 @@ class Rect { | |||||||
|   void extend(Rect rect); |   void extend(Rect rect); | ||||||
|   void shrink(Rect rect); |   void shrink(Rect rect); | ||||||
|  |  | ||||||
|   bool inside(Rect rect, bool absolute = false); |   bool inside(Rect rect, bool absolute = true); | ||||||
|   bool inside(int16_t x, int16_t y, bool absolute = false); |   bool inside(int16_t test_x, int16_t test_y, bool absolute = true); | ||||||
|  |   bool equal(Rect rect); | ||||||
|   void info(const std::string &prefix = "rect info:"); |   void info(const std::string &prefix = "rect info:"); | ||||||
| }; | }; | ||||||
|  |  | ||||||
| @@ -526,10 +527,10 @@ class Font { | |||||||
|   inline int get_baseline() { return this->baseline_; } |   inline int get_baseline() { return this->baseline_; } | ||||||
|   inline int get_height() { return this->height_; } |   inline int get_height() { return this->height_; } | ||||||
|  |  | ||||||
|   const std::vector<Glyph> &get_glyphs() const; |   const std::vector<Glyph, ExternalRAMAllocator<Glyph>> &get_glyphs() const { return glyphs_; } | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   std::vector<Glyph> glyphs_; |   std::vector<Glyph, ExternalRAMAllocator<Glyph>> glyphs_; | ||||||
|   int baseline_; |   int baseline_; | ||||||
|   int height_; |   int height_; | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -4,29 +4,43 @@ from pathlib import Path | |||||||
| import logging | import logging | ||||||
| import os | 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 ( | from esphome.const import ( | ||||||
|     CONF_BOARD, |     CONF_BOARD, | ||||||
|  |     CONF_COMPONENTS, | ||||||
|     CONF_FRAMEWORK, |     CONF_FRAMEWORK, | ||||||
|  |     CONF_NAME, | ||||||
|     CONF_SOURCE, |     CONF_SOURCE, | ||||||
|     CONF_TYPE, |     CONF_TYPE, | ||||||
|     CONF_VARIANT, |     CONF_VARIANT, | ||||||
|     CONF_VERSION, |     CONF_VERSION, | ||||||
|     CONF_ADVANCED, |     CONF_ADVANCED, | ||||||
|  |     CONF_REFRESH, | ||||||
|  |     CONF_PATH, | ||||||
|  |     CONF_URL, | ||||||
|  |     CONF_REF, | ||||||
|     CONF_IGNORE_EFUSE_MAC_CRC, |     CONF_IGNORE_EFUSE_MAC_CRC, | ||||||
|     KEY_CORE, |     KEY_CORE, | ||||||
|     KEY_FRAMEWORK_VERSION, |     KEY_FRAMEWORK_VERSION, | ||||||
|     KEY_TARGET_FRAMEWORK, |     KEY_TARGET_FRAMEWORK, | ||||||
|     KEY_TARGET_PLATFORM, |     KEY_TARGET_PLATFORM, | ||||||
|  |     TYPE_GIT, | ||||||
|  |     TYPE_LOCAL, | ||||||
|     __version__, |     __version__, | ||||||
| ) | ) | ||||||
| from esphome.core import CORE, HexInt | from esphome.core import CORE, HexInt, TimePeriod | ||||||
| import esphome.config_validation as cv | import esphome.config_validation as cv | ||||||
| import esphome.codegen as cg | import esphome.codegen as cg | ||||||
|  | from esphome import git | ||||||
|  |  | ||||||
| from .const import (  # noqa | from .const import (  # noqa | ||||||
|     KEY_BOARD, |     KEY_BOARD, | ||||||
|  |     KEY_COMPONENTS, | ||||||
|     KEY_ESP32, |     KEY_ESP32, | ||||||
|  |     KEY_PATH, | ||||||
|  |     KEY_REF, | ||||||
|  |     KEY_REFRESH, | ||||||
|  |     KEY_REPO, | ||||||
|     KEY_SDKCONFIG_OPTIONS, |     KEY_SDKCONFIG_OPTIONS, | ||||||
|     KEY_VARIANT, |     KEY_VARIANT, | ||||||
|     VARIANT_ESP32C3, |     VARIANT_ESP32C3, | ||||||
| @@ -51,6 +65,7 @@ def set_core_data(config): | |||||||
|     if conf[CONF_TYPE] == FRAMEWORK_ESP_IDF: |     if conf[CONF_TYPE] == FRAMEWORK_ESP_IDF: | ||||||
|         CORE.data[KEY_CORE][KEY_TARGET_FRAMEWORK] = "esp-idf" |         CORE.data[KEY_CORE][KEY_TARGET_FRAMEWORK] = "esp-idf" | ||||||
|         CORE.data[KEY_ESP32][KEY_SDKCONFIG_OPTIONS] = {} |         CORE.data[KEY_ESP32][KEY_SDKCONFIG_OPTIONS] = {} | ||||||
|  |         CORE.data[KEY_ESP32][KEY_COMPONENTS] = {} | ||||||
|     elif conf[CONF_TYPE] == FRAMEWORK_ARDUINO: |     elif conf[CONF_TYPE] == FRAMEWORK_ARDUINO: | ||||||
|         CORE.data[KEY_CORE][KEY_TARGET_FRAMEWORK] = "arduino" |         CORE.data[KEY_CORE][KEY_TARGET_FRAMEWORK] = "arduino" | ||||||
|     CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] = cv.Version.parse( |     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 |     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: | def _format_framework_arduino_version(ver: cv.Version) -> str: | ||||||
|     # format the given arduino (https://github.com/espressif/arduino-esp32/releases) version to |     # format the given arduino (https://github.com/espressif/arduino-esp32/releases) version to | ||||||
|     # a PIO platformio/framework-arduinoespressif32 value |     # 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 | # The default/recommended esp-idf framework version | ||||||
| #  - https://github.com/espressif/esp-idf/releases | #  - https://github.com/espressif/esp-idf/releases | ||||||
| #  - https://api.registry.platformio.org/v3/packages/platformio/tool/framework-espidf | #  - 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 | # The platformio/espressif32 version to use for esp-idf frameworks | ||||||
| #  - https://github.com/platformio/platform-espressif32/releases | #  - https://github.com/platformio/platform-espressif32/releases | ||||||
| #  - https://api.registry.platformio.org/v3/packages/platformio/platform/espressif32 | #  - 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): | def _arduino_check_versions(value): | ||||||
|     value = value.copy() |     value = value.copy() | ||||||
|     lookups = { |     lookups = { | ||||||
|         "dev": (cv.Version(2, 0, 5), "https://github.com/espressif/arduino-esp32.git"), |         "dev": (cv.Version(2, 1, 0), "https://github.com/espressif/arduino-esp32.git"), | ||||||
|         "latest": (cv.Version(2, 0, 5), None), |         "latest": (cv.Version(2, 0, 7), None), | ||||||
|         "recommended": (RECOMMENDED_ARDUINO_FRAMEWORK_VERSION, None), |         "recommended": (RECOMMENDED_ARDUINO_FRAMEWORK_VERSION, None), | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -183,8 +213,8 @@ def _arduino_check_versions(value): | |||||||
| def _esp_idf_check_versions(value): | def _esp_idf_check_versions(value): | ||||||
|     value = value.copy() |     value = value.copy() | ||||||
|     lookups = { |     lookups = { | ||||||
|         "dev": (cv.Version(5, 0, 0), "https://github.com/espressif/esp-idf.git"), |         "dev": (cv.Version(5, 1, 0), "https://github.com/espressif/esp-idf.git"), | ||||||
|         "latest": (cv.Version(4, 4, 2), None), |         "latest": (cv.Version(5, 0, 1), None), | ||||||
|         "recommended": (RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION, 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_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, |     _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: |     elif conf[CONF_TYPE] == FRAMEWORK_ARDUINO: | ||||||
|         cg.add_platformio_option("framework", "arduino") |         cg.add_platformio_option("framework", "arduino") | ||||||
|         cg.add_build_flag("-DUSE_ARDUINO") |         cg.add_build_flag("-DUSE_ARDUINO") | ||||||
| @@ -468,6 +523,32 @@ def copy_files(): | |||||||
|             __version__, |             __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__) |     dir = os.path.dirname(__file__) | ||||||
|     post_build_file = os.path.join(dir, "post_build.py.script") |     post_build_file = os.path.join(dir, "post_build.py.script") | ||||||
|     copy_file_if_changed( |     copy_file_if_changed( | ||||||
|   | |||||||
| @@ -4,6 +4,11 @@ KEY_ESP32 = "esp32" | |||||||
| KEY_BOARD = "board" | KEY_BOARD = "board" | ||||||
| KEY_VARIANT = "variant" | KEY_VARIANT = "variant" | ||||||
| KEY_SDKCONFIG_OPTIONS = "sdkconfig_options" | KEY_SDKCONFIG_OPTIONS = "sdkconfig_options" | ||||||
|  | KEY_COMPONENTS = "components" | ||||||
|  | KEY_REPO = "repo" | ||||||
|  | KEY_REF = "ref" | ||||||
|  | KEY_REFRESH = "refresh" | ||||||
|  | KEY_PATH = "path" | ||||||
|  |  | ||||||
| VARIANT_ESP32 = "ESP32" | VARIANT_ESP32 = "ESP32" | ||||||
| VARIANT_ESP32S2 = "ESP32S2" | VARIANT_ESP32S2 = "ESP32S2" | ||||||
|   | |||||||
| @@ -1,15 +1,25 @@ | |||||||
| # Source https://github.com/letscontrolit/ESPEasy/pull/3845#issuecomment-1005864664 | # 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 | # pylint: disable=E0602 | ||||||
| Import("env")  # noqa | 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): | def esp32_create_combined_bin(source, target, env): | ||||||
|     verbose = bool(int(ARGUMENTS.get("PIOVERBOSE", "0"))) |     verbose = bool(int(ARGUMENTS.get("PIOVERBOSE", "0"))) | ||||||
|   | |||||||
| @@ -62,6 +62,7 @@ bool BLEClientBase::parse_device(const espbt::ESPBTDevice &device) { | |||||||
| void BLEClientBase::connect() { | void BLEClientBase::connect() { | ||||||
|   ESP_LOGI(TAG, "[%d] [%s] 0x%02x Attempting BLE connection", this->connection_index_, this->address_str_.c_str(), |   ESP_LOGI(TAG, "[%d] [%s] 0x%02x Attempting BLE connection", this->connection_index_, this->address_str_.c_str(), | ||||||
|            this->remote_addr_type_); |            this->remote_addr_type_); | ||||||
|  |   this->paired_ = false; | ||||||
|   auto ret = esp_ble_gattc_open(this->gattc_if_, this->remote_bda_, this->remote_addr_type_, true); |   auto ret = esp_ble_gattc_open(this->gattc_if_, this->remote_bda_, this->remote_addr_type_, true); | ||||||
|   if (ret) { |   if (ret) { | ||||||
|     ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_open error, status=%d", this->connection_index_, this->address_str_.c_str(), |     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() { | void BLEClientBase::disconnect() { | ||||||
|   if (this->state_ == espbt::ClientState::IDLE || this->state_ == espbt::ClientState::DISCONNECTING) |   if (this->state_ == espbt::ClientState::IDLE || this->state_ == espbt::ClientState::DISCONNECTING) | ||||||
|     return; |     return; | ||||||
| @@ -247,11 +250,15 @@ void BLEClientBase::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_ | |||||||
|   switch (event) { |   switch (event) { | ||||||
|     // This event is sent by the server when it requests security |     // This event is sent by the server when it requests security | ||||||
|     case ESP_GAP_BLE_SEC_REQ_EVT: |     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_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); |       esp_ble_gap_security_rsp(param->ble_security.ble_req.bd_addr, true); | ||||||
|       break; |       break; | ||||||
|     // This event is sent once authentication has completed |     // This event is sent once authentication has completed | ||||||
|     case ESP_GAP_BLE_AUTH_CMPL_EVT: |     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; |       esp_bd_addr_t bd_addr; | ||||||
|       memcpy(bd_addr, param->ble_security.auth_cmpl.bd_addr, sizeof(esp_bd_addr_t)); |       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(), |       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(), |         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); |                  param->ble_security.auth_cmpl.fail_reason); | ||||||
|       } else { |       } else { | ||||||
|  |         this->paired_ = true; | ||||||
|         ESP_LOGV(TAG, "[%d] [%s] auth success. address type = %d auth mode = %d", this->connection_index_, |         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, |                  this->address_str_.c_str(), param->ble_security.auth_cmpl.addr_type, | ||||||
|                  param->ble_security.auth_cmpl.auth_mode); |                  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; |                            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 gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override; | ||||||
|   void connect() override; |   void connect() override; | ||||||
|  |   esp_err_t pair(); | ||||||
|   void disconnect(); |   void disconnect(); | ||||||
|   void release_services(); |   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; } |   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_; } |   uint16_t get_conn_id() const { return this->conn_id_; } | ||||||
|   uint64_t get_address() const { return this->address_; } |   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_; } |   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_; |   uint8_t connection_index_; | ||||||
|   int16_t service_count_{0}; |   int16_t service_count_{0}; | ||||||
|   uint16_t mtu_{23}; |   uint16_t mtu_{23}; | ||||||
|  |   bool paired_{false}; | ||||||
|   espbt::ConnectionType connection_type_{espbt::ConnectionType::V1}; |   espbt::ConnectionType connection_type_{espbt::ConnectionType::V1}; | ||||||
|  |  | ||||||
|   std::vector<BLEService *> services_; |   std::vector<BLEService *> services_; | ||||||
|   | |||||||
| @@ -53,6 +53,14 @@ void ESP32BLETracker::setup() { | |||||||
|     ESP_LOGE(TAG, "BLE Tracker was marked failed by ESP32BLE"); |     ESP_LOGE(TAG, "BLE Tracker was marked failed by ESP32BLE"); | ||||||
|     return; |     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; |   global_esp32_ble_tracker = this; | ||||||
|   this->scan_result_lock_ = xSemaphoreCreateMutex(); |   this->scan_result_lock_ = xSemaphoreCreateMutex(); | ||||||
| @@ -107,7 +115,7 @@ void ESP32BLETracker::loop() { | |||||||
|         xSemaphoreTake(this->scan_result_lock_, 5L / portTICK_PERIOD_MS)) { |         xSemaphoreTake(this->scan_result_lock_, 5L / portTICK_PERIOD_MS)) { | ||||||
|       uint32_t index = this->scan_result_index_; |       uint32_t index = this->scan_result_index_; | ||||||
|       if (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."); |           ESP_LOGW(TAG, "Too many BLE events to process. Some devices may not show up."); | ||||||
|         } |         } | ||||||
|         for (size_t i = 0; i < index; i++) { |         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) { | 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 (param.search_evt == ESP_GAP_SEARCH_INQ_RES_EVT) { | ||||||
|     if (xSemaphoreTake(this->scan_result_lock_, 0L)) { |     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; |         this->scan_result_buffer_[this->scan_result_index_++] = param; | ||||||
|       } |       } | ||||||
|       xSemaphoreGive(this->scan_result_lock_); |       xSemaphoreGive(this->scan_result_lock_); | ||||||
|   | |||||||
| @@ -101,7 +101,7 @@ class ESPBTDevice { | |||||||
|   std::vector<int8_t> tx_powers_{}; |   std::vector<int8_t> tx_powers_{}; | ||||||
|   optional<uint16_t> appearance_{}; |   optional<uint16_t> appearance_{}; | ||||||
|   optional<uint8_t> ad_flag_{}; |   optional<uint8_t> ad_flag_{}; | ||||||
|   std::vector<ESPBTUUID> service_uuids_; |   std::vector<ESPBTUUID> service_uuids_{}; | ||||||
|   std::vector<ServiceData> manufacturer_datas_{}; |   std::vector<ServiceData> manufacturer_datas_{}; | ||||||
|   std::vector<ServiceData> service_datas_{}; |   std::vector<ServiceData> service_datas_{}; | ||||||
|   esp_ble_gap_cb_param_t::ble_scan_result_evt_param scan_result_{}; |   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_result_lock_; | ||||||
|   SemaphoreHandle_t scan_end_lock_; |   SemaphoreHandle_t scan_end_lock_; | ||||||
|   size_t scan_result_index_{0}; |   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_start_failed_{ESP_BT_STATUS_SUCCESS}; | ||||||
|   esp_bt_status_t scan_set_param_failed_{ESP_BT_STATUS_SUCCESS}; |   esp_bt_status_t scan_set_param_failed_{ESP_BT_STATUS_SUCCESS}; | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -11,6 +11,7 @@ from esphome.const import ( | |||||||
|     CONF_VOLTAGE_ATTENUATION, |     CONF_VOLTAGE_ATTENUATION, | ||||||
| ) | ) | ||||||
| from esphome.core import TimePeriod | from esphome.core import TimePeriod | ||||||
|  | from esphome.components import esp32 | ||||||
|  |  | ||||||
| AUTO_LOAD = ["binary_sensor"] | AUTO_LOAD = ["binary_sensor"] | ||||||
| DEPENDENCIES = ["esp32"] | DEPENDENCIES = ["esp32"] | ||||||
| @@ -50,7 +51,8 @@ VOLTAGE_ATTENUATION = { | |||||||
|     "0V": cg.global_ns.TOUCH_HVOLT_ATTEN_0V, |     "0V": cg.global_ns.TOUCH_HVOLT_ATTEN_0V, | ||||||
| } | } | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = cv.Schema( | CONFIG_SCHEMA = cv.All( | ||||||
|  |     cv.Schema( | ||||||
|         { |         { | ||||||
|             cv.GenerateID(): cv.declare_id(ESP32TouchComponent), |             cv.GenerateID(): cv.declare_id(ESP32TouchComponent), | ||||||
|             cv.Optional(CONF_SETUP_MODE, default=False): cv.boolean, |             cv.Optional(CONF_SETUP_MODE, default=False): cv.boolean, | ||||||
| @@ -73,7 +75,13 @@ CONFIG_SCHEMA = cv.Schema( | |||||||
|                 VOLTAGE_ATTENUATION |                 VOLTAGE_ATTENUATION | ||||||
|             ), |             ), | ||||||
|         } |         } | ||||||
| ).extend(cv.COMPONENT_SCHEMA) |     ).extend(cv.COMPONENT_SCHEMA), | ||||||
|  |     esp32.only_on_variant( | ||||||
|  |         supported=[ | ||||||
|  |             esp32.const.VARIANT_ESP32, | ||||||
|  |         ] | ||||||
|  |     ), | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
| async def to_code(config): | async def to_code(config): | ||||||
|   | |||||||
| @@ -1,5 +1,6 @@ | |||||||
| import esphome.codegen as cg | import esphome.codegen as cg | ||||||
| import esphome.config_validation as cv | import esphome.config_validation as cv | ||||||
|  | from esphome.core import CORE | ||||||
| from esphome.components import binary_sensor | from esphome.components import binary_sensor | ||||||
| from esphome.const import ( | from esphome.const import ( | ||||||
|     CONF_PIN, |     CONF_PIN, | ||||||
| @@ -7,6 +8,13 @@ from esphome.const import ( | |||||||
|     CONF_ID, |     CONF_ID, | ||||||
| ) | ) | ||||||
| from esphome.components.esp32 import gpio | from esphome.components.esp32 import gpio | ||||||
|  | from esphome.components.esp32.const import ( | ||||||
|  |     KEY_ESP32, | ||||||
|  |     KEY_VARIANT, | ||||||
|  |     VARIANT_ESP32, | ||||||
|  |     VARIANT_ESP32S2, | ||||||
|  |     VARIANT_ESP32S3, | ||||||
|  | ) | ||||||
| from . import esp32_touch_ns, ESP32TouchComponent | from . import esp32_touch_ns, ESP32TouchComponent | ||||||
|  |  | ||||||
| DEPENDENCIES = ["esp32_touch", "esp32"] | DEPENDENCIES = ["esp32_touch", "esp32"] | ||||||
| @@ -15,6 +23,7 @@ CONF_ESP32_TOUCH_ID = "esp32_touch_id" | |||||||
| CONF_WAKEUP_THRESHOLD = "wakeup_threshold" | CONF_WAKEUP_THRESHOLD = "wakeup_threshold" | ||||||
|  |  | ||||||
| TOUCH_PADS = { | TOUCH_PADS = { | ||||||
|  |     VARIANT_ESP32: { | ||||||
|         4: cg.global_ns.TOUCH_PAD_NUM0, |         4: cg.global_ns.TOUCH_PAD_NUM0, | ||||||
|         0: cg.global_ns.TOUCH_PAD_NUM1, |         0: cg.global_ns.TOUCH_PAD_NUM1, | ||||||
|         2: cg.global_ns.TOUCH_PAD_NUM2, |         2: cg.global_ns.TOUCH_PAD_NUM2, | ||||||
| @@ -25,14 +34,52 @@ TOUCH_PADS = { | |||||||
|         27: cg.global_ns.TOUCH_PAD_NUM7, |         27: cg.global_ns.TOUCH_PAD_NUM7, | ||||||
|         33: cg.global_ns.TOUCH_PAD_NUM8, |         33: cg.global_ns.TOUCH_PAD_NUM8, | ||||||
|         32: cg.global_ns.TOUCH_PAD_NUM9, |         32: cg.global_ns.TOUCH_PAD_NUM9, | ||||||
|  |     }, | ||||||
|  |     VARIANT_ESP32S2: { | ||||||
|  |         1: cg.global_ns.TOUCH_PAD_NUM1, | ||||||
|  |         2: cg.global_ns.TOUCH_PAD_NUM2, | ||||||
|  |         3: cg.global_ns.TOUCH_PAD_NUM3, | ||||||
|  |         4: cg.global_ns.TOUCH_PAD_NUM4, | ||||||
|  |         5: cg.global_ns.TOUCH_PAD_NUM5, | ||||||
|  |         6: cg.global_ns.TOUCH_PAD_NUM6, | ||||||
|  |         7: cg.global_ns.TOUCH_PAD_NUM7, | ||||||
|  |         8: cg.global_ns.TOUCH_PAD_NUM8, | ||||||
|  |         9: cg.global_ns.TOUCH_PAD_NUM9, | ||||||
|  |         10: cg.global_ns.TOUCH_PAD_NUM10, | ||||||
|  |         11: cg.global_ns.TOUCH_PAD_NUM11, | ||||||
|  |         12: cg.global_ns.TOUCH_PAD_NUM12, | ||||||
|  |         13: cg.global_ns.TOUCH_PAD_NUM13, | ||||||
|  |         14: cg.global_ns.TOUCH_PAD_NUM14, | ||||||
|  |     }, | ||||||
|  |     VARIANT_ESP32S3: { | ||||||
|  |         1: cg.global_ns.TOUCH_PAD_NUM1, | ||||||
|  |         2: cg.global_ns.TOUCH_PAD_NUM2, | ||||||
|  |         3: cg.global_ns.TOUCH_PAD_NUM3, | ||||||
|  |         4: cg.global_ns.TOUCH_PAD_NUM4, | ||||||
|  |         5: cg.global_ns.TOUCH_PAD_NUM5, | ||||||
|  |         6: cg.global_ns.TOUCH_PAD_NUM6, | ||||||
|  |         7: cg.global_ns.TOUCH_PAD_NUM7, | ||||||
|  |         8: cg.global_ns.TOUCH_PAD_NUM8, | ||||||
|  |         9: cg.global_ns.TOUCH_PAD_NUM9, | ||||||
|  |         10: cg.global_ns.TOUCH_PAD_NUM10, | ||||||
|  |         11: cg.global_ns.TOUCH_PAD_NUM11, | ||||||
|  |         12: cg.global_ns.TOUCH_PAD_NUM12, | ||||||
|  |         13: cg.global_ns.TOUCH_PAD_NUM13, | ||||||
|  |         14: cg.global_ns.TOUCH_PAD_NUM14, | ||||||
|  |     }, | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| def validate_touch_pad(value): | def validate_touch_pad(value): | ||||||
|     value = gpio.validate_gpio_pin(value) |     value = gpio.validate_gpio_pin(value) | ||||||
|     if value not in TOUCH_PADS: |     variant = CORE.data[KEY_ESP32][KEY_VARIANT] | ||||||
|  |     if variant not in TOUCH_PADS: | ||||||
|  |         raise cv.Invalid(f"ESP32 variant {variant} does not support touch pads.") | ||||||
|  |  | ||||||
|  |     pads = TOUCH_PADS[variant] | ||||||
|  |     if value not in pads: | ||||||
|         raise cv.Invalid(f"Pin {value} does not support touch pads.") |         raise cv.Invalid(f"Pin {value} does not support touch pads.") | ||||||
|     return value |     return cv.enum(pads)(value) | ||||||
|  |  | ||||||
|  |  | ||||||
| ESP32TouchBinarySensor = esp32_touch_ns.class_( | ESP32TouchBinarySensor = esp32_touch_ns.class_( | ||||||
| @@ -53,7 +100,7 @@ async def to_code(config): | |||||||
|     hub = await cg.get_variable(config[CONF_ESP32_TOUCH_ID]) |     hub = await cg.get_variable(config[CONF_ESP32_TOUCH_ID]) | ||||||
|     var = cg.new_Pvariable( |     var = cg.new_Pvariable( | ||||||
|         config[CONF_ID], |         config[CONF_ID], | ||||||
|         TOUCH_PADS[config[CONF_PIN]], |         config[CONF_PIN], | ||||||
|         config[CONF_THRESHOLD], |         config[CONF_THRESHOLD], | ||||||
|         config[CONF_WAKEUP_THRESHOLD], |         config[CONF_WAKEUP_THRESHOLD], | ||||||
|     ) |     ) | ||||||
|   | |||||||
| @@ -240,7 +240,6 @@ async def to_code(config): | |||||||
|  |  | ||||||
| # Called by writer.py | # Called by writer.py | ||||||
| def copy_files(): | def copy_files(): | ||||||
|  |  | ||||||
|     dir = os.path.dirname(__file__) |     dir = os.path.dirname(__file__) | ||||||
|     post_build_file = os.path.join(dir, "post_build.py.script") |     post_build_file = os.path.join(dir, "post_build.py.script") | ||||||
|     copy_file_if_changed( |     copy_file_if_changed( | ||||||
|   | |||||||
| @@ -36,12 +36,25 @@ ETHERNET_TYPES = { | |||||||
|     "JL1101": EthernetType.ETHERNET_TYPE_JL1101, |     "JL1101": EthernetType.ETHERNET_TYPE_JL1101, | ||||||
| } | } | ||||||
|  |  | ||||||
|  | emac_rmii_clock_mode_t = cg.global_ns.enum("emac_rmii_clock_mode_t") | ||||||
| emac_rmii_clock_gpio_t = cg.global_ns.enum("emac_rmii_clock_gpio_t") | emac_rmii_clock_gpio_t = cg.global_ns.enum("emac_rmii_clock_gpio_t") | ||||||
| CLK_MODES = { | CLK_MODES = { | ||||||
|     "GPIO0_IN": emac_rmii_clock_gpio_t.EMAC_CLK_IN_GPIO, |     "GPIO0_IN": ( | ||||||
|     "GPIO0_OUT": emac_rmii_clock_gpio_t.EMAC_APPL_CLK_OUT_GPIO, |         emac_rmii_clock_mode_t.EMAC_CLK_EXT_IN, | ||||||
|     "GPIO16_OUT": emac_rmii_clock_gpio_t.EMAC_CLK_OUT_GPIO, |         emac_rmii_clock_gpio_t.EMAC_CLK_IN_GPIO, | ||||||
|     "GPIO17_OUT": emac_rmii_clock_gpio_t.EMAC_CLK_OUT_180_GPIO, |     ), | ||||||
|  |     "GPIO0_OUT": ( | ||||||
|  |         emac_rmii_clock_mode_t.EMAC_CLK_OUT, | ||||||
|  |         emac_rmii_clock_gpio_t.EMAC_APPL_CLK_OUT_GPIO, | ||||||
|  |     ), | ||||||
|  |     "GPIO16_OUT": ( | ||||||
|  |         emac_rmii_clock_mode_t.EMAC_CLK_OUT, | ||||||
|  |         emac_rmii_clock_gpio_t.EMAC_CLK_OUT_GPIO, | ||||||
|  |     ), | ||||||
|  |     "GPIO17_OUT": ( | ||||||
|  |         emac_rmii_clock_mode_t.EMAC_CLK_OUT, | ||||||
|  |         emac_rmii_clock_gpio_t.EMAC_CLK_OUT_180_GPIO, | ||||||
|  |     ), | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -114,7 +127,7 @@ async def to_code(config): | |||||||
|     cg.add(var.set_mdc_pin(config[CONF_MDC_PIN])) |     cg.add(var.set_mdc_pin(config[CONF_MDC_PIN])) | ||||||
|     cg.add(var.set_mdio_pin(config[CONF_MDIO_PIN])) |     cg.add(var.set_mdio_pin(config[CONF_MDIO_PIN])) | ||||||
|     cg.add(var.set_type(config[CONF_TYPE])) |     cg.add(var.set_type(config[CONF_TYPE])) | ||||||
|     cg.add(var.set_clk_mode(CLK_MODES[config[CONF_CLK_MODE]])) |     cg.add(var.set_clk_mode(*CLK_MODES[config[CONF_CLK_MODE]])) | ||||||
|     cg.add(var.set_use_address(config[CONF_USE_ADDRESS])) |     cg.add(var.set_use_address(config[CONF_USE_ADDRESS])) | ||||||
|  |  | ||||||
|     if CONF_POWER_PIN in config: |     if CONF_POWER_PIN in config: | ||||||
|   | |||||||
| @@ -43,13 +43,12 @@ void EthernetComponent::setup() { | |||||||
|   eth_phy_config_t phy_config = ETH_PHY_DEFAULT_CONFIG(); |   eth_phy_config_t phy_config = ETH_PHY_DEFAULT_CONFIG(); | ||||||
|  |  | ||||||
|   phy_config.phy_addr = this->phy_addr_; |   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_mdc_gpio_num = this->mdc_pin_; | ||||||
|   mac_config.smi_mdio_gpio_num = this->mdio_pin_; |   mac_config.smi_mdio_gpio_num = this->mdio_pin_; | ||||||
|   mac_config.clock_config.rmii.clock_mode = this->clk_mode_ == EMAC_CLK_IN_GPIO ? EMAC_CLK_EXT_IN : EMAC_CLK_OUT; |   mac_config.clock_config.rmii.clock_mode = this->clk_mode_; | ||||||
|   mac_config.clock_config.rmii.clock_gpio = this->clk_mode_; |   mac_config.clock_config.rmii.clock_gpio = this->clk_gpio_; | ||||||
|  |  | ||||||
|   esp_eth_mac_t *mac = esp_eth_mac_new_esp32(&mac_config); |   esp_eth_mac_t *mac = esp_eth_mac_new_esp32(&mac_config); | ||||||
|  |  | ||||||
| @@ -316,7 +315,10 @@ void EthernetComponent::set_power_pin(int power_pin) { this->power_pin_ = power_ | |||||||
| void EthernetComponent::set_mdc_pin(uint8_t mdc_pin) { this->mdc_pin_ = mdc_pin; } | void EthernetComponent::set_mdc_pin(uint8_t mdc_pin) { this->mdc_pin_ = mdc_pin; } | ||||||
| void EthernetComponent::set_mdio_pin(uint8_t mdio_pin) { this->mdio_pin_ = mdio_pin; } | void EthernetComponent::set_mdio_pin(uint8_t mdio_pin) { this->mdio_pin_ = mdio_pin; } | ||||||
| void EthernetComponent::set_type(EthernetType type) { this->type_ = type; } | void EthernetComponent::set_type(EthernetType type) { this->type_ = type; } | ||||||
| void EthernetComponent::set_clk_mode(emac_rmii_clock_gpio_t clk_mode) { this->clk_mode_ = clk_mode; } | void EthernetComponent::set_clk_mode(emac_rmii_clock_mode_t clk_mode, emac_rmii_clock_gpio_t clk_gpio) { | ||||||
|  |   this->clk_mode_ = clk_mode; | ||||||
|  |   this->clk_gpio_ = clk_gpio; | ||||||
|  | } | ||||||
| void EthernetComponent::set_manual_ip(const ManualIP &manual_ip) { this->manual_ip_ = manual_ip; } | void EthernetComponent::set_manual_ip(const ManualIP &manual_ip) { this->manual_ip_ = manual_ip; } | ||||||
|  |  | ||||||
| std::string EthernetComponent::get_use_address() const { | std::string EthernetComponent::get_use_address() const { | ||||||
|   | |||||||
| @@ -50,7 +50,7 @@ class EthernetComponent : public Component { | |||||||
|   void set_mdc_pin(uint8_t mdc_pin); |   void set_mdc_pin(uint8_t mdc_pin); | ||||||
|   void set_mdio_pin(uint8_t mdio_pin); |   void set_mdio_pin(uint8_t mdio_pin); | ||||||
|   void set_type(EthernetType type); |   void set_type(EthernetType type); | ||||||
|   void set_clk_mode(emac_rmii_clock_gpio_t clk_mode); |   void set_clk_mode(emac_rmii_clock_mode_t clk_mode, emac_rmii_clock_gpio_t clk_gpio); | ||||||
|   void set_manual_ip(const ManualIP &manual_ip); |   void set_manual_ip(const ManualIP &manual_ip); | ||||||
|  |  | ||||||
|   network::IPAddress get_ip_address(); |   network::IPAddress get_ip_address(); | ||||||
| @@ -70,7 +70,8 @@ class EthernetComponent : public Component { | |||||||
|   uint8_t mdc_pin_{23}; |   uint8_t mdc_pin_{23}; | ||||||
|   uint8_t mdio_pin_{18}; |   uint8_t mdio_pin_{18}; | ||||||
|   EthernetType type_{ETHERNET_TYPE_LAN8720}; |   EthernetType type_{ETHERNET_TYPE_LAN8720}; | ||||||
|   emac_rmii_clock_gpio_t clk_mode_{EMAC_CLK_IN_GPIO}; |   emac_rmii_clock_mode_t clk_mode_{EMAC_CLK_EXT_IN}; | ||||||
|  |   emac_rmii_clock_gpio_t clk_gpio_{EMAC_CLK_IN_GPIO}; | ||||||
|   optional<ManualIP> manual_ip_{}; |   optional<ManualIP> manual_ip_{}; | ||||||
|  |  | ||||||
|   bool started_{false}; |   bool started_{false}; | ||||||
|   | |||||||
| @@ -1,90 +1,32 @@ | |||||||
| import re |  | ||||||
| import logging | import logging | ||||||
| from pathlib import Path | from pathlib import Path | ||||||
|  |  | ||||||
| import esphome.config_validation as cv | import esphome.config_validation as cv | ||||||
|  | from esphome import git, loader | ||||||
| from esphome.const import ( | from esphome.const import ( | ||||||
|     CONF_COMPONENTS, |     CONF_COMPONENTS, | ||||||
|  |     CONF_EXTERNAL_COMPONENTS, | ||||||
|  |     CONF_PASSWORD, | ||||||
|  |     CONF_PATH, | ||||||
|     CONF_REF, |     CONF_REF, | ||||||
|     CONF_REFRESH, |     CONF_REFRESH, | ||||||
|     CONF_SOURCE, |     CONF_SOURCE, | ||||||
|     CONF_URL, |  | ||||||
|     CONF_TYPE, |     CONF_TYPE, | ||||||
|     CONF_EXTERNAL_COMPONENTS, |     CONF_URL, | ||||||
|     CONF_PATH, |  | ||||||
|     CONF_USERNAME, |     CONF_USERNAME, | ||||||
|     CONF_PASSWORD, |     TYPE_GIT, | ||||||
|  |     TYPE_LOCAL, | ||||||
| ) | ) | ||||||
| from esphome.core import CORE | from esphome.core import CORE | ||||||
| from esphome import git, loader |  | ||||||
|  |  | ||||||
| _LOGGER = logging.getLogger(__name__) | _LOGGER = logging.getLogger(__name__) | ||||||
|  |  | ||||||
| DOMAIN = CONF_EXTERNAL_COMPONENTS | 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( | 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_REFRESH, default="1d"): cv.All(cv.string, cv.source_refresh), | ||||||
|         cv.Optional(CONF_COMPONENTS, default="all"): cv.Any( |         cv.Optional(CONF_COMPONENTS, default="all"): cv.Any( | ||||||
|             "all", cv.ensure_list(cv.string) |             "all", cv.ensure_list(cv.string) | ||||||
|   | |||||||
| @@ -41,9 +41,9 @@ DeviceInformationTrigger = ezo_ns.class_( | |||||||
| LedTrigger = ezo_ns.class_("LedTrigger", automation.Trigger.template(cg.bool_)) | LedTrigger = ezo_ns.class_("LedTrigger", automation.Trigger.template(cg.bool_)) | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = ( | 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.Optional(CONF_ON_CUSTOM): automation.validate_automation( | ||||||
|                 { |                 { | ||||||
|                     cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(CustomTrigger), |                     cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(CustomTrigger), | ||||||
|   | |||||||
| @@ -13,15 +13,12 @@ FactoryResetButton = factory_reset_ns.class_( | |||||||
|     "FactoryResetButton", button.Button, cg.Component |     "FactoryResetButton", button.Button, cg.Component | ||||||
| ) | ) | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = ( | CONFIG_SCHEMA = button.button_schema( | ||||||
|     button.button_schema( |     FactoryResetButton, | ||||||
|     device_class=DEVICE_CLASS_RESTART, |     device_class=DEVICE_CLASS_RESTART, | ||||||
|     entity_category=ENTITY_CATEGORY_CONFIG, |     entity_category=ENTITY_CATEGORY_CONFIG, | ||||||
|     icon=ICON_RESTART_ALERT, |     icon=ICON_RESTART_ALERT, | ||||||
|     ) | ).extend(cv.COMPONENT_SCHEMA) | ||||||
|     .extend({cv.GenerateID(): cv.declare_id(FactoryResetButton)}) |  | ||||||
|     .extend(cv.COMPONENT_SCHEMA) |  | ||||||
| ) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| async def to_code(config): | 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): | async def to_code(config): | ||||||
|  |  | ||||||
|     var = cg.new_Pvariable(config[CONF_ID]) |     var = cg.new_Pvariable(config[CONF_ID]) | ||||||
|     await cg.register_component(var, config) |     await cg.register_component(var, config) | ||||||
|     await spi.register_spi_device(var, config) |     await spi.register_spi_device(var, config) | ||||||
|   | |||||||
| @@ -1,153 +1,5 @@ | |||||||
| import esphome.codegen as cg |  | ||||||
| import esphome.config_validation as cv | import esphome.config_validation as cv | ||||||
| from esphome import core, pins |  | ||||||
| from esphome.components import display, spi | CONFIG_SCHEMA = cv.invalid( | ||||||
| from esphome.const import ( |     "The ili9341 platform component has been renamed to ili9xxx." | ||||||
|     CONF_COLOR_PALETTE, |  | ||||||
|     CONF_DC_PIN, |  | ||||||
|     CONF_ID, |  | ||||||
|     CONF_LAMBDA, |  | ||||||
|     CONF_MODEL, |  | ||||||
|     CONF_PAGES, |  | ||||||
|     CONF_RAW_DATA_ID, |  | ||||||
|     CONF_RESET_PIN, |  | ||||||
| ) | ) | ||||||
| from esphome.core import CORE, HexInt |  | ||||||
|  |  | ||||||
| DEPENDENCIES = ["spi"] |  | ||||||
|  |  | ||||||
| CONF_COLOR_PALETTE_IMAGES = "color_palette_images" |  | ||||||
| CONF_LED_PIN = "led_pin" |  | ||||||
|  |  | ||||||
| ili9341_ns = cg.esphome_ns.namespace("ili9341") |  | ||||||
| ili9341 = ili9341_ns.class_( |  | ||||||
|     "ILI9341Display", cg.PollingComponent, spi.SPIDevice, display.DisplayBuffer |  | ||||||
| ) |  | ||||||
| ILI9341M5Stack = ili9341_ns.class_("ILI9341M5Stack", ili9341) |  | ||||||
| ILI9341TFT24 = ili9341_ns.class_("ILI9341TFT24", ili9341) |  | ||||||
| ILI9341TFT24R = ili9341_ns.class_("ILI9341TFT24R", ili9341) |  | ||||||
|  |  | ||||||
| ILI9341Model = ili9341_ns.enum("ILI9341Model") |  | ||||||
| ILI9341ColorMode = ili9341_ns.enum("ILI9341ColorMode") |  | ||||||
|  |  | ||||||
| MODELS = { |  | ||||||
|     "M5STACK": ILI9341Model.M5STACK, |  | ||||||
|     "TFT_2.4": ILI9341Model.TFT_24, |  | ||||||
|     "TFT_2.4R": ILI9341Model.TFT_24R, |  | ||||||
| } |  | ||||||
|  |  | ||||||
| ILI9341_MODEL = cv.enum(MODELS, upper=True, space="_") |  | ||||||
|  |  | ||||||
| COLOR_PALETTE = cv.one_of("NONE", "GRAYSCALE", "IMAGE_ADAPTIVE") |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def _validate(config): |  | ||||||
|     if config.get(CONF_COLOR_PALETTE) == "IMAGE_ADAPTIVE" and not config.get( |  | ||||||
|         CONF_COLOR_PALETTE_IMAGES |  | ||||||
|     ): |  | ||||||
|         raise cv.Invalid( |  | ||||||
|             "Color palette in IMAGE_ADAPTIVE mode requires at least one 'color_palette_images' entry to generate palette" |  | ||||||
|         ) |  | ||||||
|     if ( |  | ||||||
|         config.get(CONF_COLOR_PALETTE_IMAGES) |  | ||||||
|         and config.get(CONF_COLOR_PALETTE) != "IMAGE_ADAPTIVE" |  | ||||||
|     ): |  | ||||||
|         raise cv.Invalid( |  | ||||||
|             "Providing color palette images requires palette mode to be 'IMAGE_ADAPTIVE'" |  | ||||||
|         ) |  | ||||||
|     return config |  | ||||||
|  |  | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = cv.All( |  | ||||||
|     display.FULL_DISPLAY_SCHEMA.extend( |  | ||||||
|         { |  | ||||||
|             cv.GenerateID(): cv.declare_id(ili9341), |  | ||||||
|             cv.Required(CONF_MODEL): ILI9341_MODEL, |  | ||||||
|             cv.Required(CONF_DC_PIN): pins.gpio_output_pin_schema, |  | ||||||
|             cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema, |  | ||||||
|             cv.Optional(CONF_LED_PIN): pins.gpio_output_pin_schema, |  | ||||||
|             cv.Optional(CONF_COLOR_PALETTE, default="NONE"): COLOR_PALETTE, |  | ||||||
|             cv.Optional(CONF_COLOR_PALETTE_IMAGES, default=[]): cv.ensure_list( |  | ||||||
|                 cv.file_ |  | ||||||
|             ), |  | ||||||
|             cv.GenerateID(CONF_RAW_DATA_ID): cv.declare_id(cg.uint8), |  | ||||||
|         } |  | ||||||
|     ) |  | ||||||
|     .extend(cv.polling_component_schema("1s")) |  | ||||||
|     .extend(spi.spi_device_schema(False)), |  | ||||||
|     cv.has_at_most_one_key(CONF_PAGES, CONF_LAMBDA), |  | ||||||
|     _validate, |  | ||||||
| ) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| async def to_code(config): |  | ||||||
|     if config[CONF_MODEL] == "M5STACK": |  | ||||||
|         lcd_type = ILI9341M5Stack |  | ||||||
|     if config[CONF_MODEL] == "TFT_2.4": |  | ||||||
|         lcd_type = ILI9341TFT24 |  | ||||||
|     if config[CONF_MODEL] == "TFT_2.4R": |  | ||||||
|         lcd_type = ILI9341TFT24R |  | ||||||
|     rhs = lcd_type.new() |  | ||||||
|     var = cg.Pvariable(config[CONF_ID], rhs) |  | ||||||
|  |  | ||||||
|     await cg.register_component(var, config) |  | ||||||
|     await display.register_display(var, config) |  | ||||||
|     await spi.register_spi_device(var, config) |  | ||||||
|     cg.add(var.set_model(config[CONF_MODEL])) |  | ||||||
|     dc = await cg.gpio_pin_expression(config[CONF_DC_PIN]) |  | ||||||
|     cg.add(var.set_dc_pin(dc)) |  | ||||||
|  |  | ||||||
|     if CONF_LAMBDA in config: |  | ||||||
|         lambda_ = await cg.process_lambda( |  | ||||||
|             config[CONF_LAMBDA], [(display.DisplayBufferRef, "it")], return_type=cg.void |  | ||||||
|         ) |  | ||||||
|         cg.add(var.set_writer(lambda_)) |  | ||||||
|     if CONF_RESET_PIN in config: |  | ||||||
|         reset = await cg.gpio_pin_expression(config[CONF_RESET_PIN]) |  | ||||||
|         cg.add(var.set_reset_pin(reset)) |  | ||||||
|     if CONF_LED_PIN in config: |  | ||||||
|         led_pin = await cg.gpio_pin_expression(config[CONF_LED_PIN]) |  | ||||||
|         cg.add(var.set_led_pin(led_pin)) |  | ||||||
|  |  | ||||||
|     rhs = None |  | ||||||
|     if config[CONF_COLOR_PALETTE] == "GRAYSCALE": |  | ||||||
|         cg.add(var.set_buffer_color_mode(ILI9341ColorMode.BITS_8_INDEXED)) |  | ||||||
|         rhs = [] |  | ||||||
|         for x in range(256): |  | ||||||
|             rhs.extend([HexInt(x), HexInt(x), HexInt(x)]) |  | ||||||
|     elif config[CONF_COLOR_PALETTE] == "IMAGE_ADAPTIVE": |  | ||||||
|         cg.add(var.set_buffer_color_mode(ILI9341ColorMode.BITS_8_INDEXED)) |  | ||||||
|         from PIL import Image |  | ||||||
|  |  | ||||||
|         def load_image(filename): |  | ||||||
|             path = CORE.relative_config_path(filename) |  | ||||||
|             try: |  | ||||||
|                 return Image.open(path) |  | ||||||
|             except Exception as e: |  | ||||||
|                 raise core.EsphomeError(f"Could not load image file {path}: {e}") |  | ||||||
|  |  | ||||||
|         # make a wide horizontal combined image. |  | ||||||
|         images = [load_image(x) for x in config[CONF_COLOR_PALETTE_IMAGES]] |  | ||||||
|         total_width = sum(i.width for i in images) |  | ||||||
|         max_height = max(i.height for i in images) |  | ||||||
|  |  | ||||||
|         ref_image = Image.new("RGB", (total_width, max_height)) |  | ||||||
|         x = 0 |  | ||||||
|         for i in images: |  | ||||||
|             ref_image.paste(i, (x, 0)) |  | ||||||
|             x = x + i.width |  | ||||||
|  |  | ||||||
|         # reduce the colors on combined image to 256. |  | ||||||
|         converted = ref_image.convert("P", palette=Image.ADAPTIVE, colors=256) |  | ||||||
|         # if you want to verify how the images look use |  | ||||||
|         # ref_image.save("ref_in.png") |  | ||||||
|         # converted.save("ref_out.png") |  | ||||||
|         palette = converted.getpalette() |  | ||||||
|         assert len(palette) == 256 * 3 |  | ||||||
|         rhs = palette |  | ||||||
|     else: |  | ||||||
|         cg.add(var.set_buffer_color_mode(ILI9341ColorMode.BITS_8)) |  | ||||||
|  |  | ||||||
|     if rhs is not None: |  | ||||||
|         prog_arr = cg.progmem_array(config[CONF_RAW_DATA_ID], rhs) |  | ||||||
|         cg.add(var.set_palette(prog_arr)) |  | ||||||
|   | |||||||
| @@ -1,83 +0,0 @@ | |||||||
| #pragma once |  | ||||||
|  |  | ||||||
| namespace esphome { |  | ||||||
| namespace ili9341 { |  | ||||||
|  |  | ||||||
| // Color definitions |  | ||||||
| // clang-format off |  | ||||||
| static const uint8_t MADCTL_MY    = 0x80;   ///< Bit 7 Bottom to top |  | ||||||
| static const uint8_t MADCTL_MX    = 0x40;   ///< Bit 6 Right to left |  | ||||||
| static const uint8_t MADCTL_MV    = 0x20;   ///< Bit 5 Reverse Mode |  | ||||||
| static const uint8_t MADCTL_ML    = 0x10;   ///< Bit 4 LCD refresh Bottom to top |  | ||||||
| static const uint8_t MADCTL_RGB   = 0x00;  ///< Bit 3 Red-Green-Blue pixel order |  | ||||||
| static const uint8_t MADCTL_BGR   = 0x08;  ///< Bit 3 Blue-Green-Red pixel order |  | ||||||
| static const uint8_t MADCTL_MH    = 0x04;   ///< Bit 2 LCD refresh right to left |  | ||||||
| // clang-format on |  | ||||||
|  |  | ||||||
| static const uint16_t ILI9341_TFTWIDTH = 320;   ///< ILI9341 max TFT width |  | ||||||
| static const uint16_t ILI9341_TFTHEIGHT = 240;  ///< ILI9341 max TFT height |  | ||||||
|  |  | ||||||
| // All ILI9341 specific commands some are used by init() |  | ||||||
| static const uint8_t ILI9341_NOP = 0x00; |  | ||||||
| static const uint8_t ILI9341_SWRESET = 0x01; |  | ||||||
| static const uint8_t ILI9341_RDDID = 0x04; |  | ||||||
| static const uint8_t ILI9341_RDDST = 0x09; |  | ||||||
|  |  | ||||||
| static const uint8_t ILI9341_SLPIN = 0x10; |  | ||||||
| static const uint8_t ILI9341_SLPOUT = 0x11; |  | ||||||
| static const uint8_t ILI9341_PTLON = 0x12; |  | ||||||
| static const uint8_t ILI9341_NORON = 0x13; |  | ||||||
|  |  | ||||||
| static const uint8_t ILI9341_RDMODE = 0x0A; |  | ||||||
| static const uint8_t ILI9341_RDMADCTL = 0x0B; |  | ||||||
| static const uint8_t ILI9341_RDPIXFMT = 0x0C; |  | ||||||
| static const uint8_t ILI9341_RDIMGFMT = 0x0A; |  | ||||||
| static const uint8_t ILI9341_RDSELFDIAG = 0x0F; |  | ||||||
|  |  | ||||||
| static const uint8_t ILI9341_INVOFF = 0x20; |  | ||||||
| static const uint8_t ILI9341_INVON = 0x21; |  | ||||||
| static const uint8_t ILI9341_GAMMASET = 0x26; |  | ||||||
| static const uint8_t ILI9341_DISPOFF = 0x28; |  | ||||||
| static const uint8_t ILI9341_DISPON = 0x29; |  | ||||||
|  |  | ||||||
| static const uint8_t ILI9341_CASET = 0x2A; |  | ||||||
| static const uint8_t ILI9341_PASET = 0x2B; |  | ||||||
| static const uint8_t ILI9341_RAMWR = 0x2C; |  | ||||||
| static const uint8_t ILI9341_RAMRD = 0x2E; |  | ||||||
|  |  | ||||||
| static const uint8_t ILI9341_PTLAR = 0x30; |  | ||||||
| static const uint8_t ILI9341_VSCRDEF = 0x33; |  | ||||||
| static const uint8_t ILI9341_MADCTL = 0x36; |  | ||||||
| static const uint8_t ILI9341_VSCRSADD = 0x37; |  | ||||||
| static const uint8_t ILI9341_PIXFMT = 0x3A; |  | ||||||
|  |  | ||||||
| static const uint8_t ILI9341_WRDISBV = 0x51; |  | ||||||
| static const uint8_t ILI9341_RDDISBV = 0x52; |  | ||||||
| static const uint8_t ILI9341_WRCTRLD = 0x53; |  | ||||||
|  |  | ||||||
| static const uint8_t ILI9341_FRMCTR1 = 0xB1; |  | ||||||
| static const uint8_t ILI9341_FRMCTR2 = 0xB2; |  | ||||||
| static const uint8_t ILI9341_FRMCTR3 = 0xB3; |  | ||||||
| static const uint8_t ILI9341_INVCTR = 0xB4; |  | ||||||
| static const uint8_t ILI9341_DFUNCTR = 0xB6; |  | ||||||
|  |  | ||||||
| static const uint8_t ILI9341_PWCTR1 = 0xC0; |  | ||||||
| static const uint8_t ILI9341_PWCTR2 = 0xC1; |  | ||||||
| static const uint8_t ILI9341_PWCTR3 = 0xC2; |  | ||||||
| static const uint8_t ILI9341_PWCTR4 = 0xC3; |  | ||||||
| static const uint8_t ILI9341_PWCTR5 = 0xC4; |  | ||||||
| static const uint8_t ILI9341_VMCTR1 = 0xC5; |  | ||||||
| static const uint8_t ILI9341_VMCTR2 = 0xC7; |  | ||||||
|  |  | ||||||
| static const uint8_t ILI9341_RDID4 = 0xD3; |  | ||||||
| static const uint8_t ILI9341_RDINDEX = 0xD9; |  | ||||||
| static const uint8_t ILI9341_RDID1 = 0xDA; |  | ||||||
| static const uint8_t ILI9341_RDID2 = 0xDB; |  | ||||||
| static const uint8_t ILI9341_RDID3 = 0xDC; |  | ||||||
| static const uint8_t ILI9341_RDIDX = 0xDD;  // TBC |  | ||||||
|  |  | ||||||
| static const uint8_t ILI9341_GMCTRP1 = 0xE0; |  | ||||||
| static const uint8_t ILI9341_GMCTRN1 = 0xE1; |  | ||||||
|  |  | ||||||
| }  // namespace ili9341 |  | ||||||
| }  // namespace esphome |  | ||||||
| @@ -1,308 +0,0 @@ | |||||||
| #include "ili9341_display.h" |  | ||||||
| #include "esphome/core/log.h" |  | ||||||
| #include "esphome/core/application.h" |  | ||||||
| #include "esphome/core/helpers.h" |  | ||||||
| #include "esphome/core/hal.h" |  | ||||||
|  |  | ||||||
| namespace esphome { |  | ||||||
| namespace ili9341 { |  | ||||||
|  |  | ||||||
| static const char *const TAG = "ili9341"; |  | ||||||
|  |  | ||||||
| void ILI9341Display::setup_pins_() { |  | ||||||
|   this->dc_pin_->setup();  // OUTPUT |  | ||||||
|   this->dc_pin_->digital_write(false); |  | ||||||
|   if (this->reset_pin_ != nullptr) { |  | ||||||
|     this->reset_pin_->setup();  // OUTPUT |  | ||||||
|     this->reset_pin_->digital_write(true); |  | ||||||
|   } |  | ||||||
|   if (this->led_pin_ != nullptr) { |  | ||||||
|     this->led_pin_->setup(); |  | ||||||
|     this->led_pin_->digital_write(true); |  | ||||||
|   } |  | ||||||
|   this->spi_setup(); |  | ||||||
|  |  | ||||||
|   this->reset_(); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void ILI9341Display::dump_config() { |  | ||||||
|   LOG_DISPLAY("", "ili9341", this); |  | ||||||
|   LOG_PIN("  Reset Pin: ", this->reset_pin_); |  | ||||||
|   LOG_PIN("  DC Pin: ", this->dc_pin_); |  | ||||||
|   LOG_PIN("  Busy Pin: ", this->busy_pin_); |  | ||||||
|   LOG_UPDATE_INTERVAL(this); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| float ILI9341Display::get_setup_priority() const { return setup_priority::HARDWARE; } |  | ||||||
|  |  | ||||||
| void ILI9341Display::command(uint8_t value) { |  | ||||||
|   this->start_command_(); |  | ||||||
|   this->write_byte(value); |  | ||||||
|   this->end_command_(); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void ILI9341Display::reset_() { |  | ||||||
|   if (this->reset_pin_ != nullptr) { |  | ||||||
|     this->reset_pin_->digital_write(false); |  | ||||||
|     delay(10); |  | ||||||
|     this->reset_pin_->digital_write(true); |  | ||||||
|     delay(10); |  | ||||||
|   } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void ILI9341Display::data(uint8_t value) { |  | ||||||
|   this->start_data_(); |  | ||||||
|   this->write_byte(value); |  | ||||||
|   this->end_data_(); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void ILI9341Display::send_command(uint8_t command_byte, const uint8_t *data_bytes, uint8_t num_data_bytes) { |  | ||||||
|   this->command(command_byte);  // Send the command byte |  | ||||||
|   this->start_data_(); |  | ||||||
|   this->write_array(data_bytes, num_data_bytes); |  | ||||||
|   this->end_data_(); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| uint8_t ILI9341Display::read_command(uint8_t command_byte, uint8_t index) { |  | ||||||
|   uint8_t data = 0x10 + index; |  | ||||||
|   this->send_command(0xD9, &data, 1);  // Set Index Register |  | ||||||
|   uint8_t result; |  | ||||||
|   this->start_command_(); |  | ||||||
|   this->write_byte(command_byte); |  | ||||||
|   this->start_data_(); |  | ||||||
|   do { |  | ||||||
|     result = this->read_byte(); |  | ||||||
|   } while (index--); |  | ||||||
|   this->end_data_(); |  | ||||||
|   return result; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void ILI9341Display::update() { |  | ||||||
|   this->do_update_(); |  | ||||||
|   this->display_(); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void ILI9341Display::display_() { |  | ||||||
|   // we will only update the changed window to the display |  | ||||||
|   uint16_t w = this->x_high_ - this->x_low_ + 1; |  | ||||||
|   uint16_t h = this->y_high_ - this->y_low_ + 1; |  | ||||||
|   uint32_t start_pos = ((this->y_low_ * this->width_) + x_low_); |  | ||||||
|  |  | ||||||
|   // check if something was displayed |  | ||||||
|   if ((this->x_high_ < this->x_low_) || (this->y_high_ < this->y_low_)) { |  | ||||||
|     return; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   set_addr_window_(this->x_low_, this->y_low_, w, h); |  | ||||||
|  |  | ||||||
|   ESP_LOGVV("ILI9341", "Start ILI9341Display::display_(xl:%d, xh:%d, yl:%d, yh:%d, w:%d, h:%d, start_pos:%d)", |  | ||||||
|             this->x_low_, this->x_high_, this->y_low_, this->y_high_, w, h, start_pos); |  | ||||||
|  |  | ||||||
|   this->start_data_(); |  | ||||||
|   for (uint16_t row = 0; row < h; row++) { |  | ||||||
|     uint32_t pos = start_pos + (row * width_); |  | ||||||
|     uint32_t rem = w; |  | ||||||
|  |  | ||||||
|     while (rem > 0) { |  | ||||||
|       uint32_t sz = buffer_to_transfer_(pos, rem); |  | ||||||
|       this->write_array(transfer_buffer_, 2 * sz); |  | ||||||
|       pos += sz; |  | ||||||
|       rem -= sz; |  | ||||||
|       App.feed_wdt(); |  | ||||||
|     } |  | ||||||
|     App.feed_wdt(); |  | ||||||
|   } |  | ||||||
|   this->end_data_(); |  | ||||||
|  |  | ||||||
|   // invalidate watermarks |  | ||||||
|   this->x_low_ = this->width_; |  | ||||||
|   this->y_low_ = this->height_; |  | ||||||
|   this->x_high_ = 0; |  | ||||||
|   this->y_high_ = 0; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void ILI9341Display::fill(Color color) { |  | ||||||
|   uint8_t color332 = 0; |  | ||||||
|   if (this->buffer_color_mode_ == BITS_8) { |  | ||||||
|     color332 = display::ColorUtil::color_to_332(color); |  | ||||||
|   } else {  // if (this->buffer_color_mode_ == BITS_8_INDEXED) |  | ||||||
|     color332 = display::ColorUtil::color_to_index8_palette888(color, this->palette_); |  | ||||||
|   } |  | ||||||
|   memset(this->buffer_, color332, this->get_buffer_length_()); |  | ||||||
|   this->x_low_ = 0; |  | ||||||
|   this->y_low_ = 0; |  | ||||||
|   this->x_high_ = this->get_width_internal() - 1; |  | ||||||
|   this->y_high_ = this->get_height_internal() - 1; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void ILI9341Display::fill_internal_(uint8_t color) { |  | ||||||
|   memset(transfer_buffer_, color, sizeof(transfer_buffer_)); |  | ||||||
|  |  | ||||||
|   uint32_t rem = (this->get_buffer_length_() * 2); |  | ||||||
|  |  | ||||||
|   this->set_addr_window_(0, 0, this->get_width_internal(), this->get_height_internal()); |  | ||||||
|   this->start_data_(); |  | ||||||
|  |  | ||||||
|   while (rem > 0) { |  | ||||||
|     size_t sz = rem <= sizeof(transfer_buffer_) ? rem : sizeof(transfer_buffer_); |  | ||||||
|     this->write_array(transfer_buffer_, sz); |  | ||||||
|     rem -= sz; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   this->end_data_(); |  | ||||||
|  |  | ||||||
|   memset(buffer_, color, this->get_buffer_length_()); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void ILI9341Display::rotate_my_(uint8_t m) { |  | ||||||
|   uint8_t rotation = m & 3;  // can't be higher than 3 |  | ||||||
|   switch (rotation) { |  | ||||||
|     case 0: |  | ||||||
|       m = (MADCTL_MX | MADCTL_BGR); |  | ||||||
|       // _width = ILI9341_TFTWIDTH; |  | ||||||
|       // _height = ILI9341_TFTHEIGHT; |  | ||||||
|       break; |  | ||||||
|     case 1: |  | ||||||
|       m = (MADCTL_MV | MADCTL_BGR); |  | ||||||
|       // _width = ILI9341_TFTHEIGHT; |  | ||||||
|       // _height = ILI9341_TFTWIDTH; |  | ||||||
|       break; |  | ||||||
|     case 2: |  | ||||||
|       m = (MADCTL_MY | MADCTL_BGR); |  | ||||||
|       // _width = ILI9341_TFTWIDTH; |  | ||||||
|       // _height = ILI9341_TFTHEIGHT; |  | ||||||
|       break; |  | ||||||
|     case 3: |  | ||||||
|       m = (MADCTL_MX | MADCTL_MY | MADCTL_MV | MADCTL_BGR); |  | ||||||
|       // _width = ILI9341_TFTHEIGHT; |  | ||||||
|       // _height = ILI9341_TFTWIDTH; |  | ||||||
|       break; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   this->command(ILI9341_MADCTL); |  | ||||||
|   this->data(m); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void HOT ILI9341Display::draw_absolute_pixel_internal(int x, int y, Color color) { |  | ||||||
|   if (x >= this->get_width_internal() || x < 0 || y >= this->get_height_internal() || y < 0) |  | ||||||
|     return; |  | ||||||
|  |  | ||||||
|   uint32_t pos = (y * width_) + x; |  | ||||||
|   uint8_t new_color; |  | ||||||
|  |  | ||||||
|   if (this->buffer_color_mode_ == BITS_8) { |  | ||||||
|     new_color = display::ColorUtil::color_to_332(color, display::ColorOrder::COLOR_ORDER_RGB); |  | ||||||
|   } else {  // if (this->buffer_color_mode_ == BITS_8_INDEXED) { |  | ||||||
|     new_color = display::ColorUtil::color_to_index8_palette888(color, this->palette_); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   if (buffer_[pos] != new_color) { |  | ||||||
|     buffer_[pos] = new_color; |  | ||||||
|     // low and high watermark may speed up drawing from buffer |  | ||||||
|     this->x_low_ = (x < this->x_low_) ? x : this->x_low_; |  | ||||||
|     this->y_low_ = (y < this->y_low_) ? y : this->y_low_; |  | ||||||
|     this->x_high_ = (x > this->x_high_) ? x : this->x_high_; |  | ||||||
|     this->y_high_ = (y > this->y_high_) ? y : this->y_high_; |  | ||||||
|   } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // should return the total size: return this->get_width_internal() * this->get_height_internal() * 2 // 16bit color |  | ||||||
| // values per bit is huge |  | ||||||
| uint32_t ILI9341Display::get_buffer_length_() { return this->get_width_internal() * this->get_height_internal(); } |  | ||||||
|  |  | ||||||
| void ILI9341Display::start_command_() { |  | ||||||
|   this->dc_pin_->digital_write(false); |  | ||||||
|   this->enable(); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void ILI9341Display::end_command_() { this->disable(); } |  | ||||||
| void ILI9341Display::start_data_() { |  | ||||||
|   this->dc_pin_->digital_write(true); |  | ||||||
|   this->enable(); |  | ||||||
| } |  | ||||||
| void ILI9341Display::end_data_() { this->disable(); } |  | ||||||
|  |  | ||||||
| void ILI9341Display::init_lcd_(const uint8_t *init_cmd) { |  | ||||||
|   uint8_t cmd, x, num_args; |  | ||||||
|   const uint8_t *addr = init_cmd; |  | ||||||
|   while ((cmd = progmem_read_byte(addr++)) > 0) { |  | ||||||
|     x = progmem_read_byte(addr++); |  | ||||||
|     num_args = x & 0x7F; |  | ||||||
|     send_command(cmd, addr, num_args); |  | ||||||
|     addr += num_args; |  | ||||||
|     if (x & 0x80) |  | ||||||
|       delay(150);  // NOLINT |  | ||||||
|   } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void ILI9341Display::set_addr_window_(uint16_t x1, uint16_t y1, uint16_t w, uint16_t h) { |  | ||||||
|   uint16_t x2 = (x1 + w - 1), y2 = (y1 + h - 1); |  | ||||||
|   this->command(ILI9341_CASET);  // Column address set |  | ||||||
|   this->start_data_(); |  | ||||||
|   this->write_byte(x1 >> 8); |  | ||||||
|   this->write_byte(x1); |  | ||||||
|   this->write_byte(x2 >> 8); |  | ||||||
|   this->write_byte(x2); |  | ||||||
|   this->end_data_(); |  | ||||||
|   this->command(ILI9341_PASET);  // Row address set |  | ||||||
|   this->start_data_(); |  | ||||||
|   this->write_byte(y1 >> 8); |  | ||||||
|   this->write_byte(y1); |  | ||||||
|   this->write_byte(y2 >> 8); |  | ||||||
|   this->write_byte(y2); |  | ||||||
|   this->end_data_(); |  | ||||||
|   this->command(ILI9341_RAMWR);  // Write to RAM |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void ILI9341Display::invert_display_(bool invert) { this->command(invert ? ILI9341_INVON : ILI9341_INVOFF); } |  | ||||||
|  |  | ||||||
| int ILI9341Display::get_width_internal() { return this->width_; } |  | ||||||
| int ILI9341Display::get_height_internal() { return this->height_; } |  | ||||||
|  |  | ||||||
| uint32_t ILI9341Display::buffer_to_transfer_(uint32_t pos, uint32_t sz) { |  | ||||||
|   uint8_t *src = buffer_ + pos; |  | ||||||
|   uint8_t *dst = transfer_buffer_; |  | ||||||
|  |  | ||||||
|   if (sz > sizeof(transfer_buffer_) / 2) { |  | ||||||
|     sz = sizeof(transfer_buffer_) / 2; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   for (uint32_t i = 0; i < sz; ++i) { |  | ||||||
|     uint16_t color; |  | ||||||
|     if (this->buffer_color_mode_ == BITS_8) { |  | ||||||
|       color = display::ColorUtil::color_to_565(display::ColorUtil::rgb332_to_color(*src++)); |  | ||||||
|     } else {  //  if (this->buffer_color_mode == BITS_8_INDEXED) { |  | ||||||
|       Color col = display::ColorUtil::index8_to_color_palette888(*src++, this->palette_); |  | ||||||
|       color = display::ColorUtil::color_to_565(col); |  | ||||||
|     } |  | ||||||
|     *dst++ = (uint8_t)(color >> 8); |  | ||||||
|     *dst++ = (uint8_t) color; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   return sz; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| //   M5Stack display |  | ||||||
| void ILI9341M5Stack::initialize() { |  | ||||||
|   this->init_lcd_(INITCMD_M5STACK); |  | ||||||
|   this->width_ = 320; |  | ||||||
|   this->height_ = 240; |  | ||||||
|   this->invert_display_(true); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| //   24_TFT display |  | ||||||
| void ILI9341TFT24::initialize() { |  | ||||||
|   this->init_lcd_(INITCMD_TFT); |  | ||||||
|   this->width_ = 240; |  | ||||||
|   this->height_ = 320; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| //   24_TFT rotated display |  | ||||||
| void ILI9341TFT24R::initialize() { |  | ||||||
|   this->init_lcd_(INITCMD_TFT); |  | ||||||
|   this->width_ = 320; |  | ||||||
|   this->height_ = 240; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| }  // namespace ili9341 |  | ||||||
| }  // namespace esphome |  | ||||||
| @@ -1,70 +0,0 @@ | |||||||
| #pragma once |  | ||||||
| #include "esphome/core/helpers.h" |  | ||||||
|  |  | ||||||
| namespace esphome { |  | ||||||
| namespace ili9341 { |  | ||||||
|  |  | ||||||
| // clang-format off |  | ||||||
| static const uint8_t PROGMEM INITCMD_M5STACK[] = { |  | ||||||
|   0xEF, 3, 0x03, 0x80, 0x02, |  | ||||||
|   0xCF, 3, 0x00, 0xC1, 0x30, |  | ||||||
|   0xED, 4, 0x64, 0x03, 0x12, 0x81, |  | ||||||
|   0xE8, 3, 0x85, 0x00, 0x78, |  | ||||||
|   0xCB, 5, 0x39, 0x2C, 0x00, 0x34, 0x02, |  | ||||||
|   0xF7, 1, 0x20, |  | ||||||
|   0xEA, 2, 0x00, 0x00, |  | ||||||
|   ILI9341_PWCTR1  , 1, 0x23,             // Power control VRH[5:0] |  | ||||||
|   ILI9341_PWCTR2  , 1, 0x10,             // Power control SAP[2:0];BT[3:0] |  | ||||||
|   ILI9341_VMCTR1  , 2, 0x3e, 0x28,       // VCM control |  | ||||||
|   ILI9341_VMCTR2  , 1, 0x86,             // VCM control2 |  | ||||||
|   ILI9341_MADCTL  , 1, MADCTL_BGR,       // Memory Access Control |  | ||||||
|   ILI9341_VSCRSADD, 1, 0x00,             // Vertical scroll zero |  | ||||||
|   ILI9341_PIXFMT  , 1, 0x55, |  | ||||||
|   ILI9341_FRMCTR1 , 2, 0x00, 0x13, |  | ||||||
|   ILI9341_DFUNCTR , 3, 0x08, 0x82, 0x27, // Display Function Control |  | ||||||
|   0xF2, 1, 0x00,                         // 3Gamma Function Disable |  | ||||||
|   ILI9341_GAMMASET , 1, 0x01,             // Gamma curve selected |  | ||||||
|   ILI9341_GMCTRP1 , 15, 0x0F, 0x31, 0x2B, 0x0C, 0x0E, 0x08, // Set Gamma |  | ||||||
|                         0x4E, 0xF1, 0x37, 0x07, 0x10, 0x03, |  | ||||||
|                         0x0E, 0x09, 0x00, |  | ||||||
|   ILI9341_GMCTRN1 , 15, 0x00, 0x0E, 0x14, 0x03, 0x11, 0x07, // Set Gamma |  | ||||||
|                         0x31, 0xC1, 0x48, 0x08, 0x0F, 0x0C, |  | ||||||
|                         0x31, 0x36, 0x0F, |  | ||||||
|   ILI9341_SLPOUT  , 0x80,                // Exit Sleep |  | ||||||
|   ILI9341_DISPON  , 0x80,                // Display on |  | ||||||
|   0x00                                   // End of list |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| static const uint8_t PROGMEM INITCMD_TFT[] = { |  | ||||||
|   0xEF, 3, 0x03, 0x80, 0x02, |  | ||||||
|   0xCF, 3, 0x00, 0xC1, 0x30, |  | ||||||
|   0xED, 4, 0x64, 0x03, 0x12, 0x81, |  | ||||||
|   0xE8, 3, 0x85, 0x00, 0x78, |  | ||||||
|   0xCB, 5, 0x39, 0x2C, 0x00, 0x34, 0x02, |  | ||||||
|   0xF7, 1, 0x20, |  | ||||||
|   0xEA, 2, 0x00, 0x00, |  | ||||||
|   ILI9341_PWCTR1  , 1, 0x23,             // Power control VRH[5:0] |  | ||||||
|   ILI9341_PWCTR2  , 1, 0x10,             // Power control SAP[2:0];BT[3:0] |  | ||||||
|   ILI9341_VMCTR1  , 2, 0x3e, 0x28,       // VCM control |  | ||||||
|   ILI9341_VMCTR2  , 1, 0x86,             // VCM control2 |  | ||||||
|   ILI9341_MADCTL  , 1, 0x48,             // Memory Access Control |  | ||||||
|   ILI9341_VSCRSADD, 1, 0x00,             // Vertical scroll zero |  | ||||||
|   ILI9341_PIXFMT  , 1, 0x55, |  | ||||||
|   ILI9341_FRMCTR1 , 2, 0x00, 0x18, |  | ||||||
|   ILI9341_DFUNCTR , 3, 0x08, 0x82, 0x27, // Display Function Control |  | ||||||
|   0xF2, 1, 0x00,                         // 3Gamma Function Disable |  | ||||||
|   ILI9341_GAMMASET , 1, 0x01,             // Gamma curve selected |  | ||||||
|   ILI9341_GMCTRP1 , 15, 0x0F, 0x31, 0x2B, 0x0C, 0x0E, 0x08, // Set Gamma |  | ||||||
|                         0x4E, 0xF1, 0x37, 0x07, 0x10, 0x03, |  | ||||||
|                         0x0E, 0x09, 0x00, |  | ||||||
|   ILI9341_GMCTRN1 , 15, 0x00, 0x0E, 0x14, 0x03, 0x11, 0x07, // Set Gamma |  | ||||||
|                         0x31, 0xC1, 0x48, 0x08, 0x0F, 0x0C, |  | ||||||
|                         0x31, 0x36, 0x0F, |  | ||||||
|   ILI9341_SLPOUT  , 0x80,                // Exit Sleep |  | ||||||
|   ILI9341_DISPON  , 0x80,                // Display on |  | ||||||
|   0x00                                   // End of list |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| // clang-format on |  | ||||||
| }  // namespace ili9341 |  | ||||||
| }  // namespace esphome |  | ||||||
							
								
								
									
										0
									
								
								esphome/components/ili9xxx/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								esphome/components/ili9xxx/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										175
									
								
								esphome/components/ili9xxx/display.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										175
									
								
								esphome/components/ili9xxx/display.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,175 @@ | |||||||
|  | import esphome.codegen as cg | ||||||
|  | import esphome.config_validation as cv | ||||||
|  | from esphome import core, pins | ||||||
|  | from esphome.components import display, spi | ||||||
|  | from esphome.core import CORE, HexInt | ||||||
|  | from esphome.const import ( | ||||||
|  |     CONF_COLOR_PALETTE, | ||||||
|  |     CONF_DC_PIN, | ||||||
|  |     CONF_ID, | ||||||
|  |     CONF_LAMBDA, | ||||||
|  |     CONF_MODEL, | ||||||
|  |     CONF_RAW_DATA_ID, | ||||||
|  |     CONF_PAGES, | ||||||
|  |     CONF_RESET_PIN, | ||||||
|  |     CONF_DIMENSIONS, | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | DEPENDENCIES = ["spi"] | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def AUTO_LOAD(): | ||||||
|  |     if CORE.is_esp32: | ||||||
|  |         return ["psram"] | ||||||
|  |     return [] | ||||||
|  |  | ||||||
|  |  | ||||||
|  | CODEOWNERS = ["@nielsnl68"] | ||||||
|  |  | ||||||
|  | ili9XXX_ns = cg.esphome_ns.namespace("ili9xxx") | ||||||
|  | ili9XXXSPI = ili9XXX_ns.class_( | ||||||
|  |     "ILI9XXXDisplay", cg.PollingComponent, spi.SPIDevice, display.DisplayBuffer | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | ILI9XXXColorMode = ili9XXX_ns.enum("ILI9XXXColorMode") | ||||||
|  |  | ||||||
|  | MODELS = { | ||||||
|  |     "M5STACK": ili9XXX_ns.class_("ILI9XXXM5Stack", ili9XXXSPI), | ||||||
|  |     "M5CORE": ili9XXX_ns.class_("ILI9XXXM5CORE", ili9XXXSPI), | ||||||
|  |     "TFT_2.4": ili9XXX_ns.class_("ILI9XXXILI9341", ili9XXXSPI), | ||||||
|  |     "TFT_2.4R": ili9XXX_ns.class_("ILI9XXXILI9342", ili9XXXSPI), | ||||||
|  |     "ILI9341": ili9XXX_ns.class_("ILI9XXXILI9341", ili9XXXSPI), | ||||||
|  |     "ILI9342": ili9XXX_ns.class_("ILI9XXXILI9342", ili9XXXSPI), | ||||||
|  |     "ILI9481": ili9XXX_ns.class_("ILI9XXXILI9481", ili9XXXSPI), | ||||||
|  |     "ILI9486": ili9XXX_ns.class_("ILI9XXXILI9486", ili9XXXSPI), | ||||||
|  |     "ILI9488": ili9XXX_ns.class_("ILI9XXXILI9488", ili9XXXSPI), | ||||||
|  |     "ST7796": ili9XXX_ns.class_("ILI9XXXST7796", ili9XXXSPI), | ||||||
|  | } | ||||||
|  |  | ||||||
|  | COLOR_PALETTE = cv.one_of("NONE", "GRAYSCALE", "IMAGE_ADAPTIVE") | ||||||
|  |  | ||||||
|  | CONF_LED_PIN = "led_pin" | ||||||
|  | CONF_COLOR_PALETTE_IMAGES = "color_palette_images" | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def _validate(config): | ||||||
|  |     if config.get(CONF_COLOR_PALETTE) == "IMAGE_ADAPTIVE" and not config.get( | ||||||
|  |         CONF_COLOR_PALETTE_IMAGES | ||||||
|  |     ): | ||||||
|  |         raise cv.Invalid( | ||||||
|  |             "Color palette in IMAGE_ADAPTIVE mode requires at least one 'color_palette_images' entry to generate palette" | ||||||
|  |         ) | ||||||
|  |     if ( | ||||||
|  |         config.get(CONF_COLOR_PALETTE_IMAGES) | ||||||
|  |         and config.get(CONF_COLOR_PALETTE) != "IMAGE_ADAPTIVE" | ||||||
|  |     ): | ||||||
|  |         raise cv.Invalid( | ||||||
|  |             "Providing color palette images requires palette mode to be 'IMAGE_ADAPTIVE'" | ||||||
|  |         ) | ||||||
|  |     if CORE.is_esp8266 and config.get(CONF_MODEL) not in [ | ||||||
|  |         "M5STACK", | ||||||
|  |         "TFT_2.4", | ||||||
|  |         "TFT_2.4R", | ||||||
|  |         "ILI9341", | ||||||
|  |         "ILI9342", | ||||||
|  |     ]: | ||||||
|  |         raise cv.Invalid( | ||||||
|  |             "Provided model can't run on ESP8266. Use an ESP32 with PSRAM onboard" | ||||||
|  |         ) | ||||||
|  |     return config | ||||||
|  |  | ||||||
|  |  | ||||||
|  | CONFIG_SCHEMA = cv.All( | ||||||
|  |     display.FULL_DISPLAY_SCHEMA.extend( | ||||||
|  |         { | ||||||
|  |             cv.GenerateID(): cv.declare_id(ili9XXXSPI), | ||||||
|  |             cv.Required(CONF_MODEL): cv.enum(MODELS, upper=True, space="_"), | ||||||
|  |             cv.Optional(CONF_DIMENSIONS): cv.dimensions, | ||||||
|  |             cv.Required(CONF_DC_PIN): pins.gpio_output_pin_schema, | ||||||
|  |             cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema, | ||||||
|  |             cv.Optional(CONF_LED_PIN): cv.invalid( | ||||||
|  |                 "This property is removed. To use the backlight use proper light component." | ||||||
|  |             ), | ||||||
|  |             cv.Optional(CONF_COLOR_PALETTE, default="NONE"): COLOR_PALETTE, | ||||||
|  |             cv.GenerateID(CONF_RAW_DATA_ID): cv.declare_id(cg.uint8), | ||||||
|  |             cv.Optional(CONF_COLOR_PALETTE_IMAGES, default=[]): cv.ensure_list( | ||||||
|  |                 cv.file_ | ||||||
|  |             ), | ||||||
|  |         } | ||||||
|  |     ) | ||||||
|  |     .extend(cv.polling_component_schema("1s")) | ||||||
|  |     .extend(spi.spi_device_schema(False)), | ||||||
|  |     cv.has_at_most_one_key(CONF_PAGES, CONF_LAMBDA), | ||||||
|  |     _validate, | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | async def to_code(config): | ||||||
|  |     rhs = MODELS[config[CONF_MODEL]].new() | ||||||
|  |     var = cg.Pvariable(config[CONF_ID], rhs) | ||||||
|  |  | ||||||
|  |     await cg.register_component(var, config) | ||||||
|  |     await display.register_display(var, config) | ||||||
|  |     await spi.register_spi_device(var, config) | ||||||
|  |     dc = await cg.gpio_pin_expression(config[CONF_DC_PIN]) | ||||||
|  |     cg.add(var.set_dc_pin(dc)) | ||||||
|  |  | ||||||
|  |     if CONF_LAMBDA in config: | ||||||
|  |         lambda_ = await cg.process_lambda( | ||||||
|  |             config[CONF_LAMBDA], [(display.DisplayBufferRef, "it")], return_type=cg.void | ||||||
|  |         ) | ||||||
|  |         cg.add(var.set_writer(lambda_)) | ||||||
|  |  | ||||||
|  |     if CONF_RESET_PIN in config: | ||||||
|  |         reset = await cg.gpio_pin_expression(config[CONF_RESET_PIN]) | ||||||
|  |         cg.add(var.set_reset_pin(reset)) | ||||||
|  |  | ||||||
|  |     if CONF_DIMENSIONS in config: | ||||||
|  |         cg.add( | ||||||
|  |             var.set_dimentions(config[CONF_DIMENSIONS][0], config[CONF_DIMENSIONS][1]) | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |     rhs = None | ||||||
|  |     if config[CONF_COLOR_PALETTE] == "GRAYSCALE": | ||||||
|  |         cg.add(var.set_buffer_color_mode(ILI9XXXColorMode.BITS_8_INDEXED)) | ||||||
|  |         rhs = [] | ||||||
|  |         for x in range(256): | ||||||
|  |             rhs.extend([HexInt(x), HexInt(x), HexInt(x)]) | ||||||
|  |         prog_arr = cg.progmem_array(config[CONF_RAW_DATA_ID], rhs) | ||||||
|  |         cg.add(var.set_palette(prog_arr)) | ||||||
|  |     elif config[CONF_COLOR_PALETTE] == "IMAGE_ADAPTIVE": | ||||||
|  |         cg.add(var.set_buffer_color_mode(ILI9XXXColorMode.BITS_8_INDEXED)) | ||||||
|  |         from PIL import Image | ||||||
|  |  | ||||||
|  |         def load_image(filename): | ||||||
|  |             path = CORE.relative_config_path(filename) | ||||||
|  |             try: | ||||||
|  |                 return Image.open(path) | ||||||
|  |             except Exception as e: | ||||||
|  |                 raise core.EsphomeError(f"Could not load image file {path}: {e}") | ||||||
|  |  | ||||||
|  |         # make a wide horizontal combined image. | ||||||
|  |         images = [load_image(x) for x in config[CONF_COLOR_PALETTE_IMAGES]] | ||||||
|  |         total_width = sum(i.width for i in images) | ||||||
|  |         max_height = max(i.height for i in images) | ||||||
|  |  | ||||||
|  |         ref_image = Image.new("RGB", (total_width, max_height)) | ||||||
|  |         x = 0 | ||||||
|  |         for i in images: | ||||||
|  |             ref_image.paste(i, (x, 0)) | ||||||
|  |             x = x + i.width | ||||||
|  |  | ||||||
|  |         # reduce the colors on combined image to 256. | ||||||
|  |         converted = ref_image.convert("P", palette=Image.ADAPTIVE, colors=256) | ||||||
|  |         # if you want to verify how the images look use | ||||||
|  |         # ref_image.save("ref_in.png") | ||||||
|  |         # converted.save("ref_out.png") | ||||||
|  |         palette = converted.getpalette() | ||||||
|  |         assert len(palette) == 256 * 3 | ||||||
|  |         rhs = palette | ||||||
|  |     else: | ||||||
|  |         cg.add(var.set_buffer_color_mode(ILI9XXXColorMode.BITS_16)) | ||||||
|  |  | ||||||
|  |     if rhs is not None: | ||||||
|  |         prog_arr = cg.progmem_array(config[CONF_RAW_DATA_ID], rhs) | ||||||
|  |         cg.add(var.set_palette(prog_arr)) | ||||||
							
								
								
									
										96
									
								
								esphome/components/ili9xxx/ili9xxx_defines.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								esphome/components/ili9xxx/ili9xxx_defines.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,96 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace ili9xxx { | ||||||
|  |  | ||||||
|  | // Color definitions | ||||||
|  | // clang-format off | ||||||
|  | static const uint8_t MADCTL_MY    = 0x80;   ///< Bit 7 Bottom to top | ||||||
|  | static const uint8_t MADCTL_MX    = 0x40;   ///< Bit 6 Right to left | ||||||
|  | static const uint8_t MADCTL_MV    = 0x20;   ///< Bit 5 Reverse Mode | ||||||
|  | static const uint8_t MADCTL_ML    = 0x10;   ///< Bit 4 LCD refresh Bottom to top | ||||||
|  | static const uint8_t MADCTL_RGB   = 0x00;  ///< Bit 3 Red-Green-Blue pixel order | ||||||
|  | static const uint8_t MADCTL_BGR   = 0x08;  ///< Bit 3 Blue-Green-Red pixel order | ||||||
|  | static const uint8_t MADCTL_MH    = 0x04;   ///< Bit 2 LCD refresh right to left | ||||||
|  | // clang-format on | ||||||
|  |  | ||||||
|  | // All ILI9XXX specific commands some are used by init() | ||||||
|  | static const uint8_t ILI9XXX_NOP = 0x00; | ||||||
|  | static const uint8_t ILI9XXX_SWRESET = 0x01; | ||||||
|  | static const uint8_t ILI9XXX_RDDID = 0x04; | ||||||
|  | static const uint8_t ILI9XXX_RDDST = 0x09; | ||||||
|  |  | ||||||
|  | static const uint8_t ILI9XXX_SLPIN = 0x10; | ||||||
|  | static const uint8_t ILI9XXX_SLPOUT = 0x11; | ||||||
|  | static const uint8_t ILI9XXX_PTLON = 0x12; | ||||||
|  | static const uint8_t ILI9XXX_NORON = 0x13; | ||||||
|  |  | ||||||
|  | static const uint8_t ILI9XXX_RDMODE = 0x0A; | ||||||
|  | static const uint8_t ILI9XXX_RDMADCTL = 0x0B; | ||||||
|  | static const uint8_t ILI9XXX_RDPIXFMT = 0x0C; | ||||||
|  | static const uint8_t ILI9XXX_RDIMGFMT = 0x0D; | ||||||
|  | static const uint8_t ILI9XXX_RDSELFDIAG = 0x0F; | ||||||
|  |  | ||||||
|  | static const uint8_t ILI9XXX_INVOFF = 0x20; | ||||||
|  | static const uint8_t ILI9XXX_INVON = 0x21; | ||||||
|  | static const uint8_t ILI9XXX_GAMMASET = 0x26; | ||||||
|  | static const uint8_t ILI9XXX_DISPOFF = 0x28; | ||||||
|  | static const uint8_t ILI9XXX_DISPON = 0x29; | ||||||
|  |  | ||||||
|  | static const uint8_t ILI9XXX_CASET = 0x2A; | ||||||
|  | static const uint8_t ILI9XXX_PASET = 0x2B; | ||||||
|  | static const uint8_t ILI9XXX_RAMWR = 0x2C; | ||||||
|  | static const uint8_t ILI9XXX_RAMRD = 0x2E; | ||||||
|  |  | ||||||
|  | static const uint8_t ILI9XXX_PTLAR = 0x30; | ||||||
|  | static const uint8_t ILI9XXX_VSCRDEF = 0x33; | ||||||
|  | static const uint8_t ILI9XXX_MADCTL = 0x36; | ||||||
|  | static const uint8_t ILI9XXX_VSCRSADD = 0x37; | ||||||
|  | static const uint8_t ILI9XXX_IDMOFF = 0x38; | ||||||
|  | static const uint8_t ILI9XXX_IDMON = 0x39; | ||||||
|  | static const uint8_t ILI9XXX_PIXFMT = 0x3A; | ||||||
|  | static const uint8_t ILI9XXX_COLMOD = 0x3A; | ||||||
|  |  | ||||||
|  | static const uint8_t ILI9XXX_GETSCANLINE = 0x45; | ||||||
|  |  | ||||||
|  | static const uint8_t ILI9XXX_WRDISBV = 0x51; | ||||||
|  | static const uint8_t ILI9XXX_RDDISBV = 0x52; | ||||||
|  | static const uint8_t ILI9XXX_WRCTRLD = 0x53; | ||||||
|  |  | ||||||
|  | static const uint8_t ILI9XXX_IFMODE = 0xB0; | ||||||
|  | static const uint8_t ILI9XXX_FRMCTR1 = 0xB1; | ||||||
|  | static const uint8_t ILI9XXX_FRMCTR2 = 0xB2; | ||||||
|  | static const uint8_t ILI9XXX_FRMCTR3 = 0xB3; | ||||||
|  | static const uint8_t ILI9XXX_INVCTR = 0xB4; | ||||||
|  | static const uint8_t ILI9XXX_DFUNCTR = 0xB6; | ||||||
|  | static const uint8_t ILI9XXX_ETMOD = 0xB7; | ||||||
|  |  | ||||||
|  | static const uint8_t ILI9XXX_PWCTR1 = 0xC0; | ||||||
|  | static const uint8_t ILI9XXX_PWCTR2 = 0xC1; | ||||||
|  | static const uint8_t ILI9XXX_PWCTR3 = 0xC2; | ||||||
|  | static const uint8_t ILI9XXX_PWCTR4 = 0xC3; | ||||||
|  | static const uint8_t ILI9XXX_PWCTR5 = 0xC4; | ||||||
|  | static const uint8_t ILI9XXX_VMCTR1 = 0xC5; | ||||||
|  | static const uint8_t ILI9XXX_IFCTR = 0xC6; | ||||||
|  | static const uint8_t ILI9XXX_VMCTR2 = 0xC7; | ||||||
|  | static const uint8_t ILI9XXX_GMCTR = 0xC8; | ||||||
|  | static const uint8_t ILI9XXX_SETEXTC = 0xC8; | ||||||
|  |  | ||||||
|  | static const uint8_t ILI9XXX_PWSET = 0xD0; | ||||||
|  | static const uint8_t ILI9XXX_VMCTR = 0xD1; | ||||||
|  | static const uint8_t ILI9XXX_PWSETN = 0xD2; | ||||||
|  | static const uint8_t ILI9XXX_RDID4 = 0xD3; | ||||||
|  | static const uint8_t ILI9XXX_RDINDEX = 0xD9; | ||||||
|  | static const uint8_t ILI9XXX_RDID1 = 0xDA; | ||||||
|  | static const uint8_t ILI9XXX_RDID2 = 0xDB; | ||||||
|  | static const uint8_t ILI9XXX_RDID3 = 0xDC; | ||||||
|  | static const uint8_t ILI9XXX_RDIDX = 0xDD;  // TBC | ||||||
|  |  | ||||||
|  | static const uint8_t ILI9XXX_GMCTRP1 = 0xE0; | ||||||
|  | static const uint8_t ILI9XXX_GMCTRN1 = 0xE1; | ||||||
|  |  | ||||||
|  | static const uint8_t ILI9XXX_CSCON = 0xF0; | ||||||
|  | static const uint8_t ILI9XXX_ADJCTL3 = 0xF7; | ||||||
|  |  | ||||||
|  | }  // namespace ili9xxx | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										416
									
								
								esphome/components/ili9xxx/ili9xxx_display.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										416
									
								
								esphome/components/ili9xxx/ili9xxx_display.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,416 @@ | |||||||
|  | #include "ili9xxx_display.h" | ||||||
|  | #include "esphome/core/log.h" | ||||||
|  | #include "esphome/core/application.h" | ||||||
|  | #include "esphome/core/helpers.h" | ||||||
|  | #include "esphome/core/hal.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace ili9xxx { | ||||||
|  |  | ||||||
|  | static const char *const TAG = "ili9xxx"; | ||||||
|  |  | ||||||
|  | void ILI9XXXDisplay::setup() { | ||||||
|  |   this->setup_pins_(); | ||||||
|  |   this->initialize(); | ||||||
|  |  | ||||||
|  |   this->x_low_ = this->width_; | ||||||
|  |   this->y_low_ = this->height_; | ||||||
|  |   this->x_high_ = 0; | ||||||
|  |   this->y_high_ = 0; | ||||||
|  |   if (this->buffer_color_mode_ == BITS_16) { | ||||||
|  |     this->init_internal_(this->get_buffer_length_() * 2); | ||||||
|  |     if (this->buffer_ != nullptr) { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     this->buffer_color_mode_ = BITS_8; | ||||||
|  |   } | ||||||
|  |   this->init_internal_(this->get_buffer_length_()); | ||||||
|  |   if (this->buffer_ == nullptr) { | ||||||
|  |     this->mark_failed(); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void ILI9XXXDisplay::setup_pins_() { | ||||||
|  |   this->dc_pin_->setup();  // OUTPUT | ||||||
|  |   this->dc_pin_->digital_write(false); | ||||||
|  |   if (this->reset_pin_ != nullptr) { | ||||||
|  |     this->reset_pin_->setup();  // OUTPUT | ||||||
|  |     this->reset_pin_->digital_write(true); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   this->spi_setup(); | ||||||
|  |  | ||||||
|  |   this->reset_(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void ILI9XXXDisplay::dump_config() { | ||||||
|  |   LOG_DISPLAY("", "ili9xxx", this); | ||||||
|  |   switch (this->buffer_color_mode_) { | ||||||
|  |     case BITS_8_INDEXED: | ||||||
|  |       ESP_LOGCONFIG(TAG, "  Color mode: 8bit Indexed"); | ||||||
|  |       break; | ||||||
|  |     case BITS_16: | ||||||
|  |       ESP_LOGCONFIG(TAG, "  Color mode: 16bit"); | ||||||
|  |       break; | ||||||
|  |     default: | ||||||
|  |       ESP_LOGCONFIG(TAG, "  Color mode: 8bit 332 mode"); | ||||||
|  |       break; | ||||||
|  |   } | ||||||
|  |   if (this->is_18bitdisplay_) { | ||||||
|  |     ESP_LOGCONFIG(TAG, "  18-Bit Mode: YES"); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   LOG_PIN("  Reset Pin: ", this->reset_pin_); | ||||||
|  |   LOG_PIN("  DC Pin: ", this->dc_pin_); | ||||||
|  |   LOG_PIN("  Busy Pin: ", this->busy_pin_); | ||||||
|  |  | ||||||
|  |   if (this->is_failed()) { | ||||||
|  |     ESP_LOGCONFIG(TAG, "  => Failed to init Memory: YES!"); | ||||||
|  |   } | ||||||
|  |   LOG_UPDATE_INTERVAL(this); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | float ILI9XXXDisplay::get_setup_priority() const { return setup_priority::HARDWARE; } | ||||||
|  |  | ||||||
|  | void ILI9XXXDisplay::fill(Color color) { | ||||||
|  |   uint16_t new_color = 0; | ||||||
|  |   this->x_low_ = 0; | ||||||
|  |   this->y_low_ = 0; | ||||||
|  |   this->x_high_ = this->get_width_internal() - 1; | ||||||
|  |   this->y_high_ = this->get_height_internal() - 1; | ||||||
|  |   switch (this->buffer_color_mode_) { | ||||||
|  |     case BITS_8_INDEXED: | ||||||
|  |       new_color = display::ColorUtil::color_to_index8_palette888(color, this->palette_); | ||||||
|  |       break; | ||||||
|  |     case BITS_16: | ||||||
|  |       new_color = display::ColorUtil::color_to_565(color); | ||||||
|  |       for (uint32_t i = 0; i < this->get_buffer_length_() * 2; i = i + 2) { | ||||||
|  |         this->buffer_[i] = (uint8_t)(new_color >> 8); | ||||||
|  |         this->buffer_[i + 1] = (uint8_t) new_color; | ||||||
|  |       } | ||||||
|  |       return; | ||||||
|  |       break; | ||||||
|  |     default: | ||||||
|  |       new_color = display::ColorUtil::color_to_332(color, display::ColorOrder::COLOR_ORDER_RGB); | ||||||
|  |       break; | ||||||
|  |   } | ||||||
|  |   memset(this->buffer_, (uint8_t) new_color, this->get_buffer_length_()); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void HOT ILI9XXXDisplay::draw_absolute_pixel_internal(int x, int y, Color color) { | ||||||
|  |   if (x >= this->get_width_internal() || x < 0 || y >= this->get_height_internal() || y < 0) { | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   uint32_t pos = (y * width_) + x; | ||||||
|  |   uint16_t new_color; | ||||||
|  |   bool updated = false; | ||||||
|  |   switch (this->buffer_color_mode_) { | ||||||
|  |     case BITS_8_INDEXED: | ||||||
|  |       new_color = display::ColorUtil::color_to_index8_palette888(color, this->palette_); | ||||||
|  |       break; | ||||||
|  |     case BITS_16: | ||||||
|  |       pos = pos * 2; | ||||||
|  |       new_color = display::ColorUtil::color_to_565(color, display::ColorOrder::COLOR_ORDER_RGB); | ||||||
|  |       if (this->buffer_[pos] != (uint8_t)(new_color >> 8)) { | ||||||
|  |         this->buffer_[pos] = (uint8_t)(new_color >> 8); | ||||||
|  |         updated = true; | ||||||
|  |       } | ||||||
|  |       pos = pos + 1; | ||||||
|  |       new_color = new_color & 0xFF; | ||||||
|  |       break; | ||||||
|  |     default: | ||||||
|  |       new_color = display::ColorUtil::color_to_332(color, display::ColorOrder::COLOR_ORDER_RGB); | ||||||
|  |       break; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (this->buffer_[pos] != new_color) { | ||||||
|  |     this->buffer_[pos] = new_color; | ||||||
|  |     updated = true; | ||||||
|  |   } | ||||||
|  |   if (updated) { | ||||||
|  |     // low and high watermark may speed up drawing from buffer | ||||||
|  |     this->x_low_ = (x < this->x_low_) ? x : this->x_low_; | ||||||
|  |     this->y_low_ = (y < this->y_low_) ? y : this->y_low_; | ||||||
|  |     this->x_high_ = (x > this->x_high_) ? x : this->x_high_; | ||||||
|  |     this->y_high_ = (y > this->y_high_) ? y : this->y_high_; | ||||||
|  |     // ESP_LOGVV(TAG, "=>>> pixel (x:%d, y:%d) (xl:%d, xh:%d, yl:%d, yh:%d", x, y, this->x_low_, this->x_high_, | ||||||
|  |     //           this->y_low_, this->y_high_); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void ILI9XXXDisplay::update() { | ||||||
|  |   if (this->prossing_update_) { | ||||||
|  |     this->need_update_ = true; | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   do { | ||||||
|  |     this->prossing_update_ = true; | ||||||
|  |     this->need_update_ = false; | ||||||
|  |     if (!this->need_update_) { | ||||||
|  |       this->do_update_(); | ||||||
|  |     } | ||||||
|  |   } while (this->need_update_); | ||||||
|  |   this->prossing_update_ = false; | ||||||
|  |   this->display_(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void ILI9XXXDisplay::display_() { | ||||||
|  |   // we will only update the changed window to the display | ||||||
|  |   uint16_t w = this->x_high_ - this->x_low_ + 1;  // NOLINT | ||||||
|  |   uint16_t h = this->y_high_ - this->y_low_ + 1;  // NOLINT | ||||||
|  |   uint32_t start_pos = ((this->y_low_ * this->width_) + x_low_); | ||||||
|  |  | ||||||
|  |   // check if something was displayed | ||||||
|  |   if ((this->x_high_ < this->x_low_) || (this->y_high_ < this->y_low_)) { | ||||||
|  |     ESP_LOGV(TAG, "Nothing to display"); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   set_addr_window_(this->x_low_, this->y_low_, w, h); | ||||||
|  |  | ||||||
|  |   ESP_LOGV(TAG, | ||||||
|  |            "Start display(xlow:%d, ylow:%d, xhigh:%d, yhigh:%d, width:%d, " | ||||||
|  |            "heigth:%d, start_pos:%d)", | ||||||
|  |            this->x_low_, this->y_low_, this->x_high_, this->y_high_, w, h, start_pos); | ||||||
|  |  | ||||||
|  |   this->start_data_(); | ||||||
|  |   for (uint16_t row = 0; row < h; row++) { | ||||||
|  |     uint32_t pos = start_pos + (row * width_); | ||||||
|  |     uint32_t rem = w; | ||||||
|  |  | ||||||
|  |     while (rem > 0) { | ||||||
|  |       uint32_t sz = std::min(rem, ILI9XXX_TRANSFER_BUFFER_SIZE); | ||||||
|  |       // ESP_LOGVV(TAG, "Send to display(pos:%d, rem:%d, zs:%d)", pos, rem, sz); | ||||||
|  |       buffer_to_transfer_(pos, sz); | ||||||
|  |       if (this->is_18bitdisplay_) { | ||||||
|  |         for (uint32_t i = 0; i < sz; ++i) { | ||||||
|  |           uint16_t color_val = transfer_buffer_[i]; | ||||||
|  |  | ||||||
|  |           uint8_t red = color_val & 0x1F; | ||||||
|  |           uint8_t green = (color_val & 0x7E0) >> 5; | ||||||
|  |           uint8_t blue = (color_val & 0xF800) >> 11; | ||||||
|  |  | ||||||
|  |           uint8_t pass_buff[3]; | ||||||
|  |  | ||||||
|  |           pass_buff[2] = (uint8_t)((red / 32.0) * 64) << 2; | ||||||
|  |           pass_buff[1] = (uint8_t) green << 2; | ||||||
|  |           pass_buff[0] = (uint8_t)((blue / 32.0) * 64) << 2; | ||||||
|  |  | ||||||
|  |           this->write_array(pass_buff, sizeof(pass_buff)); | ||||||
|  |         } | ||||||
|  |       } else { | ||||||
|  |         this->write_array16(transfer_buffer_, sz); | ||||||
|  |       } | ||||||
|  |       pos += sz; | ||||||
|  |       rem -= sz; | ||||||
|  |     } | ||||||
|  |     App.feed_wdt(); | ||||||
|  |   } | ||||||
|  |   this->end_data_(); | ||||||
|  |  | ||||||
|  |   // invalidate watermarks | ||||||
|  |   this->x_low_ = this->width_; | ||||||
|  |   this->y_low_ = this->height_; | ||||||
|  |   this->x_high_ = 0; | ||||||
|  |   this->y_high_ = 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | uint32_t ILI9XXXDisplay::buffer_to_transfer_(uint32_t pos, uint32_t sz) { | ||||||
|  |   for (uint32_t i = 0; i < sz; ++i) { | ||||||
|  |     switch (this->buffer_color_mode_) { | ||||||
|  |       case BITS_8_INDEXED: | ||||||
|  |         transfer_buffer_[i] = display::ColorUtil::color_to_565( | ||||||
|  |             display::ColorUtil::index8_to_color_palette888(this->buffer_[pos + i], this->palette_)); | ||||||
|  |         break; | ||||||
|  |       case BITS_16: | ||||||
|  |         transfer_buffer_[i] = ((uint16_t) this->buffer_[(pos + i) * 2] << 8) | this->buffer_[((pos + i) * 2) + 1]; | ||||||
|  |         continue; | ||||||
|  |         break; | ||||||
|  |       default: | ||||||
|  |         transfer_buffer_[i] = | ||||||
|  |             display::ColorUtil::color_to_565(display::ColorUtil::rgb332_to_color(this->buffer_[pos + i])); | ||||||
|  |         break; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   return sz; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // should return the total size: return this->get_width_internal() * this->get_height_internal() * 2 // 16bit color | ||||||
|  | // values per bit is huge | ||||||
|  | uint32_t ILI9XXXDisplay::get_buffer_length_() { return this->get_width_internal() * this->get_height_internal(); } | ||||||
|  |  | ||||||
|  | void ILI9XXXDisplay::command(uint8_t value) { | ||||||
|  |   this->start_command_(); | ||||||
|  |   this->write_byte(value); | ||||||
|  |   this->end_command_(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void ILI9XXXDisplay::data(uint8_t value) { | ||||||
|  |   this->start_data_(); | ||||||
|  |   this->write_byte(value); | ||||||
|  |   this->end_data_(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void ILI9XXXDisplay::send_command(uint8_t command_byte, const uint8_t *data_bytes, uint8_t num_data_bytes) { | ||||||
|  |   this->command(command_byte);  // Send the command byte | ||||||
|  |   this->start_data_(); | ||||||
|  |   this->write_array(data_bytes, num_data_bytes); | ||||||
|  |   this->end_data_(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | uint8_t ILI9XXXDisplay::read_command(uint8_t command_byte, uint8_t index) { | ||||||
|  |   uint8_t data = 0x10 + index; | ||||||
|  |   this->send_command(0xD9, &data, 1);  // Set Index Register | ||||||
|  |   uint8_t result; | ||||||
|  |   this->start_command_(); | ||||||
|  |   this->write_byte(command_byte); | ||||||
|  |   this->start_data_(); | ||||||
|  |   do { | ||||||
|  |     result = this->read_byte(); | ||||||
|  |   } while (index--); | ||||||
|  |   this->end_data_(); | ||||||
|  |   return result; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void ILI9XXXDisplay::start_command_() { | ||||||
|  |   this->dc_pin_->digital_write(false); | ||||||
|  |   this->enable(); | ||||||
|  | } | ||||||
|  | void ILI9XXXDisplay::start_data_() { | ||||||
|  |   this->dc_pin_->digital_write(true); | ||||||
|  |   this->enable(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void ILI9XXXDisplay::end_command_() { this->disable(); } | ||||||
|  | void ILI9XXXDisplay::end_data_() { this->disable(); } | ||||||
|  |  | ||||||
|  | void ILI9XXXDisplay::reset_() { | ||||||
|  |   if (this->reset_pin_ != nullptr) { | ||||||
|  |     this->reset_pin_->digital_write(false); | ||||||
|  |     delay(10); | ||||||
|  |     this->reset_pin_->digital_write(true); | ||||||
|  |     delay(10); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void ILI9XXXDisplay::init_lcd_(const uint8_t *init_cmd) { | ||||||
|  |   uint8_t cmd, x, num_args; | ||||||
|  |   const uint8_t *addr = init_cmd; | ||||||
|  |   while ((cmd = progmem_read_byte(addr++)) > 0) { | ||||||
|  |     x = progmem_read_byte(addr++); | ||||||
|  |     num_args = x & 0x7F; | ||||||
|  |     send_command(cmd, addr, num_args); | ||||||
|  |     addr += num_args; | ||||||
|  |     if (x & 0x80) | ||||||
|  |       delay(150);  // NOLINT | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void ILI9XXXDisplay::set_addr_window_(uint16_t x1, uint16_t y1, uint16_t w, uint16_t h) { | ||||||
|  |   uint16_t x2 = (x1 + w - 1), y2 = (y1 + h - 1); | ||||||
|  |   this->command(ILI9XXX_CASET);  // Column address set | ||||||
|  |   this->start_data_(); | ||||||
|  |   this->write_byte(x1 >> 8); | ||||||
|  |   this->write_byte(x1); | ||||||
|  |   this->write_byte(x2 >> 8); | ||||||
|  |   this->write_byte(x2); | ||||||
|  |   this->end_data_(); | ||||||
|  |   this->command(ILI9XXX_PASET);  // Row address set | ||||||
|  |   this->start_data_(); | ||||||
|  |   this->write_byte(y1 >> 8); | ||||||
|  |   this->write_byte(y1); | ||||||
|  |   this->write_byte(y2 >> 8); | ||||||
|  |   this->write_byte(y2); | ||||||
|  |   this->end_data_(); | ||||||
|  |   this->command(ILI9XXX_RAMWR);  // Write to RAM | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void ILI9XXXDisplay::invert_display_(bool invert) { this->command(invert ? ILI9XXX_INVON : ILI9XXX_INVOFF); } | ||||||
|  |  | ||||||
|  | int ILI9XXXDisplay::get_width_internal() { return this->width_; } | ||||||
|  | int ILI9XXXDisplay::get_height_internal() { return this->height_; } | ||||||
|  |  | ||||||
|  | //   M5Stack display | ||||||
|  | void ILI9XXXM5Stack::initialize() { | ||||||
|  |   this->init_lcd_(INITCMD_M5STACK); | ||||||
|  |   if (this->width_ == 0) | ||||||
|  |     this->width_ = 320; | ||||||
|  |   if (this->height_ == 0) | ||||||
|  |     this->height_ = 240; | ||||||
|  |   this->invert_display_(true); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | //   M5CORE display // Based on the configuration settings of M5stact's M5GFX code. | ||||||
|  | void ILI9XXXM5CORE::initialize() { | ||||||
|  |   this->init_lcd_(INITCMD_M5CORE); | ||||||
|  |   if (this->width_ == 0) | ||||||
|  |     this->width_ = 320; | ||||||
|  |   if (this->height_ == 0) | ||||||
|  |     this->height_ = 240; | ||||||
|  |   this->invert_display_(true); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | //   24_TFT display | ||||||
|  | void ILI9XXXILI9341::initialize() { | ||||||
|  |   this->init_lcd_(INITCMD_ILI9341); | ||||||
|  |   if (this->width_ == 0) | ||||||
|  |     this->width_ = 240; | ||||||
|  |   if (this->height_ == 0) | ||||||
|  |     this->height_ = 320; | ||||||
|  | } | ||||||
|  | //   24_TFT rotated display | ||||||
|  | void ILI9XXXILI9342::initialize() { | ||||||
|  |   this->init_lcd_(INITCMD_ILI9341); | ||||||
|  |   if (this->width_ == 0) { | ||||||
|  |     this->width_ = 320; | ||||||
|  |   } | ||||||
|  |   if (this->height_ == 0) { | ||||||
|  |     this->height_ = 240; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | //   35_TFT display | ||||||
|  | void ILI9XXXILI9481::initialize() { | ||||||
|  |   this->init_lcd_(INITCMD_ILI9481); | ||||||
|  |   if (this->width_ == 0) { | ||||||
|  |     this->width_ = 480; | ||||||
|  |   } | ||||||
|  |   if (this->height_ == 0) { | ||||||
|  |     this->height_ = 320; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | //   35_TFT display | ||||||
|  | void ILI9XXXILI9486::initialize() { | ||||||
|  |   this->init_lcd_(INITCMD_ILI9486); | ||||||
|  |   if (this->width_ == 0) { | ||||||
|  |     this->width_ = 480; | ||||||
|  |   } | ||||||
|  |   if (this->height_ == 0) { | ||||||
|  |     this->height_ = 320; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | //    40_TFT display | ||||||
|  | void ILI9XXXILI9488::initialize() { | ||||||
|  |   this->init_lcd_(INITCMD_ILI9488); | ||||||
|  |   if (this->width_ == 0) { | ||||||
|  |     this->width_ = 480; | ||||||
|  |   } | ||||||
|  |   if (this->height_ == 0) { | ||||||
|  |     this->height_ = 320; | ||||||
|  |   } | ||||||
|  |   this->is_18bitdisplay_ = true; | ||||||
|  | } | ||||||
|  | //    40_TFT display | ||||||
|  | void ILI9XXXST7796::initialize() { | ||||||
|  |   this->init_lcd_(INITCMD_ST7796); | ||||||
|  |   if (this->width_ == 0) { | ||||||
|  |     this->width_ = 320; | ||||||
|  |   } | ||||||
|  |   if (this->height_ == 0) { | ||||||
|  |     this->height_ = 480; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | }  // namespace ili9xxx | ||||||
|  | }  // namespace esphome | ||||||
| @@ -1,27 +1,21 @@ | |||||||
| #pragma once | #pragma once | ||||||
| 
 |  | ||||||
| #include "esphome/core/component.h" |  | ||||||
| #include "esphome/components/spi/spi.h" | #include "esphome/components/spi/spi.h" | ||||||
| #include "esphome/components/display/display_buffer.h" | #include "esphome/components/display/display_buffer.h" | ||||||
| #include "ili9341_defines.h" | #include "ili9xxx_defines.h" | ||||||
| #include "ili9341_init.h" | #include "ili9xxx_init.h" | ||||||
| #include "esphome/core/log.h" |  | ||||||
| 
 | 
 | ||||||
| namespace esphome { | namespace esphome { | ||||||
| namespace ili9341 { | namespace ili9xxx { | ||||||
| 
 | 
 | ||||||
| enum ILI9341Model { | const uint32_t ILI9XXX_TRANSFER_BUFFER_SIZE = 64; | ||||||
|   M5STACK = 0, | 
 | ||||||
|   TFT_24, | enum ILI9XXXColorMode { | ||||||
|   TFT_24R, |   BITS_8 = 0x08, | ||||||
|  |   BITS_8_INDEXED = 0x09, | ||||||
|  |   BITS_16 = 0x10, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| enum ILI9341ColorMode { | class ILI9XXXDisplay : public PollingComponent, | ||||||
|   BITS_8, |  | ||||||
|   BITS_8_INDEXED, |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| class ILI9341Display : public PollingComponent, |  | ||||||
|                        public display::DisplayBuffer, |                        public display::DisplayBuffer, | ||||||
|                        public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW, |                        public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW, | ||||||
|                                              spi::CLOCK_PHASE_LEADING, spi::DATA_RATE_40MHZ> { |                                              spi::CLOCK_PHASE_LEADING, spi::DATA_RATE_40MHZ> { | ||||||
| @@ -29,59 +23,46 @@ class ILI9341Display : public PollingComponent, | |||||||
|   void set_dc_pin(GPIOPin *dc_pin) { dc_pin_ = dc_pin; } |   void set_dc_pin(GPIOPin *dc_pin) { dc_pin_ = dc_pin; } | ||||||
|   float get_setup_priority() const override; |   float get_setup_priority() const override; | ||||||
|   void set_reset_pin(GPIOPin *reset) { this->reset_pin_ = reset; } |   void set_reset_pin(GPIOPin *reset) { this->reset_pin_ = reset; } | ||||||
|   void set_led_pin(GPIOPin *led) { this->led_pin_ = led; } |  | ||||||
|   void set_model(ILI9341Model model) { this->model_ = model; } |  | ||||||
|   void set_palette(const uint8_t *palette) { this->palette_ = palette; } |   void set_palette(const uint8_t *palette) { this->palette_ = palette; } | ||||||
|   void set_buffer_color_mode(ILI9341ColorMode color_mode) { this->buffer_color_mode_ = color_mode; } |   void set_buffer_color_mode(ILI9XXXColorMode color_mode) { this->buffer_color_mode_ = color_mode; } | ||||||
| 
 |   void set_dimentions(int16_t width, int16_t height) { | ||||||
|  |     this->height_ = height; | ||||||
|  |     this->width_ = width; | ||||||
|  |   } | ||||||
|   void command(uint8_t value); |   void command(uint8_t value); | ||||||
|   void data(uint8_t value); |   void data(uint8_t value); | ||||||
|   void send_command(uint8_t command_byte, const uint8_t *data_bytes, uint8_t num_data_bytes); |   void send_command(uint8_t command_byte, const uint8_t *data_bytes, uint8_t num_data_bytes); | ||||||
|   uint8_t read_command(uint8_t command_byte, uint8_t index); |   uint8_t read_command(uint8_t command_byte, uint8_t index); | ||||||
|   virtual void initialize() = 0; |  | ||||||
| 
 | 
 | ||||||
|   void update() override; |   void update() override; | ||||||
| 
 | 
 | ||||||
|   void fill(Color color) override; |   void fill(Color color) override; | ||||||
| 
 | 
 | ||||||
|   void dump_config() override; |   void dump_config() override; | ||||||
|   void setup() override { |   void setup() override; | ||||||
|     this->setup_pins_(); |  | ||||||
|     this->initialize(); |  | ||||||
| 
 |  | ||||||
|     this->x_low_ = this->width_; |  | ||||||
|     this->y_low_ = this->height_; |  | ||||||
|     this->x_high_ = 0; |  | ||||||
|     this->y_high_ = 0; |  | ||||||
| 
 |  | ||||||
|     this->init_internal_(this->get_buffer_length_()); |  | ||||||
|     this->fill_internal_(0x00); |  | ||||||
|   } |  | ||||||
| 
 | 
 | ||||||
|   display::DisplayType get_display_type() override { return display::DisplayType::DISPLAY_TYPE_COLOR; } |   display::DisplayType get_display_type() override { return display::DisplayType::DISPLAY_TYPE_COLOR; } | ||||||
| 
 | 
 | ||||||
|  protected: |  protected: | ||||||
|   void draw_absolute_pixel_internal(int x, int y, Color color) override; |   void draw_absolute_pixel_internal(int x, int y, Color color) override; | ||||||
|   void setup_pins_(); |   void setup_pins_(); | ||||||
|  |   virtual void initialize() = 0; | ||||||
| 
 | 
 | ||||||
|  |   void display_(); | ||||||
|   void init_lcd_(const uint8_t *init_cmd); |   void init_lcd_(const uint8_t *init_cmd); | ||||||
|   void set_addr_window_(uint16_t x, uint16_t y, uint16_t w, uint16_t h); |   void set_addr_window_(uint16_t x, uint16_t y, uint16_t w, uint16_t h); | ||||||
|   void invert_display_(bool invert); |   void invert_display_(bool invert); | ||||||
|   void reset_(); |   void reset_(); | ||||||
|   void fill_internal_(uint8_t color); |  | ||||||
|   void display_(); |  | ||||||
|   void rotate_my_(uint8_t m); |  | ||||||
| 
 | 
 | ||||||
|   ILI9341Model model_; |   int16_t width_{0};   ///< Display width as modified by current rotation
 | ||||||
|   int16_t width_{320};   ///< Display width as modified by current rotation
 |   int16_t height_{0};  ///< Display height as modified by current rotation
 | ||||||
|   int16_t height_{240};  ///< Display height as modified by current rotation
 |  | ||||||
|   uint16_t x_low_{0}; |   uint16_t x_low_{0}; | ||||||
|   uint16_t y_low_{0}; |   uint16_t y_low_{0}; | ||||||
|   uint16_t x_high_{0}; |   uint16_t x_high_{0}; | ||||||
|   uint16_t y_high_{0}; |   uint16_t y_high_{0}; | ||||||
|   const uint8_t *palette_; |   const uint8_t *palette_; | ||||||
| 
 | 
 | ||||||
|   ILI9341ColorMode buffer_color_mode_{BITS_8}; |   ILI9XXXColorMode buffer_color_mode_{BITS_16}; | ||||||
| 
 | 
 | ||||||
|   uint32_t get_buffer_length_(); |   uint32_t get_buffer_length_(); | ||||||
|   int get_width_internal() override; |   int get_width_internal() override; | ||||||
| @@ -92,33 +73,66 @@ class ILI9341Display : public PollingComponent, | |||||||
|   void start_data_(); |   void start_data_(); | ||||||
|   void end_data_(); |   void end_data_(); | ||||||
| 
 | 
 | ||||||
|   uint8_t transfer_buffer_[64]; |   uint16_t transfer_buffer_[ILI9XXX_TRANSFER_BUFFER_SIZE]; | ||||||
| 
 | 
 | ||||||
|   uint32_t buffer_to_transfer_(uint32_t pos, uint32_t sz); |   uint32_t buffer_to_transfer_(uint32_t pos, uint32_t sz); | ||||||
| 
 | 
 | ||||||
|   GPIOPin *reset_pin_{nullptr}; |   GPIOPin *reset_pin_{nullptr}; | ||||||
|   GPIOPin *led_pin_{nullptr}; |   GPIOPin *dc_pin_{nullptr}; | ||||||
|   GPIOPin *dc_pin_; |  | ||||||
|   GPIOPin *busy_pin_{nullptr}; |   GPIOPin *busy_pin_{nullptr}; | ||||||
|  | 
 | ||||||
|  |   bool prossing_update_ = false; | ||||||
|  |   bool need_update_ = false; | ||||||
|  |   bool is_18bitdisplay_ = false; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| //-----------   M5Stack display --------------
 | //-----------   M5Stack display --------------
 | ||||||
| class ILI9341M5Stack : public ILI9341Display { | class ILI9XXXM5Stack : public ILI9XXXDisplay { | ||||||
|  public: |  protected: | ||||||
|   void initialize() override; |   void initialize() override; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| //-----------   ILI9341_24_TFT display --------------
 | //-----------   M5Stack display --------------
 | ||||||
| class ILI9341TFT24 : public ILI9341Display { | class ILI9XXXM5CORE : public ILI9XXXDisplay { | ||||||
|  public: |  protected: | ||||||
|   void initialize() override; |   void initialize() override; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| //-----------   ILI9341_24_TFT rotated display --------------
 | //-----------   ILI9XXX_24_TFT display --------------
 | ||||||
| class ILI9341TFT24R : public ILI9341Display { | class ILI9XXXILI9341 : public ILI9XXXDisplay { | ||||||
|  public: |  protected: | ||||||
|   void initialize() override; |   void initialize() override; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| }  // namespace ili9341
 | //-----------   ILI9XXX_24_TFT rotated display --------------
 | ||||||
|  | class ILI9XXXILI9342 : public ILI9XXXDisplay { | ||||||
|  |  protected: | ||||||
|  |   void initialize() override; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | //-----------   ILI9XXX_??_TFT rotated display --------------
 | ||||||
|  | class ILI9XXXILI9481 : public ILI9XXXDisplay { | ||||||
|  |  protected: | ||||||
|  |   void initialize() override; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | //-----------   ILI9XXX_35_TFT rotated display --------------
 | ||||||
|  | class ILI9XXXILI9486 : public ILI9XXXDisplay { | ||||||
|  |  protected: | ||||||
|  |   void initialize() override; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | //-----------   ILI9XXX_35_TFT rotated display --------------
 | ||||||
|  | class ILI9XXXILI9488 : public ILI9XXXDisplay { | ||||||
|  |  protected: | ||||||
|  |   void initialize() override; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | //-----------   ILI9XXX_35_TFT rotated display --------------
 | ||||||
|  | class ILI9XXXST7796 : public ILI9XXXDisplay { | ||||||
|  |  protected: | ||||||
|  |   void initialize() override; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | }  // namespace ili9xxx
 | ||||||
| }  // namespace esphome
 | }  // namespace esphome
 | ||||||
							
								
								
									
										174
									
								
								esphome/components/ili9xxx/ili9xxx_init.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										174
									
								
								esphome/components/ili9xxx/ili9xxx_init.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,174 @@ | |||||||
|  | #pragma once | ||||||
|  | #include "esphome/core/helpers.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace ili9xxx { | ||||||
|  |  | ||||||
|  | // clang-format off | ||||||
|  | static const uint8_t PROGMEM INITCMD_M5STACK[] = { | ||||||
|  |   0xEF, 3, 0x03, 0x80, 0x02, | ||||||
|  |   0xCF, 3, 0x00, 0xC1, 0x30, | ||||||
|  |   0xED, 4, 0x64, 0x03, 0x12, 0x81, | ||||||
|  |   0xE8, 3, 0x85, 0x00, 0x78, | ||||||
|  |   0xCB, 5, 0x39, 0x2C, 0x00, 0x34, 0x02, | ||||||
|  |   0xF7, 1, 0x20, | ||||||
|  |   0xEA, 2, 0x00, 0x00, | ||||||
|  |   ILI9XXX_PWCTR1  , 1, 0x23,             // Power control VRH[5:0] | ||||||
|  |   ILI9XXX_PWCTR2  , 1, 0x10,             // Power control SAP[2:0];BT[3:0] | ||||||
|  |   ILI9XXX_VMCTR1  , 2, 0x3e, 0x28,       // VCM control | ||||||
|  |   ILI9XXX_VMCTR2  , 1, 0x86,             // VCM control2 | ||||||
|  |   ILI9XXX_MADCTL  , 1, MADCTL_BGR,       // Memory Access Control | ||||||
|  |   ILI9XXX_VSCRSADD, 1, 0x00,             // Vertical scroll zero | ||||||
|  |   ILI9XXX_PIXFMT  , 1, 0x55, | ||||||
|  |   ILI9XXX_FRMCTR1 , 2, 0x00, 0x13, | ||||||
|  |   ILI9XXX_DFUNCTR , 3, 0x08, 0x82, 0x27, // Display Function Control | ||||||
|  |   0xF2, 1, 0x00,                         // 3Gamma Function Disable | ||||||
|  |   ILI9XXX_GAMMASET , 1, 0x01,             // Gamma curve selected | ||||||
|  |   ILI9XXX_GMCTRP1 , 15, 0x0F, 0x31, 0x2B, 0x0C, 0x0E, 0x08, // Set Gamma | ||||||
|  |                         0x4E, 0xF1, 0x37, 0x07, 0x10, 0x03, | ||||||
|  |                         0x0E, 0x09, 0x00, | ||||||
|  |   ILI9XXX_GMCTRN1 , 15, 0x00, 0x0E, 0x14, 0x03, 0x11, 0x07, // Set Gamma | ||||||
|  |                         0x31, 0xC1, 0x48, 0x08, 0x0F, 0x0C, | ||||||
|  |                         0x31, 0x36, 0x0F, | ||||||
|  |   ILI9XXX_SLPOUT  , 0x80,                // Exit Sleep | ||||||
|  |   ILI9XXX_DISPON  , 0x80,                // Display on | ||||||
|  |   0x00                                   // End of list | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | static const uint8_t PROGMEM INITCMD_M5CORE[] = { | ||||||
|  |   ILI9XXX_SETEXTC, 3, 0xFF,0x93,0x42,   // Turn on the external command | ||||||
|  |   ILI9XXX_PWCTR1 , 2, 0x12, 0x12, | ||||||
|  |   ILI9XXX_PWCTR2 , 1, 0x03, | ||||||
|  |   ILI9XXX_VMCTR1 , 1, 0xF2, | ||||||
|  |   ILI9XXX_IFMODE , 1, 0xE0, | ||||||
|  |   0xF6           , 3, 0x01, 0x00, 0x00, | ||||||
|  |   ILI9XXX_GMCTRP1,15, 0x00,0x0C,0x11,0x04,0x11,0x08,0x37,0x89,0x4C,0x06,0x0C,0x0A,0x2E,0x34,0x0F, | ||||||
|  |   ILI9XXX_GMCTRN1,15, 0x00,0x0B,0x11,0x05,0x13,0x09,0x33,0x67,0x48,0x07,0x0E,0x0B,0x2E,0x33,0x0F, | ||||||
|  |   ILI9XXX_DFUNCTR, 4, 0x08,0x82,0x1D,0x04, | ||||||
|  |   ILI9XXX_IDMOFF , 0, | ||||||
|  |   ILI9XXX_DISPON , 0x80,                // Display on | ||||||
|  |   ILI9XXX_SLPOUT , 0x80,                // Exit Sleep | ||||||
|  |  | ||||||
|  |   0x00                                   // End of list | ||||||
|  | }; | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | static const uint8_t PROGMEM INITCMD_ILI9341[] = { | ||||||
|  |   0xEF, 3, 0x03, 0x80, 0x02, | ||||||
|  |   0xCF, 3, 0x00, 0xC1, 0x30, | ||||||
|  |   0xED, 4, 0x64, 0x03, 0x12, 0x81, | ||||||
|  |   0xE8, 3, 0x85, 0x00, 0x78, | ||||||
|  |   0xCB, 5, 0x39, 0x2C, 0x00, 0x34, 0x02, | ||||||
|  |   0xF7, 1, 0x20, | ||||||
|  |   0xEA, 2, 0x00, 0x00, | ||||||
|  |   ILI9XXX_PWCTR1  , 1, 0x23,             // Power control VRH[5:0] | ||||||
|  |   ILI9XXX_PWCTR2  , 1, 0x10,             // Power control SAP[2:0];BT[3:0] | ||||||
|  |   ILI9XXX_VMCTR1  , 2, 0x3e, 0x28,       // VCM control | ||||||
|  |   ILI9XXX_VMCTR2  , 1, 0x86,             // VCM control2 | ||||||
|  |   ILI9XXX_MADCTL  , 1, 0x48,             // Memory Access Control | ||||||
|  |   ILI9XXX_VSCRSADD, 1, 0x00,             // Vertical scroll zero | ||||||
|  |   ILI9XXX_PIXFMT  , 1, 0x55, | ||||||
|  |   ILI9XXX_FRMCTR1 , 2, 0x00, 0x18, | ||||||
|  |   ILI9XXX_DFUNCTR , 3, 0x08, 0x82, 0x27, // Display Function Control | ||||||
|  |   0xF2, 1, 0x00,                         // 3Gamma Function Disable | ||||||
|  |   ILI9XXX_GAMMASET , 1, 0x01,             // Gamma curve selected | ||||||
|  |   ILI9XXX_GMCTRP1 , 15, 0x0F, 0x31, 0x2B, 0x0C, 0x0E, 0x08, // Set Gamma | ||||||
|  |                         0x4E, 0xF1, 0x37, 0x07, 0x10, 0x03, | ||||||
|  |                         0x0E, 0x09, 0x00, | ||||||
|  |   ILI9XXX_GMCTRN1 , 15, 0x00, 0x0E, 0x14, 0x03, 0x11, 0x07, // Set Gamma | ||||||
|  |                         0x31, 0xC1, 0x48, 0x08, 0x0F, 0x0C, | ||||||
|  |                         0x31, 0x36, 0x0F, | ||||||
|  |   ILI9XXX_SLPOUT  , 0x80,                // Exit Sleep | ||||||
|  |   ILI9XXX_DISPON  , 0x80,                // Display on | ||||||
|  |   0x00                                   // End of list | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | static const uint8_t PROGMEM INITCMD_ILI9481[] = { | ||||||
|  |   ILI9XXX_SLPOUT ,  0x80,    // Exit sleep mode | ||||||
|  |   ILI9XXX_PWSET  , 3, 0x07, 0x41, 0x1D, | ||||||
|  |   ILI9XXX_VMCTR  , 3, 0x00, 0x1C, 0x1F, | ||||||
|  |   ILI9XXX_PWSETN , 2, 0x01, 0x11, | ||||||
|  |   ILI9XXX_PWCTR1 , 5, 0x10, 0x3B, 0x00, 0x02, 0x11, | ||||||
|  |   ILI9XXX_VMCTR1 , 1, 0x03, | ||||||
|  |   ILI9XXX_IFCTR  , 1, 0x83, | ||||||
|  |   ILI9XXX_GMCTR  ,12, 0x00, 0x26, 0x21, 0x00, 0x00, 0x1F, 0x65, 0x23, 0x77, 0x00, 0x0F, 0x00, | ||||||
|  |   ILI9XXX_IFMODE , 1, 0x00,  // CommandAccessProtect | ||||||
|  |   0xE4        , 1, 0xA0, | ||||||
|  |   ILI9XXX_CSCON , 1, 0x01, | ||||||
|  |   ILI9XXX_DISPON, 0x80,     // Set display on | ||||||
|  |   0x00 // end | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | static const uint8_t PROGMEM INITCMD_ILI9486[] = { | ||||||
|  |   ILI9XXX_SLPOUT, 0x80, | ||||||
|  |   ILI9XXX_PIXFMT, 1, 0x55, | ||||||
|  |   ILI9XXX_PWCTR3, 1, 0x44, | ||||||
|  |   ILI9XXX_VMCTR1, 4, 0x00, 0x00, 0x00, 0x00, | ||||||
|  |   ILI9XXX_GMCTRP1, 15, 0x0f,0x1f,0x1c,0x0c,0x0f,0x08,0x48,0x98,0x37,0x0a,0x13,0x04,0x11,0x0d,0x00, | ||||||
|  |   ILI9XXX_GMCTRN1, 15, 0x0f,0x32,0x2e,0x0b,0x0d,0x05,0x47,0x75,0x37,0x06,0x10,0x03,0x24,0x20,0x00, | ||||||
|  |   ILI9XXX_INVOFF, 0x80, | ||||||
|  |   ILI9XXX_MADCTL, 1, 0x48, | ||||||
|  |   ILI9XXX_DISPON, 0x80, | ||||||
|  |  | ||||||
|  |   // ILI9XXX_MADCTL, 1, MADCTL_BGR | MADCTL_MV, //hardware rotation | ||||||
|  |   0x00                                   // End of list | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | static const uint8_t PROGMEM INITCMD_ILI9488[] = { | ||||||
|  |   ILI9XXX_GMCTRP1,15, 0x00, 0x03, 0x09, 0x08, 0x16, 0x0A, 0x3F, 0x78, 0x4C, 0x09, 0x0A, 0x08, 0x16, 0x1A, 0x0F, | ||||||
|  |   ILI9XXX_GMCTRN1,15, 0x00, 0x16, 0x19, 0x03, 0x0F, 0x05, 0x32, 0x45, 0x46, 0x04, 0x0E, 0x0D, 0x35, 0x37, 0x0F, | ||||||
|  |  | ||||||
|  |   ILI9XXX_PWCTR1,  2, 0x17, 0x15,  // VRH1 VRH2 | ||||||
|  |   ILI9XXX_PWCTR2,  1, 0x41,  // VGH, VGL | ||||||
|  |   ILI9XXX_VMCTR1,  3, 0x00, 0x12, 0x80,    // nVM VCM_REG VCM_REG_EN | ||||||
|  |  | ||||||
|  |   ILI9XXX_IFMODE,  1, 0x00, | ||||||
|  |   ILI9XXX_FRMCTR1, 1, 0xA0,  // Frame rate = 60Hz | ||||||
|  |   ILI9XXX_INVCTR,  1, 0x02,  // Display Inversion Control = 2dot | ||||||
|  |  | ||||||
|  |   ILI9XXX_DFUNCTR, 2, 0x02, 0x02, // Nomal scan | ||||||
|  |  | ||||||
|  |   0xE9, 1, 0x00,   // Set Image Functio. Disable 24 bit data | ||||||
|  |  | ||||||
|  |   ILI9XXX_ADJCTL3, 4, 0xA9, 0x51, 0x2C, 0x82,  // Adjust Control 3 | ||||||
|  |  | ||||||
|  |   ILI9XXX_MADCTL,  1, 0x28, | ||||||
|  |   //ILI9XXX_PIXFMT,  1, 0x55,  // Interface Pixel Format = 16bit | ||||||
|  |   ILI9XXX_PIXFMT, 1, 0x66,   //ILI9488 only supports 18-bit pixel format in 4/3 wire SPI mode | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   // 5 frames | ||||||
|  |   //ILI9XXX_ETMOD,   1, 0xC6,  // | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   ILI9XXX_SLPOUT,  0x80,    // Exit sleep mode | ||||||
|  |   //ILI9XXX_INVON  , 0, | ||||||
|  |   ILI9XXX_DISPON,  0x80,    // Set display on | ||||||
|  |   0x00 // end | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | static const uint8_t PROGMEM INITCMD_ST7796[] = { | ||||||
|  |   // This ST7796S initilization routine was copied from https://github.com/prenticedavid/Adafruit_ST7796S_kbv/blob/master/Adafruit_ST7796S_kbv.cpp | ||||||
|  |   ILI9XXX_SWRESET, 0x80,         // Soft reset, then delay 150 ms | ||||||
|  |   ILI9XXX_CSCON, 1, 0xC3,              // ?? Unlock Manufacturer | ||||||
|  |   ILI9XXX_CSCON, 1, 0x96, | ||||||
|  |   ILI9XXX_VMCTR1, 1, 0x1C,              //VCOM  Control 1 [1C] | ||||||
|  |   ILI9XXX_MADCTL, 1, 0x48,              //Memory Access [00] | ||||||
|  |   ILI9XXX_PIXFMT, 1, 0x55,              //565 | ||||||
|  |   ILI9XXX_IFMODE, 1, 0x80,              //Interface     [00] | ||||||
|  |   ILI9XXX_INVCTR, 1, 0x01,              //Inversion Control [01] | ||||||
|  |   ILI9XXX_DFUNCTR, 3, 0x80, 0x02, 0x3B,  // Display Function Control [80 02 3B] .kbv SS=1, NL=480 | ||||||
|  |   ILI9XXX_ETMOD, 1, 0xC6,              //Entry Mode      [06] | ||||||
|  |  | ||||||
|  |   ILI9XXX_CSCON, 1, 0x69,              //?? lock manufacturer commands | ||||||
|  |   ILI9XXX_CSCON, 1, 0x3C,              // | ||||||
|  |   ILI9XXX_SLPOUT, 0x80, // Exit Sleep, then delay 150 ms | ||||||
|  |   ILI9XXX_DISPON, 0x80, // Main screen turn on, delay 150 ms | ||||||
|  |   0x00                                   // End of list | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | // clang-format on | ||||||
|  | }  // namespace ili9xxx | ||||||
|  | }  // namespace esphome | ||||||
| @@ -48,9 +48,10 @@ def inherit_accuracy_decimals(decimals, config): | |||||||
|     return decimals + 2 |     return decimals + 2 | ||||||
|  |  | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = sensor.SENSOR_SCHEMA.extend( | CONFIG_SCHEMA = ( | ||||||
|  |     sensor.sensor_schema(IntegrationSensor) | ||||||
|  |     .extend( | ||||||
|         { |         { | ||||||
|         cv.GenerateID(): cv.declare_id(IntegrationSensor), |  | ||||||
|             cv.Required(CONF_SENSOR): cv.use_id(sensor.Sensor), |             cv.Required(CONF_SENSOR): cv.use_id(sensor.Sensor), | ||||||
|             cv.Required(CONF_TIME_UNIT): cv.enum(INTEGRATION_TIMES, lower=True), |             cv.Required(CONF_TIME_UNIT): cv.enum(INTEGRATION_TIMES, lower=True), | ||||||
|             cv.Optional(CONF_INTEGRATION_METHOD, default="trapezoid"): cv.enum( |             cv.Optional(CONF_INTEGRATION_METHOD, default="trapezoid"): cv.enum( | ||||||
| @@ -61,7 +62,9 @@ CONFIG_SCHEMA = sensor.SENSOR_SCHEMA.extend( | |||||||
|                 "min_save_interval was removed in 2022.8.0. Please use the `preferences` -> `flash_write_interval` to adjust." |                 "min_save_interval was removed in 2022.8.0. Please use the `preferences` -> `flash_write_interval` to adjust." | ||||||
|             ), |             ), | ||||||
|         } |         } | ||||||
| ).extend(cv.COMPONENT_SCHEMA) |     ) | ||||||
|  |     .extend(cv.COMPONENT_SCHEMA) | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
| FINAL_VALIDATE_SCHEMA = cv.All( | 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,9 +23,11 @@ CONF_PROCESS_STD_DEV = "process_std_dev" | |||||||
| CONF_STD_DEV = "std_dev" | CONF_STD_DEV = "std_dev" | ||||||
|  |  | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = sensor.SENSOR_SCHEMA.extend(cv.COMPONENT_SCHEMA).extend( | CONFIG_SCHEMA = ( | ||||||
|  |     sensor.sensor_schema(KalmanCombinatorComponent) | ||||||
|  |     .extend(cv.COMPONENT_SCHEMA) | ||||||
|  |     .extend( | ||||||
|         { |         { | ||||||
|         cv.GenerateID(): cv.declare_id(KalmanCombinatorComponent), |  | ||||||
|             cv.Required(CONF_PROCESS_STD_DEV): cv.positive_float, |             cv.Required(CONF_PROCESS_STD_DEV): cv.positive_float, | ||||||
|             cv.Required(CONF_SOURCES): cv.ensure_list( |             cv.Required(CONF_SOURCES): cv.ensure_list( | ||||||
|                 cv.Schema( |                 cv.Schema( | ||||||
| @@ -35,8 +37,9 @@ CONFIG_SCHEMA = sensor.SENSOR_SCHEMA.extend(cv.COMPONENT_SCHEMA).extend( | |||||||
|                     } |                     } | ||||||
|                 ), |                 ), | ||||||
|             ), |             ), | ||||||
|         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 | # 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
									
								
							Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user