mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 15:12:06 +00:00 
			
		
		
		
	Merge branch 'dev' into bump-2021.12.0b1
This commit is contained in:
		
							
								
								
									
										15
									
								
								.clang-tidy
									
									
									
									
									
								
							
							
						
						
									
										15
									
								
								.clang-tidy
									
									
									
									
									
								
							| @@ -5,11 +5,8 @@ Checks: >- | |||||||
|   -altera-*, |   -altera-*, | ||||||
|   -android-*, |   -android-*, | ||||||
|   -boost-*, |   -boost-*, | ||||||
|   -bugprone-branch-clone, |  | ||||||
|   -bugprone-easily-swappable-parameters, |  | ||||||
|   -bugprone-narrowing-conversions, |   -bugprone-narrowing-conversions, | ||||||
|   -bugprone-signed-char-misuse, |   -bugprone-signed-char-misuse, | ||||||
|   -bugprone-too-small-loop-variable, |  | ||||||
|   -cert-dcl50-cpp, |   -cert-dcl50-cpp, | ||||||
|   -cert-err58-cpp, |   -cert-err58-cpp, | ||||||
|   -cert-oop57-cpp, |   -cert-oop57-cpp, | ||||||
| @@ -19,12 +16,10 @@ Checks: >- | |||||||
|   -clang-diagnostic-delete-abstract-non-virtual-dtor, |   -clang-diagnostic-delete-abstract-non-virtual-dtor, | ||||||
|   -clang-diagnostic-delete-non-abstract-non-virtual-dtor, |   -clang-diagnostic-delete-non-abstract-non-virtual-dtor, | ||||||
|   -clang-diagnostic-shadow-field, |   -clang-diagnostic-shadow-field, | ||||||
|   -clang-diagnostic-sign-compare, |  | ||||||
|   -clang-diagnostic-unused-variable, |  | ||||||
|   -clang-diagnostic-unused-const-variable, |   -clang-diagnostic-unused-const-variable, | ||||||
|  |   -clang-diagnostic-unused-parameter, | ||||||
|   -concurrency-*, |   -concurrency-*, | ||||||
|   -cppcoreguidelines-avoid-c-arrays, |   -cppcoreguidelines-avoid-c-arrays, | ||||||
|   -cppcoreguidelines-avoid-goto, |  | ||||||
|   -cppcoreguidelines-avoid-magic-numbers, |   -cppcoreguidelines-avoid-magic-numbers, | ||||||
|   -cppcoreguidelines-init-variables, |   -cppcoreguidelines-init-variables, | ||||||
|   -cppcoreguidelines-macro-usage, |   -cppcoreguidelines-macro-usage, | ||||||
| @@ -41,7 +36,6 @@ Checks: >- | |||||||
|   -cppcoreguidelines-pro-type-union-access, |   -cppcoreguidelines-pro-type-union-access, | ||||||
|   -cppcoreguidelines-pro-type-vararg, |   -cppcoreguidelines-pro-type-vararg, | ||||||
|   -cppcoreguidelines-special-member-functions, |   -cppcoreguidelines-special-member-functions, | ||||||
|   -fuchsia-default-arguments, |  | ||||||
|   -fuchsia-multiple-inheritance, |   -fuchsia-multiple-inheritance, | ||||||
|   -fuchsia-overloaded-operator, |   -fuchsia-overloaded-operator, | ||||||
|   -fuchsia-statically-constructed-objects, |   -fuchsia-statically-constructed-objects, | ||||||
| @@ -51,6 +45,7 @@ Checks: >- | |||||||
|   -google-explicit-constructor, |   -google-explicit-constructor, | ||||||
|   -google-readability-braces-around-statements, |   -google-readability-braces-around-statements, | ||||||
|   -google-readability-casting, |   -google-readability-casting, | ||||||
|  |   -google-readability-namespace-comments, | ||||||
|   -google-readability-todo, |   -google-readability-todo, | ||||||
|   -google-runtime-references, |   -google-runtime-references, | ||||||
|   -hicpp-*, |   -hicpp-*, | ||||||
| @@ -97,9 +92,11 @@ CheckOptions: | |||||||
|     value:           '1' |     value:           '1' | ||||||
|   - key:             google-readability-function-size.StatementThreshold |   - key:             google-readability-function-size.StatementThreshold | ||||||
|     value:           '800' |     value:           '800' | ||||||
|   - key:             google-readability-namespace-comments.ShortNamespaceLines |   - key:             google-runtime-int.TypeSuffix | ||||||
|  |     value:           '_t' | ||||||
|  |   - key:             llvm-namespace-comment.ShortNamespaceLines | ||||||
|     value:           '10' |     value:           '10' | ||||||
|   - key:             google-readability-namespace-comments.SpacesBeforeComments |   - key:             llvm-namespace-comment.SpacesBeforeComments | ||||||
|     value:           '2' |     value:           '2' | ||||||
|   - key:             modernize-loop-convert.MaxCopySize |   - key:             modernize-loop-convert.MaxCopySize | ||||||
|     value:           '16' |     value:           '16' | ||||||
|   | |||||||
							
								
								
									
										27
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										27
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							| @@ -1,5 +1,3 @@ | |||||||
| # THESE JOBS ARE COPIED IN release.yml and release-dev.yml |  | ||||||
| # PLEASE ALSO UPDATE THOSE FILES WHEN CHANGING LINES HERE |  | ||||||
| name: CI | name: CI | ||||||
|  |  | ||||||
| on: | on: | ||||||
| @@ -11,6 +9,10 @@ on: | |||||||
| permissions: | permissions: | ||||||
|   contents: read |   contents: read | ||||||
|  |  | ||||||
|  | concurrency: | ||||||
|  |   group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} | ||||||
|  |   cancel-in-progress: true | ||||||
|  |  | ||||||
| jobs: | jobs: | ||||||
|   ci: |   ci: | ||||||
|     name: ${{ matrix.name }} |     name: ${{ matrix.name }} | ||||||
| @@ -34,7 +36,7 @@ jobs: | |||||||
|           - id: test |           - id: test | ||||||
|             file: tests/test3.yaml |             file: tests/test3.yaml | ||||||
|             name: Test tests/test3.yaml |             name: Test tests/test3.yaml | ||||||
|             pio_cache_key: test1 |             pio_cache_key: test3 | ||||||
|           - id: test |           - id: test | ||||||
|             file: tests/test4.yaml |             file: tests/test4.yaml | ||||||
|             name: Test tests/test4.yaml |             name: Test tests/test4.yaml | ||||||
| @@ -80,18 +82,23 @@ jobs: | |||||||
|         with: |         with: | ||||||
|           python-version: '3.7' |           python-version: '3.7' | ||||||
|  |  | ||||||
|       - name: Cache pip modules |       - name: Cache virtualenv | ||||||
|         uses: actions/cache@v2 |         uses: actions/cache@v2 | ||||||
|         with: |         with: | ||||||
|           path: ~/.cache/pip |           path: .venv | ||||||
|           key: pip-${{ steps.python.outputs.python-version }}-${{ hashFiles('requirements*.txt') }} |           key: venv-${{ steps.python.outputs.python-version }}-${{ hashFiles('requirements*.txt') }} | ||||||
|           restore-keys: | |           restore-keys: | | ||||||
|             pip-${{ steps.python.outputs.python-version }}- |             venv-${{ steps.python.outputs.python-version }}- | ||||||
|  |  | ||||||
|       - name: Set up python environment |       - name: Set up virtualenv | ||||||
|         run: | |         run: | | ||||||
|           pip3 install -r requirements.txt -r requirements_optional.txt -r requirements_test.txt |           python -m venv .venv | ||||||
|           pip3 install -e . |           source .venv/bin/activate | ||||||
|  |           pip install -U pip | ||||||
|  |           pip install -r requirements.txt -r requirements_optional.txt -r requirements_test.txt | ||||||
|  |           pip install -e . | ||||||
|  |           echo "$GITHUB_WORKSPACE/.venv/bin" >> $GITHUB_PATH | ||||||
|  |           echo "VIRTUAL_ENV=$GITHUB_WORKSPACE/.venv" >> $GITHUB_ENV | ||||||
|  |  | ||||||
|       # Use per check platformio cache because checks use different parts |       # Use per check platformio cache because checks use different parts | ||||||
|       - name: Cache platformio |       - name: Cache platformio | ||||||
|   | |||||||
							
								
								
									
										2
									
								
								.github/workflows/matchers/ci-custom.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/matchers/ci-custom.json
									
									
									
									
										vendored
									
									
								
							| @@ -4,7 +4,7 @@ | |||||||
|             "owner": "ci-custom", |             "owner": "ci-custom", | ||||||
|             "pattern": [ |             "pattern": [ | ||||||
|                 { |                 { | ||||||
|                     "regexp": "^ERROR (.*):(\\d+):(\\d+) - (.*)$", |                     "regexp": "^(.*):(\\d+):(\\d+):\\s+lint:\\s+(.*)$", | ||||||
|                     "file": 1, |                     "file": 1, | ||||||
|                     "line": 2, |                     "line": 2, | ||||||
|                     "column": 3, |                     "column": 3, | ||||||
|   | |||||||
							
								
								
									
										2
									
								
								.github/workflows/matchers/gcc.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/matchers/gcc.json
									
									
									
									
										vendored
									
									
								
							| @@ -5,7 +5,7 @@ | |||||||
|       "severity": "error", |       "severity": "error", | ||||||
|       "pattern": [ |       "pattern": [ | ||||||
|         { |         { | ||||||
|           "regexp": "^(.*):(\\d+):(\\d+):\\s+(?:fatal\\s+)?(warning|error):\\s+(.*)$", |           "regexp": "^src/(.*):(\\d+):(\\d+):\\s+(?:fatal\\s+)?(warning|error):\\s+(.*)$", | ||||||
|           "file": 1, |           "file": 1, | ||||||
|           "line": 2, |           "line": 2, | ||||||
|           "column": 3, |           "column": 3, | ||||||
|   | |||||||
							
								
								
									
										15
									
								
								.github/workflows/matchers/lint-python.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										15
									
								
								.github/workflows/matchers/lint-python.json
									
									
									
									
										vendored
									
									
								
							| @@ -1,11 +1,22 @@ | |||||||
| { | { | ||||||
|   "problemMatcher": [ |   "problemMatcher": [ | ||||||
|  |     { | ||||||
|  |       "owner": "black", | ||||||
|  |       "severity": "error", | ||||||
|  |       "pattern": [ | ||||||
|  |         { | ||||||
|  |           "regexp": "^(.*): (Please format this file with the black formatter)", | ||||||
|  |           "file": 1, | ||||||
|  |           "message": 2 | ||||||
|  |         } | ||||||
|  |       ] | ||||||
|  |     }, | ||||||
|     { |     { | ||||||
|       "owner": "flake8", |       "owner": "flake8", | ||||||
|       "severity": "error", |       "severity": "error", | ||||||
|       "pattern": [ |       "pattern": [ | ||||||
|           { |           { | ||||||
|           "regexp": "^(.*):(\\d+) - ([EFCDNW]\\d{3}.*)$", |           "regexp": "^(.*):(\\d+): ([EFCDNW]\\d{3}.*)$", | ||||||
|           "file": 1, |           "file": 1, | ||||||
|           "line": 2, |           "line": 2, | ||||||
|           "message": 3 |           "message": 3 | ||||||
| @@ -17,7 +28,7 @@ | |||||||
|       "severity": "error", |       "severity": "error", | ||||||
|       "pattern": [ |       "pattern": [ | ||||||
|         { |         { | ||||||
|           "regexp": "^(.*):(\\d+) - (\\[[EFCRW]\\d{4}\\(.*\\),.*\\].*)$", |           "regexp": "^(.*):(\\d+): (\\[[EFCRW]\\d{4}\\(.*\\),.*\\].*)$", | ||||||
|           "file": 1, |           "file": 1, | ||||||
|           "line": 2, |           "line": 2, | ||||||
|           "message": 3 |           "message": 3 | ||||||
|   | |||||||
| @@ -30,6 +30,7 @@ esphome/components/bang_bang/* @OttoWinter | |||||||
| esphome/components/binary_sensor/* @esphome/core | esphome/components/binary_sensor/* @esphome/core | ||||||
| esphome/components/ble_client/* @buxtronix | esphome/components/ble_client/* @buxtronix | ||||||
| esphome/components/bme680_bsec/* @trvrnrth | esphome/components/bme680_bsec/* @trvrnrth | ||||||
|  | esphome/components/button/* @esphome/core | ||||||
| esphome/components/canbus/* @danielschramm @mvturnho | esphome/components/canbus/* @danielschramm @mvturnho | ||||||
| esphome/components/cap1188/* @MrEditor97 | esphome/components/cap1188/* @MrEditor97 | ||||||
| esphome/components/captive_portal/* @OttoWinter | esphome/components/captive_portal/* @OttoWinter | ||||||
| @@ -72,7 +73,6 @@ esphome/components/hitachi_ac424/* @sourabhjaiswal | |||||||
| esphome/components/homeassistant/* @OttoWinter | esphome/components/homeassistant/* @OttoWinter | ||||||
| esphome/components/hrxl_maxsonar_wr/* @netmikey | esphome/components/hrxl_maxsonar_wr/* @netmikey | ||||||
| esphome/components/i2c/* @esphome/core | esphome/components/i2c/* @esphome/core | ||||||
| esphome/components/improv/* @jesserockz |  | ||||||
| esphome/components/improv_serial/* @esphome/core | esphome/components/improv_serial/* @esphome/core | ||||||
| esphome/components/inkbird_ibsth1_mini/* @fkirill | esphome/components/inkbird_ibsth1_mini/* @fkirill | ||||||
| esphome/components/inkplate6/* @jesserockz | esphome/components/inkplate6/* @jesserockz | ||||||
| @@ -181,6 +181,7 @@ esphome/components/tuya/binary_sensor/* @jesserockz | |||||||
| esphome/components/tuya/climate/* @jesserockz | esphome/components/tuya/climate/* @jesserockz | ||||||
| esphome/components/tuya/sensor/* @jesserockz | esphome/components/tuya/sensor/* @jesserockz | ||||||
| esphome/components/tuya/switch/* @jesserockz | esphome/components/tuya/switch/* @jesserockz | ||||||
|  | esphome/components/tuya/text_sensor/* @dentra | ||||||
| esphome/components/uart/* @esphome/core | esphome/components/uart/* @esphome/core | ||||||
| esphome/components/ultrasonic/* @OttoWinter | esphome/components/ultrasonic/* @OttoWinter | ||||||
| esphome/components/version/* @esphome/core | esphome/components/version/* @esphome/core | ||||||
|   | |||||||
| @@ -147,9 +147,9 @@ RUN \ | |||||||
|         /var/{cache,log}/* \ |         /var/{cache,log}/* \ | ||||||
|         /var/lib/apt/lists/* |         /var/lib/apt/lists/* | ||||||
|  |  | ||||||
| COPY requirements.txt requirements_optional.txt docker/platformio_install_deps.py platformio.ini / | COPY requirements.txt requirements_optional.txt requirements_test.txt docker/platformio_install_deps.py platformio.ini / | ||||||
| RUN \ | RUN \ | ||||||
|     pip3 install --no-cache-dir -r /requirements.txt -r /requirements_optional.txt \ |     pip3 install --no-cache-dir -r /requirements.txt -r /requirements_optional.txt -r /requirements_test.txt \ | ||||||
|     && /platformio_install_deps.py /platformio.ini |     && /platformio_install_deps.py /platformio.ini | ||||||
|  |  | ||||||
| VOLUME ["/esphome"] | VOLUME ["/esphome"] | ||||||
|   | |||||||
| @@ -18,6 +18,7 @@ from esphome.const import ( | |||||||
|     CONF_PORT, |     CONF_PORT, | ||||||
|     CONF_ESPHOME, |     CONF_ESPHOME, | ||||||
|     CONF_PLATFORMIO_OPTIONS, |     CONF_PLATFORMIO_OPTIONS, | ||||||
|  |     SECRETS_FILES, | ||||||
| ) | ) | ||||||
| from esphome.core import CORE, EsphomeError, coroutine | from esphome.core import CORE, EsphomeError, coroutine | ||||||
| from esphome.helpers import indent | from esphome.helpers import indent | ||||||
| @@ -200,8 +201,7 @@ def upload_using_esptool(config, port): | |||||||
|         firmware_offset = "0x10000" if CORE.is_esp32 else "0x0" |         firmware_offset = "0x10000" if CORE.is_esp32 else "0x0" | ||||||
|         flash_images = [ |         flash_images = [ | ||||||
|             platformio_api.FlashImage( |             platformio_api.FlashImage( | ||||||
|                 path=idedata.firmware_bin_path, |                 path=idedata.firmware_bin_path, offset=firmware_offset | ||||||
|                 offset=firmware_offset, |  | ||||||
|             ), |             ), | ||||||
|             *idedata.extra_flash_images, |             *idedata.extra_flash_images, | ||||||
|         ] |         ] | ||||||
| @@ -607,10 +607,7 @@ def parse_args(argv): | |||||||
|         "wizard", |         "wizard", | ||||||
|         help="A helpful setup wizard that will guide you through setting up ESPHome.", |         help="A helpful setup wizard that will guide you through setting up ESPHome.", | ||||||
|     ) |     ) | ||||||
|     parser_wizard.add_argument( |     parser_wizard.add_argument("configuration", help="Your YAML configuration file.") | ||||||
|         "configuration", |  | ||||||
|         help="Your YAML configuration file.", |  | ||||||
|     ) |  | ||||||
|  |  | ||||||
|     parser_fingerprint = subparsers.add_parser( |     parser_fingerprint = subparsers.add_parser( | ||||||
|         "mqtt-fingerprint", help="Get the SSL fingerprint from a MQTT broker." |         "mqtt-fingerprint", help="Get the SSL fingerprint from a MQTT broker." | ||||||
| @@ -632,8 +629,7 @@ def parse_args(argv): | |||||||
|         "dashboard", help="Create a simple web server for a dashboard." |         "dashboard", help="Create a simple web server for a dashboard." | ||||||
|     ) |     ) | ||||||
|     parser_dashboard.add_argument( |     parser_dashboard.add_argument( | ||||||
|         "configuration", |         "configuration", help="Your YAML configuration file directory." | ||||||
|         help="Your YAML configuration file directory.", |  | ||||||
|     ) |     ) | ||||||
|     parser_dashboard.add_argument( |     parser_dashboard.add_argument( | ||||||
|         "--port", |         "--port", | ||||||
| @@ -641,6 +637,12 @@ def parse_args(argv): | |||||||
|         type=int, |         type=int, | ||||||
|         default=6052, |         default=6052, | ||||||
|     ) |     ) | ||||||
|  |     parser_dashboard.add_argument( | ||||||
|  |         "--address", | ||||||
|  |         help="The address to bind to.", | ||||||
|  |         type=str, | ||||||
|  |         default="0.0.0.0", | ||||||
|  |     ) | ||||||
|     parser_dashboard.add_argument( |     parser_dashboard.add_argument( | ||||||
|         "--username", |         "--username", | ||||||
|         help="The optional username to require for authentication.", |         help="The optional username to require for authentication.", | ||||||
| @@ -789,6 +791,10 @@ def run_esphome(argv): | |||||||
|             return 1 |             return 1 | ||||||
|  |  | ||||||
|     for conf_path in args.configuration: |     for conf_path in args.configuration: | ||||||
|  |         if any(os.path.basename(conf_path) == x for x in SECRETS_FILES): | ||||||
|  |             _LOGGER.warning("Skipping secrets file %s", conf_path) | ||||||
|  |             continue | ||||||
|  |  | ||||||
|         CORE.config_path = conf_path |         CORE.config_path = conf_path | ||||||
|         CORE.dashboard = args.dashboard |         CORE.dashboard = args.dashboard | ||||||
|  |  | ||||||
|   | |||||||
| @@ -25,7 +25,7 @@ void AdalightLightEffect::stop() { | |||||||
|   AddressableLightEffect::stop(); |   AddressableLightEffect::stop(); | ||||||
| } | } | ||||||
|  |  | ||||||
| int AdalightLightEffect::get_frame_size_(int led_count) const { | unsigned int AdalightLightEffect::get_frame_size_(int led_count) const { | ||||||
|   // 3 bytes: Ada |   // 3 bytes: Ada | ||||||
|   // 2 bytes: LED count |   // 2 bytes: LED count | ||||||
|   // 1 byte: checksum |   // 1 byte: checksum | ||||||
|   | |||||||
| @@ -25,7 +25,7 @@ class AdalightLightEffect : public light::AddressableLightEffect, public uart::U | |||||||
|     CONSUMED, |     CONSUMED, | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
|   int get_frame_size_(int led_count) const; |   unsigned int get_frame_size_(int led_count) const; | ||||||
|   void reset_frame_(light::AddressableLight &it); |   void reset_frame_(light::AddressableLight &it); | ||||||
|   void blank_all_leds_(light::AddressableLight &it); |   void blank_all_leds_(light::AddressableLight &it); | ||||||
|   Frame parse_frame_(light::AddressableLight &it); |   Frame parse_frame_(light::AddressableLight &it); | ||||||
|   | |||||||
| @@ -73,13 +73,6 @@ void AHT10Component::update() { | |||||||
|   bool success = false; |   bool success = false; | ||||||
|   for (int i = 0; i < AHT10_ATTEMPTS; ++i) { |   for (int i = 0; i < AHT10_ATTEMPTS; ++i) { | ||||||
|     ESP_LOGVV(TAG, "Attempt %d at %6u", i, millis()); |     ESP_LOGVV(TAG, "Attempt %d at %6u", i, millis()); | ||||||
|     delayMicroseconds(4); |  | ||||||
|  |  | ||||||
|     uint8_t reg = 0; |  | ||||||
|     if (this->write(®, 1) != i2c::ERROR_OK) { |  | ||||||
|       ESP_LOGD(TAG, "Communication with AHT10 failed, waiting..."); |  | ||||||
|       continue; |  | ||||||
|     } |  | ||||||
|     delay(delay_ms); |     delay(delay_ms); | ||||||
|     if (this->read(data, 6) != i2c::ERROR_OK) { |     if (this->read(data, 6) != i2c::ERROR_OK) { | ||||||
|       ESP_LOGD(TAG, "Communication with AHT10 failed, waiting..."); |       ESP_LOGD(TAG, "Communication with AHT10 failed, waiting..."); | ||||||
| @@ -117,12 +110,12 @@ void AHT10Component::update() { | |||||||
|   uint32_t raw_temperature = ((data[3] & 0x0F) << 16) | (data[4] << 8) | data[5]; |   uint32_t raw_temperature = ((data[3] & 0x0F) << 16) | (data[4] << 8) | data[5]; | ||||||
|   uint32_t raw_humidity = ((data[1] << 16) | (data[2] << 8) | data[3]) >> 4; |   uint32_t raw_humidity = ((data[1] << 16) | (data[2] << 8) | data[3]) >> 4; | ||||||
|  |  | ||||||
|   float temperature = ((200.0 * (float) raw_temperature) / 1048576.0) - 50.0; |   float temperature = ((200.0f * (float) raw_temperature) / 1048576.0f) - 50.0f; | ||||||
|   float humidity; |   float humidity; | ||||||
|   if (raw_humidity == 0) {  // unrealistic value |   if (raw_humidity == 0) {  // unrealistic value | ||||||
|     humidity = NAN; |     humidity = NAN; | ||||||
|   } else { |   } else { | ||||||
|     humidity = (float) raw_humidity * 100.0 / 1048576.0; |     humidity = (float) raw_humidity * 100.0f / 1048576.0f; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   if (this->temperature_sensor_ != nullptr) { |   if (this->temperature_sensor_ != nullptr) { | ||||||
|   | |||||||
| @@ -38,9 +38,9 @@ void AM2320Component::update() { | |||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   float temperature = (((data[4] & 0x7F) << 8) + data[5]) / 10.0; |   float temperature = (((data[4] & 0x7F) << 8) + data[5]) / 10.0f; | ||||||
|   temperature = (data[4] & 0x80) ? -temperature : temperature; |   temperature = (data[4] & 0x80) ? -temperature : temperature; | ||||||
|   float humidity = ((data[2] << 8) + data[3]) / 10.0; |   float humidity = ((data[2] << 8) + data[3]) / 10.0f; | ||||||
|  |  | ||||||
|   ESP_LOGD(TAG, "Got temperature=%.1f°C humidity=%.1f%%", temperature, humidity); |   ESP_LOGD(TAG, "Got temperature=%.1f°C humidity=%.1f%%", temperature, humidity); | ||||||
|   if (this->temperature_sensor_ != nullptr) |   if (this->temperature_sensor_ != nullptr) | ||||||
|   | |||||||
| @@ -44,8 +44,9 @@ async def to_code(config): | |||||||
|     width, height = image.size |     width, height = image.size | ||||||
|     frames = image.n_frames |     frames = image.n_frames | ||||||
|     if CONF_RESIZE in config: |     if CONF_RESIZE in config: | ||||||
|         image.thumbnail(config[CONF_RESIZE]) |         new_width_max, new_height_max = config[CONF_RESIZE] | ||||||
|         width, height = image.size |         ratio = min(new_width_max / width, new_height_max / height) | ||||||
|  |         width, height = int(width * ratio), int(height * ratio) | ||||||
|     else: |     else: | ||||||
|         if width > 500 or height > 500: |         if width > 500 or height > 500: | ||||||
|             _LOGGER.warning( |             _LOGGER.warning( | ||||||
| @@ -59,10 +60,12 @@ async def to_code(config): | |||||||
|         for frameIndex in range(frames): |         for frameIndex in range(frames): | ||||||
|             image.seek(frameIndex) |             image.seek(frameIndex) | ||||||
|             frame = image.convert("L", dither=Image.NONE) |             frame = image.convert("L", dither=Image.NONE) | ||||||
|  |             if CONF_RESIZE in config: | ||||||
|  |                 frame = frame.resize([width, height]) | ||||||
|             pixels = list(frame.getdata()) |             pixels = list(frame.getdata()) | ||||||
|             if len(pixels) != height * width: |             if len(pixels) != height * width: | ||||||
|                 raise core.EsphomeError( |                 raise core.EsphomeError( | ||||||
|                     f"Unexpected number of pixels in frame {frameIndex}: {len(pixels)} != {height*width}" |                     f"Unexpected number of pixels in {path} frame {frameIndex}: ({len(pixels)} != {height*width})" | ||||||
|                 ) |                 ) | ||||||
|             for pix in pixels: |             for pix in pixels: | ||||||
|                 data[pos] = pix |                 data[pos] = pix | ||||||
| @@ -76,10 +79,12 @@ async def to_code(config): | |||||||
|             if CONF_RESIZE in config: |             if CONF_RESIZE in config: | ||||||
|                 image.thumbnail(config[CONF_RESIZE]) |                 image.thumbnail(config[CONF_RESIZE]) | ||||||
|             frame = image.convert("RGB") |             frame = image.convert("RGB") | ||||||
|  |             if CONF_RESIZE in config: | ||||||
|  |                 frame = frame.resize([width, height]) | ||||||
|             pixels = list(frame.getdata()) |             pixels = list(frame.getdata()) | ||||||
|             if len(pixels) != height * width: |             if len(pixels) != height * width: | ||||||
|                 raise core.EsphomeError( |                 raise core.EsphomeError( | ||||||
|                     f"Unexpected number of pixels in frame {frameIndex}: {len(pixels)} != {height*width}" |                     f"Unexpected number of pixels in {path} frame {frameIndex}: ({len(pixels)} != {height*width})" | ||||||
|                 ) |                 ) | ||||||
|             for pix in pixels: |             for pix in pixels: | ||||||
|                 data[pos] = pix[0] |                 data[pos] = pix[0] | ||||||
| @@ -95,6 +100,8 @@ async def to_code(config): | |||||||
|         for frameIndex in range(frames): |         for frameIndex in range(frames): | ||||||
|             image.seek(frameIndex) |             image.seek(frameIndex) | ||||||
|             frame = image.convert("1", dither=Image.NONE) |             frame = image.convert("1", dither=Image.NONE) | ||||||
|  |             if CONF_RESIZE in config: | ||||||
|  |                 frame = frame.resize([width, height]) | ||||||
|             for y in range(height): |             for y in range(height): | ||||||
|                 for x in range(width): |                 for x in range(width): | ||||||
|                     if frame.getpixel((x, y)): |                     if frame.getpixel((x, y)): | ||||||
|   | |||||||
| @@ -30,7 +30,7 @@ class Anova : public climate::Climate, public esphome::ble_client::BLEClientNode | |||||||
|   climate::ClimateTraits traits() override { |   climate::ClimateTraits traits() override { | ||||||
|     auto traits = climate::ClimateTraits(); |     auto traits = climate::ClimateTraits(); | ||||||
|     traits.set_supports_current_temperature(true); |     traits.set_supports_current_temperature(true); | ||||||
|     traits.set_supports_heat_mode(true); |     traits.set_supported_modes({climate::CLIMATE_MODE_OFF, climate::ClimateMode::CLIMATE_MODE_HEAT}); | ||||||
|     traits.set_visual_min_temperature(25.0); |     traits.set_visual_min_temperature(25.0); | ||||||
|     traits.set_visual_max_temperature(100.0); |     traits.set_visual_max_temperature(100.0); | ||||||
|     traits.set_visual_temperature_step(0.1); |     traits.set_visual_temperature_step(0.1); | ||||||
|   | |||||||
| @@ -103,13 +103,7 @@ void AnovaCodec::decode(const uint8_t *data, uint16_t length) { | |||||||
|       } |       } | ||||||
|       break; |       break; | ||||||
|     } |     } | ||||||
|     case READ_TARGET_TEMPERATURE: { |     case READ_TARGET_TEMPERATURE: | ||||||
|       this->target_temp_ = parse_number<float>(str_until(buf, '\r')).value_or(0.0f); |  | ||||||
|       if (this->fahrenheit_) |  | ||||||
|         this->target_temp_ = ftoc(this->target_temp_); |  | ||||||
|       this->has_target_temp_ = true; |  | ||||||
|       break; |  | ||||||
|     } |  | ||||||
|     case SET_TARGET_TEMPERATURE: { |     case SET_TARGET_TEMPERATURE: { | ||||||
|       this->target_temp_ = parse_number<float>(str_until(buf, '\r')).value_or(0.0f); |       this->target_temp_ = parse_number<float>(str_until(buf, '\r')).value_or(0.0f); | ||||||
|       if (this->fahrenheit_) |       if (this->fahrenheit_) | ||||||
|   | |||||||
| @@ -40,6 +40,7 @@ service APIConnection { | |||||||
|   rpc climate_command (ClimateCommandRequest) returns (void) {} |   rpc climate_command (ClimateCommandRequest) returns (void) {} | ||||||
|   rpc number_command (NumberCommandRequest) returns (void) {} |   rpc number_command (NumberCommandRequest) returns (void) {} | ||||||
|   rpc select_command (SelectCommandRequest) returns (void) {} |   rpc select_command (SelectCommandRequest) returns (void) {} | ||||||
|  |   rpc button_command (ButtonCommandRequest) returns (void) {} | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -868,6 +869,11 @@ message ClimateCommandRequest { | |||||||
| } | } | ||||||
|  |  | ||||||
| // ==================== NUMBER ==================== | // ==================== NUMBER ==================== | ||||||
|  | enum NumberMode { | ||||||
|  |   NUMBER_MODE_AUTO = 0; | ||||||
|  |   NUMBER_MODE_BOX = 1; | ||||||
|  |   NUMBER_MODE_SLIDER = 2; | ||||||
|  | } | ||||||
| message ListEntitiesNumberResponse { | message ListEntitiesNumberResponse { | ||||||
|   option (id) = 49; |   option (id) = 49; | ||||||
|   option (source) = SOURCE_SERVER; |   option (source) = SOURCE_SERVER; | ||||||
| @@ -884,6 +890,8 @@ message ListEntitiesNumberResponse { | |||||||
|   float step = 8; |   float step = 8; | ||||||
|   bool disabled_by_default = 9; |   bool disabled_by_default = 9; | ||||||
|   EntityCategory entity_category = 10; |   EntityCategory entity_category = 10; | ||||||
|  |   string unit_of_measurement = 11; | ||||||
|  |   NumberMode mode = 12; | ||||||
| } | } | ||||||
| message NumberStateResponse { | message NumberStateResponse { | ||||||
|   option (id) = 50; |   option (id) = 50; | ||||||
| @@ -944,3 +952,28 @@ message SelectCommandRequest { | |||||||
|   fixed32 key = 1; |   fixed32 key = 1; | ||||||
|   string state = 2; |   string state = 2; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // ==================== BUTTON ==================== | ||||||
|  | message ListEntitiesButtonResponse { | ||||||
|  |   option (id) = 61; | ||||||
|  |   option (source) = SOURCE_SERVER; | ||||||
|  |   option (ifdef) = "USE_BUTTON"; | ||||||
|  |  | ||||||
|  |   string object_id = 1; | ||||||
|  |   fixed32 key = 2; | ||||||
|  |   string name = 3; | ||||||
|  |   string unique_id = 4; | ||||||
|  |  | ||||||
|  |   string icon = 5; | ||||||
|  |   bool disabled_by_default = 6; | ||||||
|  |   EntityCategory entity_category = 7; | ||||||
|  |   string device_class = 8; | ||||||
|  | } | ||||||
|  | message ButtonCommandRequest { | ||||||
|  |   option (id) = 62; | ||||||
|  |   option (source) = SOURCE_CLIENT; | ||||||
|  |   option (ifdef) = "USE_BUTTON"; | ||||||
|  |   option (no_delay) = true; | ||||||
|  |  | ||||||
|  |   fixed32 key = 1; | ||||||
|  | } | ||||||
|   | |||||||
| @@ -132,7 +132,7 @@ void APIConnection::loop() { | |||||||
|  |  | ||||||
|   if (state_subs_at_ != -1) { |   if (state_subs_at_ != -1) { | ||||||
|     const auto &subs = this->parent_->get_state_subs(); |     const auto &subs = this->parent_->get_state_subs(); | ||||||
|     if (state_subs_at_ >= subs.size()) { |     if (state_subs_at_ >= (int) subs.size()) { | ||||||
|       state_subs_at_ = -1; |       state_subs_at_ = -1; | ||||||
|     } else { |     } else { | ||||||
|       auto &it = subs[state_subs_at_]; |       auto &it = subs[state_subs_at_]; | ||||||
| @@ -619,6 +619,8 @@ bool APIConnection::send_number_info(number::Number *number) { | |||||||
|   msg.icon = number->get_icon(); |   msg.icon = number->get_icon(); | ||||||
|   msg.disabled_by_default = number->is_disabled_by_default(); |   msg.disabled_by_default = number->is_disabled_by_default(); | ||||||
|   msg.entity_category = static_cast<enums::EntityCategory>(number->get_entity_category()); |   msg.entity_category = static_cast<enums::EntityCategory>(number->get_entity_category()); | ||||||
|  |   msg.unit_of_measurement = number->traits.get_unit_of_measurement(); | ||||||
|  |   msg.mode = static_cast<enums::NumberMode>(number->traits.get_mode()); | ||||||
|  |  | ||||||
|   msg.min_value = number->traits.get_min_value(); |   msg.min_value = number->traits.get_min_value(); | ||||||
|   msg.max_value = number->traits.get_max_value(); |   msg.max_value = number->traits.get_max_value(); | ||||||
| @@ -674,6 +676,28 @@ void APIConnection::select_command(const SelectCommandRequest &msg) { | |||||||
| } | } | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
|  | #ifdef USE_BUTTON | ||||||
|  | bool APIConnection::send_button_info(button::Button *button) { | ||||||
|  |   ListEntitiesButtonResponse msg; | ||||||
|  |   msg.key = button->get_object_id_hash(); | ||||||
|  |   msg.object_id = button->get_object_id(); | ||||||
|  |   msg.name = button->get_name(); | ||||||
|  |   msg.unique_id = get_default_unique_id("button", button); | ||||||
|  |   msg.icon = button->get_icon(); | ||||||
|  |   msg.disabled_by_default = button->is_disabled_by_default(); | ||||||
|  |   msg.entity_category = static_cast<enums::EntityCategory>(button->get_entity_category()); | ||||||
|  |   msg.device_class = button->get_device_class(); | ||||||
|  |   return this->send_list_entities_button_response(msg); | ||||||
|  | } | ||||||
|  | void APIConnection::button_command(const ButtonCommandRequest &msg) { | ||||||
|  |   button::Button *button = App.get_button_by_key(msg.key); | ||||||
|  |   if (button == nullptr) | ||||||
|  |     return; | ||||||
|  |  | ||||||
|  |   button->press(); | ||||||
|  | } | ||||||
|  | #endif | ||||||
|  |  | ||||||
| #ifdef USE_ESP32_CAMERA | #ifdef USE_ESP32_CAMERA | ||||||
| void APIConnection::send_camera_state(std::shared_ptr<esp32_camera::CameraImage> image) { | void APIConnection::send_camera_state(std::shared_ptr<esp32_camera::CameraImage> image) { | ||||||
|   if (!this->state_subscription_) |   if (!this->state_subscription_) | ||||||
|   | |||||||
| @@ -73,6 +73,10 @@ class APIConnection : public APIServerConnection { | |||||||
|   bool send_select_state(select::Select *select, std::string state); |   bool send_select_state(select::Select *select, std::string state); | ||||||
|   bool send_select_info(select::Select *select); |   bool send_select_info(select::Select *select); | ||||||
|   void select_command(const SelectCommandRequest &msg) override; |   void select_command(const SelectCommandRequest &msg) override; | ||||||
|  | #endif | ||||||
|  | #ifdef USE_BUTTON | ||||||
|  |   bool send_button_info(button::Button *button); | ||||||
|  |   void button_command(const ButtonCommandRequest &msg) override; | ||||||
| #endif | #endif | ||||||
|   bool send_log_message(int level, const char *tag, const char *line); |   bool send_log_message(int level, const char *tag, const char *line); | ||||||
|   void send_homeassistant_service_call(const HomeassistantServiceResponse &call) { |   void send_homeassistant_service_call(const HomeassistantServiceResponse &call) { | ||||||
|   | |||||||
| @@ -174,9 +174,6 @@ APIError APINoiseFrameHelper::loop() { | |||||||
|  * errno API_ERROR_HANDSHAKE_PACKET_LEN: Packet too big for this phase. |  * errno API_ERROR_HANDSHAKE_PACKET_LEN: Packet too big for this phase. | ||||||
|  */ |  */ | ||||||
| APIError APINoiseFrameHelper::try_read_frame_(ParsedFrame *frame) { | APIError APINoiseFrameHelper::try_read_frame_(ParsedFrame *frame) { | ||||||
|   int err; |  | ||||||
|   APIError aerr; |  | ||||||
|  |  | ||||||
|   if (frame == nullptr) { |   if (frame == nullptr) { | ||||||
|     HELPER_LOG("Bad argument for try_read_frame_"); |     HELPER_LOG("Bad argument for try_read_frame_"); | ||||||
|     return APIError::BAD_ARG; |     return APIError::BAD_ARG; | ||||||
| @@ -200,7 +197,7 @@ APIError APINoiseFrameHelper::try_read_frame_(ParsedFrame *frame) { | |||||||
|       return APIError::CONNECTION_CLOSED; |       return APIError::CONNECTION_CLOSED; | ||||||
|     } |     } | ||||||
|     rx_header_buf_len_ += received; |     rx_header_buf_len_ += received; | ||||||
|     if (received != to_read) { |     if ((size_t) received != to_read) { | ||||||
|       // not a full read |       // not a full read | ||||||
|       return APIError::WOULD_BLOCK; |       return APIError::WOULD_BLOCK; | ||||||
|     } |     } | ||||||
| @@ -247,7 +244,7 @@ APIError APINoiseFrameHelper::try_read_frame_(ParsedFrame *frame) { | |||||||
|       return APIError::CONNECTION_CLOSED; |       return APIError::CONNECTION_CLOSED; | ||||||
|     } |     } | ||||||
|     rx_buf_len_ += received; |     rx_buf_len_ += received; | ||||||
|     if (received != to_read) { |     if ((size_t) received != to_read) { | ||||||
|       // not all read |       // not all read | ||||||
|       return APIError::WOULD_BLOCK; |       return APIError::WOULD_BLOCK; | ||||||
|     } |     } | ||||||
| @@ -544,7 +541,6 @@ APIError APINoiseFrameHelper::try_send_tx_buf_() { | |||||||
| APIError APINoiseFrameHelper::write_raw_(const struct iovec *iov, int iovcnt) { | APIError APINoiseFrameHelper::write_raw_(const struct iovec *iov, int iovcnt) { | ||||||
|   if (iovcnt == 0) |   if (iovcnt == 0) | ||||||
|     return APIError::OK; |     return APIError::OK; | ||||||
|   int err; |  | ||||||
|   APIError aerr; |   APIError aerr; | ||||||
|  |  | ||||||
|   size_t total_write_len = 0; |   size_t total_write_len = 0; | ||||||
| @@ -584,7 +580,7 @@ APIError APINoiseFrameHelper::write_raw_(const struct iovec *iov, int iovcnt) { | |||||||
|     state_ = State::FAILED; |     state_ = State::FAILED; | ||||||
|     HELPER_LOG("Socket write failed with errno %d", errno); |     HELPER_LOG("Socket write failed with errno %d", errno); | ||||||
|     return APIError::SOCKET_WRITE_FAILED; |     return APIError::SOCKET_WRITE_FAILED; | ||||||
|   } else if (sent != total_write_len) { |   } else if ((size_t) sent != total_write_len) { | ||||||
|     // partially sent, add end to tx_buf |     // partially sent, add end to tx_buf | ||||||
|     size_t to_consume = sent; |     size_t to_consume = sent; | ||||||
|     for (int i = 0; i < iovcnt; i++) { |     for (int i = 0; i < iovcnt; i++) { | ||||||
| @@ -778,9 +774,6 @@ APIError APIPlaintextFrameHelper::loop() { | |||||||
|  * error API_ERROR_BAD_INDICATOR: Bad indicator byte at start of frame. |  * error API_ERROR_BAD_INDICATOR: Bad indicator byte at start of frame. | ||||||
|  */ |  */ | ||||||
| APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) { | APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) { | ||||||
|   int err; |  | ||||||
|   APIError aerr; |  | ||||||
|  |  | ||||||
|   if (frame == nullptr) { |   if (frame == nullptr) { | ||||||
|     HELPER_LOG("Bad argument for try_read_frame_"); |     HELPER_LOG("Bad argument for try_read_frame_"); | ||||||
|     return APIError::BAD_ARG; |     return APIError::BAD_ARG; | ||||||
| @@ -854,7 +847,7 @@ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) { | |||||||
|       return APIError::CONNECTION_CLOSED; |       return APIError::CONNECTION_CLOSED; | ||||||
|     } |     } | ||||||
|     rx_buf_len_ += received; |     rx_buf_len_ += received; | ||||||
|     if (received != to_read) { |     if ((size_t) received != to_read) { | ||||||
|       // not all read |       // not all read | ||||||
|       return APIError::WOULD_BLOCK; |       return APIError::WOULD_BLOCK; | ||||||
|     } |     } | ||||||
| @@ -874,7 +867,6 @@ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) { | |||||||
| } | } | ||||||
|  |  | ||||||
| APIError APIPlaintextFrameHelper::read_packet(ReadPacketBuffer *buffer) { | APIError APIPlaintextFrameHelper::read_packet(ReadPacketBuffer *buffer) { | ||||||
|   int err; |  | ||||||
|   APIError aerr; |   APIError aerr; | ||||||
|  |  | ||||||
|   if (state_ != State::DATA) { |   if (state_ != State::DATA) { | ||||||
| @@ -894,9 +886,6 @@ APIError APIPlaintextFrameHelper::read_packet(ReadPacketBuffer *buffer) { | |||||||
| } | } | ||||||
| bool APIPlaintextFrameHelper::can_write_without_blocking() { return state_ == State::DATA && tx_buf_.empty(); } | bool APIPlaintextFrameHelper::can_write_without_blocking() { return state_ == State::DATA && tx_buf_.empty(); } | ||||||
| APIError APIPlaintextFrameHelper::write_packet(uint16_t type, const uint8_t *payload, size_t payload_len) { | APIError APIPlaintextFrameHelper::write_packet(uint16_t type, const uint8_t *payload, size_t payload_len) { | ||||||
|   int err; |  | ||||||
|   APIError aerr; |  | ||||||
|  |  | ||||||
|   if (state_ != State::DATA) { |   if (state_ != State::DATA) { | ||||||
|     return APIError::BAD_STATE; |     return APIError::BAD_STATE; | ||||||
|   } |   } | ||||||
| @@ -940,7 +929,6 @@ APIError APIPlaintextFrameHelper::try_send_tx_buf_() { | |||||||
| APIError APIPlaintextFrameHelper::write_raw_(const struct iovec *iov, int iovcnt) { | APIError APIPlaintextFrameHelper::write_raw_(const struct iovec *iov, int iovcnt) { | ||||||
|   if (iovcnt == 0) |   if (iovcnt == 0) | ||||||
|     return APIError::OK; |     return APIError::OK; | ||||||
|   int err; |  | ||||||
|   APIError aerr; |   APIError aerr; | ||||||
|  |  | ||||||
|   size_t total_write_len = 0; |   size_t total_write_len = 0; | ||||||
| @@ -980,7 +968,7 @@ APIError APIPlaintextFrameHelper::write_raw_(const struct iovec *iov, int iovcnt | |||||||
|     state_ = State::FAILED; |     state_ = State::FAILED; | ||||||
|     HELPER_LOG("Socket write failed with errno %d", errno); |     HELPER_LOG("Socket write failed with errno %d", errno); | ||||||
|     return APIError::SOCKET_WRITE_FAILED; |     return APIError::SOCKET_WRITE_FAILED; | ||||||
|   } else if (sent != total_write_len) { |   } else if ((size_t) sent != total_write_len) { | ||||||
|     // partially sent, add end to tx_buf |     // partially sent, add end to tx_buf | ||||||
|     size_t to_consume = sent; |     size_t to_consume = sent; | ||||||
|     for (int i = 0; i < iovcnt; i++) { |     for (int i = 0; i < iovcnt; i++) { | ||||||
|   | |||||||
| @@ -266,6 +266,18 @@ template<> const char *proto_enum_to_string<enums::ClimatePreset>(enums::Climate | |||||||
|       return "UNKNOWN"; |       return "UNKNOWN"; | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  | template<> const char *proto_enum_to_string<enums::NumberMode>(enums::NumberMode value) { | ||||||
|  |   switch (value) { | ||||||
|  |     case enums::NUMBER_MODE_AUTO: | ||||||
|  |       return "NUMBER_MODE_AUTO"; | ||||||
|  |     case enums::NUMBER_MODE_BOX: | ||||||
|  |       return "NUMBER_MODE_BOX"; | ||||||
|  |     case enums::NUMBER_MODE_SLIDER: | ||||||
|  |       return "NUMBER_MODE_SLIDER"; | ||||||
|  |     default: | ||||||
|  |       return "UNKNOWN"; | ||||||
|  |   } | ||||||
|  | } | ||||||
| bool HelloRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { | bool HelloRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { | ||||||
|   switch (field_id) { |   switch (field_id) { | ||||||
|     case 1: { |     case 1: { | ||||||
| @@ -279,7 +291,7 @@ bool HelloRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) | |||||||
| void HelloRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(1, this->client_info); } | void HelloRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(1, this->client_info); } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void HelloRequest::dump_to(std::string &out) const { | void HelloRequest::dump_to(std::string &out) const { | ||||||
|   char buffer[64]; |   __attribute__((unused)) char buffer[64]; | ||||||
|   out.append("HelloRequest {\n"); |   out.append("HelloRequest {\n"); | ||||||
|   out.append("  client_info: "); |   out.append("  client_info: "); | ||||||
|   out.append("'").append(this->client_info).append("'"); |   out.append("'").append(this->client_info).append("'"); | ||||||
| @@ -318,7 +330,7 @@ void HelloResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void HelloResponse::dump_to(std::string &out) const { | void HelloResponse::dump_to(std::string &out) const { | ||||||
|   char buffer[64]; |   __attribute__((unused)) char buffer[64]; | ||||||
|   out.append("HelloResponse {\n"); |   out.append("HelloResponse {\n"); | ||||||
|   out.append("  api_version_major: "); |   out.append("  api_version_major: "); | ||||||
|   sprintf(buffer, "%u", this->api_version_major); |   sprintf(buffer, "%u", this->api_version_major); | ||||||
| @@ -349,7 +361,7 @@ bool ConnectRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value | |||||||
| void ConnectRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(1, this->password); } | void ConnectRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_string(1, this->password); } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void ConnectRequest::dump_to(std::string &out) const { | void ConnectRequest::dump_to(std::string &out) const { | ||||||
|   char buffer[64]; |   __attribute__((unused)) char buffer[64]; | ||||||
|   out.append("ConnectRequest {\n"); |   out.append("ConnectRequest {\n"); | ||||||
|   out.append("  password: "); |   out.append("  password: "); | ||||||
|   out.append("'").append(this->password).append("'"); |   out.append("'").append(this->password).append("'"); | ||||||
| @@ -370,7 +382,7 @@ bool ConnectResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { | |||||||
| void ConnectResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(1, this->invalid_password); } | void ConnectResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(1, this->invalid_password); } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void ConnectResponse::dump_to(std::string &out) const { | void ConnectResponse::dump_to(std::string &out) const { | ||||||
|   char buffer[64]; |   __attribute__((unused)) char buffer[64]; | ||||||
|   out.append("ConnectResponse {\n"); |   out.append("ConnectResponse {\n"); | ||||||
|   out.append("  invalid_password: "); |   out.append("  invalid_password: "); | ||||||
|   out.append(YESNO(this->invalid_password)); |   out.append(YESNO(this->invalid_password)); | ||||||
| @@ -464,7 +476,7 @@ void DeviceInfoResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void DeviceInfoResponse::dump_to(std::string &out) const { | void DeviceInfoResponse::dump_to(std::string &out) const { | ||||||
|   char buffer[64]; |   __attribute__((unused)) char buffer[64]; | ||||||
|   out.append("DeviceInfoResponse {\n"); |   out.append("DeviceInfoResponse {\n"); | ||||||
|   out.append("  uses_password: "); |   out.append("  uses_password: "); | ||||||
|   out.append(YESNO(this->uses_password)); |   out.append(YESNO(this->uses_password)); | ||||||
| @@ -588,7 +600,7 @@ void ListEntitiesBinarySensorResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void ListEntitiesBinarySensorResponse::dump_to(std::string &out) const { | void ListEntitiesBinarySensorResponse::dump_to(std::string &out) const { | ||||||
|   char buffer[64]; |   __attribute__((unused)) char buffer[64]; | ||||||
|   out.append("ListEntitiesBinarySensorResponse {\n"); |   out.append("ListEntitiesBinarySensorResponse {\n"); | ||||||
|   out.append("  object_id: "); |   out.append("  object_id: "); | ||||||
|   out.append("'").append(this->object_id).append("'"); |   out.append("'").append(this->object_id).append("'"); | ||||||
| @@ -660,7 +672,7 @@ void BinarySensorStateResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void BinarySensorStateResponse::dump_to(std::string &out) const { | void BinarySensorStateResponse::dump_to(std::string &out) const { | ||||||
|   char buffer[64]; |   __attribute__((unused)) char buffer[64]; | ||||||
|   out.append("BinarySensorStateResponse {\n"); |   out.append("BinarySensorStateResponse {\n"); | ||||||
|   out.append("  key: "); |   out.append("  key: "); | ||||||
|   sprintf(buffer, "%u", this->key); |   sprintf(buffer, "%u", this->key); | ||||||
| @@ -754,7 +766,7 @@ void ListEntitiesCoverResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void ListEntitiesCoverResponse::dump_to(std::string &out) const { | void ListEntitiesCoverResponse::dump_to(std::string &out) const { | ||||||
|   char buffer[64]; |   __attribute__((unused)) char buffer[64]; | ||||||
|   out.append("ListEntitiesCoverResponse {\n"); |   out.append("ListEntitiesCoverResponse {\n"); | ||||||
|   out.append("  object_id: "); |   out.append("  object_id: "); | ||||||
|   out.append("'").append(this->object_id).append("'"); |   out.append("'").append(this->object_id).append("'"); | ||||||
| @@ -844,7 +856,7 @@ void CoverStateResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void CoverStateResponse::dump_to(std::string &out) const { | void CoverStateResponse::dump_to(std::string &out) const { | ||||||
|   char buffer[64]; |   __attribute__((unused)) char buffer[64]; | ||||||
|   out.append("CoverStateResponse {\n"); |   out.append("CoverStateResponse {\n"); | ||||||
|   out.append("  key: "); |   out.append("  key: "); | ||||||
|   sprintf(buffer, "%u", this->key); |   sprintf(buffer, "%u", this->key); | ||||||
| @@ -927,7 +939,7 @@ void CoverCommandRequest::encode(ProtoWriteBuffer buffer) const { | |||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void CoverCommandRequest::dump_to(std::string &out) const { | void CoverCommandRequest::dump_to(std::string &out) const { | ||||||
|   char buffer[64]; |   __attribute__((unused)) char buffer[64]; | ||||||
|   out.append("CoverCommandRequest {\n"); |   out.append("CoverCommandRequest {\n"); | ||||||
|   out.append("  key: "); |   out.append("  key: "); | ||||||
|   sprintf(buffer, "%u", this->key); |   sprintf(buffer, "%u", this->key); | ||||||
| @@ -1043,7 +1055,7 @@ void ListEntitiesFanResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void ListEntitiesFanResponse::dump_to(std::string &out) const { | void ListEntitiesFanResponse::dump_to(std::string &out) const { | ||||||
|   char buffer[64]; |   __attribute__((unused)) char buffer[64]; | ||||||
|   out.append("ListEntitiesFanResponse {\n"); |   out.append("ListEntitiesFanResponse {\n"); | ||||||
|   out.append("  object_id: "); |   out.append("  object_id: "); | ||||||
|   out.append("'").append(this->object_id).append("'"); |   out.append("'").append(this->object_id).append("'"); | ||||||
| @@ -1139,7 +1151,7 @@ void FanStateResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void FanStateResponse::dump_to(std::string &out) const { | void FanStateResponse::dump_to(std::string &out) const { | ||||||
|   char buffer[64]; |   __attribute__((unused)) char buffer[64]; | ||||||
|   out.append("FanStateResponse {\n"); |   out.append("FanStateResponse {\n"); | ||||||
|   out.append("  key: "); |   out.append("  key: "); | ||||||
|   sprintf(buffer, "%u", this->key); |   sprintf(buffer, "%u", this->key); | ||||||
| @@ -1240,7 +1252,7 @@ void FanCommandRequest::encode(ProtoWriteBuffer buffer) const { | |||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void FanCommandRequest::dump_to(std::string &out) const { | void FanCommandRequest::dump_to(std::string &out) const { | ||||||
|   char buffer[64]; |   __attribute__((unused)) char buffer[64]; | ||||||
|   out.append("FanCommandRequest {\n"); |   out.append("FanCommandRequest {\n"); | ||||||
|   out.append("  key: "); |   out.append("  key: "); | ||||||
|   sprintf(buffer, "%u", this->key); |   sprintf(buffer, "%u", this->key); | ||||||
| @@ -1391,7 +1403,7 @@ void ListEntitiesLightResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void ListEntitiesLightResponse::dump_to(std::string &out) const { | void ListEntitiesLightResponse::dump_to(std::string &out) const { | ||||||
|   char buffer[64]; |   __attribute__((unused)) char buffer[64]; | ||||||
|   out.append("ListEntitiesLightResponse {\n"); |   out.append("ListEntitiesLightResponse {\n"); | ||||||
|   out.append("  object_id: "); |   out.append("  object_id: "); | ||||||
|   out.append("'").append(this->object_id).append("'"); |   out.append("'").append(this->object_id).append("'"); | ||||||
| @@ -1549,7 +1561,7 @@ void LightStateResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void LightStateResponse::dump_to(std::string &out) const { | void LightStateResponse::dump_to(std::string &out) const { | ||||||
|   char buffer[64]; |   __attribute__((unused)) char buffer[64]; | ||||||
|   out.append("LightStateResponse {\n"); |   out.append("LightStateResponse {\n"); | ||||||
|   out.append("  key: "); |   out.append("  key: "); | ||||||
|   sprintf(buffer, "%u", this->key); |   sprintf(buffer, "%u", this->key); | ||||||
| @@ -1772,7 +1784,7 @@ void LightCommandRequest::encode(ProtoWriteBuffer buffer) const { | |||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void LightCommandRequest::dump_to(std::string &out) const { | void LightCommandRequest::dump_to(std::string &out) const { | ||||||
|   char buffer[64]; |   __attribute__((unused)) char buffer[64]; | ||||||
|   out.append("LightCommandRequest {\n"); |   out.append("LightCommandRequest {\n"); | ||||||
|   out.append("  key: "); |   out.append("  key: "); | ||||||
|   sprintf(buffer, "%u", this->key); |   sprintf(buffer, "%u", this->key); | ||||||
| @@ -1983,7 +1995,7 @@ void ListEntitiesSensorResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void ListEntitiesSensorResponse::dump_to(std::string &out) const { | void ListEntitiesSensorResponse::dump_to(std::string &out) const { | ||||||
|   char buffer[64]; |   __attribute__((unused)) char buffer[64]; | ||||||
|   out.append("ListEntitiesSensorResponse {\n"); |   out.append("ListEntitiesSensorResponse {\n"); | ||||||
|   out.append("  object_id: "); |   out.append("  object_id: "); | ||||||
|   out.append("'").append(this->object_id).append("'"); |   out.append("'").append(this->object_id).append("'"); | ||||||
| @@ -2072,7 +2084,7 @@ void SensorStateResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void SensorStateResponse::dump_to(std::string &out) const { | void SensorStateResponse::dump_to(std::string &out) const { | ||||||
|   char buffer[64]; |   __attribute__((unused)) char buffer[64]; | ||||||
|   out.append("SensorStateResponse {\n"); |   out.append("SensorStateResponse {\n"); | ||||||
|   out.append("  key: "); |   out.append("  key: "); | ||||||
|   sprintf(buffer, "%u", this->key); |   sprintf(buffer, "%u", this->key); | ||||||
| @@ -2152,7 +2164,7 @@ void ListEntitiesSwitchResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void ListEntitiesSwitchResponse::dump_to(std::string &out) const { | void ListEntitiesSwitchResponse::dump_to(std::string &out) const { | ||||||
|   char buffer[64]; |   __attribute__((unused)) char buffer[64]; | ||||||
|   out.append("ListEntitiesSwitchResponse {\n"); |   out.append("ListEntitiesSwitchResponse {\n"); | ||||||
|   out.append("  object_id: "); |   out.append("  object_id: "); | ||||||
|   out.append("'").append(this->object_id).append("'"); |   out.append("'").append(this->object_id).append("'"); | ||||||
| @@ -2215,7 +2227,7 @@ void SwitchStateResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void SwitchStateResponse::dump_to(std::string &out) const { | void SwitchStateResponse::dump_to(std::string &out) const { | ||||||
|   char buffer[64]; |   __attribute__((unused)) char buffer[64]; | ||||||
|   out.append("SwitchStateResponse {\n"); |   out.append("SwitchStateResponse {\n"); | ||||||
|   out.append("  key: "); |   out.append("  key: "); | ||||||
|   sprintf(buffer, "%u", this->key); |   sprintf(buffer, "%u", this->key); | ||||||
| @@ -2254,7 +2266,7 @@ void SwitchCommandRequest::encode(ProtoWriteBuffer buffer) const { | |||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void SwitchCommandRequest::dump_to(std::string &out) const { | void SwitchCommandRequest::dump_to(std::string &out) const { | ||||||
|   char buffer[64]; |   __attribute__((unused)) char buffer[64]; | ||||||
|   out.append("SwitchCommandRequest {\n"); |   out.append("SwitchCommandRequest {\n"); | ||||||
|   out.append("  key: "); |   out.append("  key: "); | ||||||
|   sprintf(buffer, "%u", this->key); |   sprintf(buffer, "%u", this->key); | ||||||
| @@ -2324,7 +2336,7 @@ void ListEntitiesTextSensorResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void ListEntitiesTextSensorResponse::dump_to(std::string &out) const { | void ListEntitiesTextSensorResponse::dump_to(std::string &out) const { | ||||||
|   char buffer[64]; |   __attribute__((unused)) char buffer[64]; | ||||||
|   out.append("ListEntitiesTextSensorResponse {\n"); |   out.append("ListEntitiesTextSensorResponse {\n"); | ||||||
|   out.append("  object_id: "); |   out.append("  object_id: "); | ||||||
|   out.append("'").append(this->object_id).append("'"); |   out.append("'").append(this->object_id).append("'"); | ||||||
| @@ -2394,7 +2406,7 @@ void TextSensorStateResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void TextSensorStateResponse::dump_to(std::string &out) const { | void TextSensorStateResponse::dump_to(std::string &out) const { | ||||||
|   char buffer[64]; |   __attribute__((unused)) char buffer[64]; | ||||||
|   out.append("TextSensorStateResponse {\n"); |   out.append("TextSensorStateResponse {\n"); | ||||||
|   out.append("  key: "); |   out.append("  key: "); | ||||||
|   sprintf(buffer, "%u", this->key); |   sprintf(buffer, "%u", this->key); | ||||||
| @@ -2431,7 +2443,7 @@ void SubscribeLogsRequest::encode(ProtoWriteBuffer buffer) const { | |||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void SubscribeLogsRequest::dump_to(std::string &out) const { | void SubscribeLogsRequest::dump_to(std::string &out) const { | ||||||
|   char buffer[64]; |   __attribute__((unused)) char buffer[64]; | ||||||
|   out.append("SubscribeLogsRequest {\n"); |   out.append("SubscribeLogsRequest {\n"); | ||||||
|   out.append("  level: "); |   out.append("  level: "); | ||||||
|   out.append(proto_enum_to_string<enums::LogLevel>(this->level)); |   out.append(proto_enum_to_string<enums::LogLevel>(this->level)); | ||||||
| @@ -2474,7 +2486,7 @@ void SubscribeLogsResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void SubscribeLogsResponse::dump_to(std::string &out) const { | void SubscribeLogsResponse::dump_to(std::string &out) const { | ||||||
|   char buffer[64]; |   __attribute__((unused)) char buffer[64]; | ||||||
|   out.append("SubscribeLogsResponse {\n"); |   out.append("SubscribeLogsResponse {\n"); | ||||||
|   out.append("  level: "); |   out.append("  level: "); | ||||||
|   out.append(proto_enum_to_string<enums::LogLevel>(this->level)); |   out.append(proto_enum_to_string<enums::LogLevel>(this->level)); | ||||||
| @@ -2516,7 +2528,7 @@ void HomeassistantServiceMap::encode(ProtoWriteBuffer buffer) const { | |||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void HomeassistantServiceMap::dump_to(std::string &out) const { | void HomeassistantServiceMap::dump_to(std::string &out) const { | ||||||
|   char buffer[64]; |   __attribute__((unused)) char buffer[64]; | ||||||
|   out.append("HomeassistantServiceMap {\n"); |   out.append("HomeassistantServiceMap {\n"); | ||||||
|   out.append("  key: "); |   out.append("  key: "); | ||||||
|   out.append("'").append(this->key).append("'"); |   out.append("'").append(this->key).append("'"); | ||||||
| @@ -2575,7 +2587,7 @@ void HomeassistantServiceResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void HomeassistantServiceResponse::dump_to(std::string &out) const { | void HomeassistantServiceResponse::dump_to(std::string &out) const { | ||||||
|   char buffer[64]; |   __attribute__((unused)) char buffer[64]; | ||||||
|   out.append("HomeassistantServiceResponse {\n"); |   out.append("HomeassistantServiceResponse {\n"); | ||||||
|   out.append("  service: "); |   out.append("  service: "); | ||||||
|   out.append("'").append(this->service).append("'"); |   out.append("'").append(this->service).append("'"); | ||||||
| @@ -2631,7 +2643,7 @@ void SubscribeHomeAssistantStateResponse::encode(ProtoWriteBuffer buffer) const | |||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void SubscribeHomeAssistantStateResponse::dump_to(std::string &out) const { | void SubscribeHomeAssistantStateResponse::dump_to(std::string &out) const { | ||||||
|   char buffer[64]; |   __attribute__((unused)) char buffer[64]; | ||||||
|   out.append("SubscribeHomeAssistantStateResponse {\n"); |   out.append("SubscribeHomeAssistantStateResponse {\n"); | ||||||
|   out.append("  entity_id: "); |   out.append("  entity_id: "); | ||||||
|   out.append("'").append(this->entity_id).append("'"); |   out.append("'").append(this->entity_id).append("'"); | ||||||
| @@ -2668,7 +2680,7 @@ void HomeAssistantStateResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void HomeAssistantStateResponse::dump_to(std::string &out) const { | void HomeAssistantStateResponse::dump_to(std::string &out) const { | ||||||
|   char buffer[64]; |   __attribute__((unused)) char buffer[64]; | ||||||
|   out.append("HomeAssistantStateResponse {\n"); |   out.append("HomeAssistantStateResponse {\n"); | ||||||
|   out.append("  entity_id: "); |   out.append("  entity_id: "); | ||||||
|   out.append("'").append(this->entity_id).append("'"); |   out.append("'").append(this->entity_id).append("'"); | ||||||
| @@ -2701,7 +2713,7 @@ bool GetTimeResponse::decode_32bit(uint32_t field_id, Proto32Bit value) { | |||||||
| void GetTimeResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_fixed32(1, this->epoch_seconds); } | void GetTimeResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_fixed32(1, this->epoch_seconds); } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void GetTimeResponse::dump_to(std::string &out) const { | void GetTimeResponse::dump_to(std::string &out) const { | ||||||
|   char buffer[64]; |   __attribute__((unused)) char buffer[64]; | ||||||
|   out.append("GetTimeResponse {\n"); |   out.append("GetTimeResponse {\n"); | ||||||
|   out.append("  epoch_seconds: "); |   out.append("  epoch_seconds: "); | ||||||
|   sprintf(buffer, "%u", this->epoch_seconds); |   sprintf(buffer, "%u", this->epoch_seconds); | ||||||
| @@ -2736,7 +2748,7 @@ void ListEntitiesServicesArgument::encode(ProtoWriteBuffer buffer) const { | |||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void ListEntitiesServicesArgument::dump_to(std::string &out) const { | void ListEntitiesServicesArgument::dump_to(std::string &out) const { | ||||||
|   char buffer[64]; |   __attribute__((unused)) char buffer[64]; | ||||||
|   out.append("ListEntitiesServicesArgument {\n"); |   out.append("ListEntitiesServicesArgument {\n"); | ||||||
|   out.append("  name: "); |   out.append("  name: "); | ||||||
|   out.append("'").append(this->name).append("'"); |   out.append("'").append(this->name).append("'"); | ||||||
| @@ -2781,7 +2793,7 @@ void ListEntitiesServicesResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void ListEntitiesServicesResponse::dump_to(std::string &out) const { | void ListEntitiesServicesResponse::dump_to(std::string &out) const { | ||||||
|   char buffer[64]; |   __attribute__((unused)) char buffer[64]; | ||||||
|   out.append("ListEntitiesServicesResponse {\n"); |   out.append("ListEntitiesServicesResponse {\n"); | ||||||
|   out.append("  name: "); |   out.append("  name: "); | ||||||
|   out.append("'").append(this->name).append("'"); |   out.append("'").append(this->name).append("'"); | ||||||
| @@ -2875,7 +2887,7 @@ void ExecuteServiceArgument::encode(ProtoWriteBuffer buffer) const { | |||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void ExecuteServiceArgument::dump_to(std::string &out) const { | void ExecuteServiceArgument::dump_to(std::string &out) const { | ||||||
|   char buffer[64]; |   __attribute__((unused)) char buffer[64]; | ||||||
|   out.append("ExecuteServiceArgument {\n"); |   out.append("ExecuteServiceArgument {\n"); | ||||||
|   out.append("  bool_: "); |   out.append("  bool_: "); | ||||||
|   out.append(YESNO(this->bool_)); |   out.append(YESNO(this->bool_)); | ||||||
| @@ -2956,7 +2968,7 @@ void ExecuteServiceRequest::encode(ProtoWriteBuffer buffer) const { | |||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void ExecuteServiceRequest::dump_to(std::string &out) const { | void ExecuteServiceRequest::dump_to(std::string &out) const { | ||||||
|   char buffer[64]; |   __attribute__((unused)) char buffer[64]; | ||||||
|   out.append("ExecuteServiceRequest {\n"); |   out.append("ExecuteServiceRequest {\n"); | ||||||
|   out.append("  key: "); |   out.append("  key: "); | ||||||
|   sprintf(buffer, "%u", this->key); |   sprintf(buffer, "%u", this->key); | ||||||
| @@ -3028,7 +3040,7 @@ void ListEntitiesCameraResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void ListEntitiesCameraResponse::dump_to(std::string &out) const { | void ListEntitiesCameraResponse::dump_to(std::string &out) const { | ||||||
|   char buffer[64]; |   __attribute__((unused)) char buffer[64]; | ||||||
|   out.append("ListEntitiesCameraResponse {\n"); |   out.append("ListEntitiesCameraResponse {\n"); | ||||||
|   out.append("  object_id: "); |   out.append("  object_id: "); | ||||||
|   out.append("'").append(this->object_id).append("'"); |   out.append("'").append(this->object_id).append("'"); | ||||||
| @@ -3098,7 +3110,7 @@ void CameraImageResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void CameraImageResponse::dump_to(std::string &out) const { | void CameraImageResponse::dump_to(std::string &out) const { | ||||||
|   char buffer[64]; |   __attribute__((unused)) char buffer[64]; | ||||||
|   out.append("CameraImageResponse {\n"); |   out.append("CameraImageResponse {\n"); | ||||||
|   out.append("  key: "); |   out.append("  key: "); | ||||||
|   sprintf(buffer, "%u", this->key); |   sprintf(buffer, "%u", this->key); | ||||||
| @@ -3135,7 +3147,7 @@ void CameraImageRequest::encode(ProtoWriteBuffer buffer) const { | |||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void CameraImageRequest::dump_to(std::string &out) const { | void CameraImageRequest::dump_to(std::string &out) const { | ||||||
|   char buffer[64]; |   __attribute__((unused)) char buffer[64]; | ||||||
|   out.append("CameraImageRequest {\n"); |   out.append("CameraImageRequest {\n"); | ||||||
|   out.append("  single: "); |   out.append("  single: "); | ||||||
|   out.append(YESNO(this->single)); |   out.append(YESNO(this->single)); | ||||||
| @@ -3281,7 +3293,7 @@ void ListEntitiesClimateResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
| } | } | ||||||
| #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 { | ||||||
|   char buffer[64]; |   __attribute__((unused)) char buffer[64]; | ||||||
|   out.append("ListEntitiesClimateResponse {\n"); |   out.append("ListEntitiesClimateResponse {\n"); | ||||||
|   out.append("  object_id: "); |   out.append("  object_id: "); | ||||||
|   out.append("'").append(this->object_id).append("'"); |   out.append("'").append(this->object_id).append("'"); | ||||||
| @@ -3468,7 +3480,7 @@ void ClimateStateResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void ClimateStateResponse::dump_to(std::string &out) const { | void ClimateStateResponse::dump_to(std::string &out) const { | ||||||
|   char buffer[64]; |   __attribute__((unused)) char buffer[64]; | ||||||
|   out.append("ClimateStateResponse {\n"); |   out.append("ClimateStateResponse {\n"); | ||||||
|   out.append("  key: "); |   out.append("  key: "); | ||||||
|   sprintf(buffer, "%u", this->key); |   sprintf(buffer, "%u", this->key); | ||||||
| @@ -3656,7 +3668,7 @@ void ClimateCommandRequest::encode(ProtoWriteBuffer buffer) const { | |||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void ClimateCommandRequest::dump_to(std::string &out) const { | void ClimateCommandRequest::dump_to(std::string &out) const { | ||||||
|   char buffer[64]; |   __attribute__((unused)) char buffer[64]; | ||||||
|   out.append("ClimateCommandRequest {\n"); |   out.append("ClimateCommandRequest {\n"); | ||||||
|   out.append("  key: "); |   out.append("  key: "); | ||||||
|   sprintf(buffer, "%u", this->key); |   sprintf(buffer, "%u", this->key); | ||||||
| @@ -3758,6 +3770,10 @@ bool ListEntitiesNumberResponse::decode_varint(uint32_t field_id, ProtoVarInt va | |||||||
|       this->entity_category = value.as_enum<enums::EntityCategory>(); |       this->entity_category = value.as_enum<enums::EntityCategory>(); | ||||||
|       return true; |       return true; | ||||||
|     } |     } | ||||||
|  |     case 12: { | ||||||
|  |       this->mode = value.as_enum<enums::NumberMode>(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|     default: |     default: | ||||||
|       return false; |       return false; | ||||||
|   } |   } | ||||||
| @@ -3780,6 +3796,10 @@ bool ListEntitiesNumberResponse::decode_length(uint32_t field_id, ProtoLengthDel | |||||||
|       this->icon = value.as_string(); |       this->icon = value.as_string(); | ||||||
|       return true; |       return true; | ||||||
|     } |     } | ||||||
|  |     case 11: { | ||||||
|  |       this->unit_of_measurement = value.as_string(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|     default: |     default: | ||||||
|       return false; |       return false; | ||||||
|   } |   } | ||||||
| @@ -3817,10 +3837,12 @@ void ListEntitiesNumberResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
|   buffer.encode_float(8, this->step); |   buffer.encode_float(8, this->step); | ||||||
|   buffer.encode_bool(9, this->disabled_by_default); |   buffer.encode_bool(9, this->disabled_by_default); | ||||||
|   buffer.encode_enum<enums::EntityCategory>(10, this->entity_category); |   buffer.encode_enum<enums::EntityCategory>(10, this->entity_category); | ||||||
|  |   buffer.encode_string(11, this->unit_of_measurement); | ||||||
|  |   buffer.encode_enum<enums::NumberMode>(12, this->mode); | ||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void ListEntitiesNumberResponse::dump_to(std::string &out) const { | void ListEntitiesNumberResponse::dump_to(std::string &out) const { | ||||||
|   char buffer[64]; |   __attribute__((unused)) char buffer[64]; | ||||||
|   out.append("ListEntitiesNumberResponse {\n"); |   out.append("ListEntitiesNumberResponse {\n"); | ||||||
|   out.append("  object_id: "); |   out.append("  object_id: "); | ||||||
|   out.append("'").append(this->object_id).append("'"); |   out.append("'").append(this->object_id).append("'"); | ||||||
| @@ -3865,6 +3887,14 @@ void ListEntitiesNumberResponse::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("  unit_of_measurement: "); | ||||||
|  |   out.append("'").append(this->unit_of_measurement).append("'"); | ||||||
|  |   out.append("\n"); | ||||||
|  |  | ||||||
|  |   out.append("  mode: "); | ||||||
|  |   out.append(proto_enum_to_string<enums::NumberMode>(this->mode)); | ||||||
|  |   out.append("\n"); | ||||||
|   out.append("}"); |   out.append("}"); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| @@ -3899,7 +3929,7 @@ void NumberStateResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void NumberStateResponse::dump_to(std::string &out) const { | void NumberStateResponse::dump_to(std::string &out) const { | ||||||
|   char buffer[64]; |   __attribute__((unused)) char buffer[64]; | ||||||
|   out.append("NumberStateResponse {\n"); |   out.append("NumberStateResponse {\n"); | ||||||
|   out.append("  key: "); |   out.append("  key: "); | ||||||
|   sprintf(buffer, "%u", this->key); |   sprintf(buffer, "%u", this->key); | ||||||
| @@ -3937,7 +3967,7 @@ void NumberCommandRequest::encode(ProtoWriteBuffer buffer) const { | |||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void NumberCommandRequest::dump_to(std::string &out) const { | void NumberCommandRequest::dump_to(std::string &out) const { | ||||||
|   char buffer[64]; |   __attribute__((unused)) char buffer[64]; | ||||||
|   out.append("NumberCommandRequest {\n"); |   out.append("NumberCommandRequest {\n"); | ||||||
|   out.append("  key: "); |   out.append("  key: "); | ||||||
|   sprintf(buffer, "%u", this->key); |   sprintf(buffer, "%u", this->key); | ||||||
| @@ -4015,7 +4045,7 @@ void ListEntitiesSelectResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void ListEntitiesSelectResponse::dump_to(std::string &out) const { | void ListEntitiesSelectResponse::dump_to(std::string &out) const { | ||||||
|   char buffer[64]; |   __attribute__((unused)) char buffer[64]; | ||||||
|   out.append("ListEntitiesSelectResponse {\n"); |   out.append("ListEntitiesSelectResponse {\n"); | ||||||
|   out.append("  object_id: "); |   out.append("  object_id: "); | ||||||
|   out.append("'").append(this->object_id).append("'"); |   out.append("'").append(this->object_id).append("'"); | ||||||
| @@ -4091,7 +4121,7 @@ void SelectStateResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void SelectStateResponse::dump_to(std::string &out) const { | void SelectStateResponse::dump_to(std::string &out) const { | ||||||
|   char buffer[64]; |   __attribute__((unused)) char buffer[64]; | ||||||
|   out.append("SelectStateResponse {\n"); |   out.append("SelectStateResponse {\n"); | ||||||
|   out.append("  key: "); |   out.append("  key: "); | ||||||
|   sprintf(buffer, "%u", this->key); |   sprintf(buffer, "%u", this->key); | ||||||
| @@ -4134,7 +4164,7 @@ void SelectCommandRequest::encode(ProtoWriteBuffer buffer) const { | |||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void SelectCommandRequest::dump_to(std::string &out) const { | void SelectCommandRequest::dump_to(std::string &out) const { | ||||||
|   char buffer[64]; |   __attribute__((unused)) char buffer[64]; | ||||||
|   out.append("SelectCommandRequest {\n"); |   out.append("SelectCommandRequest {\n"); | ||||||
|   out.append("  key: "); |   out.append("  key: "); | ||||||
|   sprintf(buffer, "%u", this->key); |   sprintf(buffer, "%u", this->key); | ||||||
| @@ -4147,6 +4177,127 @@ void SelectCommandRequest::dump_to(std::string &out) const { | |||||||
|   out.append("}"); |   out.append("}"); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
|  | bool ListEntitiesButtonResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { | ||||||
|  |   switch (field_id) { | ||||||
|  |     case 6: { | ||||||
|  |       this->disabled_by_default = value.as_bool(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|  |     case 7: { | ||||||
|  |       this->entity_category = value.as_enum<enums::EntityCategory>(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|  |     default: | ||||||
|  |       return false; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | bool ListEntitiesButtonResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { | ||||||
|  |   switch (field_id) { | ||||||
|  |     case 1: { | ||||||
|  |       this->object_id = value.as_string(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|  |     case 3: { | ||||||
|  |       this->name = value.as_string(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|  |     case 4: { | ||||||
|  |       this->unique_id = value.as_string(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|  |     case 5: { | ||||||
|  |       this->icon = value.as_string(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|  |     case 8: { | ||||||
|  |       this->device_class = value.as_string(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|  |     default: | ||||||
|  |       return false; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | bool ListEntitiesButtonResponse::decode_32bit(uint32_t field_id, Proto32Bit value) { | ||||||
|  |   switch (field_id) { | ||||||
|  |     case 2: { | ||||||
|  |       this->key = value.as_fixed32(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|  |     default: | ||||||
|  |       return false; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | void ListEntitiesButtonResponse::encode(ProtoWriteBuffer buffer) const { | ||||||
|  |   buffer.encode_string(1, this->object_id); | ||||||
|  |   buffer.encode_fixed32(2, this->key); | ||||||
|  |   buffer.encode_string(3, this->name); | ||||||
|  |   buffer.encode_string(4, this->unique_id); | ||||||
|  |   buffer.encode_string(5, this->icon); | ||||||
|  |   buffer.encode_bool(6, this->disabled_by_default); | ||||||
|  |   buffer.encode_enum<enums::EntityCategory>(7, this->entity_category); | ||||||
|  |   buffer.encode_string(8, this->device_class); | ||||||
|  | } | ||||||
|  | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|  | void ListEntitiesButtonResponse::dump_to(std::string &out) const { | ||||||
|  |   char buffer[64]; | ||||||
|  |   out.append("ListEntitiesButtonResponse {\n"); | ||||||
|  |   out.append("  object_id: "); | ||||||
|  |   out.append("'").append(this->object_id).append("'"); | ||||||
|  |   out.append("\n"); | ||||||
|  |  | ||||||
|  |   out.append("  key: "); | ||||||
|  |   sprintf(buffer, "%u", this->key); | ||||||
|  |   out.append(buffer); | ||||||
|  |   out.append("\n"); | ||||||
|  |  | ||||||
|  |   out.append("  name: "); | ||||||
|  |   out.append("'").append(this->name).append("'"); | ||||||
|  |   out.append("\n"); | ||||||
|  |  | ||||||
|  |   out.append("  unique_id: "); | ||||||
|  |   out.append("'").append(this->unique_id).append("'"); | ||||||
|  |   out.append("\n"); | ||||||
|  |  | ||||||
|  |   out.append("  icon: "); | ||||||
|  |   out.append("'").append(this->icon).append("'"); | ||||||
|  |   out.append("\n"); | ||||||
|  |  | ||||||
|  |   out.append("  disabled_by_default: "); | ||||||
|  |   out.append(YESNO(this->disabled_by_default)); | ||||||
|  |   out.append("\n"); | ||||||
|  |  | ||||||
|  |   out.append("  entity_category: "); | ||||||
|  |   out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category)); | ||||||
|  |   out.append("\n"); | ||||||
|  |  | ||||||
|  |   out.append("  device_class: "); | ||||||
|  |   out.append("'").append(this->device_class).append("'"); | ||||||
|  |   out.append("\n"); | ||||||
|  |   out.append("}"); | ||||||
|  | } | ||||||
|  | #endif | ||||||
|  | bool ButtonCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) { | ||||||
|  |   switch (field_id) { | ||||||
|  |     case 1: { | ||||||
|  |       this->key = value.as_fixed32(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|  |     default: | ||||||
|  |       return false; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | void ButtonCommandRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_fixed32(1, this->key); } | ||||||
|  | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|  | void ButtonCommandRequest::dump_to(std::string &out) const { | ||||||
|  |   char buffer[64]; | ||||||
|  |   out.append("ButtonCommandRequest {\n"); | ||||||
|  |   out.append("  key: "); | ||||||
|  |   sprintf(buffer, "%u", this->key); | ||||||
|  |   out.append(buffer); | ||||||
|  |   out.append("\n"); | ||||||
|  |   out.append("}"); | ||||||
|  | } | ||||||
|  | #endif | ||||||
|  |  | ||||||
| }  // namespace api | }  // namespace api | ||||||
| }  // namespace esphome | }  // namespace esphome | ||||||
|   | |||||||
| @@ -123,6 +123,11 @@ enum ClimatePreset : uint32_t { | |||||||
|   CLIMATE_PRESET_SLEEP = 6, |   CLIMATE_PRESET_SLEEP = 6, | ||||||
|   CLIMATE_PRESET_ACTIVITY = 7, |   CLIMATE_PRESET_ACTIVITY = 7, | ||||||
| }; | }; | ||||||
|  | enum NumberMode : uint32_t { | ||||||
|  |   NUMBER_MODE_AUTO = 0, | ||||||
|  |   NUMBER_MODE_BOX = 1, | ||||||
|  |   NUMBER_MODE_SLIDER = 2, | ||||||
|  | }; | ||||||
|  |  | ||||||
| }  // namespace enums | }  // namespace enums | ||||||
|  |  | ||||||
| @@ -957,6 +962,8 @@ class ListEntitiesNumberResponse : public ProtoMessage { | |||||||
|   float step{0.0f}; |   float step{0.0f}; | ||||||
|   bool disabled_by_default{false}; |   bool disabled_by_default{false}; | ||||||
|   enums::EntityCategory entity_category{}; |   enums::EntityCategory entity_category{}; | ||||||
|  |   std::string unit_of_measurement{}; | ||||||
|  |   enums::NumberMode mode{}; | ||||||
|   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; | ||||||
| @@ -1041,6 +1048,37 @@ class SelectCommandRequest : public ProtoMessage { | |||||||
|   bool decode_32bit(uint32_t field_id, Proto32Bit value) override; |   bool decode_32bit(uint32_t field_id, Proto32Bit value) override; | ||||||
|   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; |   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; | ||||||
| }; | }; | ||||||
|  | class ListEntitiesButtonResponse : public ProtoMessage { | ||||||
|  |  public: | ||||||
|  |   std::string object_id{}; | ||||||
|  |   uint32_t key{0}; | ||||||
|  |   std::string name{}; | ||||||
|  |   std::string unique_id{}; | ||||||
|  |   std::string icon{}; | ||||||
|  |   bool disabled_by_default{false}; | ||||||
|  |   enums::EntityCategory entity_category{}; | ||||||
|  |   std::string device_class{}; | ||||||
|  |   void encode(ProtoWriteBuffer buffer) const override; | ||||||
|  | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|  |   void dump_to(std::string &out) const override; | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   bool decode_32bit(uint32_t field_id, Proto32Bit value) override; | ||||||
|  |   bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; | ||||||
|  |   bool decode_varint(uint32_t field_id, ProtoVarInt value) override; | ||||||
|  | }; | ||||||
|  | class ButtonCommandRequest : public ProtoMessage { | ||||||
|  |  public: | ||||||
|  |   uint32_t key{0}; | ||||||
|  |   void encode(ProtoWriteBuffer buffer) const override; | ||||||
|  | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|  |   void dump_to(std::string &out) const override; | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   bool decode_32bit(uint32_t field_id, Proto32Bit value) override; | ||||||
|  | }; | ||||||
|  |  | ||||||
| }  // namespace api | }  // namespace api | ||||||
| }  // namespace esphome | }  // namespace esphome | ||||||
|   | |||||||
| @@ -282,6 +282,16 @@ bool APIServerConnectionBase::send_select_state_response(const SelectStateRespon | |||||||
| #endif | #endif | ||||||
| #ifdef USE_SELECT | #ifdef USE_SELECT | ||||||
| #endif | #endif | ||||||
|  | #ifdef USE_BUTTON | ||||||
|  | bool APIServerConnectionBase::send_list_entities_button_response(const ListEntitiesButtonResponse &msg) { | ||||||
|  | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|  |   ESP_LOGVV(TAG, "send_list_entities_button_response: %s", msg.dump().c_str()); | ||||||
|  | #endif | ||||||
|  |   return this->send_message_<ListEntitiesButtonResponse>(msg, 61); | ||||||
|  | } | ||||||
|  | #endif | ||||||
|  | #ifdef USE_BUTTON | ||||||
|  | #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: { | ||||||
| @@ -513,6 +523,17 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, | |||||||
|       ESP_LOGVV(TAG, "on_select_command_request: %s", msg.dump().c_str()); |       ESP_LOGVV(TAG, "on_select_command_request: %s", msg.dump().c_str()); | ||||||
| #endif | #endif | ||||||
|       this->on_select_command_request(msg); |       this->on_select_command_request(msg); | ||||||
|  | #endif | ||||||
|  |       break; | ||||||
|  |     } | ||||||
|  |     case 62: { | ||||||
|  | #ifdef USE_BUTTON | ||||||
|  |       ButtonCommandRequest msg; | ||||||
|  |       msg.decode(msg_data, msg_size); | ||||||
|  | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|  |       ESP_LOGVV(TAG, "on_button_command_request: %s", msg.dump().c_str()); | ||||||
|  | #endif | ||||||
|  |       this->on_button_command_request(msg); | ||||||
| #endif | #endif | ||||||
|       break; |       break; | ||||||
|     } |     } | ||||||
| @@ -737,6 +758,19 @@ void APIServerConnection::on_select_command_request(const SelectCommandRequest & | |||||||
|   this->select_command(msg); |   this->select_command(msg); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
|  | #ifdef USE_BUTTON | ||||||
|  | void APIServerConnection::on_button_command_request(const ButtonCommandRequest &msg) { | ||||||
|  |   if (!this->is_connection_setup()) { | ||||||
|  |     this->on_no_setup_connection(); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   if (!this->is_authenticated()) { | ||||||
|  |     this->on_unauthenticated_access(); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   this->button_command(msg); | ||||||
|  | } | ||||||
|  | #endif | ||||||
|  |  | ||||||
| }  // namespace api | }  // namespace api | ||||||
| }  // namespace esphome | }  // namespace esphome | ||||||
|   | |||||||
| @@ -129,6 +129,12 @@ class APIServerConnectionBase : public ProtoService { | |||||||
| #endif | #endif | ||||||
| #ifdef USE_SELECT | #ifdef USE_SELECT | ||||||
|   virtual void on_select_command_request(const SelectCommandRequest &value){}; |   virtual void on_select_command_request(const SelectCommandRequest &value){}; | ||||||
|  | #endif | ||||||
|  | #ifdef USE_BUTTON | ||||||
|  |   bool send_list_entities_button_response(const ListEntitiesButtonResponse &msg); | ||||||
|  | #endif | ||||||
|  | #ifdef USE_BUTTON | ||||||
|  |   virtual void on_button_command_request(const ButtonCommandRequest &value){}; | ||||||
| #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; | ||||||
| @@ -171,6 +177,9 @@ class APIServerConnection : public APIServerConnectionBase { | |||||||
| #endif | #endif | ||||||
| #ifdef USE_SELECT | #ifdef USE_SELECT | ||||||
|   virtual void select_command(const SelectCommandRequest &msg) = 0; |   virtual void select_command(const SelectCommandRequest &msg) = 0; | ||||||
|  | #endif | ||||||
|  | #ifdef USE_BUTTON | ||||||
|  |   virtual void button_command(const ButtonCommandRequest &msg) = 0; | ||||||
| #endif | #endif | ||||||
|  protected: |  protected: | ||||||
|   void on_hello_request(const HelloRequest &msg) override; |   void on_hello_request(const HelloRequest &msg) override; | ||||||
| @@ -209,6 +218,9 @@ class APIServerConnection : public APIServerConnectionBase { | |||||||
| #ifdef USE_SELECT | #ifdef USE_SELECT | ||||||
|   void on_select_command_request(const SelectCommandRequest &msg) override; |   void on_select_command_request(const SelectCommandRequest &msg) override; | ||||||
| #endif | #endif | ||||||
|  | #ifdef USE_BUTTON | ||||||
|  |   void on_button_command_request(const ButtonCommandRequest &msg) override; | ||||||
|  | #endif | ||||||
| }; | }; | ||||||
|  |  | ||||||
| }  // namespace api | }  // namespace api | ||||||
|   | |||||||
| @@ -27,6 +27,9 @@ bool ListEntitiesIterator::on_sensor(sensor::Sensor *sensor) { return this->clie | |||||||
| #ifdef USE_SWITCH | #ifdef USE_SWITCH | ||||||
| bool ListEntitiesIterator::on_switch(switch_::Switch *a_switch) { return this->client_->send_switch_info(a_switch); } | bool ListEntitiesIterator::on_switch(switch_::Switch *a_switch) { return this->client_->send_switch_info(a_switch); } | ||||||
| #endif | #endif | ||||||
|  | #ifdef USE_BUTTON | ||||||
|  | bool ListEntitiesIterator::on_button(button::Button *button) { return this->client_->send_button_info(button); } | ||||||
|  | #endif | ||||||
| #ifdef USE_TEXT_SENSOR | #ifdef USE_TEXT_SENSOR | ||||||
| bool ListEntitiesIterator::on_text_sensor(text_sensor::TextSensor *text_sensor) { | bool ListEntitiesIterator::on_text_sensor(text_sensor::TextSensor *text_sensor) { | ||||||
|   return this->client_->send_text_sensor_info(text_sensor); |   return this->client_->send_text_sensor_info(text_sensor); | ||||||
|   | |||||||
| @@ -30,6 +30,9 @@ class ListEntitiesIterator : public ComponentIterator { | |||||||
| #ifdef USE_SWITCH | #ifdef USE_SWITCH | ||||||
|   bool on_switch(switch_::Switch *a_switch) override; |   bool on_switch(switch_::Switch *a_switch) override; | ||||||
| #endif | #endif | ||||||
|  | #ifdef USE_BUTTON | ||||||
|  |   bool on_button(button::Button *button) override; | ||||||
|  | #endif | ||||||
| #ifdef USE_TEXT_SENSOR | #ifdef USE_TEXT_SENSOR | ||||||
|   bool on_text_sensor(text_sensor::TextSensor *text_sensor) override; |   bool on_text_sensor(text_sensor::TextSensor *text_sensor) override; | ||||||
| #endif | #endif | ||||||
|   | |||||||
| @@ -31,6 +31,9 @@ class InitialStateIterator : public ComponentIterator { | |||||||
| #ifdef USE_SWITCH | #ifdef USE_SWITCH | ||||||
|   bool on_switch(switch_::Switch *a_switch) override; |   bool on_switch(switch_::Switch *a_switch) override; | ||||||
| #endif | #endif | ||||||
|  | #ifdef USE_BUTTON | ||||||
|  |   bool on_button(button::Button *button) override { return true; }; | ||||||
|  | #endif | ||||||
| #ifdef USE_TEXT_SENSOR | #ifdef USE_TEXT_SENSOR | ||||||
|   bool on_text_sensor(text_sensor::TextSensor *text_sensor) override; |   bool on_text_sensor(text_sensor::TextSensor *text_sensor) override; | ||||||
| #endif | #endif | ||||||
|   | |||||||
| @@ -116,6 +116,21 @@ void ComponentIterator::advance() { | |||||||
|       } |       } | ||||||
|       break; |       break; | ||||||
| #endif | #endif | ||||||
|  | #ifdef USE_BUTTON | ||||||
|  |     case IteratorState::BUTTON: | ||||||
|  |       if (this->at_ >= App.get_buttons().size()) { | ||||||
|  |         advance_platform = true; | ||||||
|  |       } else { | ||||||
|  |         auto *button = App.get_buttons()[this->at_]; | ||||||
|  |         if (button->is_internal()) { | ||||||
|  |           success = true; | ||||||
|  |           break; | ||||||
|  |         } else { | ||||||
|  |           success = this->on_button(button); | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |       break; | ||||||
|  | #endif | ||||||
| #ifdef USE_TEXT_SENSOR | #ifdef USE_TEXT_SENSOR | ||||||
|     case IteratorState::TEXT_SENSOR: |     case IteratorState::TEXT_SENSOR: | ||||||
|       if (this->at_ >= App.get_text_sensors().size()) { |       if (this->at_ >= App.get_text_sensors().size()) { | ||||||
|   | |||||||
| @@ -38,6 +38,9 @@ class ComponentIterator { | |||||||
| #ifdef USE_SWITCH | #ifdef USE_SWITCH | ||||||
|   virtual bool on_switch(switch_::Switch *a_switch) = 0; |   virtual bool on_switch(switch_::Switch *a_switch) = 0; | ||||||
| #endif | #endif | ||||||
|  | #ifdef USE_BUTTON | ||||||
|  |   virtual bool on_button(button::Button *button) = 0; | ||||||
|  | #endif | ||||||
| #ifdef USE_TEXT_SENSOR | #ifdef USE_TEXT_SENSOR | ||||||
|   virtual bool on_text_sensor(text_sensor::TextSensor *text_sensor) = 0; |   virtual bool on_text_sensor(text_sensor::TextSensor *text_sensor) = 0; | ||||||
| #endif | #endif | ||||||
| @@ -78,6 +81,9 @@ class ComponentIterator { | |||||||
| #ifdef USE_SWITCH | #ifdef USE_SWITCH | ||||||
|     SWITCH, |     SWITCH, | ||||||
| #endif | #endif | ||||||
|  | #ifdef USE_BUTTON | ||||||
|  |     BUTTON, | ||||||
|  | #endif | ||||||
| #ifdef USE_TEXT_SENSOR | #ifdef USE_TEXT_SENSOR | ||||||
|     TEXT_SENSOR, |     TEXT_SENSOR, | ||||||
| #endif | #endif | ||||||
|   | |||||||
| @@ -388,6 +388,15 @@ BLEDescriptor *BLECharacteristic::get_descriptor(uint16_t uuid) { | |||||||
|   return this->get_descriptor(espbt::ESPBTUUID::from_uint16(uuid)); |   return this->get_descriptor(espbt::ESPBTUUID::from_uint16(uuid)); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | void BLECharacteristic::write_value(uint8_t *new_val, int16_t new_val_size) { | ||||||
|  |   auto client = this->service->client; | ||||||
|  |   auto status = esp_ble_gattc_write_char(client->gattc_if, client->conn_id, this->handle, new_val_size, new_val, | ||||||
|  |                                          ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); | ||||||
|  |   if (status) { | ||||||
|  |     ESP_LOGW(TAG, "Error sending write value to BLE gattc server, status=%d", status); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
| }  // namespace ble_client | }  // namespace ble_client | ||||||
| }  // namespace esphome | }  // namespace esphome | ||||||
|  |  | ||||||
|   | |||||||
| @@ -59,7 +59,7 @@ class BLECharacteristic { | |||||||
|   void parse_descriptors(); |   void parse_descriptors(); | ||||||
|   BLEDescriptor *get_descriptor(espbt::ESPBTUUID uuid); |   BLEDescriptor *get_descriptor(espbt::ESPBTUUID uuid); | ||||||
|   BLEDescriptor *get_descriptor(uint16_t uuid); |   BLEDescriptor *get_descriptor(uint16_t uuid); | ||||||
|  |   void write_value(uint8_t *new_val, int16_t new_val_size); | ||||||
|   BLEService *service; |   BLEService *service; | ||||||
| }; | }; | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										67
									
								
								esphome/components/ble_client/output/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								esphome/components/ble_client/output/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,67 @@ | |||||||
|  | import esphome.codegen as cg | ||||||
|  | import esphome.config_validation as cv | ||||||
|  | from esphome.components import output, ble_client, esp32_ble_tracker | ||||||
|  | from esphome.const import CONF_ID, CONF_SERVICE_UUID | ||||||
|  | from .. import ble_client_ns | ||||||
|  |  | ||||||
|  |  | ||||||
|  | DEPENDENCIES = ["ble_client"] | ||||||
|  |  | ||||||
|  | CONF_CHARACTERISTIC_UUID = "characteristic_uuid" | ||||||
|  |  | ||||||
|  | BLEBinaryOutput = ble_client_ns.class_( | ||||||
|  |     "BLEBinaryOutput", output.BinaryOutput, ble_client.BLEClientNode, cg.Component | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | CONFIG_SCHEMA = cv.All( | ||||||
|  |     output.BINARY_OUTPUT_SCHEMA.extend( | ||||||
|  |         { | ||||||
|  |             cv.Required(CONF_ID): cv.declare_id(BLEBinaryOutput), | ||||||
|  |             cv.Required(CONF_SERVICE_UUID): esp32_ble_tracker.bt_uuid, | ||||||
|  |             cv.Required(CONF_CHARACTERISTIC_UUID): esp32_ble_tracker.bt_uuid, | ||||||
|  |         } | ||||||
|  |     ) | ||||||
|  |     .extend(cv.COMPONENT_SCHEMA) | ||||||
|  |     .extend(ble_client.BLE_CLIENT_SCHEMA) | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def to_code(config): | ||||||
|  |     var = cg.new_Pvariable(config[CONF_ID]) | ||||||
|  |     if len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid16_format): | ||||||
|  |         cg.add( | ||||||
|  |             var.set_service_uuid16(esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID])) | ||||||
|  |         ) | ||||||
|  |     elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid32_format): | ||||||
|  |         cg.add( | ||||||
|  |             var.set_service_uuid32(esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID])) | ||||||
|  |         ) | ||||||
|  |     elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid128_format): | ||||||
|  |         uuid128 = esp32_ble_tracker.as_reversed_hex_array(config[CONF_SERVICE_UUID]) | ||||||
|  |         cg.add(var.set_service_uuid128(uuid128)) | ||||||
|  |  | ||||||
|  |     if len(config[CONF_CHARACTERISTIC_UUID]) == len(esp32_ble_tracker.bt_uuid16_format): | ||||||
|  |         cg.add( | ||||||
|  |             var.set_char_uuid16( | ||||||
|  |                 esp32_ble_tracker.as_hex(config[CONF_CHARACTERISTIC_UUID]) | ||||||
|  |             ) | ||||||
|  |         ) | ||||||
|  |     elif len(config[CONF_CHARACTERISTIC_UUID]) == len( | ||||||
|  |         esp32_ble_tracker.bt_uuid32_format | ||||||
|  |     ): | ||||||
|  |         cg.add( | ||||||
|  |             var.set_char_uuid32( | ||||||
|  |                 esp32_ble_tracker.as_hex(config[CONF_CHARACTERISTIC_UUID]) | ||||||
|  |             ) | ||||||
|  |         ) | ||||||
|  |     elif len(config[CONF_CHARACTERISTIC_UUID]) == len( | ||||||
|  |         esp32_ble_tracker.bt_uuid128_format | ||||||
|  |     ): | ||||||
|  |         uuid128 = esp32_ble_tracker.as_reversed_hex_array( | ||||||
|  |             config[CONF_CHARACTERISTIC_UUID] | ||||||
|  |         ) | ||||||
|  |         cg.add(var.set_char_uuid128(uuid128)) | ||||||
|  |  | ||||||
|  |     yield output.register_output(var, config) | ||||||
|  |     yield ble_client.register_ble_node(var, config) | ||||||
|  |     yield cg.register_component(var, config) | ||||||
							
								
								
									
										71
									
								
								esphome/components/ble_client/output/ble_binary_output.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								esphome/components/ble_client/output/ble_binary_output.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,71 @@ | |||||||
|  | #include "ble_binary_output.h" | ||||||
|  | #include "esphome/core/log.h" | ||||||
|  | #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" | ||||||
|  |  | ||||||
|  | #ifdef USE_ESP32 | ||||||
|  | namespace esphome { | ||||||
|  | namespace ble_client { | ||||||
|  |  | ||||||
|  | static const char *const TAG = "ble_binary_output"; | ||||||
|  |  | ||||||
|  | void BLEBinaryOutput::dump_config() { | ||||||
|  |   ESP_LOGCONFIG(TAG, "BLE Binary Output:"); | ||||||
|  |   ESP_LOGCONFIG(TAG, "  MAC address        : %s", this->parent_->address_str().c_str()); | ||||||
|  |   ESP_LOGCONFIG(TAG, "  Service UUID       : %s", this->service_uuid_.to_string().c_str()); | ||||||
|  |   ESP_LOGCONFIG(TAG, "  Characteristic UUID: %s", this->char_uuid_.to_string().c_str()); | ||||||
|  |   LOG_BINARY_OUTPUT(this); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void BLEBinaryOutput::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, | ||||||
|  |                                           esp_ble_gattc_cb_param_t *param) { | ||||||
|  |   switch (event) { | ||||||
|  |     case ESP_GATTC_OPEN_EVT: | ||||||
|  |       this->client_state_ = espbt::ClientState::ESTABLISHED; | ||||||
|  |       ESP_LOGW(TAG, "[%s] Connected successfully!", this->char_uuid_.to_string().c_str()); | ||||||
|  |       break; | ||||||
|  |     case ESP_GATTC_DISCONNECT_EVT: | ||||||
|  |       ESP_LOGW(TAG, "[%s] Disconnected", this->char_uuid_.to_string().c_str()); | ||||||
|  |       this->client_state_ = espbt::ClientState::IDLE; | ||||||
|  |       break; | ||||||
|  |     case ESP_GATTC_WRITE_CHAR_EVT: { | ||||||
|  |       if (param->write.status == 0) { | ||||||
|  |         break; | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       auto chr = this->parent()->get_characteristic(this->service_uuid_, this->char_uuid_); | ||||||
|  |       if (chr == nullptr) { | ||||||
|  |         ESP_LOGW(TAG, "[%s] Characteristic not found.", this->char_uuid_.to_string().c_str()); | ||||||
|  |         break; | ||||||
|  |       } | ||||||
|  |       if (param->write.handle == chr->handle) { | ||||||
|  |         ESP_LOGW(TAG, "[%s] Write error, status=%d", this->char_uuid_.to_string().c_str(), param->write.status); | ||||||
|  |       } | ||||||
|  |       break; | ||||||
|  |     } | ||||||
|  |     default: | ||||||
|  |       break; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void BLEBinaryOutput::write_state(bool state) { | ||||||
|  |   if (this->client_state_ != espbt::ClientState::ESTABLISHED) { | ||||||
|  |     ESP_LOGW(TAG, "[%s] Not connected to BLE client.  State update can not be written.", | ||||||
|  |              this->char_uuid_.to_string().c_str()); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   auto chr = this->parent()->get_characteristic(this->service_uuid_, this->char_uuid_); | ||||||
|  |   if (chr == nullptr) { | ||||||
|  |     ESP_LOGW(TAG, "[%s] Characteristic not found.  State update can not be written.", | ||||||
|  |              this->char_uuid_.to_string().c_str()); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   uint8_t state_as_uint = (uint8_t) state; | ||||||
|  |   ESP_LOGV(TAG, "[%s] Write State: %d", this->char_uuid_.to_string().c_str(), state_as_uint); | ||||||
|  |   chr->write_value(&state_as_uint, sizeof(state_as_uint)); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | }  // namespace ble_client | ||||||
|  | }  // namespace esphome | ||||||
|  | #endif | ||||||
							
								
								
									
										39
									
								
								esphome/components/ble_client/output/ble_binary_output.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								esphome/components/ble_client/output/ble_binary_output.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,39 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include "esphome/core/component.h" | ||||||
|  | #include "esphome/components/ble_client/ble_client.h" | ||||||
|  | #include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" | ||||||
|  | #include "esphome/components/output/binary_output.h" | ||||||
|  |  | ||||||
|  | #ifdef USE_ESP32 | ||||||
|  | #include <esp_gattc_api.h> | ||||||
|  | namespace esphome { | ||||||
|  | namespace ble_client { | ||||||
|  |  | ||||||
|  | namespace espbt = esphome::esp32_ble_tracker; | ||||||
|  |  | ||||||
|  | class BLEBinaryOutput : public output::BinaryOutput, public BLEClientNode, public Component { | ||||||
|  |  public: | ||||||
|  |   void dump_config() override; | ||||||
|  |   void loop() override {} | ||||||
|  |   float get_setup_priority() const override { return setup_priority::DATA; } | ||||||
|  |   void set_service_uuid16(uint16_t uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_uint16(uuid); } | ||||||
|  |   void set_service_uuid32(uint32_t uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_uint32(uuid); } | ||||||
|  |   void set_service_uuid128(uint8_t *uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_raw(uuid); } | ||||||
|  |   void set_char_uuid16(uint16_t uuid) { this->char_uuid_ = espbt::ESPBTUUID::from_uint16(uuid); } | ||||||
|  |   void set_char_uuid32(uint32_t uuid) { this->char_uuid_ = espbt::ESPBTUUID::from_uint32(uuid); } | ||||||
|  |   void set_char_uuid128(uint8_t *uuid) { this->char_uuid_ = espbt::ESPBTUUID::from_raw(uuid); } | ||||||
|  |   void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, | ||||||
|  |                            esp_ble_gattc_cb_param_t *param) override; | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   void write_state(bool state) override; | ||||||
|  |   espbt::ESPBTUUID service_uuid_; | ||||||
|  |   espbt::ESPBTUUID char_uuid_; | ||||||
|  |   espbt::ClientState client_state_; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | }  // namespace ble_client | ||||||
|  | }  // namespace esphome | ||||||
|  |  | ||||||
|  | #endif | ||||||
| @@ -33,6 +33,7 @@ static const uint8_t BME280_REGISTER_CONTROLHUMID = 0xF2; | |||||||
| static const uint8_t BME280_REGISTER_STATUS = 0xF3; | static const uint8_t BME280_REGISTER_STATUS = 0xF3; | ||||||
| static const uint8_t BME280_REGISTER_CONTROL = 0xF4; | static const uint8_t BME280_REGISTER_CONTROL = 0xF4; | ||||||
| static const uint8_t BME280_REGISTER_CONFIG = 0xF5; | static const uint8_t BME280_REGISTER_CONFIG = 0xF5; | ||||||
|  | static const uint8_t BME280_REGISTER_MEASUREMENTS = 0xF7; | ||||||
| static const uint8_t BME280_REGISTER_PRESSUREDATA = 0xF7; | static const uint8_t BME280_REGISTER_PRESSUREDATA = 0xF7; | ||||||
| static const uint8_t BME280_REGISTER_TEMPDATA = 0xFA; | static const uint8_t BME280_REGISTER_TEMPDATA = 0xFA; | ||||||
| static const uint8_t BME280_REGISTER_HUMIDDATA = 0xFD; | static const uint8_t BME280_REGISTER_HUMIDDATA = 0xFD; | ||||||
| @@ -178,21 +179,27 @@ void BME280Component::update() { | |||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   float meas_time = 1.5; |   float meas_time = 1.5f; | ||||||
|   meas_time += 2.3f * oversampling_to_time(this->temperature_oversampling_); |   meas_time += 2.3f * oversampling_to_time(this->temperature_oversampling_); | ||||||
|   meas_time += 2.3f * oversampling_to_time(this->pressure_oversampling_) + 0.575f; |   meas_time += 2.3f * oversampling_to_time(this->pressure_oversampling_) + 0.575f; | ||||||
|   meas_time += 2.3f * oversampling_to_time(this->humidity_oversampling_) + 0.575f; |   meas_time += 2.3f * oversampling_to_time(this->humidity_oversampling_) + 0.575f; | ||||||
|  |  | ||||||
|   this->set_timeout("data", uint32_t(ceilf(meas_time)), [this]() { |   this->set_timeout("data", uint32_t(ceilf(meas_time)), [this]() { | ||||||
|  |     uint8_t data[8]; | ||||||
|  |     if (!this->read_bytes(BME280_REGISTER_MEASUREMENTS, data, 8)) { | ||||||
|  |       ESP_LOGW(TAG, "Error reading registers."); | ||||||
|  |       this->status_set_warning(); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|     int32_t t_fine = 0; |     int32_t t_fine = 0; | ||||||
|     float temperature = this->read_temperature_(&t_fine); |     float temperature = this->read_temperature_(data, &t_fine); | ||||||
|     if (std::isnan(temperature)) { |     if (std::isnan(temperature)) { | ||||||
|       ESP_LOGW(TAG, "Invalid temperature, cannot read pressure & humidity values."); |       ESP_LOGW(TAG, "Invalid temperature, cannot read pressure & humidity values."); | ||||||
|       this->status_set_warning(); |       this->status_set_warning(); | ||||||
|       return; |       return; | ||||||
|     } |     } | ||||||
|     float pressure = this->read_pressure_(t_fine); |     float pressure = this->read_pressure_(data, t_fine); | ||||||
|     float humidity = this->read_humidity_(t_fine); |     float humidity = this->read_humidity_(data, t_fine); | ||||||
|  |  | ||||||
|     ESP_LOGD(TAG, "Got temperature=%.1f°C pressure=%.1fhPa humidity=%.1f%%", temperature, pressure, humidity); |     ESP_LOGD(TAG, "Got temperature=%.1f°C pressure=%.1fhPa humidity=%.1f%%", temperature, pressure, humidity); | ||||||
|     if (this->temperature_sensor_ != nullptr) |     if (this->temperature_sensor_ != nullptr) | ||||||
| @@ -204,11 +211,8 @@ void BME280Component::update() { | |||||||
|     this->status_clear_warning(); |     this->status_clear_warning(); | ||||||
|   }); |   }); | ||||||
| } | } | ||||||
| float BME280Component::read_temperature_(int32_t *t_fine) { | float BME280Component::read_temperature_(const uint8_t *data, int32_t *t_fine) { | ||||||
|   uint8_t data[3]; |   int32_t adc = ((data[3] & 0xFF) << 16) | ((data[4] & 0xFF) << 8) | (data[5] & 0xFF); | ||||||
|   if (!this->read_bytes(BME280_REGISTER_TEMPDATA, data, 3)) |  | ||||||
|     return NAN; |  | ||||||
|   int32_t adc = ((data[0] & 0xFF) << 16) | ((data[1] & 0xFF) << 8) | (data[2] & 0xFF); |  | ||||||
|   adc >>= 4; |   adc >>= 4; | ||||||
|   if (adc == 0x80000) |   if (adc == 0x80000) | ||||||
|     // temperature was disabled |     // temperature was disabled | ||||||
| @@ -226,10 +230,7 @@ float BME280Component::read_temperature_(int32_t *t_fine) { | |||||||
|   return temperature / 100.0f; |   return temperature / 100.0f; | ||||||
| } | } | ||||||
|  |  | ||||||
| float BME280Component::read_pressure_(int32_t t_fine) { | float BME280Component::read_pressure_(const uint8_t *data, int32_t t_fine) { | ||||||
|   uint8_t data[3]; |  | ||||||
|   if (!this->read_bytes(BME280_REGISTER_PRESSUREDATA, data, 3)) |  | ||||||
|     return NAN; |  | ||||||
|   int32_t adc = ((data[0] & 0xFF) << 16) | ((data[1] & 0xFF) << 8) | (data[2] & 0xFF); |   int32_t adc = ((data[0] & 0xFF) << 16) | ((data[1] & 0xFF) << 8) | (data[2] & 0xFF); | ||||||
|   adc >>= 4; |   adc >>= 4; | ||||||
|   if (adc == 0x80000) |   if (adc == 0x80000) | ||||||
| @@ -265,9 +266,9 @@ float BME280Component::read_pressure_(int32_t t_fine) { | |||||||
|   return (p / 256.0f) / 100.0f; |   return (p / 256.0f) / 100.0f; | ||||||
| } | } | ||||||
|  |  | ||||||
| float BME280Component::read_humidity_(int32_t t_fine) { | float BME280Component::read_humidity_(const uint8_t *data, int32_t t_fine) { | ||||||
|   uint16_t raw_adc; |   uint16_t raw_adc = ((data[6] & 0xFF) << 8) | (data[7] & 0xFF); | ||||||
|   if (!this->read_byte_16(BME280_REGISTER_HUMIDDATA, &raw_adc) || raw_adc == 0x8000) |   if (raw_adc == 0x8000) | ||||||
|     return NAN; |     return NAN; | ||||||
|  |  | ||||||
|   int32_t adc = raw_adc; |   int32_t adc = raw_adc; | ||||||
|   | |||||||
| @@ -82,11 +82,11 @@ class BME280Component : public PollingComponent, public i2c::I2CDevice { | |||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   /// Read the temperature value and store the calculated ambient temperature in t_fine. |   /// Read the temperature value and store the calculated ambient temperature in t_fine. | ||||||
|   float read_temperature_(int32_t *t_fine); |   float read_temperature_(const uint8_t *data, int32_t *t_fine); | ||||||
|   /// Read the pressure value in hPa using the provided t_fine value. |   /// Read the pressure value in hPa using the provided t_fine value. | ||||||
|   float read_pressure_(int32_t t_fine); |   float read_pressure_(const uint8_t *data, int32_t t_fine); | ||||||
|   /// Read the humidity value in % using the provided t_fine value. |   /// Read the humidity value in % using the provided t_fine value. | ||||||
|   float read_humidity_(int32_t t_fine); |   float read_humidity_(const uint8_t *data, int32_t t_fine); | ||||||
|   uint8_t read_u8_(uint8_t a_register); |   uint8_t read_u8_(uint8_t a_register); | ||||||
|   uint16_t read_u16_le_(uint8_t a_register); |   uint16_t read_u16_le_(uint8_t a_register); | ||||||
|   int16_t read_s16_le_(uint8_t a_register); |   int16_t read_s16_le_(uint8_t a_register); | ||||||
|   | |||||||
							
								
								
									
										127
									
								
								esphome/components/button/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										127
									
								
								esphome/components/button/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,127 @@ | |||||||
|  | import esphome.codegen as cg | ||||||
|  | import esphome.config_validation as cv | ||||||
|  | from esphome import automation | ||||||
|  | from esphome.automation import maybe_simple_id | ||||||
|  | from esphome.components import mqtt | ||||||
|  | from esphome.const import ( | ||||||
|  |     CONF_DEVICE_CLASS, | ||||||
|  |     CONF_ENTITY_CATEGORY, | ||||||
|  |     CONF_ICON, | ||||||
|  |     CONF_ID, | ||||||
|  |     CONF_ON_PRESS, | ||||||
|  |     CONF_TRIGGER_ID, | ||||||
|  |     CONF_MQTT_ID, | ||||||
|  |     DEVICE_CLASS_RESTART, | ||||||
|  |     DEVICE_CLASS_UPDATE, | ||||||
|  | ) | ||||||
|  | from esphome.core import CORE, coroutine_with_priority | ||||||
|  | from esphome.cpp_helpers import setup_entity | ||||||
|  |  | ||||||
|  | CODEOWNERS = ["@esphome/core"] | ||||||
|  | IS_PLATFORM_COMPONENT = True | ||||||
|  |  | ||||||
|  | DEVICE_CLASSES = [ | ||||||
|  |     DEVICE_CLASS_RESTART, | ||||||
|  |     DEVICE_CLASS_UPDATE, | ||||||
|  | ] | ||||||
|  |  | ||||||
|  | button_ns = cg.esphome_ns.namespace("button") | ||||||
|  | Button = button_ns.class_("Button", cg.EntityBase) | ||||||
|  | ButtonPtr = Button.operator("ptr") | ||||||
|  |  | ||||||
|  | PressAction = button_ns.class_("PressAction", automation.Action) | ||||||
|  |  | ||||||
|  | ButtonPressTrigger = button_ns.class_( | ||||||
|  |     "ButtonPressTrigger", automation.Trigger.template() | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | validate_device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space="_") | ||||||
|  |  | ||||||
|  |  | ||||||
|  | BUTTON_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend( | ||||||
|  |     { | ||||||
|  |         cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTButtonComponent), | ||||||
|  |         cv.Optional(CONF_DEVICE_CLASS): validate_device_class, | ||||||
|  |         cv.Optional(CONF_ON_PRESS): automation.validate_automation( | ||||||
|  |             { | ||||||
|  |                 cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ButtonPressTrigger), | ||||||
|  |             } | ||||||
|  |         ), | ||||||
|  |     } | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | _UNDEF = object() | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def button_schema( | ||||||
|  |     icon: str = _UNDEF, | ||||||
|  |     entity_category: str = _UNDEF, | ||||||
|  |     device_class: str = _UNDEF, | ||||||
|  | ) -> cv.Schema: | ||||||
|  |     schema = BUTTON_SCHEMA | ||||||
|  |     if icon is not _UNDEF: | ||||||
|  |         schema = schema.extend({cv.Optional(CONF_ICON, default=icon): cv.icon}) | ||||||
|  |     if entity_category is not _UNDEF: | ||||||
|  |         schema = schema.extend( | ||||||
|  |             { | ||||||
|  |                 cv.Optional( | ||||||
|  |                     CONF_ENTITY_CATEGORY, default=entity_category | ||||||
|  |                 ): cv.entity_category | ||||||
|  |             } | ||||||
|  |         ) | ||||||
|  |     if device_class is not _UNDEF: | ||||||
|  |         schema = schema.extend( | ||||||
|  |             { | ||||||
|  |                 cv.Optional( | ||||||
|  |                     CONF_DEVICE_CLASS, default=device_class | ||||||
|  |                 ): validate_device_class | ||||||
|  |             } | ||||||
|  |         ) | ||||||
|  |     return schema | ||||||
|  |  | ||||||
|  |  | ||||||
|  | async def setup_button_core_(var, config): | ||||||
|  |     await setup_entity(var, config) | ||||||
|  |  | ||||||
|  |     for conf in config.get(CONF_ON_PRESS, []): | ||||||
|  |         trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) | ||||||
|  |         await automation.build_automation(trigger, [], conf) | ||||||
|  |  | ||||||
|  |     if CONF_DEVICE_CLASS in config: | ||||||
|  |         cg.add(var.set_device_class(config[CONF_DEVICE_CLASS])) | ||||||
|  |  | ||||||
|  |     if CONF_MQTT_ID in config: | ||||||
|  |         mqtt_ = cg.new_Pvariable(config[CONF_MQTT_ID], var) | ||||||
|  |         await mqtt.register_mqtt_component(mqtt_, config) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | async def register_button(var, config): | ||||||
|  |     if not CORE.has_id(config[CONF_ID]): | ||||||
|  |         var = cg.Pvariable(config[CONF_ID], var) | ||||||
|  |     cg.add(cg.App.register_button(var)) | ||||||
|  |     await setup_button_core_(var, config) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | async def new_button(config): | ||||||
|  |     var = cg.new_Pvariable(config[CONF_ID]) | ||||||
|  |     await register_button(var, config) | ||||||
|  |     return var | ||||||
|  |  | ||||||
|  |  | ||||||
|  | BUTTON_PRESS_SCHEMA = maybe_simple_id( | ||||||
|  |     { | ||||||
|  |         cv.Required(CONF_ID): cv.use_id(Button), | ||||||
|  |     } | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @automation.register_action("button.press", PressAction, BUTTON_PRESS_SCHEMA) | ||||||
|  | async def button_press_to_code(config, action_id, template_arg, args): | ||||||
|  |     paren = await cg.get_variable(config[CONF_ID]) | ||||||
|  |     return cg.new_Pvariable(action_id, template_arg, paren) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @coroutine_with_priority(100.0) | ||||||
|  | async def to_code(config): | ||||||
|  |     cg.add_global(button_ns.using) | ||||||
|  |     cg.add_define("USE_BUTTON") | ||||||
							
								
								
									
										28
									
								
								esphome/components/button/automation.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								esphome/components/button/automation.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,28 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include "esphome/components/button/button.h" | ||||||
|  | #include "esphome/core/automation.h" | ||||||
|  | #include "esphome/core/component.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace button { | ||||||
|  |  | ||||||
|  | template<typename... Ts> class PressAction : public Action<Ts...> { | ||||||
|  |  public: | ||||||
|  |   explicit PressAction(Button *button) : button_(button) {} | ||||||
|  |  | ||||||
|  |   void play(Ts... x) override { this->button_->press(); } | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   Button *button_; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | class ButtonPressTrigger : public Trigger<> { | ||||||
|  |  public: | ||||||
|  |   ButtonPressTrigger(Button *button) { | ||||||
|  |     button->add_on_press_callback([this]() { this->trigger(); }); | ||||||
|  |   } | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | }  // namespace button | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										28
									
								
								esphome/components/button/button.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								esphome/components/button/button.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,28 @@ | |||||||
|  | #include "button.h" | ||||||
|  | #include "esphome/core/log.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace button { | ||||||
|  |  | ||||||
|  | static const char *const TAG = "button"; | ||||||
|  |  | ||||||
|  | Button::Button(const std::string &name) : EntityBase(name) {} | ||||||
|  | Button::Button() : Button("") {} | ||||||
|  |  | ||||||
|  | void Button::press() { | ||||||
|  |   ESP_LOGD(TAG, "'%s' Pressed.", this->get_name().c_str()); | ||||||
|  |   this->press_action(); | ||||||
|  |   this->press_callback_.call(); | ||||||
|  | } | ||||||
|  | void Button::add_on_press_callback(std::function<void()> &&callback) { this->press_callback_.add(std::move(callback)); } | ||||||
|  | uint32_t Button::hash_base() { return 1495763804UL; } | ||||||
|  |  | ||||||
|  | void Button::set_device_class(const std::string &device_class) { this->device_class_ = device_class; } | ||||||
|  | std::string Button::get_device_class() { | ||||||
|  |   if (this->device_class_.has_value()) | ||||||
|  |     return *this->device_class_; | ||||||
|  |   return ""; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | }  // namespace button | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										57
									
								
								esphome/components/button/button.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								esphome/components/button/button.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,57 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include "esphome/core/component.h" | ||||||
|  | #include "esphome/core/entity_base.h" | ||||||
|  | #include "esphome/core/helpers.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace button { | ||||||
|  |  | ||||||
|  | #define LOG_BUTTON(prefix, type, obj) \ | ||||||
|  |   if ((obj) != nullptr) { \ | ||||||
|  |     ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(type), (obj)->get_name().c_str()); \ | ||||||
|  |     if (!(obj)->get_icon().empty()) { \ | ||||||
|  |       ESP_LOGCONFIG(TAG, "%s  Icon: '%s'", prefix, (obj)->get_icon().c_str()); \ | ||||||
|  |     } \ | ||||||
|  |   } | ||||||
|  |  | ||||||
|  | /** Base class for all buttons. | ||||||
|  |  * | ||||||
|  |  * A button is just a momentary switch that does not have a state, only a trigger. | ||||||
|  |  */ | ||||||
|  | class Button : public EntityBase { | ||||||
|  |  public: | ||||||
|  |   explicit Button(); | ||||||
|  |   explicit Button(const std::string &name); | ||||||
|  |  | ||||||
|  |   /** Press this button. This is called by the front-end. | ||||||
|  |    * | ||||||
|  |    * For implementing buttons, please override press_action. | ||||||
|  |    */ | ||||||
|  |   void press(); | ||||||
|  |  | ||||||
|  |   /** Set callback for state changes. | ||||||
|  |    * | ||||||
|  |    * @param callback The void() callback. | ||||||
|  |    */ | ||||||
|  |   void add_on_press_callback(std::function<void()> &&callback); | ||||||
|  |  | ||||||
|  |   /// Set the Home Assistant device class (see button::device_class). | ||||||
|  |   void set_device_class(const std::string &device_class); | ||||||
|  |  | ||||||
|  |   /// Get the device class for this button. | ||||||
|  |   std::string get_device_class(); | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   /** You should implement this virtual method if you want to create your own button. | ||||||
|  |    */ | ||||||
|  |   virtual void press_action(){}; | ||||||
|  |  | ||||||
|  |   uint32_t hash_base() override; | ||||||
|  |  | ||||||
|  |   CallbackManager<void()> press_callback_{}; | ||||||
|  |   optional<std::string> device_class_{}; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | }  // namespace button | ||||||
|  | }  // namespace esphome | ||||||
| @@ -85,14 +85,7 @@ void CaptivePortal::start() { | |||||||
|   this->dns_server_->start(53, "*", (uint32_t) ip); |   this->dns_server_->start(53, "*", (uint32_t) ip); | ||||||
|  |  | ||||||
|   this->base_->get_server()->onNotFound([this](AsyncWebServerRequest *req) { |   this->base_->get_server()->onNotFound([this](AsyncWebServerRequest *req) { | ||||||
|     bool not_found = false; |     if (!this->active_ || req->host().c_str() == wifi::global_wifi_component->wifi_soft_ap_ip().str()) { | ||||||
|     if (!this->active_) { |  | ||||||
|       not_found = true; |  | ||||||
|     } else if (req->host().c_str() == wifi::global_wifi_component->wifi_soft_ap_ip().str()) { |  | ||||||
|       not_found = true; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     if (not_found) { |  | ||||||
|       req->send(404, "text/html", "File not found"); |       req->send(404, "text/html", "File not found"); | ||||||
|       return; |       return; | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -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_STATE, | ||||||
|     CONF_PRESET, |     CONF_PRESET, | ||||||
|     CONF_SWING_MODE, |     CONF_SWING_MODE, | ||||||
|     CONF_SWING_MODE_COMMAND_TOPIC, |     CONF_SWING_MODE_COMMAND_TOPIC, | ||||||
| @@ -34,6 +35,7 @@ from esphome.const import ( | |||||||
|     CONF_TARGET_TEMPERATURE_LOW_COMMAND_TOPIC, |     CONF_TARGET_TEMPERATURE_LOW_COMMAND_TOPIC, | ||||||
|     CONF_TARGET_TEMPERATURE_LOW_STATE_TOPIC, |     CONF_TARGET_TEMPERATURE_LOW_STATE_TOPIC, | ||||||
|     CONF_TEMPERATURE_STEP, |     CONF_TEMPERATURE_STEP, | ||||||
|  |     CONF_TRIGGER_ID, | ||||||
|     CONF_VISUAL, |     CONF_VISUAL, | ||||||
|     CONF_MQTT_ID, |     CONF_MQTT_ID, | ||||||
| ) | ) | ||||||
| @@ -101,6 +103,7 @@ validate_climate_swing_mode = cv.enum(CLIMATE_SWING_MODES, upper=True) | |||||||
|  |  | ||||||
| # Actions | # Actions | ||||||
| ControlAction = climate_ns.class_("ControlAction", automation.Action) | ControlAction = climate_ns.class_("ControlAction", automation.Action) | ||||||
|  | StateTrigger = climate_ns.class_("StateTrigger", automation.Trigger.template()) | ||||||
|  |  | ||||||
| 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( | ||||||
|     { |     { | ||||||
| @@ -161,6 +164,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_STATE): automation.validate_automation( | ||||||
|  |             { | ||||||
|  |                 cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StateTrigger), | ||||||
|  |             } | ||||||
|  |         ), | ||||||
|     } |     } | ||||||
| ) | ) | ||||||
|  |  | ||||||
| @@ -205,7 +213,7 @@ async def setup_climate_core_(var, config): | |||||||
|         if CONF_MODE_COMMAND_TOPIC in config: |         if CONF_MODE_COMMAND_TOPIC in config: | ||||||
|             cg.add(mqtt_.set_custom_mode_command_topic(config[CONF_MODE_COMMAND_TOPIC])) |             cg.add(mqtt_.set_custom_mode_command_topic(config[CONF_MODE_COMMAND_TOPIC])) | ||||||
|         if CONF_MODE_STATE_TOPIC in config: |         if CONF_MODE_STATE_TOPIC in config: | ||||||
|             cg.add(mqtt_.set_custom_state_topic(config[CONF_MODE_STATE_TOPIC])) |             cg.add(mqtt_.set_custom_mode_state_topic(config[CONF_MODE_STATE_TOPIC])) | ||||||
|  |  | ||||||
|         if CONF_SWING_MODE_COMMAND_TOPIC in config: |         if CONF_SWING_MODE_COMMAND_TOPIC in config: | ||||||
|             cg.add( |             cg.add( | ||||||
| @@ -256,6 +264,10 @@ async def setup_climate_core_(var, config): | |||||||
|                 ) |                 ) | ||||||
|             ) |             ) | ||||||
|  |  | ||||||
|  |     for conf in config.get(CONF_ON_STATE, []): | ||||||
|  |         trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) | ||||||
|  |         await automation.build_automation(trigger, [], conf) | ||||||
|  |  | ||||||
|  |  | ||||||
| async def register_climate(var, config): | async def register_climate(var, config): | ||||||
|     if not CORE.has_id(config[CONF_ID]): |     if not CORE.has_id(config[CONF_ID]): | ||||||
|   | |||||||
| @@ -42,5 +42,12 @@ template<typename... Ts> class ControlAction : public Action<Ts...> { | |||||||
|   Climate *climate_; |   Climate *climate_; | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | class StateTrigger : public Trigger<> { | ||||||
|  |  public: | ||||||
|  |   StateTrigger(Climate *climate) { | ||||||
|  |     climate->add_on_state_callback([this]() { this->trigger(); }); | ||||||
|  |   } | ||||||
|  | }; | ||||||
|  |  | ||||||
| }  // namespace climate | }  // namespace climate | ||||||
| }  // namespace esphome | }  // namespace esphome | ||||||
|   | |||||||
| @@ -102,8 +102,6 @@ void CS5460AComponent::hw_init_() { | |||||||
|  |  | ||||||
| /* Doesn't reset the register values etc., just restarts the "computation cycle" */ | /* Doesn't reset the register values etc., just restarts the "computation cycle" */ | ||||||
| void CS5460AComponent::restart_() { | void CS5460AComponent::restart_() { | ||||||
|   int cnt; |  | ||||||
|  |  | ||||||
|   this->enable(); |   this->enable(); | ||||||
|   /* Stop running conversion, wake up if needed */ |   /* Stop running conversion, wake up if needed */ | ||||||
|   this->write_byte(CMD_POWER_UP); |   this->write_byte(CMD_POWER_UP); | ||||||
|   | |||||||
| @@ -90,6 +90,7 @@ void CSE7766Component::parse_data_() { | |||||||
|   uint32_t power_cycle = this->get_24_bit_uint_(17); |   uint32_t power_cycle = this->get_24_bit_uint_(17); | ||||||
|  |  | ||||||
|   uint8_t adj = this->raw_data_[20]; |   uint8_t adj = this->raw_data_[20]; | ||||||
|  |   uint32_t cf_pulses = (this->raw_data_[21] << 8) + this->raw_data_[22]; | ||||||
|  |  | ||||||
|   bool power_ok = true; |   bool power_ok = true; | ||||||
|   bool voltage_ok = true; |   bool voltage_ok = true; | ||||||
| @@ -127,6 +128,18 @@ void CSE7766Component::parse_data_() { | |||||||
|     power = power_calib / float(power_cycle); |     power = power_calib / float(power_cycle); | ||||||
|     this->power_acc_ += power; |     this->power_acc_ += power; | ||||||
|     this->power_counts_ += 1; |     this->power_counts_ += 1; | ||||||
|  |  | ||||||
|  |     uint32_t difference; | ||||||
|  |     if (this->cf_pulses_last_ == 0) | ||||||
|  |       this->cf_pulses_last_ = cf_pulses; | ||||||
|  |  | ||||||
|  |     if (cf_pulses < this->cf_pulses_last_) { | ||||||
|  |       difference = cf_pulses + (0x10000 - this->cf_pulses_last_); | ||||||
|  |     } else { | ||||||
|  |       difference = cf_pulses - this->cf_pulses_last_; | ||||||
|  |     } | ||||||
|  |     this->cf_pulses_last_ = cf_pulses; | ||||||
|  |     this->energy_total_ += difference * float(power_calib) / 1000000.0 / 3600.0; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   if ((adj & 0x20) == 0x20 && current_ok && voltage_ok && power != 0.0) { |   if ((adj & 0x20) == 0x20 && current_ok && voltage_ok && power != 0.0) { | ||||||
| @@ -136,9 +149,9 @@ void CSE7766Component::parse_data_() { | |||||||
|   } |   } | ||||||
| } | } | ||||||
| void CSE7766Component::update() { | void CSE7766Component::update() { | ||||||
|   float voltage = this->voltage_counts_ > 0 ? this->voltage_acc_ / this->voltage_counts_ : 0.0; |   float voltage = this->voltage_counts_ > 0 ? this->voltage_acc_ / this->voltage_counts_ : 0.0f; | ||||||
|   float current = this->current_counts_ > 0 ? this->current_acc_ / this->current_counts_ : 0.0; |   float current = this->current_counts_ > 0 ? this->current_acc_ / this->current_counts_ : 0.0f; | ||||||
|   float power = this->power_counts_ > 0 ? this->power_acc_ / this->power_counts_ : 0.0; |   float power = this->power_counts_ > 0 ? this->power_acc_ / this->power_counts_ : 0.0f; | ||||||
|  |  | ||||||
|   ESP_LOGV(TAG, "Got voltage_acc=%.2f current_acc=%.2f power_acc=%.2f", this->voltage_acc_, this->current_acc_, |   ESP_LOGV(TAG, "Got voltage_acc=%.2f current_acc=%.2f power_acc=%.2f", this->voltage_acc_, this->current_acc_, | ||||||
|            this->power_acc_); |            this->power_acc_); | ||||||
| @@ -152,6 +165,8 @@ void CSE7766Component::update() { | |||||||
|     this->current_sensor_->publish_state(current); |     this->current_sensor_->publish_state(current); | ||||||
|   if (this->power_sensor_ != nullptr) |   if (this->power_sensor_ != nullptr) | ||||||
|     this->power_sensor_->publish_state(power); |     this->power_sensor_->publish_state(power); | ||||||
|  |   if (this->energy_sensor_ != nullptr) | ||||||
|  |     this->energy_sensor_->publish_state(this->energy_total_); | ||||||
|  |  | ||||||
|   this->voltage_acc_ = 0.0f; |   this->voltage_acc_ = 0.0f; | ||||||
|   this->current_acc_ = 0.0f; |   this->current_acc_ = 0.0f; | ||||||
| @@ -172,6 +187,7 @@ void CSE7766Component::dump_config() { | |||||||
|   LOG_SENSOR("  ", "Voltage", this->voltage_sensor_); |   LOG_SENSOR("  ", "Voltage", this->voltage_sensor_); | ||||||
|   LOG_SENSOR("  ", "Current", this->current_sensor_); |   LOG_SENSOR("  ", "Current", this->current_sensor_); | ||||||
|   LOG_SENSOR("  ", "Power", this->power_sensor_); |   LOG_SENSOR("  ", "Power", this->power_sensor_); | ||||||
|  |   LOG_SENSOR("  ", "Energy", this->energy_sensor_); | ||||||
|   this->check_uart_settings(4800); |   this->check_uart_settings(4800); | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -12,6 +12,7 @@ class CSE7766Component : public PollingComponent, public uart::UARTDevice { | |||||||
|   void set_voltage_sensor(sensor::Sensor *voltage_sensor) { voltage_sensor_ = voltage_sensor; } |   void set_voltage_sensor(sensor::Sensor *voltage_sensor) { voltage_sensor_ = voltage_sensor; } | ||||||
|   void set_current_sensor(sensor::Sensor *current_sensor) { current_sensor_ = current_sensor; } |   void set_current_sensor(sensor::Sensor *current_sensor) { current_sensor_ = current_sensor; } | ||||||
|   void set_power_sensor(sensor::Sensor *power_sensor) { power_sensor_ = power_sensor; } |   void set_power_sensor(sensor::Sensor *power_sensor) { power_sensor_ = power_sensor; } | ||||||
|  |   void set_energy_sensor(sensor::Sensor *energy_sensor) { energy_sensor_ = energy_sensor; } | ||||||
|  |  | ||||||
|   void loop() override; |   void loop() override; | ||||||
|   float get_setup_priority() const override; |   float get_setup_priority() const override; | ||||||
| @@ -29,9 +30,12 @@ class CSE7766Component : public PollingComponent, public uart::UARTDevice { | |||||||
|   sensor::Sensor *voltage_sensor_{nullptr}; |   sensor::Sensor *voltage_sensor_{nullptr}; | ||||||
|   sensor::Sensor *current_sensor_{nullptr}; |   sensor::Sensor *current_sensor_{nullptr}; | ||||||
|   sensor::Sensor *power_sensor_{nullptr}; |   sensor::Sensor *power_sensor_{nullptr}; | ||||||
|  |   sensor::Sensor *energy_sensor_{nullptr}; | ||||||
|   float voltage_acc_{0.0f}; |   float voltage_acc_{0.0f}; | ||||||
|   float current_acc_{0.0f}; |   float current_acc_{0.0f}; | ||||||
|   float power_acc_{0.0f}; |   float power_acc_{0.0f}; | ||||||
|  |   float energy_total_{0.0f}; | ||||||
|  |   uint32_t cf_pulses_last_{0}; | ||||||
|   uint32_t voltage_counts_{0}; |   uint32_t voltage_counts_{0}; | ||||||
|   uint32_t current_counts_{0}; |   uint32_t current_counts_{0}; | ||||||
|   uint32_t power_counts_{0}; |   uint32_t power_counts_{0}; | ||||||
|   | |||||||
| @@ -3,16 +3,20 @@ import esphome.config_validation as cv | |||||||
| from esphome.components import sensor, uart | from esphome.components import sensor, uart | ||||||
| from esphome.const import ( | from esphome.const import ( | ||||||
|     CONF_CURRENT, |     CONF_CURRENT, | ||||||
|  |     CONF_ENERGY, | ||||||
|     CONF_ID, |     CONF_ID, | ||||||
|     CONF_POWER, |     CONF_POWER, | ||||||
|     CONF_VOLTAGE, |     CONF_VOLTAGE, | ||||||
|     DEVICE_CLASS_CURRENT, |     DEVICE_CLASS_CURRENT, | ||||||
|  |     DEVICE_CLASS_ENERGY, | ||||||
|     DEVICE_CLASS_POWER, |     DEVICE_CLASS_POWER, | ||||||
|     DEVICE_CLASS_VOLTAGE, |     DEVICE_CLASS_VOLTAGE, | ||||||
|     STATE_CLASS_MEASUREMENT, |     STATE_CLASS_MEASUREMENT, | ||||||
|  |     STATE_CLASS_TOTAL_INCREASING, | ||||||
|     UNIT_VOLT, |     UNIT_VOLT, | ||||||
|     UNIT_AMPERE, |     UNIT_AMPERE, | ||||||
|     UNIT_WATT, |     UNIT_WATT, | ||||||
|  |     UNIT_WATT_HOURS, | ||||||
| ) | ) | ||||||
|  |  | ||||||
| DEPENDENCIES = ["uart"] | DEPENDENCIES = ["uart"] | ||||||
| @@ -44,6 +48,12 @@ CONFIG_SCHEMA = ( | |||||||
|                 device_class=DEVICE_CLASS_POWER, |                 device_class=DEVICE_CLASS_POWER, | ||||||
|                 state_class=STATE_CLASS_MEASUREMENT, |                 state_class=STATE_CLASS_MEASUREMENT, | ||||||
|             ), |             ), | ||||||
|  |             cv.Optional(CONF_ENERGY): sensor.sensor_schema( | ||||||
|  |                 unit_of_measurement=UNIT_WATT_HOURS, | ||||||
|  |                 accuracy_decimals=3, | ||||||
|  |                 device_class=DEVICE_CLASS_ENERGY, | ||||||
|  |                 state_class=STATE_CLASS_TOTAL_INCREASING, | ||||||
|  |             ), | ||||||
|         } |         } | ||||||
|     ) |     ) | ||||||
|     .extend(cv.polling_component_schema("60s")) |     .extend(cv.polling_component_schema("60s")) | ||||||
| @@ -71,3 +81,7 @@ async def to_code(config): | |||||||
|         conf = config[CONF_POWER] |         conf = config[CONF_POWER] | ||||||
|         sens = await sensor.new_sensor(conf) |         sens = await sensor.new_sensor(conf) | ||||||
|         cg.add(var.set_power_sensor(sens)) |         cg.add(var.set_power_sensor(sens)) | ||||||
|  |     if CONF_ENERGY in config: | ||||||
|  |         conf = config[CONF_ENERGY] | ||||||
|  |         sens = await sensor.new_sensor(conf) | ||||||
|  |         cg.add(var.set_energy_sensor(sens)) | ||||||
|   | |||||||
| @@ -231,7 +231,7 @@ bool DaikinClimate::on_receive(remote_base::RemoteReceiveData data) { | |||||||
|       // frame header |       // frame header | ||||||
|       if (byte != 0x27) |       if (byte != 0x27) | ||||||
|         return false; |         return false; | ||||||
|     } else if (pos == 3) { |     } else if (pos == 3) {  // NOLINT(bugprone-branch-clone) | ||||||
|       // frame header |       // frame header | ||||||
|       if (byte != 0x00) |       if (byte != 0x00) | ||||||
|         return false; |         return false; | ||||||
|   | |||||||
| @@ -4,20 +4,23 @@ | |||||||
| #include "esphome/core/defines.h" | #include "esphome/core/defines.h" | ||||||
| #include "esphome/core/version.h" | #include "esphome/core/version.h" | ||||||
|  |  | ||||||
|  | #ifdef USE_ESP_IDF | ||||||
|  | #include <esp_heap_caps.h> | ||||||
|  | #include <esp_system.h> | ||||||
|  | #endif | ||||||
|  |  | ||||||
| #ifdef USE_ESP32 | #ifdef USE_ESP32 | ||||||
|  | #if ESP_IDF_VERSION_MAJOR >= 4 | ||||||
|  | #include <esp32/rom/rtc.h> | ||||||
|  | #else | ||||||
| #include <rom/rtc.h> | #include <rom/rtc.h> | ||||||
| #include <esp_idf_version.h> | #endif | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
| #ifdef USE_ARDUINO | #ifdef USE_ARDUINO | ||||||
| #include <Esp.h> | #include <Esp.h> | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
| #ifdef USE_ESP_IDF |  | ||||||
| #include <esp_heap_caps.h> |  | ||||||
| #include <esp_system.h> |  | ||||||
| #endif |  | ||||||
|  |  | ||||||
| namespace esphome { | namespace esphome { | ||||||
| namespace debug { | namespace debug { | ||||||
|  |  | ||||||
|   | |||||||
| @@ -77,8 +77,8 @@ void DeepSleepComponent::begin_sleep(bool manual) { | |||||||
|   if (this->sleep_duration_.has_value()) |   if (this->sleep_duration_.has_value()) | ||||||
|     esp_sleep_enable_timer_wakeup(*this->sleep_duration_); |     esp_sleep_enable_timer_wakeup(*this->sleep_duration_); | ||||||
|   if (this->wakeup_pin_ != nullptr) { |   if (this->wakeup_pin_ != nullptr) { | ||||||
|     bool level = this->wakeup_pin_->is_inverted(); |     bool level = !this->wakeup_pin_->is_inverted(); | ||||||
|     if (this->wakeup_pin_mode_ == WAKEUP_PIN_MODE_INVERT_WAKEUP && !this->wakeup_pin_->digital_read()) { |     if (this->wakeup_pin_mode_ == WAKEUP_PIN_MODE_INVERT_WAKEUP && this->wakeup_pin_->digital_read()) { | ||||||
|       level = !level; |       level = !level; | ||||||
|     } |     } | ||||||
|     esp_sleep_enable_ext0_wakeup(gpio_num_t(this->wakeup_pin_->get_pin()), level); |     esp_sleep_enable_ext0_wakeup(gpio_num_t(this->wakeup_pin_->get_pin()), level); | ||||||
|   | |||||||
| @@ -496,7 +496,7 @@ bool Animation::get_pixel(int x, int y) const { | |||||||
|     return false; |     return false; | ||||||
|   const uint32_t width_8 = ((this->width_ + 7u) / 8u) * 8u; |   const uint32_t width_8 = ((this->width_ + 7u) / 8u) * 8u; | ||||||
|   const uint32_t frame_index = this->height_ * width_8 * this->current_frame_; |   const uint32_t frame_index = this->height_ * width_8 * this->current_frame_; | ||||||
|   if (frame_index >= this->width_ * this->height_ * this->animation_frame_count_) |   if (frame_index >= (uint32_t)(this->width_ * this->height_ * this->animation_frame_count_)) | ||||||
|     return false; |     return false; | ||||||
|   const uint32_t pos = x + y * width_8 + frame_index; |   const uint32_t pos = x + y * width_8 + frame_index; | ||||||
|   return progmem_read_byte(this->data_start_ + (pos / 8u)) & (0x80 >> (pos % 8u)); |   return progmem_read_byte(this->data_start_ + (pos / 8u)) & (0x80 >> (pos % 8u)); | ||||||
| @@ -505,7 +505,7 @@ Color Animation::get_color_pixel(int x, int y) const { | |||||||
|   if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_) |   if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_) | ||||||
|     return Color::BLACK; |     return Color::BLACK; | ||||||
|   const uint32_t frame_index = this->width_ * this->height_ * this->current_frame_; |   const uint32_t frame_index = this->width_ * this->height_ * this->current_frame_; | ||||||
|   if (frame_index >= this->width_ * this->height_ * this->animation_frame_count_) |   if (frame_index >= (uint32_t)(this->width_ * this->height_ * this->animation_frame_count_)) | ||||||
|     return Color::BLACK; |     return Color::BLACK; | ||||||
|   const uint32_t pos = (x + y * this->width_ + frame_index) * 3; |   const uint32_t pos = (x + y * this->width_ + frame_index) * 3; | ||||||
|   const uint32_t color32 = (progmem_read_byte(this->data_start_ + pos + 2) << 0) | |   const uint32_t color32 = (progmem_read_byte(this->data_start_ + pos + 2) << 0) | | ||||||
| @@ -517,7 +517,7 @@ Color Animation::get_grayscale_pixel(int x, int y) const { | |||||||
|   if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_) |   if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_) | ||||||
|     return Color::BLACK; |     return Color::BLACK; | ||||||
|   const uint32_t frame_index = this->width_ * this->height_ * this->current_frame_; |   const uint32_t frame_index = this->width_ * this->height_ * this->current_frame_; | ||||||
|   if (frame_index >= this->width_ * this->height_ * this->animation_frame_count_) |   if (frame_index >= (uint32_t)(this->width_ * this->height_ * this->animation_frame_count_)) | ||||||
|     return Color::BLACK; |     return Color::BLACK; | ||||||
|   const uint32_t pos = (x + y * this->width_ + frame_index); |   const uint32_t pos = (x + y * this->width_ + frame_index); | ||||||
|   const uint8_t gray = progmem_read_byte(this->data_start_ + pos); |   const uint8_t gray = progmem_read_byte(this->data_start_ + pos); | ||||||
|   | |||||||
| @@ -1,9 +1,11 @@ | |||||||
| import esphome.codegen as cg | import esphome.codegen as cg | ||||||
| import esphome.config_validation as cv | import esphome.config_validation as cv | ||||||
|  | from esphome import pins | ||||||
| from esphome.components import uart | from esphome.components import uart | ||||||
| from esphome.const import ( | from esphome.const import ( | ||||||
|     CONF_ID, |     CONF_ID, | ||||||
|     CONF_UART_ID, |     CONF_UART_ID, | ||||||
|  |     CONF_RECEIVE_TIMEOUT, | ||||||
| ) | ) | ||||||
|  |  | ||||||
| CODEOWNERS = ["@glmnet", "@zuidwijk"] | CODEOWNERS = ["@glmnet", "@zuidwijk"] | ||||||
| @@ -11,10 +13,13 @@ CODEOWNERS = ["@glmnet", "@zuidwijk"] | |||||||
| DEPENDENCIES = ["uart"] | DEPENDENCIES = ["uart"] | ||||||
| AUTO_LOAD = ["sensor", "text_sensor"] | AUTO_LOAD = ["sensor", "text_sensor"] | ||||||
|  |  | ||||||
| CONF_DSMR_ID = "dsmr_id" |  | ||||||
| CONF_DECRYPTION_KEY = "decryption_key" |  | ||||||
| CONF_CRC_CHECK = "crc_check" | CONF_CRC_CHECK = "crc_check" | ||||||
|  | CONF_DECRYPTION_KEY = "decryption_key" | ||||||
|  | CONF_DSMR_ID = "dsmr_id" | ||||||
| CONF_GAS_MBUS_ID = "gas_mbus_id" | CONF_GAS_MBUS_ID = "gas_mbus_id" | ||||||
|  | CONF_MAX_TELEGRAM_LENGTH = "max_telegram_length" | ||||||
|  | CONF_REQUEST_INTERVAL = "request_interval" | ||||||
|  | CONF_REQUEST_PIN = "request_pin" | ||||||
|  |  | ||||||
| # Hack to prevent compile error due to ambiguity with lib namespace | # Hack to prevent compile error due to ambiguity with lib namespace | ||||||
| dsmr_ns = cg.esphome_ns.namespace("esphome::dsmr") | dsmr_ns = cg.esphome_ns.namespace("esphome::dsmr") | ||||||
| @@ -46,6 +51,14 @@ CONFIG_SCHEMA = cv.All( | |||||||
|             cv.Optional(CONF_DECRYPTION_KEY): _validate_key, |             cv.Optional(CONF_DECRYPTION_KEY): _validate_key, | ||||||
|             cv.Optional(CONF_CRC_CHECK, default=True): cv.boolean, |             cv.Optional(CONF_CRC_CHECK, default=True): cv.boolean, | ||||||
|             cv.Optional(CONF_GAS_MBUS_ID, default=1): cv.int_, |             cv.Optional(CONF_GAS_MBUS_ID, default=1): cv.int_, | ||||||
|  |             cv.Optional(CONF_MAX_TELEGRAM_LENGTH, default=1500): cv.int_, | ||||||
|  |             cv.Optional(CONF_REQUEST_PIN): pins.gpio_output_pin_schema, | ||||||
|  |             cv.Optional( | ||||||
|  |                 CONF_REQUEST_INTERVAL, default="0ms" | ||||||
|  |             ): cv.positive_time_period_milliseconds, | ||||||
|  |             cv.Optional( | ||||||
|  |                 CONF_RECEIVE_TIMEOUT, default="200ms" | ||||||
|  |             ): cv.positive_time_period_milliseconds, | ||||||
|         } |         } | ||||||
|     ).extend(uart.UART_DEVICE_SCHEMA), |     ).extend(uart.UART_DEVICE_SCHEMA), | ||||||
|     cv.only_with_arduino, |     cv.only_with_arduino, | ||||||
| @@ -55,10 +68,17 @@ CONFIG_SCHEMA = cv.All( | |||||||
| async def to_code(config): | async def to_code(config): | ||||||
|     uart_component = await cg.get_variable(config[CONF_UART_ID]) |     uart_component = await cg.get_variable(config[CONF_UART_ID]) | ||||||
|     var = cg.new_Pvariable(config[CONF_ID], uart_component, config[CONF_CRC_CHECK]) |     var = cg.new_Pvariable(config[CONF_ID], uart_component, config[CONF_CRC_CHECK]) | ||||||
|  |     cg.add(var.set_max_telegram_length(config[CONF_MAX_TELEGRAM_LENGTH])) | ||||||
|     if CONF_DECRYPTION_KEY in config: |     if CONF_DECRYPTION_KEY in config: | ||||||
|         cg.add(var.set_decryption_key(config[CONF_DECRYPTION_KEY])) |         cg.add(var.set_decryption_key(config[CONF_DECRYPTION_KEY])) | ||||||
|     await cg.register_component(var, config) |     await cg.register_component(var, config) | ||||||
|  |  | ||||||
|  |     if CONF_REQUEST_PIN in config: | ||||||
|  |         request_pin = await cg.gpio_pin_expression(config[CONF_REQUEST_PIN]) | ||||||
|  |         cg.add(var.set_request_pin(request_pin)) | ||||||
|  |     cg.add(var.set_request_interval(config[CONF_REQUEST_INTERVAL].total_milliseconds)) | ||||||
|  |     cg.add(var.set_receive_timeout(config[CONF_RECEIVE_TIMEOUT].total_milliseconds)) | ||||||
|  |  | ||||||
|     cg.add_define("DSMR_GAS_MBUS_ID", config[CONF_GAS_MBUS_ID]) |     cg.add_define("DSMR_GAS_MBUS_ID", config[CONF_GAS_MBUS_ID]) | ||||||
|  |  | ||||||
|     # DSMR Parser |     # DSMR Parser | ||||||
|   | |||||||
| @@ -12,171 +12,275 @@ namespace dsmr { | |||||||
|  |  | ||||||
| static const char *const TAG = "dsmr"; | static const char *const TAG = "dsmr"; | ||||||
|  |  | ||||||
| void Dsmr::loop() { | void Dsmr::setup() { | ||||||
|   if (this->decryption_key_.empty()) |   this->telegram_ = new char[this->max_telegram_len_];  // NOLINT | ||||||
|     this->receive_telegram_(); |   if (this->request_pin_ != nullptr) { | ||||||
|   else |     this->request_pin_->setup(); | ||||||
|     this->receive_encrypted_(); |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | void Dsmr::loop() { | ||||||
|  |   if (this->ready_to_request_data_()) { | ||||||
|  |     if (this->decryption_key_.empty()) { | ||||||
|  |       this->receive_telegram_(); | ||||||
|  |     } else { | ||||||
|  |       this->receive_encrypted_telegram_(); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool Dsmr::ready_to_request_data_() { | ||||||
|  |   // When using a request pin, then wait for the next request interval. | ||||||
|  |   if (this->request_pin_ != nullptr) { | ||||||
|  |     if (!this->requesting_data_ && this->request_interval_reached_()) { | ||||||
|  |       this->start_requesting_data_(); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   // Otherwise, sink serial data until next request interval. | ||||||
|  |   else { | ||||||
|  |     if (this->request_interval_reached_()) { | ||||||
|  |       this->start_requesting_data_(); | ||||||
|  |     } | ||||||
|  |     if (!this->requesting_data_) { | ||||||
|  |       while (this->available()) { | ||||||
|  |         this->read(); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   return this->requesting_data_; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool Dsmr::request_interval_reached_() { | ||||||
|  |   if (this->last_request_time_ == 0) { | ||||||
|  |     return true; | ||||||
|  |   } | ||||||
|  |   return millis() - this->last_request_time_ > this->request_interval_; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool Dsmr::receive_timeout_reached_() { return millis() - this->last_read_time_ > this->receive_timeout_; } | ||||||
|  |  | ||||||
| bool Dsmr::available_within_timeout_() { | bool Dsmr::available_within_timeout_() { | ||||||
|   uint8_t tries = READ_TIMEOUT_MS / 5; |   // Data are available for reading on the UART bus? | ||||||
|   while (tries--) { |   // Then we can start reading right away. | ||||||
|  |   if (this->available()) { | ||||||
|  |     this->last_read_time_ = millis(); | ||||||
|  |     return true; | ||||||
|  |   } | ||||||
|  |   // When we're not in the process of reading a telegram, then there is | ||||||
|  |   // no need to actively wait for new data to come in. | ||||||
|  |   if (!header_found_) { | ||||||
|  |     return false; | ||||||
|  |   } | ||||||
|  |   // A telegram is being read. The smart meter might not deliver a telegram | ||||||
|  |   // in one go, but instead send it in chunks with small pauses in between. | ||||||
|  |   // When the UART RX buffer cannot hold a full telegram, then make sure | ||||||
|  |   // that the UART read buffer does not overflow while other components | ||||||
|  |   // perform their work in their loop. Do this by not returning control to | ||||||
|  |   // the main loop, until the read timeout is reached. | ||||||
|  |   if (this->parent_->get_rx_buffer_size() < this->max_telegram_len_) { | ||||||
|  |     while (!this->receive_timeout_reached_()) { | ||||||
|       delay(5); |       delay(5); | ||||||
|     if (available()) { |       if (this->available()) { | ||||||
|  |         this->last_read_time_ = millis(); | ||||||
|         return true; |         return true; | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|  |   } | ||||||
|  |   // No new data has come in during the read timeout? Then stop reading the | ||||||
|  |   // telegram and start waiting for the next one to arrive. | ||||||
|  |   if (this->receive_timeout_reached_()) { | ||||||
|  |     ESP_LOGW(TAG, "Timeout while reading data for telegram"); | ||||||
|  |     this->reset_telegram_(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|   return false; |   return false; | ||||||
| } | } | ||||||
|  |  | ||||||
| void Dsmr::receive_telegram_() { | void Dsmr::start_requesting_data_() { | ||||||
|   while (true) { |   if (!this->requesting_data_) { | ||||||
|     if (!available()) { |     if (this->request_pin_ != nullptr) { | ||||||
|       if (!header_found_ || !available_within_timeout_()) { |       ESP_LOGV(TAG, "Start requesting data from P1 port"); | ||||||
|         return; |       this->request_pin_->digital_write(true); | ||||||
|  |     } else { | ||||||
|  |       ESP_LOGV(TAG, "Start reading data from P1 port"); | ||||||
|  |     } | ||||||
|  |     this->requesting_data_ = true; | ||||||
|  |     this->last_request_time_ = millis(); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|     const char c = read(); | void Dsmr::stop_requesting_data_() { | ||||||
|  |   if (this->requesting_data_) { | ||||||
|  |     if (this->request_pin_ != nullptr) { | ||||||
|  |       ESP_LOGV(TAG, "Stop requesting data from P1 port"); | ||||||
|  |       this->request_pin_->digital_write(false); | ||||||
|  |     } else { | ||||||
|  |       ESP_LOGV(TAG, "Stop reading data from P1 port"); | ||||||
|  |     } | ||||||
|  |     while (this->available()) { | ||||||
|  |       this->read(); | ||||||
|  |     } | ||||||
|  |     this->requesting_data_ = false; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void Dsmr::reset_telegram_() { | ||||||
|  |   this->header_found_ = false; | ||||||
|  |   this->footer_found_ = false; | ||||||
|  |   this->bytes_read_ = 0; | ||||||
|  |   this->crypt_bytes_read_ = 0; | ||||||
|  |   this->crypt_telegram_len_ = 0; | ||||||
|  |   this->last_read_time_ = 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void Dsmr::receive_telegram_() { | ||||||
|  |   while (this->available_within_timeout_()) { | ||||||
|  |     const char c = this->read(); | ||||||
|  |  | ||||||
|     // Find a new telegram header, i.e. forward slash. |     // Find a new telegram header, i.e. forward slash. | ||||||
|     if (c == '/') { |     if (c == '/') { | ||||||
|       ESP_LOGV(TAG, "Header of telegram found"); |       ESP_LOGV(TAG, "Header of telegram found"); | ||||||
|       header_found_ = true; |       this->reset_telegram_(); | ||||||
|       footer_found_ = false; |       this->header_found_ = true; | ||||||
|       telegram_len_ = 0; |  | ||||||
|     } |     } | ||||||
|     if (!header_found_) |     if (!this->header_found_) | ||||||
|       continue; |       continue; | ||||||
|  |  | ||||||
|     // Check for buffer overflow. |     // Check for buffer overflow. | ||||||
|     if (telegram_len_ >= MAX_TELEGRAM_LENGTH) { |     if (this->bytes_read_ >= this->max_telegram_len_) { | ||||||
|       header_found_ = false; |       this->reset_telegram_(); | ||||||
|       footer_found_ = false; |       ESP_LOGE(TAG, "Error: telegram larger than buffer (%d bytes)", this->max_telegram_len_); | ||||||
|       ESP_LOGE(TAG, "Error: telegram larger than buffer (%d bytes)", MAX_TELEGRAM_LENGTH); |  | ||||||
|       return; |       return; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     // Some v2.2 or v3 meters will send a new value which starts with '(' |     // Some v2.2 or v3 meters will send a new value which starts with '(' | ||||||
|     // in a new line while the value belongs to the previous ObisId. For |     // in a new line, while the value belongs to the previous ObisId. For | ||||||
|     // proper parsing remove these new line characters |     // proper parsing, remove these new line characters. | ||||||
|     while (c == '(' && (telegram_[telegram_len_ - 1] == '\n' || telegram_[telegram_len_ - 1] == '\r')) |     if (c == '(') { | ||||||
|       telegram_len_--; |       while (true) { | ||||||
|  |         auto previous_char = this->telegram_[this->bytes_read_ - 1]; | ||||||
|  |         if (previous_char == '\n' || previous_char == '\r') { | ||||||
|  |           this->bytes_read_--; | ||||||
|  |         } else { | ||||||
|  |           break; | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |  | ||||||
|     // Store the byte in the buffer. |     // Store the byte in the buffer. | ||||||
|     telegram_[telegram_len_] = c; |     this->telegram_[this->bytes_read_] = c; | ||||||
|     telegram_len_++; |     this->bytes_read_++; | ||||||
|  |  | ||||||
|     // Check for a footer, i.e. exlamation mark, followed by a hex checksum. |     // Check for a footer, i.e. exlamation mark, followed by a hex checksum. | ||||||
|     if (c == '!') { |     if (c == '!') { | ||||||
|       ESP_LOGV(TAG, "Footer of telegram found"); |       ESP_LOGV(TAG, "Footer of telegram found"); | ||||||
|       footer_found_ = true; |       this->footer_found_ = true; | ||||||
|       continue; |       continue; | ||||||
|     } |     } | ||||||
|     // Check for the end of the hex checksum, i.e. a newline. |     // Check for the end of the hex checksum, i.e. a newline. | ||||||
|     if (footer_found_ && c == '\n') { |     if (this->footer_found_ && c == '\n') { | ||||||
|       // Parse the telegram and publish sensor values. |       // Parse the telegram and publish sensor values. | ||||||
|       parse_telegram(); |       this->parse_telegram(); | ||||||
|  |       this->reset_telegram_(); | ||||||
|       header_found_ = false; |  | ||||||
|       return; |       return; | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| void Dsmr::receive_encrypted_() { | void Dsmr::receive_encrypted_telegram_() { | ||||||
|   // Encrypted buffer |   while (this->available_within_timeout_()) { | ||||||
|   uint8_t buffer[MAX_TELEGRAM_LENGTH]; |     const char c = this->read(); | ||||||
|   size_t buffer_length = 0; |  | ||||||
|   size_t packet_size = 0; |  | ||||||
|  |  | ||||||
|   while (true) { |  | ||||||
|     if (!available()) { |  | ||||||
|       if (!header_found_) { |  | ||||||
|         return; |  | ||||||
|       } |  | ||||||
|       if (!available_within_timeout_()) { |  | ||||||
|         ESP_LOGW(TAG, "Timeout while reading data for encrypted telegram"); |  | ||||||
|         return; |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     const char c = read(); |  | ||||||
|  |  | ||||||
|     // Find a new telegram start byte. |     // Find a new telegram start byte. | ||||||
|     if (!header_found_) { |     if (!this->header_found_) { | ||||||
|       if ((uint8_t) c != 0xDB) { |       if ((uint8_t) c != 0xDB) { | ||||||
|         continue; |         continue; | ||||||
|       } |       } | ||||||
|       ESP_LOGV(TAG, "Start byte 0xDB of encrypted telegram found"); |       ESP_LOGV(TAG, "Start byte 0xDB of encrypted telegram found"); | ||||||
|       header_found_ = true; |       this->reset_telegram_(); | ||||||
|  |       this->header_found_ = true; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     // Check for buffer overflow. |     // Check for buffer overflow. | ||||||
|     if (buffer_length >= MAX_TELEGRAM_LENGTH) { |     if (this->crypt_bytes_read_ >= this->max_telegram_len_) { | ||||||
|       header_found_ = false; |       this->reset_telegram_(); | ||||||
|       ESP_LOGE(TAG, "Error: encrypted telegram larger than buffer (%d bytes)", MAX_TELEGRAM_LENGTH); |       ESP_LOGE(TAG, "Error: encrypted telegram larger than buffer (%d bytes)", this->max_telegram_len_); | ||||||
|       return; |       return; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     buffer[buffer_length++] = c; |     // Store the byte in the buffer. | ||||||
|  |     this->crypt_telegram_[this->crypt_bytes_read_] = c; | ||||||
|  |     this->crypt_bytes_read_++; | ||||||
|  |  | ||||||
|     if (packet_size == 0 && buffer_length > 20) { |     // Read the length of the incoming encrypted telegram. | ||||||
|  |     if (this->crypt_telegram_len_ == 0 && this->crypt_bytes_read_ > 20) { | ||||||
|       // Complete header + data bytes |       // Complete header + data bytes | ||||||
|       packet_size = 13 + (buffer[11] << 8 | buffer[12]); |       this->crypt_telegram_len_ = 13 + (this->crypt_telegram_[11] << 8 | this->crypt_telegram_[12]); | ||||||
|       ESP_LOGV(TAG, "Encrypted telegram size: %d bytes", packet_size); |       ESP_LOGV(TAG, "Encrypted telegram length: %d bytes", this->crypt_telegram_len_); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // Check for the end of the encrypted telegram. | ||||||
|  |     if (this->crypt_telegram_len_ == 0 || this->crypt_bytes_read_ != this->crypt_telegram_len_) { | ||||||
|  |       continue; | ||||||
|     } |     } | ||||||
|     if (buffer_length == packet_size && packet_size > 0) { |  | ||||||
|     ESP_LOGV(TAG, "End of encrypted telegram found"); |     ESP_LOGV(TAG, "End of encrypted telegram found"); | ||||||
|  |  | ||||||
|  |     // Decrypt the encrypted telegram. | ||||||
|     GCM<AES128> *gcmaes128{new GCM<AES128>()}; |     GCM<AES128> *gcmaes128{new GCM<AES128>()}; | ||||||
|     gcmaes128->setKey(this->decryption_key_.data(), gcmaes128->keySize()); |     gcmaes128->setKey(this->decryption_key_.data(), gcmaes128->keySize()); | ||||||
|     // the iv is 8 bytes of the system title + 4 bytes frame counter |     // the iv is 8 bytes of the system title + 4 bytes frame counter | ||||||
|     // system title is at byte 2 and frame counter at byte 15 |     // system title is at byte 2 and frame counter at byte 15 | ||||||
|     for (int i = 10; i < 14; i++) |     for (int i = 10; i < 14; i++) | ||||||
|         buffer[i] = buffer[i + 4]; |       this->crypt_telegram_[i] = this->crypt_telegram_[i + 4]; | ||||||
|     constexpr uint16_t iv_size{12}; |     constexpr uint16_t iv_size{12}; | ||||||
|       gcmaes128->setIV(&buffer[2], iv_size); |     gcmaes128->setIV(&this->crypt_telegram_[2], iv_size); | ||||||
|     gcmaes128->decrypt(reinterpret_cast<uint8_t *>(this->telegram_), |     gcmaes128->decrypt(reinterpret_cast<uint8_t *>(this->telegram_), | ||||||
|                        // the ciphertext start at byte 18 |                        // the ciphertext start at byte 18 | ||||||
|                          &buffer[18], |                        &this->crypt_telegram_[18], | ||||||
|                        // cipher size |                        // cipher size | ||||||
|                          buffer_length - 17); |                        this->crypt_bytes_read_ - 17); | ||||||
|     delete gcmaes128;  // NOLINT(cppcoreguidelines-owning-memory) |     delete gcmaes128;  // NOLINT(cppcoreguidelines-owning-memory) | ||||||
|  |  | ||||||
|       telegram_len_ = strnlen(this->telegram_, sizeof(this->telegram_)); |     this->bytes_read_ = strnlen(this->telegram_, this->max_telegram_len_); | ||||||
|       ESP_LOGV(TAG, "Decrypted telegram size: %d bytes", telegram_len_); |     ESP_LOGV(TAG, "Decrypted telegram size: %d bytes", this->bytes_read_); | ||||||
|     ESP_LOGVV(TAG, "Decrypted telegram: %s", this->telegram_); |     ESP_LOGVV(TAG, "Decrypted telegram: %s", this->telegram_); | ||||||
|  |  | ||||||
|       parse_telegram(); |     // Parse the decrypted telegram and publish sensor values. | ||||||
|  |     this->parse_telegram(); | ||||||
|       header_found_ = false; |     this->reset_telegram_(); | ||||||
|       telegram_len_ = 0; |  | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
| } | } | ||||||
| } |  | ||||||
|  |  | ||||||
| bool Dsmr::parse_telegram() { | bool Dsmr::parse_telegram() { | ||||||
|   MyData data; |   MyData data; | ||||||
|   ESP_LOGV(TAG, "Trying to parse telegram"); |   ESP_LOGV(TAG, "Trying to parse telegram"); | ||||||
|  |   this->stop_requesting_data_(); | ||||||
|   ::dsmr::ParseResult<void> res = |   ::dsmr::ParseResult<void> res = | ||||||
|       ::dsmr::P1Parser::parse(&data, telegram_, telegram_len_, false, |       ::dsmr::P1Parser::parse(&data, this->telegram_, this->bytes_read_, false, | ||||||
|                               this->crc_check_);  // Parse telegram according to data definition. Ignore unknown values. |                               this->crc_check_);  // Parse telegram according to data definition. Ignore unknown values. | ||||||
|   if (res.err) { |   if (res.err) { | ||||||
|     // Parsing error, show it |     // Parsing error, show it | ||||||
|     auto err_str = res.fullError(telegram_, telegram_ + telegram_len_); |     auto err_str = res.fullError(this->telegram_, this->telegram_ + this->bytes_read_); | ||||||
|     ESP_LOGE(TAG, "%s", err_str.c_str()); |     ESP_LOGE(TAG, "%s", err_str.c_str()); | ||||||
|     return false; |     return false; | ||||||
|   } else { |   } else { | ||||||
|     this->status_clear_warning(); |     this->status_clear_warning(); | ||||||
|     publish_sensors(data); |     this->publish_sensors(data); | ||||||
|     return true; |     return true; | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| void Dsmr::dump_config() { | void Dsmr::dump_config() { | ||||||
|   ESP_LOGCONFIG(TAG, "DSMR:"); |   ESP_LOGCONFIG(TAG, "DSMR:"); | ||||||
|  |   ESP_LOGCONFIG(TAG, "  Max telegram length: %d", this->max_telegram_len_); | ||||||
|  |   ESP_LOGCONFIG(TAG, "  Receive timeout: %.1fs", this->receive_timeout_ / 1e3f); | ||||||
|  |   if (this->request_pin_ != nullptr) { | ||||||
|  |     LOG_PIN("  Request Pin: ", this->request_pin_); | ||||||
|  |   } | ||||||
|  |   if (this->request_interval_ > 0) { | ||||||
|  |     ESP_LOGCONFIG(TAG, "  Request Interval: %.1fs", this->request_interval_ / 1e3f); | ||||||
|  |   } | ||||||
|  |  | ||||||
| #define DSMR_LOG_SENSOR(s) LOG_SENSOR("  ", #s, this->s_##s##_); | #define DSMR_LOG_SENSOR(s) LOG_SENSOR("  ", #s, this->s_##s##_); | ||||||
|   DSMR_SENSOR_LIST(DSMR_LOG_SENSOR, ) |   DSMR_SENSOR_LIST(DSMR_LOG_SENSOR, ) | ||||||
| @@ -189,6 +293,10 @@ void Dsmr::set_decryption_key(const std::string &decryption_key) { | |||||||
|   if (decryption_key.length() == 0) { |   if (decryption_key.length() == 0) { | ||||||
|     ESP_LOGI(TAG, "Disabling decryption"); |     ESP_LOGI(TAG, "Disabling decryption"); | ||||||
|     this->decryption_key_.clear(); |     this->decryption_key_.clear(); | ||||||
|  |     if (this->crypt_telegram_ != nullptr) { | ||||||
|  |       delete[] this->crypt_telegram_; | ||||||
|  |       this->crypt_telegram_ = nullptr; | ||||||
|  |     } | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @@ -205,7 +313,11 @@ void Dsmr::set_decryption_key(const std::string &decryption_key) { | |||||||
|   char temp[3] = {0}; |   char temp[3] = {0}; | ||||||
|   for (int i = 0; i < 16; i++) { |   for (int i = 0; i < 16; i++) { | ||||||
|     strncpy(temp, &(decryption_key.c_str()[i * 2]), 2); |     strncpy(temp, &(decryption_key.c_str()[i * 2]), 2); | ||||||
|     decryption_key_.push_back(std::strtoul(temp, nullptr, 16)); |     this->decryption_key_.push_back(std::strtoul(temp, nullptr, 16)); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (this->crypt_telegram_ == nullptr) { | ||||||
|  |     this->crypt_telegram_ = new uint8_t[this->max_telegram_len_];  // NOLINT | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -16,9 +16,6 @@ | |||||||
| namespace esphome { | namespace esphome { | ||||||
| namespace dsmr { | namespace dsmr { | ||||||
|  |  | ||||||
| static constexpr uint32_t MAX_TELEGRAM_LENGTH = 1500; |  | ||||||
| static constexpr uint32_t READ_TIMEOUT_MS = 200; |  | ||||||
|  |  | ||||||
| using namespace ::dsmr::fields; | using namespace ::dsmr::fields; | ||||||
|  |  | ||||||
| // DSMR_**_LIST generated by ESPHome and written in esphome/core/defines | // DSMR_**_LIST generated by ESPHome and written in esphome/core/defines | ||||||
| @@ -52,6 +49,7 @@ class Dsmr : public Component, public uart::UARTDevice { | |||||||
|  public: |  public: | ||||||
|   Dsmr(uart::UARTComponent *uart, bool crc_check) : uart::UARTDevice(uart), crc_check_(crc_check) {} |   Dsmr(uart::UARTComponent *uart, bool crc_check) : uart::UARTDevice(uart), crc_check_(crc_check) {} | ||||||
|  |  | ||||||
|  |   void setup() override; | ||||||
|   void loop() override; |   void loop() override; | ||||||
|  |  | ||||||
|   bool parse_telegram(); |   bool parse_telegram(); | ||||||
| @@ -71,6 +69,10 @@ class Dsmr : public Component, public uart::UARTDevice { | |||||||
|   void dump_config() override; |   void dump_config() override; | ||||||
|  |  | ||||||
|   void set_decryption_key(const std::string &decryption_key); |   void set_decryption_key(const std::string &decryption_key); | ||||||
|  |   void set_max_telegram_length(size_t length) { this->max_telegram_len_ = length; } | ||||||
|  |   void set_request_pin(GPIOPin *request_pin) { this->request_pin_ = request_pin; } | ||||||
|  |   void set_request_interval(uint32_t interval) { this->request_interval_ = interval; } | ||||||
|  |   void set_receive_timeout(uint32_t timeout) { this->receive_timeout_ = timeout; } | ||||||
|  |  | ||||||
| // Sensor setters | // Sensor setters | ||||||
| #define DSMR_SET_SENSOR(s) \ | #define DSMR_SET_SENSOR(s) \ | ||||||
| @@ -83,7 +85,8 @@ class Dsmr : public Component, public uart::UARTDevice { | |||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   void receive_telegram_(); |   void receive_telegram_(); | ||||||
|   void receive_encrypted_(); |   void receive_encrypted_telegram_(); | ||||||
|  |   void reset_telegram_(); | ||||||
|  |  | ||||||
|   /// Wait for UART data to become available within the read timeout. |   /// Wait for UART data to become available within the read timeout. | ||||||
|   /// |   /// | ||||||
| @@ -96,11 +99,26 @@ class Dsmr : public Component, public uart::UARTDevice { | |||||||
|   /// lost in the process. |   /// lost in the process. | ||||||
|   bool available_within_timeout_(); |   bool available_within_timeout_(); | ||||||
|  |  | ||||||
|   // Telegram buffer |   // Request telegram | ||||||
|   char telegram_[MAX_TELEGRAM_LENGTH]; |   uint32_t request_interval_; | ||||||
|   int telegram_len_{0}; |   bool request_interval_reached_(); | ||||||
|  |   GPIOPin *request_pin_{nullptr}; | ||||||
|  |   uint32_t last_request_time_{0}; | ||||||
|  |   bool requesting_data_{false}; | ||||||
|  |   bool ready_to_request_data_(); | ||||||
|  |   void start_requesting_data_(); | ||||||
|  |   void stop_requesting_data_(); | ||||||
|  |  | ||||||
|   // Serial parser |   // Read telegram | ||||||
|  |   uint32_t receive_timeout_; | ||||||
|  |   bool receive_timeout_reached_(); | ||||||
|  |   size_t max_telegram_len_; | ||||||
|  |   char *telegram_{nullptr}; | ||||||
|  |   size_t bytes_read_{0}; | ||||||
|  |   uint8_t *crypt_telegram_{nullptr}; | ||||||
|  |   size_t crypt_telegram_len_{0}; | ||||||
|  |   size_t crypt_bytes_read_{0}; | ||||||
|  |   uint32_t last_read_time_{0}; | ||||||
|   bool header_found_{false}; |   bool header_found_{false}; | ||||||
|   bool footer_found_{false}; |   bool footer_found_{false}; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -311,9 +311,16 @@ async def to_code(config): | |||||||
|         ) |         ) | ||||||
|         add_idf_sdkconfig_option("CONFIG_COMPILER_OPTIMIZATION_DEFAULT", False) |         add_idf_sdkconfig_option("CONFIG_COMPILER_OPTIMIZATION_DEFAULT", False) | ||||||
|         add_idf_sdkconfig_option("CONFIG_COMPILER_OPTIMIZATION_SIZE", True) |         add_idf_sdkconfig_option("CONFIG_COMPILER_OPTIMIZATION_SIZE", True) | ||||||
|  |  | ||||||
|         # Increase freertos tick speed from 100Hz to 1kHz so that delay() resolution is 1ms |         # Increase freertos tick speed from 100Hz to 1kHz so that delay() resolution is 1ms | ||||||
|         add_idf_sdkconfig_option("CONFIG_FREERTOS_HZ", 1000) |         add_idf_sdkconfig_option("CONFIG_FREERTOS_HZ", 1000) | ||||||
|  |  | ||||||
|  |         # Setup watchdog | ||||||
|  |         add_idf_sdkconfig_option("CONFIG_ESP_TASK_WDT", True) | ||||||
|  |         add_idf_sdkconfig_option("CONFIG_ESP_TASK_WDT_PANIC", True) | ||||||
|  |         add_idf_sdkconfig_option("CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0", False) | ||||||
|  |         add_idf_sdkconfig_option("CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU1", False) | ||||||
|  |  | ||||||
|         cg.add_platformio_option("board_build.partitions", "partitions.csv") |         cg.add_platformio_option("board_build.partitions", "partitions.csv") | ||||||
|  |  | ||||||
|         for name, value in conf[CONF_SDKCONFIG_OPTIONS].items(): |         for name, value in conf[CONF_SDKCONFIG_OPTIONS].items(): | ||||||
|   | |||||||
| @@ -6,12 +6,17 @@ | |||||||
| #include <freertos/FreeRTOS.h> | #include <freertos/FreeRTOS.h> | ||||||
| #include <freertos/task.h> | #include <freertos/task.h> | ||||||
| #include <esp_idf_version.h> | #include <esp_idf_version.h> | ||||||
|  | #include <esp_task_wdt.h> | ||||||
| #include <soc/rtc.h> | #include <soc/rtc.h> | ||||||
|  |  | ||||||
| #if ESP_IDF_VERSION_MAJOR >= 4 | #if ESP_IDF_VERSION_MAJOR >= 4 | ||||||
| #include <hal/cpu_hal.h> | #include <hal/cpu_hal.h> | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
|  | #ifdef USE_ARDUINO | ||||||
|  | #include <esp32-hal.h> | ||||||
|  | #endif | ||||||
|  |  | ||||||
| void setup(); | void setup(); | ||||||
| void loop(); | void loop(); | ||||||
|  |  | ||||||
| @@ -29,24 +34,24 @@ void arch_restart() { | |||||||
|     yield(); |     yield(); | ||||||
|   } |   } | ||||||
| } | } | ||||||
| void IRAM_ATTR HOT arch_feed_wdt() { |  | ||||||
| #ifdef USE_ARDUINO |  | ||||||
| #if CONFIG_ARDUINO_RUNNING_CORE == 0 |  | ||||||
| #ifdef CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU0 |  | ||||||
|   // ESP32 uses "Task Watchdog" which is hooked to the FreeRTOS idle task. |  | ||||||
|   // To cause the Watchdog to be triggered we need to put the current task |  | ||||||
|   // to sleep to get the idle task scheduled. |  | ||||||
|   delay(1); |  | ||||||
| #endif |  | ||||||
| #endif |  | ||||||
| #endif  // USE_ARDUINO |  | ||||||
|  |  | ||||||
| #ifdef USE_ESP_IDF | void arch_init() { | ||||||
| #ifdef CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU0 |   // Enable the task watchdog only on the loop task (from which we're currently running) | ||||||
|   delay(1); | #if defined(USE_ESP_IDF) | ||||||
|  |   esp_task_wdt_add(nullptr); | ||||||
|  |   // Idle task watchdog is disabled on ESP-IDF | ||||||
|  | #elif defined(USE_ARDUINO) | ||||||
|  |   enableLoopWDT(); | ||||||
|  |   // Disable idle task watchdog on the core we're using (Arduino pins the process to a core) | ||||||
|  | #if CONFIG_ARDUINO_RUNNING_CORE == 0 | ||||||
|  |   disableCore0WDT(); | ||||||
|  | #endif | ||||||
|  | #if CONFIG_ARDUINO_RUNNING_CORE == 1 | ||||||
|  |   disableCore1WDT(); | ||||||
|  | #endif | ||||||
| #endif | #endif | ||||||
| #endif  // USE_ESP_IDF |  | ||||||
| } | } | ||||||
|  | void IRAM_ATTR HOT arch_feed_wdt() { esp_task_wdt_reset(); } | ||||||
|  |  | ||||||
| uint8_t progmem_read_byte(const uint8_t *addr) { return *addr; } | uint8_t progmem_read_byte(const uint8_t *addr) { return *addr; } | ||||||
| uint32_t arch_get_cpu_cycle_count() { | uint32_t arch_get_cpu_cycle_count() { | ||||||
|   | |||||||
| @@ -4,7 +4,7 @@ from esphome.components import binary_sensor, output, esp32_ble_server | |||||||
| from esphome.const import CONF_ID | from esphome.const import CONF_ID | ||||||
|  |  | ||||||
|  |  | ||||||
| AUTO_LOAD = ["binary_sensor", "output", "improv", "esp32_ble_server"] | AUTO_LOAD = ["binary_sensor", "output", "esp32_ble_server"] | ||||||
| CODEOWNERS = ["@jesserockz"] | CODEOWNERS = ["@jesserockz"] | ||||||
| CONFLICTS_WITH = ["esp32_ble_tracker", "esp32_ble_beacon"] | CONFLICTS_WITH = ["esp32_ble_tracker", "esp32_ble_beacon"] | ||||||
| DEPENDENCIES = ["wifi", "esp32"] | DEPENDENCIES = ["wifi", "esp32"] | ||||||
| @@ -56,6 +56,7 @@ async def to_code(config): | |||||||
|     cg.add(ble_server.register_service_component(var)) |     cg.add(ble_server.register_service_component(var)) | ||||||
|  |  | ||||||
|     cg.add_define("USE_IMPROV") |     cg.add_define("USE_IMPROV") | ||||||
|  |     cg.add_library("esphome/Improv", "1.0.0") | ||||||
|  |  | ||||||
|     cg.add(var.set_identify_duration(config[CONF_IDENTIFY_DURATION])) |     cg.add(var.set_identify_duration(config[CONF_IDENTIFY_DURATION])) | ||||||
|     cg.add(var.set_authorized_duration(config[CONF_AUTHORIZED_DURATION])) |     cg.add(var.set_authorized_duration(config[CONF_AUTHORIZED_DURATION])) | ||||||
|   | |||||||
| @@ -1,9 +1,8 @@ | |||||||
| #pragma once | #pragma once | ||||||
|  |  | ||||||
| #include "esphome/components/binary_sensor/binary_sensor.h" | #include "esphome/components/binary_sensor/binary_sensor.h" | ||||||
| #include "esphome/components/esp32_ble_server/ble_server.h" |  | ||||||
| #include "esphome/components/esp32_ble_server/ble_characteristic.h" | #include "esphome/components/esp32_ble_server/ble_characteristic.h" | ||||||
| #include "esphome/components/improv/improv.h" | #include "esphome/components/esp32_ble_server/ble_server.h" | ||||||
| #include "esphome/components/output/binary_output.h" | #include "esphome/components/output/binary_output.h" | ||||||
| #include "esphome/components/wifi/wifi_component.h" | #include "esphome/components/wifi/wifi_component.h" | ||||||
| #include "esphome/core/component.h" | #include "esphome/core/component.h" | ||||||
| @@ -12,6 +11,8 @@ | |||||||
|  |  | ||||||
| #ifdef USE_ESP32 | #ifdef USE_ESP32 | ||||||
|  |  | ||||||
|  | #include <improv.h> | ||||||
|  |  | ||||||
| namespace esphome { | namespace esphome { | ||||||
| namespace esp32_improv { | namespace esp32_improv { | ||||||
|  |  | ||||||
|   | |||||||
| @@ -206,61 +206,3 @@ ESP8266_BOARD_PINS = { | |||||||
|     "wio_node": {"LED": 2, "GROVE": 15, "D0": 3, "D1": 5, "BUTTON": 0}, |     "wio_node": {"LED": 2, "GROVE": 15, "D0": 3, "D1": 5, "BUTTON": 0}, | ||||||
|     "xinabox_cw01": {"SDA": 2, "SCL": 14, "LED": 5, "LED_RED": 12, "LED_GREEN": 13}, |     "xinabox_cw01": {"SDA": 2, "SCL": 14, "LED": 5, "LED_RED": 12, "LED_GREEN": 13}, | ||||||
| } | } | ||||||
|  |  | ||||||
| FLASH_SIZE_1_MB = 2 ** 20 |  | ||||||
| FLASH_SIZE_512_KB = FLASH_SIZE_1_MB // 2 |  | ||||||
| FLASH_SIZE_2_MB = 2 * FLASH_SIZE_1_MB |  | ||||||
| FLASH_SIZE_4_MB = 4 * FLASH_SIZE_1_MB |  | ||||||
| FLASH_SIZE_16_MB = 16 * FLASH_SIZE_1_MB |  | ||||||
|  |  | ||||||
| ESP8266_FLASH_SIZES = { |  | ||||||
|     "d1": FLASH_SIZE_4_MB, |  | ||||||
|     "d1_mini": FLASH_SIZE_4_MB, |  | ||||||
|     "d1_mini_lite": FLASH_SIZE_1_MB, |  | ||||||
|     "d1_mini_pro": FLASH_SIZE_16_MB, |  | ||||||
|     "esp01": FLASH_SIZE_512_KB, |  | ||||||
|     "esp01_1m": FLASH_SIZE_1_MB, |  | ||||||
|     "esp07": FLASH_SIZE_4_MB, |  | ||||||
|     "esp12e": FLASH_SIZE_4_MB, |  | ||||||
|     "esp210": FLASH_SIZE_4_MB, |  | ||||||
|     "esp8285": FLASH_SIZE_1_MB, |  | ||||||
|     "esp_wroom_02": FLASH_SIZE_2_MB, |  | ||||||
|     "espduino": FLASH_SIZE_4_MB, |  | ||||||
|     "espectro": FLASH_SIZE_4_MB, |  | ||||||
|     "espino": FLASH_SIZE_4_MB, |  | ||||||
|     "espinotee": FLASH_SIZE_4_MB, |  | ||||||
|     "espmxdevkit": FLASH_SIZE_1_MB, |  | ||||||
|     "espresso_lite_v1": FLASH_SIZE_4_MB, |  | ||||||
|     "espresso_lite_v2": FLASH_SIZE_4_MB, |  | ||||||
|     "gen4iod": FLASH_SIZE_512_KB, |  | ||||||
|     "heltec_wifi_kit_8": FLASH_SIZE_4_MB, |  | ||||||
|     "huzzah": FLASH_SIZE_4_MB, |  | ||||||
|     "inventone": FLASH_SIZE_4_MB, |  | ||||||
|     "modwifi": FLASH_SIZE_2_MB, |  | ||||||
|     "nodemcu": FLASH_SIZE_4_MB, |  | ||||||
|     "nodemcuv2": FLASH_SIZE_4_MB, |  | ||||||
|     "oak": FLASH_SIZE_4_MB, |  | ||||||
|     "phoenix_v1": FLASH_SIZE_4_MB, |  | ||||||
|     "phoenix_v2": FLASH_SIZE_4_MB, |  | ||||||
|     "sonoff_basic": FLASH_SIZE_1_MB, |  | ||||||
|     "sonoff_s20": FLASH_SIZE_1_MB, |  | ||||||
|     "sonoff_sv": FLASH_SIZE_1_MB, |  | ||||||
|     "sonoff_th": FLASH_SIZE_1_MB, |  | ||||||
|     "sparkfunBlynk": FLASH_SIZE_4_MB, |  | ||||||
|     "thing": FLASH_SIZE_512_KB, |  | ||||||
|     "thingdev": FLASH_SIZE_512_KB, |  | ||||||
|     "wifi_slot": FLASH_SIZE_1_MB, |  | ||||||
|     "wifiduino": FLASH_SIZE_4_MB, |  | ||||||
|     "wifinfo": FLASH_SIZE_1_MB, |  | ||||||
|     "wio_link": FLASH_SIZE_4_MB, |  | ||||||
|     "wio_node": FLASH_SIZE_4_MB, |  | ||||||
|     "xinabox_cw01": FLASH_SIZE_4_MB, |  | ||||||
| } |  | ||||||
|  |  | ||||||
| ESP8266_LD_SCRIPTS = { |  | ||||||
|     FLASH_SIZE_512_KB: ("eagle.flash.512k0.ld", "eagle.flash.512k.ld"), |  | ||||||
|     FLASH_SIZE_1_MB: ("eagle.flash.1m0.ld", "eagle.flash.1m.ld"), |  | ||||||
|     FLASH_SIZE_2_MB: ("eagle.flash.2m.ld", "eagle.flash.2m.ld"), |  | ||||||
|     FLASH_SIZE_4_MB: ("eagle.flash.4m.ld", "eagle.flash.4m.ld"), |  | ||||||
|     FLASH_SIZE_16_MB: ("eagle.flash.16m.ld", "eagle.flash.16m14m.ld"), |  | ||||||
| } |  | ||||||
|   | |||||||
| @@ -20,6 +20,7 @@ void arch_restart() { | |||||||
|     yield(); |     yield(); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  | void arch_init() {} | ||||||
| void IRAM_ATTR HOT arch_feed_wdt() { | void IRAM_ATTR HOT arch_feed_wdt() { | ||||||
|   ESP.wdtFeed();  // NOLINT(readability-static-accessed-through-instance) |   ESP.wdtFeed();  // NOLINT(readability-static-accessed-through-instance) | ||||||
| } | } | ||||||
| @@ -27,7 +28,7 @@ void IRAM_ATTR HOT arch_feed_wdt() { | |||||||
| uint8_t progmem_read_byte(const uint8_t *addr) { | uint8_t progmem_read_byte(const uint8_t *addr) { | ||||||
|   return pgm_read_byte(addr);  // NOLINT |   return pgm_read_byte(addr);  // NOLINT | ||||||
| } | } | ||||||
| uint32_t arch_get_cpu_cycle_count() { | uint32_t IRAM_ATTR HOT arch_get_cpu_cycle_count() { | ||||||
|   return ESP.getCycleCount();  // NOLINT(readability-static-accessed-through-instance) |   return ESP.getCycleCount();  // NOLINT(readability-static-accessed-through-instance) | ||||||
| } | } | ||||||
| uint32_t arch_get_cpu_freq_hz() { return F_CPU; } | uint32_t arch_get_cpu_freq_hz() { return F_CPU; } | ||||||
|   | |||||||
| @@ -9,7 +9,7 @@ namespace esp8266 { | |||||||
| static const char *const TAG = "esp8266"; | static const char *const TAG = "esp8266"; | ||||||
|  |  | ||||||
| static int IRAM_ATTR flags_to_mode(gpio::Flags flags, uint8_t pin) { | static int IRAM_ATTR flags_to_mode(gpio::Flags flags, uint8_t pin) { | ||||||
|   if (flags == gpio::FLAG_INPUT) { |   if (flags == gpio::FLAG_INPUT) {  // NOLINT(bugprone-branch-clone) | ||||||
|     return INPUT; |     return INPUT; | ||||||
|   } else if (flags == gpio::FLAG_OUTPUT) { |   } else if (flags == gpio::FLAG_OUTPUT) { | ||||||
|     return OUTPUT; |     return OUTPUT; | ||||||
|   | |||||||
| @@ -55,7 +55,7 @@ static inline bool esp_rtc_user_mem_write(uint32_t index, uint32_t value) { | |||||||
|  |  | ||||||
| extern "C" uint32_t _SPIFFS_end;  // NOLINT | extern "C" uint32_t _SPIFFS_end;  // NOLINT | ||||||
|  |  | ||||||
| static const uint32_t get_esp8266_flash_sector() { | static uint32_t get_esp8266_flash_sector() { | ||||||
|   union { |   union { | ||||||
|     uint32_t *ptr; |     uint32_t *ptr; | ||||||
|     uint32_t uint; |     uint32_t uint; | ||||||
| @@ -63,7 +63,7 @@ static const uint32_t get_esp8266_flash_sector() { | |||||||
|   data.ptr = &_SPIFFS_end; |   data.ptr = &_SPIFFS_end; | ||||||
|   return (data.uint - 0x40200000) / SPI_FLASH_SEC_SIZE; |   return (data.uint - 0x40200000) / SPI_FLASH_SEC_SIZE; | ||||||
| } | } | ||||||
| static const uint32_t get_esp8266_flash_address() { return get_esp8266_flash_sector() * SPI_FLASH_SEC_SIZE; } | static uint32_t get_esp8266_flash_address() { return get_esp8266_flash_sector() * SPI_FLASH_SEC_SIZE; } | ||||||
|  |  | ||||||
| template<class It> uint32_t calculate_crc(It first, It last, uint32_t type) { | template<class It> uint32_t calculate_crc(It first, It last, uint32_t type) { | ||||||
|   uint32_t crc = type; |   uint32_t crc = type; | ||||||
|   | |||||||
| @@ -12,6 +12,8 @@ from esphome.const import ( | |||||||
|     CONF_TYPE, |     CONF_TYPE, | ||||||
|     CONF_EXTERNAL_COMPONENTS, |     CONF_EXTERNAL_COMPONENTS, | ||||||
|     CONF_PATH, |     CONF_PATH, | ||||||
|  |     CONF_USERNAME, | ||||||
|  |     CONF_PASSWORD, | ||||||
| ) | ) | ||||||
| from esphome.core import CORE | from esphome.core import CORE | ||||||
| from esphome import git, loader | from esphome import git, loader | ||||||
| @@ -27,6 +29,8 @@ TYPE_LOCAL = "local" | |||||||
| GIT_SCHEMA = { | GIT_SCHEMA = { | ||||||
|     cv.Required(CONF_URL): cv.url, |     cv.Required(CONF_URL): cv.url, | ||||||
|     cv.Optional(CONF_REF): cv.git_ref, |     cv.Optional(CONF_REF): cv.git_ref, | ||||||
|  |     cv.Optional(CONF_USERNAME): cv.string, | ||||||
|  |     cv.Optional(CONF_PASSWORD): cv.string, | ||||||
| } | } | ||||||
| LOCAL_SCHEMA = { | LOCAL_SCHEMA = { | ||||||
|     cv.Required(CONF_PATH): cv.directory, |     cv.Required(CONF_PATH): cv.directory, | ||||||
| @@ -99,6 +103,8 @@ def _process_git_config(config: dict, refresh) -> str: | |||||||
|         ref=config.get(CONF_REF), |         ref=config.get(CONF_REF), | ||||||
|         refresh=refresh, |         refresh=refresh, | ||||||
|         domain=DOMAIN, |         domain=DOMAIN, | ||||||
|  |         username=config.get(CONF_USERNAME), | ||||||
|  |         password=config.get(CONF_PASSWORD), | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
|     if (repo_dir / "esphome" / "components").is_dir(): |     if (repo_dir / "esphome" / "components").is_dir(): | ||||||
|   | |||||||
| @@ -75,7 +75,7 @@ void EZOSensor::loop() { | |||||||
|     return; |     return; | ||||||
|  |  | ||||||
|   // some sensors return multiple comma-separated values, terminate string after first one |   // some sensors return multiple comma-separated values, terminate string after first one | ||||||
|   for (int i = 1; i < sizeof(buf) - 1; i++) |   for (size_t i = 1; i < sizeof(buf) - 1; i++) | ||||||
|     if (buf[i] == ',') |     if (buf[i] == ',') | ||||||
|       buf[i] = '\0'; |       buf[i] = '\0'; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -86,7 +86,7 @@ void Graph::draw(DisplayBuffer *buff, uint16_t x_offset, uint16_t y_offset, Colo | |||||||
|     // Look back in trace data to best-fit into local range |     // Look back in trace data to best-fit into local range | ||||||
|     float mx = NAN; |     float mx = NAN; | ||||||
|     float mn = NAN; |     float mn = NAN; | ||||||
|     for (int16_t i = 0; i < this->width_; i++) { |     for (uint32_t i = 0; i < this->width_; i++) { | ||||||
|       for (auto *trace : traces_) { |       for (auto *trace : traces_) { | ||||||
|         float v = trace->get_tracedata()->get_value(i); |         float v = trace->get_tracedata()->get_value(i); | ||||||
|         if (!std::isnan(v)) { |         if (!std::isnan(v)) { | ||||||
| @@ -132,7 +132,7 @@ void Graph::draw(DisplayBuffer *buff, uint16_t x_offset, uint16_t y_offset, Colo | |||||||
|   if (!std::isnan(this->gridspacing_y_)) { |   if (!std::isnan(this->gridspacing_y_)) { | ||||||
|     for (int y = yn; y <= ym; y++) { |     for (int y = yn; y <= ym; y++) { | ||||||
|       int16_t py = (int16_t) roundf((this->height_ - 1) * (1.0 - (float) (y - yn) / (ym - yn))); |       int16_t py = (int16_t) roundf((this->height_ - 1) * (1.0 - (float) (y - yn) / (ym - yn))); | ||||||
|       for (int x = 0; x < this->width_; x += 2) { |       for (uint32_t x = 0; x < this->width_; x += 2) { | ||||||
|         buff->draw_pixel_at(x_offset + x, y_offset + py, color); |         buff->draw_pixel_at(x_offset + x, y_offset + py, color); | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
| @@ -147,7 +147,7 @@ void Graph::draw(DisplayBuffer *buff, uint16_t x_offset, uint16_t y_offset, Colo | |||||||
|       ESP_LOGW(TAG, "Graphing reducing x-scale to prevent too many gridlines"); |       ESP_LOGW(TAG, "Graphing reducing x-scale to prevent too many gridlines"); | ||||||
|     } |     } | ||||||
|     for (int i = 0; i <= n; i++) { |     for (int i = 0; i <= n; i++) { | ||||||
|       for (int y = 0; y < this->height_; y += 2) { |       for (uint32_t y = 0; y < this->height_; y += 2) { | ||||||
|         buff->draw_pixel_at(x_offset + i * (this->width_ - 1) / n, y_offset + y, color); |         buff->draw_pixel_at(x_offset + i * (this->width_ - 1) / n, y_offset + y, color); | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
| @@ -158,14 +158,14 @@ void Graph::draw(DisplayBuffer *buff, uint16_t x_offset, uint16_t y_offset, Colo | |||||||
|   for (auto *trace : traces_) { |   for (auto *trace : traces_) { | ||||||
|     Color c = trace->get_line_color(); |     Color c = trace->get_line_color(); | ||||||
|     uint16_t thick = trace->get_line_thickness(); |     uint16_t thick = trace->get_line_thickness(); | ||||||
|     for (int16_t i = 0; i < this->width_; i++) { |     for (uint32_t i = 0; i < this->width_; i++) { | ||||||
|       float v = (trace->get_tracedata()->get_value(i) - ymin) / yrange; |       float v = (trace->get_tracedata()->get_value(i) - ymin) / yrange; | ||||||
|       if (!std::isnan(v) && (thick > 0)) { |       if (!std::isnan(v) && (thick > 0)) { | ||||||
|         int16_t x = this->width_ - 1 - i; |         int16_t x = this->width_ - 1 - i; | ||||||
|         uint8_t b = (i % (thick * LineType::PATTERN_LENGTH)) / thick; |         uint8_t b = (i % (thick * LineType::PATTERN_LENGTH)) / thick; | ||||||
|         if (((uint8_t) trace->get_line_type() & (1 << b)) == (1 << b)) { |         if (((uint8_t) trace->get_line_type() & (1 << b)) == (1 << b)) { | ||||||
|           int16_t y = (int16_t) roundf((this->height_ - 1) * (1.0 - v)) - thick / 2; |           int16_t y = (int16_t) roundf((this->height_ - 1) * (1.0 - v)) - thick / 2; | ||||||
|           for (int16_t t = 0; t < thick; t++) { |           for (uint16_t t = 0; t < thick; t++) { | ||||||
|             buff->draw_pixel_at(x_offset + x, y_offset + y + t, c); |             buff->draw_pixel_at(x_offset + x, y_offset + y + t, c); | ||||||
|           } |           } | ||||||
|         } |         } | ||||||
| @@ -179,8 +179,8 @@ void GraphLegend::init(Graph *g) { | |||||||
|   parent_ = g; |   parent_ = g; | ||||||
|  |  | ||||||
|   // Determine maximum expected text and value width / height |   // Determine maximum expected text and value width / height | ||||||
|   int txtw = 0, txtos = 0, txtbl = 0, txth = 0; |   int txtw = 0, txth = 0; | ||||||
|   int valw = 0, valos = 0, valbl = 0, valh = 0; |   int valw = 0, valh = 0; | ||||||
|   int lt = 0; |   int lt = 0; | ||||||
|   for (auto *trace : g->traces_) { |   for (auto *trace : g->traces_) { | ||||||
|     std::string txtstr = trace->get_name(); |     std::string txtstr = trace->get_name(); | ||||||
| @@ -320,7 +320,7 @@ void Graph::draw_legend(display::DisplayBuffer *buff, uint16_t x_offset, uint16_ | |||||||
|  |  | ||||||
|     if (legend_->lines_) { |     if (legend_->lines_) { | ||||||
|       uint16_t thick = trace->get_line_thickness(); |       uint16_t thick = trace->get_line_thickness(); | ||||||
|       for (int16_t i = 0; i < legend_->x0_ * 4 / 3; i++) { |       for (int i = 0; i < legend_->x0_ * 4 / 3; i++) { | ||||||
|         uint8_t b = (i % (thick * LineType::PATTERN_LENGTH)) / thick; |         uint8_t b = (i % (thick * LineType::PATTERN_LENGTH)) / thick; | ||||||
|         if (((uint8_t) trace->get_line_type() & (1 << b)) == (1 << b)) { |         if (((uint8_t) trace->get_line_type() & (1 << b)) == (1 << b)) { | ||||||
|           buff->vertical_line(x - legend_->x0_ * 2 / 3 + i, y + legend_->yl_ - thick / 2, thick, |           buff->vertical_line(x - legend_->x0_ * 2 / 3 + i, y + legend_->yl_ - thick / 2, thick, | ||||||
|   | |||||||
| @@ -30,6 +30,7 @@ PROTOCOLS = { | |||||||
|     "gree": Protocol.PROTOCOL_GREE, |     "gree": Protocol.PROTOCOL_GREE, | ||||||
|     "greeya": Protocol.PROTOCOL_GREEYAA, |     "greeya": Protocol.PROTOCOL_GREEYAA, | ||||||
|     "greeyan": Protocol.PROTOCOL_GREEYAN, |     "greeyan": Protocol.PROTOCOL_GREEYAN, | ||||||
|  |     "greeyac": Protocol.PROTOCOL_GREEYAC, | ||||||
|     "hisense_aud": Protocol.PROTOCOL_HISENSE_AUD, |     "hisense_aud": Protocol.PROTOCOL_HISENSE_AUD, | ||||||
|     "hitachi": Protocol.PROTOCOL_HITACHI, |     "hitachi": Protocol.PROTOCOL_HITACHI, | ||||||
|     "hyundai": Protocol.PROTOCOL_HYUNDAI, |     "hyundai": Protocol.PROTOCOL_HYUNDAI, | ||||||
| @@ -111,4 +112,6 @@ def to_code(config): | |||||||
|     cg.add(var.set_max_temperature(config[CONF_MIN_TEMPERATURE])) |     cg.add(var.set_max_temperature(config[CONF_MIN_TEMPERATURE])) | ||||||
|     cg.add(var.set_min_temperature(config[CONF_MAX_TEMPERATURE])) |     cg.add(var.set_min_temperature(config[CONF_MAX_TEMPERATURE])) | ||||||
|  |  | ||||||
|     cg.add_library("tonia/HeatpumpIR", "1.0.15") |     # PIO isn't updating releases, so referencing the release tag directly. See: | ||||||
|  |     # https://github.com/ToniA/arduino-heatpumpir/commit/0948c619d86407a4e50e8db2f3c193e0576c86fd | ||||||
|  |     cg.add_library("", "", "https://github.com/ToniA/arduino-heatpumpir.git#1.0.18") | ||||||
|   | |||||||
| @@ -25,6 +25,7 @@ const std::map<Protocol, std::function<HeatpumpIR *()>> PROTOCOL_CONSTRUCTOR_MAP | |||||||
|     {PROTOCOL_GREE, []() { return new GreeGenericHeatpumpIR(); }},                           // NOLINT |     {PROTOCOL_GREE, []() { return new GreeGenericHeatpumpIR(); }},                           // NOLINT | ||||||
|     {PROTOCOL_GREEYAA, []() { return new GreeYAAHeatpumpIR(); }},                            // NOLINT |     {PROTOCOL_GREEYAA, []() { return new GreeYAAHeatpumpIR(); }},                            // NOLINT | ||||||
|     {PROTOCOL_GREEYAN, []() { return new GreeYANHeatpumpIR(); }},                            // NOLINT |     {PROTOCOL_GREEYAN, []() { return new GreeYANHeatpumpIR(); }},                            // NOLINT | ||||||
|  |     {PROTOCOL_GREEYAC, []() { return new GreeYACHeatpumpIR(); }},                            // NOLINT | ||||||
|     {PROTOCOL_HISENSE_AUD, []() { return new HisenseHeatpumpIR(); }},                        // NOLINT |     {PROTOCOL_HISENSE_AUD, []() { return new HisenseHeatpumpIR(); }},                        // NOLINT | ||||||
|     {PROTOCOL_HITACHI, []() { return new HitachiHeatpumpIR(); }},                            // NOLINT |     {PROTOCOL_HITACHI, []() { return new HitachiHeatpumpIR(); }},                            // NOLINT | ||||||
|     {PROTOCOL_HYUNDAI, []() { return new HyundaiHeatpumpIR(); }},                            // NOLINT |     {PROTOCOL_HYUNDAI, []() { return new HyundaiHeatpumpIR(); }},                            // NOLINT | ||||||
| @@ -61,6 +62,19 @@ void HeatpumpIRClimate::setup() { | |||||||
|   } |   } | ||||||
|   this->heatpump_ir_ = protocol_constructor->second(); |   this->heatpump_ir_ = protocol_constructor->second(); | ||||||
|   climate_ir::ClimateIR::setup(); |   climate_ir::ClimateIR::setup(); | ||||||
|  |   if (this->sensor_) { | ||||||
|  |     this->sensor_->add_on_state_callback([this](float state) { | ||||||
|  |       this->current_temperature = state; | ||||||
|  |  | ||||||
|  |       IRSenderESPHome esp_sender(this->transmitter_); | ||||||
|  |       this->heatpump_ir_->send(esp_sender, uint8_t(lround(this->current_temperature + 0.5))); | ||||||
|  |  | ||||||
|  |       // current temperature changed, publish state | ||||||
|  |       this->publish_state(); | ||||||
|  |     }); | ||||||
|  |     this->current_temperature = this->sensor_->state; | ||||||
|  |   } else | ||||||
|  |     this->current_temperature = NAN; | ||||||
| } | } | ||||||
|  |  | ||||||
| void HeatpumpIRClimate::transmit_state() { | void HeatpumpIRClimate::transmit_state() { | ||||||
| @@ -171,8 +185,7 @@ void HeatpumpIRClimate::transmit_state() { | |||||||
|  |  | ||||||
|   temperature_cmd = (uint8_t) clamp(this->target_temperature, this->min_temperature_, this->max_temperature_); |   temperature_cmd = (uint8_t) clamp(this->target_temperature, this->min_temperature_, this->max_temperature_); | ||||||
|  |  | ||||||
|   IRSenderESPHome esp_sender(0, this->transmitter_); |   IRSenderESPHome esp_sender(this->transmitter_); | ||||||
|  |  | ||||||
|   heatpump_ir_->send(esp_sender, power_mode_cmd, operating_mode_cmd, fan_speed_cmd, temperature_cmd, swing_v_cmd, |   heatpump_ir_->send(esp_sender, power_mode_cmd, operating_mode_cmd, fan_speed_cmd, temperature_cmd, swing_v_cmd, | ||||||
|                      swing_h_cmd); |                      swing_h_cmd); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -25,6 +25,7 @@ enum Protocol { | |||||||
|   PROTOCOL_GREE, |   PROTOCOL_GREE, | ||||||
|   PROTOCOL_GREEYAA, |   PROTOCOL_GREEYAA, | ||||||
|   PROTOCOL_GREEYAN, |   PROTOCOL_GREEYAN, | ||||||
|  |   PROTOCOL_GREEYAC, | ||||||
|   PROTOCOL_HISENSE_AUD, |   PROTOCOL_HISENSE_AUD, | ||||||
|   PROTOCOL_HITACHI, |   PROTOCOL_HITACHI, | ||||||
|   PROTOCOL_HYUNDAI, |   PROTOCOL_HYUNDAI, | ||||||
|   | |||||||
| @@ -11,8 +11,8 @@ namespace heatpumpir { | |||||||
|  |  | ||||||
| class IRSenderESPHome : public IRSender { | class IRSenderESPHome : public IRSender { | ||||||
|  public: |  public: | ||||||
|   IRSenderESPHome(uint8_t pin, remote_transmitter::RemoteTransmitterComponent *transmitter) |   IRSenderESPHome(remote_transmitter::RemoteTransmitterComponent *transmitter) | ||||||
|       : IRSender(pin), transmit_(transmitter->transmit()){}; |       : IRSender(0), transmit_(transmitter->transmit()){}; | ||||||
|   void setFrequency(int frequency) override;  // NOLINT(readability-identifier-naming) |   void setFrequency(int frequency) override;  // NOLINT(readability-identifier-naming) | ||||||
|   void space(int space_length) override; |   void space(int space_length) override; | ||||||
|   void mark(int mark_length) override; |   void mark(int mark_length) override; | ||||||
|   | |||||||
| @@ -299,9 +299,7 @@ bool HitachiClimate::parse_swing_(const uint8_t remote_state[]) { | |||||||
|       GETBITS8(remote_state[HITACHI_AC344_SWINGH_BYTE], HITACHI_AC344_SWINGH_OFFSET, HITACHI_AC344_SWINGH_SIZE); |       GETBITS8(remote_state[HITACHI_AC344_SWINGH_BYTE], HITACHI_AC344_SWINGH_OFFSET, HITACHI_AC344_SWINGH_SIZE); | ||||||
|   ESP_LOGV(TAG, "SwingH: %02X %02X", remote_state[HITACHI_AC344_SWINGH_BYTE], swing_modeh); |   ESP_LOGV(TAG, "SwingH: %02X %02X", remote_state[HITACHI_AC344_SWINGH_BYTE], swing_modeh); | ||||||
|  |  | ||||||
|   if ((swing_modeh & 0x7) == 0x0) { |   if ((swing_modeh & 0x3) == 0x3) { | ||||||
|     this->swing_mode = climate::CLIMATE_SWING_HORIZONTAL; |  | ||||||
|   } else if ((swing_modeh & 0x3) == 0x3) { |  | ||||||
|     this->swing_mode = climate::CLIMATE_SWING_OFF; |     this->swing_mode = climate::CLIMATE_SWING_OFF; | ||||||
|   } else { |   } else { | ||||||
|     this->swing_mode = climate::CLIMATE_SWING_HORIZONTAL; |     this->swing_mode = climate::CLIMATE_SWING_HORIZONTAL; | ||||||
|   | |||||||
| @@ -300,9 +300,7 @@ bool HitachiClimate::parse_swing_(const uint8_t remote_state[]) { | |||||||
|                                                HITACHI_AC424_SWINGH_SIZE); |                                                HITACHI_AC424_SWINGH_SIZE); | ||||||
|   ESP_LOGV(TAG, "SwingH: %02X %02X", remote_state[HITACHI_AC424_SWINGH_BYTE], swing_modeh); |   ESP_LOGV(TAG, "SwingH: %02X %02X", remote_state[HITACHI_AC424_SWINGH_BYTE], swing_modeh); | ||||||
|  |  | ||||||
|   if ((swing_modeh & 0x7) == 0x0) { |   if ((swing_modeh & 0x3) == 0x3) { | ||||||
|     this->swing_mode = climate::CLIMATE_SWING_HORIZONTAL; |  | ||||||
|   } else if ((swing_modeh & 0x3) == 0x3) { |  | ||||||
|     this->swing_mode = climate::CLIMATE_SWING_OFF; |     this->swing_mode = climate::CLIMATE_SWING_OFF; | ||||||
|   } else { |   } else { | ||||||
|     this->swing_mode = climate::CLIMATE_SWING_HORIZONTAL; |     this->swing_mode = climate::CLIMATE_SWING_HORIZONTAL; | ||||||
|   | |||||||
| @@ -1,6 +1,5 @@ | |||||||
| #pragma once | #pragma once | ||||||
|  |  | ||||||
| #ifdef USE_ARDUINO |  | ||||||
| #include <cstdint> | #include <cstdint> | ||||||
|  |  | ||||||
| namespace esphome { | namespace esphome { | ||||||
| @@ -13,5 +12,3 @@ class AbstractAQICalculator { | |||||||
|  |  | ||||||
| }  // namespace hm3301 | }  // namespace hm3301 | ||||||
| }  // namespace esphome | }  // namespace esphome | ||||||
|  |  | ||||||
| #endif  // USE_ARDUINO |  | ||||||
|   | |||||||
| @@ -1,7 +1,5 @@ | |||||||
| #pragma once | #pragma once | ||||||
|  |  | ||||||
| #ifdef USE_ARDUINO |  | ||||||
|  |  | ||||||
| #include "abstract_aqi_calculator.h" | #include "abstract_aqi_calculator.h" | ||||||
|  |  | ||||||
| namespace esphome { | namespace esphome { | ||||||
| @@ -48,5 +46,3 @@ class AQICalculator : public AbstractAQICalculator { | |||||||
|  |  | ||||||
| }  // namespace hm3301 | }  // namespace hm3301 | ||||||
| }  // namespace esphome | }  // namespace esphome | ||||||
|  |  | ||||||
| #endif  // USE_ARDUINO |  | ||||||
|   | |||||||
| @@ -1,7 +1,5 @@ | |||||||
| #pragma once | #pragma once | ||||||
|  |  | ||||||
| #ifdef USE_ARDUINO |  | ||||||
|  |  | ||||||
| #include "caqi_calculator.h" | #include "caqi_calculator.h" | ||||||
| #include "aqi_calculator.h" | #include "aqi_calculator.h" | ||||||
|  |  | ||||||
| @@ -29,5 +27,3 @@ class AQICalculatorFactory { | |||||||
|  |  | ||||||
| }  // namespace hm3301 | }  // namespace hm3301 | ||||||
| }  // namespace esphome | }  // namespace esphome | ||||||
|  |  | ||||||
| #endif  // USE_ARDUINO |  | ||||||
|   | |||||||
| @@ -1,7 +1,5 @@ | |||||||
| #pragma once | #pragma once | ||||||
|  |  | ||||||
| #ifdef USE_ARDUINO |  | ||||||
|  |  | ||||||
| #include "esphome/core/log.h" | #include "esphome/core/log.h" | ||||||
| #include "abstract_aqi_calculator.h" | #include "abstract_aqi_calculator.h" | ||||||
|  |  | ||||||
| @@ -52,5 +50,3 @@ class CAQICalculator : public AbstractAQICalculator { | |||||||
|  |  | ||||||
| }  // namespace hm3301 | }  // namespace hm3301 | ||||||
| }  // namespace esphome | }  // namespace esphome | ||||||
|  |  | ||||||
| #endif  // USE_ARDUINO |  | ||||||
|   | |||||||
| @@ -1,5 +1,3 @@ | |||||||
| #ifdef USE_ARDUINO |  | ||||||
|  |  | ||||||
| #include "esphome/core/log.h" | #include "esphome/core/log.h" | ||||||
| #include "hm3301.h" | #include "hm3301.h" | ||||||
|  |  | ||||||
| @@ -14,9 +12,8 @@ static const uint8_t PM_10_0_VALUE_INDEX = 7; | |||||||
|  |  | ||||||
| void HM3301Component::setup() { | void HM3301Component::setup() { | ||||||
|   ESP_LOGCONFIG(TAG, "Setting up HM3301..."); |   ESP_LOGCONFIG(TAG, "Setting up HM3301..."); | ||||||
|   hm3301_ = make_unique<HM330X>(); |   if (i2c::ERROR_OK != this->write(&SELECT_COMM_CMD, 1)) { | ||||||
|   error_code_ = hm3301_->init(); |     error_code_ = ERROR_COMM; | ||||||
|   if (error_code_ != NO_ERROR) { |  | ||||||
|     this->mark_failed(); |     this->mark_failed(); | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
| @@ -38,7 +35,7 @@ void HM3301Component::dump_config() { | |||||||
| float HM3301Component::get_setup_priority() const { return setup_priority::DATA; } | float HM3301Component::get_setup_priority() const { return setup_priority::DATA; } | ||||||
|  |  | ||||||
| void HM3301Component::update() { | void HM3301Component::update() { | ||||||
|   if (!this->read_sensor_value_(data_buffer_)) { |   if (this->read(data_buffer_, 29) != i2c::ERROR_OK) { | ||||||
|     ESP_LOGW(TAG, "Read result failed"); |     ESP_LOGW(TAG, "Read result failed"); | ||||||
|     this->status_set_warning(); |     this->status_set_warning(); | ||||||
|     return; |     return; | ||||||
| @@ -87,8 +84,6 @@ void HM3301Component::update() { | |||||||
|   this->status_clear_warning(); |   this->status_clear_warning(); | ||||||
| } | } | ||||||
|  |  | ||||||
| bool HM3301Component::read_sensor_value_(uint8_t *data) { return !hm3301_->read_sensor_value(data, 29); } |  | ||||||
|  |  | ||||||
| bool HM3301Component::validate_checksum_(const uint8_t *data) { | bool HM3301Component::validate_checksum_(const uint8_t *data) { | ||||||
|   uint8_t sum = 0; |   uint8_t sum = 0; | ||||||
|   for (int i = 0; i < 28; i++) { |   for (int i = 0; i < 28; i++) { | ||||||
| @@ -104,5 +99,3 @@ uint16_t HM3301Component::get_sensor_value_(const uint8_t *data, uint8_t i) { | |||||||
|  |  | ||||||
| }  // namespace hm3301 | }  // namespace hm3301 | ||||||
| }  // namespace esphome | }  // namespace esphome | ||||||
|  |  | ||||||
| #endif  // USE_ARDUINO |  | ||||||
|   | |||||||
| @@ -1,17 +1,15 @@ | |||||||
| #pragma once | #pragma once | ||||||
|  |  | ||||||
| #ifdef USE_ARDUINO |  | ||||||
|  |  | ||||||
| #include "esphome/core/component.h" | #include "esphome/core/component.h" | ||||||
| #include "esphome/components/sensor/sensor.h" | #include "esphome/components/sensor/sensor.h" | ||||||
| #include "esphome/components/i2c/i2c.h" | #include "esphome/components/i2c/i2c.h" | ||||||
| #include "aqi_calculator_factory.h" | #include "aqi_calculator_factory.h" | ||||||
|  |  | ||||||
| #include <Seeed_HM330X.h> |  | ||||||
|  |  | ||||||
| namespace esphome { | namespace esphome { | ||||||
| namespace hm3301 { | namespace hm3301 { | ||||||
|  |  | ||||||
|  | static const uint8_t SELECT_COMM_CMD = 0X88; | ||||||
|  |  | ||||||
| class HM3301Component : public PollingComponent, public i2c::I2CDevice { | class HM3301Component : public PollingComponent, public i2c::I2CDevice { | ||||||
|  public: |  public: | ||||||
|   HM3301Component() = default; |   HM3301Component() = default; | ||||||
| @@ -29,9 +27,12 @@ class HM3301Component : public PollingComponent, public i2c::I2CDevice { | |||||||
|   void update() override; |   void update() override; | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   std::unique_ptr<HM330X> hm3301_; |   enum { | ||||||
|  |     NO_ERROR = 0, | ||||||
|   HM330XErrorCode error_code_{NO_ERROR}; |     ERROR_PARAM = -1, | ||||||
|  |     ERROR_COMM = -2, | ||||||
|  |     ERROR_OTHERS = -128, | ||||||
|  |   } error_code_{NO_ERROR}; | ||||||
|  |  | ||||||
|   uint8_t data_buffer_[30]; |   uint8_t data_buffer_[30]; | ||||||
|  |  | ||||||
| @@ -43,12 +44,9 @@ class HM3301Component : public PollingComponent, public i2c::I2CDevice { | |||||||
|   AQICalculatorType aqi_calc_type_; |   AQICalculatorType aqi_calc_type_; | ||||||
|   AQICalculatorFactory aqi_calculator_factory_ = AQICalculatorFactory(); |   AQICalculatorFactory aqi_calculator_factory_ = AQICalculatorFactory(); | ||||||
|  |  | ||||||
|   bool read_sensor_value_(uint8_t *); |  | ||||||
|   bool validate_checksum_(const uint8_t *); |   bool validate_checksum_(const uint8_t *); | ||||||
|   uint16_t get_sensor_value_(const uint8_t *, uint8_t); |   uint16_t get_sensor_value_(const uint8_t *, uint8_t); | ||||||
| }; | }; | ||||||
|  |  | ||||||
| }  // namespace hm3301 | }  // namespace hm3301 | ||||||
| }  // namespace esphome | }  // namespace esphome | ||||||
|  |  | ||||||
| #endif  // USE_ARDUINO |  | ||||||
|   | |||||||
| @@ -84,7 +84,6 @@ CONFIG_SCHEMA = cv.All( | |||||||
|     .extend(cv.polling_component_schema("60s")) |     .extend(cv.polling_component_schema("60s")) | ||||||
|     .extend(i2c.i2c_device_schema(0x40)), |     .extend(i2c.i2c_device_schema(0x40)), | ||||||
|     _validate, |     _validate, | ||||||
|     cv.only_with_arduino, |  | ||||||
| ) | ) | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -109,6 +108,3 @@ async def to_code(config): | |||||||
|         sens = await sensor.new_sensor(config[CONF_AQI]) |         sens = await sensor.new_sensor(config[CONF_AQI]) | ||||||
|         cg.add(var.set_aqi_sensor(sens)) |         cg.add(var.set_aqi_sensor(sens)) | ||||||
|         cg.add(var.set_aqi_calculation_type(config[CONF_AQI][CONF_CALCULATION_TYPE])) |         cg.add(var.set_aqi_calculation_type(config[CONF_AQI][CONF_CALCULATION_TYPE])) | ||||||
|  |  | ||||||
|     # https://platformio.org/lib/show/6306/Grove%20-%20Laser%20PM2.5%20Sensor%20HM3301 |  | ||||||
|     cg.add_library("seeed-studio/Grove - Laser PM2.5 Sensor HM3301", "1.0.3") |  | ||||||
|   | |||||||
| @@ -114,8 +114,8 @@ CONFIG_SCHEMA = ( | |||||||
|  |  | ||||||
|  |  | ||||||
| def auto_data_rate(config): | def auto_data_rate(config): | ||||||
|     interval_sec = config[CONF_UPDATE_INTERVAL].seconds |     interval_msec = config[CONF_UPDATE_INTERVAL].total_milliseconds | ||||||
|     interval_hz = 1.0 / interval_sec |     interval_hz = 1000.0 / interval_msec | ||||||
|     for datarate in sorted(HMC5883LDatarates.keys()): |     for datarate in sorted(HMC5883LDatarates.keys()): | ||||||
|         if float(datarate) >= interval_hz: |         if float(datarate) >= interval_hz: | ||||||
|             return HMC5883LDatarates[datarate] |             return HMC5883LDatarates[datarate] | ||||||
|   | |||||||
| @@ -86,8 +86,8 @@ void ILI9341Display::update() { | |||||||
|  |  | ||||||
| void ILI9341Display::display_() { | void ILI9341Display::display_() { | ||||||
|   // we will only update the changed window to the display |   // we will only update the changed window to the display | ||||||
|   int w = this->x_high_ - this->x_low_ + 1; |   uint16_t w = this->x_high_ - this->x_low_ + 1; | ||||||
|   int h = this->y_high_ - this->y_low_ + 1; |   uint16_t h = this->y_high_ - this->y_low_ + 1; | ||||||
|  |  | ||||||
|   set_addr_window_(this->x_low_, this->y_low_, w, h); |   set_addr_window_(this->x_low_, this->y_low_, w, h); | ||||||
|   this->start_data_(); |   this->start_data_(); | ||||||
|   | |||||||
| @@ -1 +0,0 @@ | |||||||
| CODEOWNERS = ["@jesserockz"] |  | ||||||
| @@ -1,99 +0,0 @@ | |||||||
| #include "improv.h" |  | ||||||
|  |  | ||||||
| namespace improv { |  | ||||||
|  |  | ||||||
| ImprovCommand parse_improv_data(const std::vector<uint8_t> &data, bool check_checksum) { |  | ||||||
|   return parse_improv_data(data.data(), data.size(), check_checksum); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| ImprovCommand parse_improv_data(const uint8_t *data, size_t length, bool check_checksum) { |  | ||||||
|   ImprovCommand improv_command; |  | ||||||
|   Command command = (Command) data[0]; |  | ||||||
|   uint8_t data_length = data[1]; |  | ||||||
|  |  | ||||||
|   if (data_length != length - 2 - check_checksum) { |  | ||||||
|     improv_command.command = UNKNOWN; |  | ||||||
|     return improv_command; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   if (check_checksum) { |  | ||||||
|     uint8_t checksum = data[length - 1]; |  | ||||||
|  |  | ||||||
|     uint32_t calculated_checksum = 0; |  | ||||||
|     for (uint8_t i = 0; i < length - 1; i++) { |  | ||||||
|       calculated_checksum += data[i]; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     if ((uint8_t) calculated_checksum != checksum) { |  | ||||||
|       improv_command.command = BAD_CHECKSUM; |  | ||||||
|       return improv_command; |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   if (command == WIFI_SETTINGS) { |  | ||||||
|     uint8_t ssid_length = data[2]; |  | ||||||
|     uint8_t ssid_start = 3; |  | ||||||
|     size_t ssid_end = ssid_start + ssid_length; |  | ||||||
|  |  | ||||||
|     uint8_t pass_length = data[ssid_end]; |  | ||||||
|     size_t pass_start = ssid_end + 1; |  | ||||||
|     size_t pass_end = pass_start + pass_length; |  | ||||||
|  |  | ||||||
|     std::string ssid(data + ssid_start, data + ssid_end); |  | ||||||
|     std::string password(data + pass_start, data + pass_end); |  | ||||||
|     return {.command = command, .ssid = ssid, .password = password}; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   improv_command.command = command; |  | ||||||
|   return improv_command; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| std::vector<uint8_t> build_rpc_response(Command command, const std::vector<std::string> &datum, bool add_checksum) { |  | ||||||
|   std::vector<uint8_t> out; |  | ||||||
|   uint32_t length = 0; |  | ||||||
|   out.push_back(command); |  | ||||||
|   for (auto str : datum) { |  | ||||||
|     uint8_t len = str.length(); |  | ||||||
|     length += len; |  | ||||||
|     out.push_back(len); |  | ||||||
|     out.insert(out.end(), str.begin(), str.end()); |  | ||||||
|   } |  | ||||||
|   out.insert(out.begin() + 1, length); |  | ||||||
|  |  | ||||||
|   if (add_checksum) { |  | ||||||
|     uint32_t calculated_checksum = 0; |  | ||||||
|  |  | ||||||
|     for (uint8_t byte : out) { |  | ||||||
|       calculated_checksum += byte; |  | ||||||
|     } |  | ||||||
|     out.push_back(calculated_checksum); |  | ||||||
|   } |  | ||||||
|   return out; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #ifdef ARDUINO |  | ||||||
| std::vector<uint8_t> build_rpc_response(Command command, const std::vector<String> &datum, bool add_checksum) { |  | ||||||
|   std::vector<uint8_t> out; |  | ||||||
|   uint32_t length = 0; |  | ||||||
|   out.push_back(command); |  | ||||||
|   for (auto str : datum) { |  | ||||||
|     uint8_t len = str.length(); |  | ||||||
|     length += len; |  | ||||||
|     out.push_back(len); |  | ||||||
|     out.insert(out.end(), str.begin(), str.end()); |  | ||||||
|   } |  | ||||||
|   out.insert(out.begin() + 1, length); |  | ||||||
|  |  | ||||||
|   if (add_checksum) { |  | ||||||
|     uint32_t calculated_checksum = 0; |  | ||||||
|  |  | ||||||
|     for (uint8_t byte : out) { |  | ||||||
|       calculated_checksum += byte; |  | ||||||
|     } |  | ||||||
|     out.push_back(calculated_checksum); |  | ||||||
|   } |  | ||||||
|   return out; |  | ||||||
| } |  | ||||||
| #endif  // ARDUINO |  | ||||||
|  |  | ||||||
| }  // namespace improv |  | ||||||
| @@ -1,63 +0,0 @@ | |||||||
| #pragma once |  | ||||||
|  |  | ||||||
| #ifdef ARDUINO |  | ||||||
| #include "WString.h" |  | ||||||
| #endif  // ARDUINO |  | ||||||
|  |  | ||||||
| #include <cstdint> |  | ||||||
| #include <string> |  | ||||||
| #include <vector> |  | ||||||
|  |  | ||||||
| namespace improv { |  | ||||||
|  |  | ||||||
| static const char *const SERVICE_UUID = "00467768-6228-2272-4663-277478268000"; |  | ||||||
| static const char *const STATUS_UUID = "00467768-6228-2272-4663-277478268001"; |  | ||||||
| static const char *const ERROR_UUID = "00467768-6228-2272-4663-277478268002"; |  | ||||||
| static const char *const RPC_COMMAND_UUID = "00467768-6228-2272-4663-277478268003"; |  | ||||||
| static const char *const RPC_RESULT_UUID = "00467768-6228-2272-4663-277478268004"; |  | ||||||
| static const char *const CAPABILITIES_UUID = "00467768-6228-2272-4663-277478268005"; |  | ||||||
|  |  | ||||||
| enum Error : uint8_t { |  | ||||||
|   ERROR_NONE = 0x00, |  | ||||||
|   ERROR_INVALID_RPC = 0x01, |  | ||||||
|   ERROR_UNKNOWN_RPC = 0x02, |  | ||||||
|   ERROR_UNABLE_TO_CONNECT = 0x03, |  | ||||||
|   ERROR_NOT_AUTHORIZED = 0x04, |  | ||||||
|   ERROR_UNKNOWN = 0xFF, |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| enum State : uint8_t { |  | ||||||
|   STATE_STOPPED = 0x00, |  | ||||||
|   STATE_AWAITING_AUTHORIZATION = 0x01, |  | ||||||
|   STATE_AUTHORIZED = 0x02, |  | ||||||
|   STATE_PROVISIONING = 0x03, |  | ||||||
|   STATE_PROVISIONED = 0x04, |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| enum Command : uint8_t { |  | ||||||
|   UNKNOWN = 0x00, |  | ||||||
|   WIFI_SETTINGS = 0x01, |  | ||||||
|   IDENTIFY = 0x02, |  | ||||||
|   GET_CURRENT_STATE = 0x02, |  | ||||||
|   GET_DEVICE_INFO = 0x03, |  | ||||||
|   BAD_CHECKSUM = 0xFF, |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| static const uint8_t CAPABILITY_IDENTIFY = 0x01; |  | ||||||
|  |  | ||||||
| struct ImprovCommand { |  | ||||||
|   Command command; |  | ||||||
|   std::string ssid; |  | ||||||
|   std::string password; |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| ImprovCommand parse_improv_data(const std::vector<uint8_t> &data, bool check_checksum = true); |  | ||||||
| ImprovCommand parse_improv_data(const uint8_t *data, size_t length, bool check_checksum = true); |  | ||||||
|  |  | ||||||
| std::vector<uint8_t> build_rpc_response(Command command, const std::vector<std::string> &datum, |  | ||||||
|                                         bool add_checksum = true); |  | ||||||
| #ifdef ARDUINO |  | ||||||
| std::vector<uint8_t> build_rpc_response(Command command, const std::vector<String> &datum, bool add_checksum = true); |  | ||||||
| #endif  // ARDUINO |  | ||||||
|  |  | ||||||
| }  // namespace improv |  | ||||||
| @@ -5,7 +5,6 @@ import esphome.final_validate as fv | |||||||
|  |  | ||||||
| CODEOWNERS = ["@esphome/core"] | CODEOWNERS = ["@esphome/core"] | ||||||
| DEPENDENCIES = ["logger", "wifi"] | DEPENDENCIES = ["logger", "wifi"] | ||||||
| AUTO_LOAD = ["improv"] |  | ||||||
|  |  | ||||||
| improv_serial_ns = cg.esphome_ns.namespace("improv_serial") | improv_serial_ns = cg.esphome_ns.namespace("improv_serial") | ||||||
|  |  | ||||||
| @@ -31,3 +30,4 @@ FINAL_VALIDATE_SCHEMA = validate_logger_baud_rate | |||||||
| 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) | ||||||
|  |     cg.add_library("esphome/Improv", "1.0.0") | ||||||
|   | |||||||
| @@ -145,7 +145,7 @@ bool ImprovSerialComponent::parse_improv_serial_byte_(uint8_t byte) { | |||||||
|  |  | ||||||
|   if (at == 8 + data_len + 1) { |   if (at == 8 + data_len + 1) { | ||||||
|     uint8_t checksum = 0x00; |     uint8_t checksum = 0x00; | ||||||
|     for (uint8_t i = 0; i < at; i++) |     for (size_t i = 0; i < at; i++) | ||||||
|       checksum += raw[i]; |       checksum += raw[i]; | ||||||
|  |  | ||||||
|     if (checksum != byte) { |     if (checksum != byte) { | ||||||
|   | |||||||
| @@ -1,11 +1,12 @@ | |||||||
| #pragma once | #pragma once | ||||||
|  |  | ||||||
| #include "esphome/components/improv/improv.h" |  | ||||||
| #include "esphome/components/wifi/wifi_component.h" | #include "esphome/components/wifi/wifi_component.h" | ||||||
| #include "esphome/core/component.h" | #include "esphome/core/component.h" | ||||||
| #include "esphome/core/defines.h" | #include "esphome/core/defines.h" | ||||||
| #include "esphome/core/helpers.h" | #include "esphome/core/helpers.h" | ||||||
|  |  | ||||||
|  | #include <improv.h> | ||||||
|  |  | ||||||
| #ifdef USE_ARDUINO | #ifdef USE_ARDUINO | ||||||
| #include <HardwareSerial.h> | #include <HardwareSerial.h> | ||||||
| #endif | #endif | ||||||
|   | |||||||
| @@ -17,7 +17,7 @@ void GPIOLCDDisplay::setup() { | |||||||
|   this->enable_pin_->setup();  // OUTPUT |   this->enable_pin_->setup();  // OUTPUT | ||||||
|   this->enable_pin_->digital_write(false); |   this->enable_pin_->digital_write(false); | ||||||
|  |  | ||||||
|   for (uint8_t i = 0; i < (this->is_four_bit_mode() ? 4 : 8); i++) { |   for (uint8_t i = 0; i < (uint8_t)(this->is_four_bit_mode() ? 4u : 8u); i++) { | ||||||
|     this->data_pins_[i]->setup();  // OUTPUT |     this->data_pins_[i]->setup();  // OUTPUT | ||||||
|     this->data_pins_[i]->digital_write(false); |     this->data_pins_[i]->digital_write(false); | ||||||
|   } |   } | ||||||
|   | |||||||
| @@ -15,18 +15,38 @@ namespace ledc { | |||||||
|  |  | ||||||
| static const char *const TAG = "ledc.output"; | static const char *const TAG = "ledc.output"; | ||||||
|  |  | ||||||
|  | #ifdef USE_ESP_IDF | ||||||
|  | static const int MAX_RES_BITS = LEDC_TIMER_BIT_MAX - 1; | ||||||
|  | #if SOC_LEDC_SUPPORT_HS_MODE | ||||||
|  | // Only ESP32 has LEDC_HIGH_SPEED_MODE | ||||||
|  | inline ledc_mode_t get_speed_mode(uint8_t channel) { return channel < 8 ? LEDC_HIGH_SPEED_MODE : LEDC_LOW_SPEED_MODE; } | ||||||
|  | #else | ||||||
|  | // S2, C3, S3 only support LEDC_LOW_SPEED_MODE | ||||||
|  | // See | ||||||
|  | // https://docs.espressif.com/projects/esp-idf/en/latest/esp32c3/api-reference/peripherals/ledc.html#functionality-overview | ||||||
|  | inline ledc_mode_t get_speed_mode(uint8_t) { return LEDC_LOW_SPEED_MODE; } | ||||||
|  | #endif | ||||||
|  | #else | ||||||
|  | static const int MAX_RES_BITS = 20; | ||||||
|  | #endif | ||||||
|  |  | ||||||
| float ledc_max_frequency_for_bit_depth(uint8_t bit_depth) { return 80e6f / float(1 << bit_depth); } | float ledc_max_frequency_for_bit_depth(uint8_t bit_depth) { return 80e6f / float(1 << bit_depth); } | ||||||
| float ledc_min_frequency_for_bit_depth(uint8_t bit_depth) { |  | ||||||
|   const float max_div_num = ((1 << 20) - 1) / 256.0f; | float ledc_min_frequency_for_bit_depth(uint8_t bit_depth, bool low_frequency) { | ||||||
|  |   const float max_div_num = ((1 << MAX_RES_BITS) - 1) / (low_frequency ? 32.0f : 256.0f); | ||||||
|   return 80e6f / (max_div_num * float(1 << bit_depth)); |   return 80e6f / (max_div_num * float(1 << bit_depth)); | ||||||
| } | } | ||||||
|  |  | ||||||
| optional<uint8_t> ledc_bit_depth_for_frequency(float frequency) { | optional<uint8_t> ledc_bit_depth_for_frequency(float frequency) { | ||||||
|   for (int i = 20; i >= 1; i--) { |   ESP_LOGD(TAG, "Calculating resolution bit-depth for frequency %f", frequency); | ||||||
|     const float min_frequency = ledc_min_frequency_for_bit_depth(i); |   for (int i = MAX_RES_BITS; i >= 1; i--) { | ||||||
|  |     const float min_frequency = ledc_min_frequency_for_bit_depth(i, (frequency < 100)); | ||||||
|     const float max_frequency = ledc_max_frequency_for_bit_depth(i); |     const float max_frequency = ledc_max_frequency_for_bit_depth(i); | ||||||
|     if (min_frequency <= frequency && frequency <= max_frequency) |     if (min_frequency <= frequency && frequency <= max_frequency) { | ||||||
|  |       ESP_LOGD(TAG, "Resolution calculated as %d", i); | ||||||
|       return i; |       return i; | ||||||
|     } |     } | ||||||
|  |   } | ||||||
|   return {}; |   return {}; | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -48,7 +68,7 @@ void LEDCOutput::write_state(float state) { | |||||||
|   ledcWrite(this->channel_, duty); |   ledcWrite(this->channel_, duty); | ||||||
| #endif | #endif | ||||||
| #ifdef USE_ESP_IDF | #ifdef USE_ESP_IDF | ||||||
|   auto speed_mode = channel_ < 8 ? LEDC_HIGH_SPEED_MODE : LEDC_LOW_SPEED_MODE; |   auto speed_mode = get_speed_mode(channel_); | ||||||
|   auto chan_num = static_cast<ledc_channel_t>(channel_ % 8); |   auto chan_num = static_cast<ledc_channel_t>(channel_ % 8); | ||||||
|   ledc_set_duty(speed_mode, chan_num, duty); |   ledc_set_duty(speed_mode, chan_num, duty); | ||||||
|   ledc_update_duty(speed_mode, chan_num); |   ledc_update_duty(speed_mode, chan_num); | ||||||
| @@ -63,11 +83,15 @@ void LEDCOutput::setup() { | |||||||
|   ledcAttachPin(this->pin_->get_pin(), this->channel_); |   ledcAttachPin(this->pin_->get_pin(), this->channel_); | ||||||
| #endif | #endif | ||||||
| #ifdef USE_ESP_IDF | #ifdef USE_ESP_IDF | ||||||
|   auto speed_mode = channel_ < 8 ? LEDC_HIGH_SPEED_MODE : LEDC_LOW_SPEED_MODE; |   auto speed_mode = get_speed_mode(channel_); | ||||||
|   auto timer_num = static_cast<ledc_timer_t>((channel_ % 8) / 2); |   auto timer_num = static_cast<ledc_timer_t>((channel_ % 8) / 2); | ||||||
|   auto chan_num = static_cast<ledc_channel_t>(channel_ % 8); |   auto chan_num = static_cast<ledc_channel_t>(channel_ % 8); | ||||||
|  |  | ||||||
|   bit_depth_ = *ledc_bit_depth_for_frequency(frequency_); |   bit_depth_ = *ledc_bit_depth_for_frequency(frequency_); | ||||||
|  |   if (bit_depth_ < 1) { | ||||||
|  |     ESP_LOGW(TAG, "Frequency %f can't be achieved with any bit depth", frequency_); | ||||||
|  |     this->status_set_warning(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|   ledc_timer_config_t timer_conf{}; |   ledc_timer_config_t timer_conf{}; | ||||||
|   timer_conf.speed_mode = speed_mode; |   timer_conf.speed_mode = speed_mode; | ||||||
| @@ -114,7 +138,7 @@ void LEDCOutput::update_frequency(float frequency) { | |||||||
|     ESP_LOGW(TAG, "LEDC output hasn't been initialized yet!"); |     ESP_LOGW(TAG, "LEDC output hasn't been initialized yet!"); | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|   auto speed_mode = channel_ < 8 ? LEDC_HIGH_SPEED_MODE : LEDC_LOW_SPEED_MODE; |   auto speed_mode = get_speed_mode(channel_); | ||||||
|   auto timer_num = static_cast<ledc_timer_t>((channel_ % 8) / 2); |   auto timer_num = static_cast<ledc_timer_t>((channel_ % 8) / 2); | ||||||
|  |  | ||||||
|   ledc_timer_config_t timer_conf{}; |   ledc_timer_config_t timer_conf{}; | ||||||
|   | |||||||
| @@ -167,7 +167,7 @@ class AddressableScanEffect : public AddressableLightEffect { | |||||||
|     this->last_move_ = now; |     this->last_move_ = now; | ||||||
|  |  | ||||||
|     it.all() = Color::BLACK; |     it.all() = Color::BLACK; | ||||||
|     for (auto i = 0; i < this->scan_width_; i++) { |     for (uint32_t i = 0; i < this->scan_width_; i++) { | ||||||
|       it[this->at_led_ + i] = current_color; |       it[this->at_led_ + i] = current_color; | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -178,7 +178,7 @@ class AddressableScanEffect : public AddressableLightEffect { | |||||||
|   uint32_t move_interval_{}; |   uint32_t move_interval_{}; | ||||||
|   uint32_t scan_width_{1}; |   uint32_t scan_width_{1}; | ||||||
|   uint32_t last_move_{0}; |   uint32_t last_move_{0}; | ||||||
|   int at_led_{0}; |   uint32_t at_led_{0}; | ||||||
|   bool direction_{true}; |   bool direction_{true}; | ||||||
| }; | }; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -98,7 +98,7 @@ void LightCall::perform() { | |||||||
|     // EFFECT |     // EFFECT | ||||||
|     auto effect = this->effect_; |     auto effect = this->effect_; | ||||||
|     const char *effect_s; |     const char *effect_s; | ||||||
|     if (effect == 0) |     if (effect == 0u) | ||||||
|       effect_s = "None"; |       effect_s = "None"; | ||||||
|     else |     else | ||||||
|       effect_s = this->parent_->effects_[*this->effect_ - 1]->get_name().c_str(); |       effect_s = this->parent_->effects_[*this->effect_ - 1]->get_name().c_str(); | ||||||
|   | |||||||
| @@ -97,7 +97,7 @@ void LTR390Component::read_mode_(int mode_index) { | |||||||
|  |  | ||||||
|     // If there are more modes to read then begin the next |     // If there are more modes to read then begin the next | ||||||
|     // otherwise stop |     // otherwise stop | ||||||
|     if (mode_index + 1 < this->mode_funcs_.size()) { |     if (mode_index + 1 < (int) this->mode_funcs_.size()) { | ||||||
|       this->read_mode_(mode_index + 1); |       this->read_mode_(mode_index + 1); | ||||||
|     } else { |     } else { | ||||||
|       this->reading_ = false; |       this->reading_ = false; | ||||||
|   | |||||||
| @@ -203,16 +203,16 @@ float MAX31865Sensor::calc_temperature_(float rtd_ratio) { | |||||||
|     rtd_resistance *= 100; |     rtd_resistance *= 100; | ||||||
|   } |   } | ||||||
|   float rpoly = rtd_resistance; |   float rpoly = rtd_resistance; | ||||||
|   float neg_temp = -242.02; |   float neg_temp = -242.02f; | ||||||
|   neg_temp += 2.2228 * rpoly; |   neg_temp += 2.2228f * rpoly; | ||||||
|   rpoly *= rtd_resistance;  // square |   rpoly *= rtd_resistance;  // square | ||||||
|   neg_temp += 2.5859e-3 * rpoly; |   neg_temp += 2.5859e-3f * rpoly; | ||||||
|   rpoly *= rtd_resistance;  // ^3 |   rpoly *= rtd_resistance;  // ^3 | ||||||
|   neg_temp -= 4.8260e-6 * rpoly; |   neg_temp -= 4.8260e-6f * rpoly; | ||||||
|   rpoly *= rtd_resistance;  // ^4 |   rpoly *= rtd_resistance;  // ^4 | ||||||
|   neg_temp -= 2.8183e-8 * rpoly; |   neg_temp -= 2.8183e-8f * rpoly; | ||||||
|   rpoly *= rtd_resistance;  // ^5 |   rpoly *= rtd_resistance;  // ^5 | ||||||
|   neg_temp += 1.5243e-10 * rpoly; |   neg_temp += 1.5243e-10f * rpoly; | ||||||
|   return neg_temp; |   return neg_temp; | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -76,7 +76,7 @@ void MAX7219Component::loop() { | |||||||
|     this->stepsleft_ = 0; |     this->stepsleft_ = 0; | ||||||
|  |  | ||||||
|   // Return if there is no need to scroll or scroll is off |   // Return if there is no need to scroll or scroll is off | ||||||
|   if (!this->scroll_ || (this->max_displaybuffer_[0].size() <= get_width_internal())) { |   if (!this->scroll_ || (this->max_displaybuffer_[0].size() <= (size_t) get_width_internal())) { | ||||||
|     this->display(); |     this->display(); | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
| @@ -88,7 +88,7 @@ void MAX7219Component::loop() { | |||||||
|  |  | ||||||
|   // Dwell time at end of string in case of stop at end |   // Dwell time at end of string in case of stop at end | ||||||
|   if (this->scroll_mode_ == ScrollMode::STOP) { |   if (this->scroll_mode_ == ScrollMode::STOP) { | ||||||
|     if (this->stepsleft_ >= this->max_displaybuffer_[0].size() - get_width_internal() + 1) { |     if (this->stepsleft_ >= this->max_displaybuffer_[0].size() - (size_t) get_width_internal() + 1) { | ||||||
|       if (now - this->last_scroll_ >= this->scroll_dwell_) { |       if (now - this->last_scroll_ >= this->scroll_dwell_) { | ||||||
|         this->stepsleft_ = 0; |         this->stepsleft_ = 0; | ||||||
|         this->last_scroll_ = now; |         this->last_scroll_ = now; | ||||||
| @@ -155,7 +155,7 @@ int MAX7219Component::get_height_internal() { | |||||||
| int MAX7219Component::get_width_internal() { return this->num_chips_ / this->num_chip_lines_ * 8; } | int MAX7219Component::get_width_internal() { return this->num_chips_ / this->num_chip_lines_ * 8; } | ||||||
|  |  | ||||||
| void HOT MAX7219Component::draw_absolute_pixel_internal(int x, int y, Color color) { | void HOT MAX7219Component::draw_absolute_pixel_internal(int x, int y, Color color) { | ||||||
|   if (x + 1 > this->max_displaybuffer_[0].size()) {  // Extend the display buffer in case required |   if (x + 1 > (int) this->max_displaybuffer_[0].size()) {  // Extend the display buffer in case required | ||||||
|     for (int chip_line = 0; chip_line < this->num_chip_lines_; chip_line++) { |     for (int chip_line = 0; chip_line < this->num_chip_lines_; chip_line++) { | ||||||
|       this->max_displaybuffer_[chip_line].resize(x + 1, this->bckgrnd_); |       this->max_displaybuffer_[chip_line].resize(x + 1, this->bckgrnd_); | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -35,7 +35,6 @@ void MCP23S08::dump_config() { | |||||||
| } | } | ||||||
|  |  | ||||||
| bool MCP23S08::read_reg(uint8_t reg, uint8_t *value) { | bool MCP23S08::read_reg(uint8_t reg, uint8_t *value) { | ||||||
|   uint8_t data; |  | ||||||
|   this->enable(); |   this->enable(); | ||||||
|   this->transfer_byte(this->device_opcode_ | 1); |   this->transfer_byte(this->device_opcode_ | 1); | ||||||
|   this->transfer_byte(reg); |   this->transfer_byte(reg); | ||||||
|   | |||||||
| @@ -127,9 +127,6 @@ canbus::Error MCP2515::set_mode_(const CanctrlReqopMode mode) { | |||||||
| } | } | ||||||
|  |  | ||||||
| canbus::Error MCP2515::set_clk_out_(const CanClkOut divisor) { | canbus::Error MCP2515::set_clk_out_(const CanClkOut divisor) { | ||||||
|   canbus::Error res; |  | ||||||
|   uint8_t cfg3; |  | ||||||
|  |  | ||||||
|   if (divisor == CLKOUT_DISABLE) { |   if (divisor == CLKOUT_DISABLE) { | ||||||
|     /* Turn off CLKEN */ |     /* Turn off CLKEN */ | ||||||
|     modify_register_(MCP_CANCTRL, CANCTRL_CLKEN, 0x00); |     modify_register_(MCP_CANCTRL, CANCTRL_CLKEN, 0x00); | ||||||
|   | |||||||
| @@ -1,10 +1,21 @@ | |||||||
|  | import binascii | ||||||
| import esphome.codegen as cg | import esphome.codegen as cg | ||||||
| import esphome.config_validation as cv | import esphome.config_validation as cv | ||||||
| from esphome.components import modbus | from esphome.components import modbus | ||||||
| from esphome.const import CONF_ID, CONF_ADDRESS | from esphome.const import CONF_ADDRESS, CONF_ID, CONF_NAME, CONF_LAMBDA, CONF_OFFSET | ||||||
| from esphome.cpp_helpers import logging | from esphome.cpp_helpers import logging | ||||||
| from .const import ( | from .const import ( | ||||||
|  |     CONF_BITMASK, | ||||||
|  |     CONF_BYTE_OFFSET, | ||||||
|     CONF_COMMAND_THROTTLE, |     CONF_COMMAND_THROTTLE, | ||||||
|  |     CONF_CUSTOM_COMMAND, | ||||||
|  |     CONF_FORCE_NEW_RANGE, | ||||||
|  |     CONF_MODBUS_CONTROLLER_ID, | ||||||
|  |     CONF_REGISTER_COUNT, | ||||||
|  |     CONF_REGISTER_TYPE, | ||||||
|  |     CONF_RESPONSE_SIZE, | ||||||
|  |     CONF_SKIP_UPDATES, | ||||||
|  |     CONF_VALUE_TYPE, | ||||||
| ) | ) | ||||||
|  |  | ||||||
| CODEOWNERS = ["@martgras"] | CODEOWNERS = ["@martgras"] | ||||||
| @@ -37,6 +48,7 @@ MODBUS_FUNCTION_CODE = { | |||||||
| ModbusRegisterType_ns = modbus_controller_ns.namespace("ModbusRegisterType") | ModbusRegisterType_ns = modbus_controller_ns.namespace("ModbusRegisterType") | ||||||
| ModbusRegisterType = ModbusRegisterType_ns.enum("ModbusRegisterType") | ModbusRegisterType = ModbusRegisterType_ns.enum("ModbusRegisterType") | ||||||
| MODBUS_REGISTER_TYPE = { | MODBUS_REGISTER_TYPE = { | ||||||
|  |     "custom": ModbusRegisterType.CUSTOM, | ||||||
|     "coil": ModbusRegisterType.COIL, |     "coil": ModbusRegisterType.COIL, | ||||||
|     "discrete_input": ModbusRegisterType.DISCRETE_INPUT, |     "discrete_input": ModbusRegisterType.DISCRETE_INPUT, | ||||||
|     "holding": ModbusRegisterType.HOLDING, |     "holding": ModbusRegisterType.HOLDING, | ||||||
| @@ -95,6 +107,100 @@ CONFIG_SCHEMA = cv.All( | |||||||
| ) | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ModbusItemBaseSchema = cv.Schema( | ||||||
|  |     { | ||||||
|  |         cv.GenerateID(CONF_MODBUS_CONTROLLER_ID): cv.use_id(ModbusController), | ||||||
|  |         cv.Optional(CONF_ADDRESS): cv.positive_int, | ||||||
|  |         cv.Optional(CONF_CUSTOM_COMMAND): cv.ensure_list(cv.hex_uint8_t), | ||||||
|  |         cv.Exclusive( | ||||||
|  |             CONF_OFFSET, | ||||||
|  |             "offset", | ||||||
|  |             f"{CONF_OFFSET} and {CONF_BYTE_OFFSET} can't be used together", | ||||||
|  |         ): cv.positive_int, | ||||||
|  |         cv.Exclusive( | ||||||
|  |             CONF_BYTE_OFFSET, | ||||||
|  |             "offset", | ||||||
|  |             f"{CONF_OFFSET} and {CONF_BYTE_OFFSET} can't be used together", | ||||||
|  |         ): cv.positive_int, | ||||||
|  |         cv.Optional(CONF_BITMASK, default=0xFFFFFFFF): cv.hex_uint32_t, | ||||||
|  |         cv.Optional(CONF_SKIP_UPDATES, default=0): cv.positive_int, | ||||||
|  |         cv.Optional(CONF_FORCE_NEW_RANGE, default=False): cv.boolean, | ||||||
|  |         cv.Optional(CONF_LAMBDA): cv.returning_lambda, | ||||||
|  |         cv.Optional(CONF_RESPONSE_SIZE, default=0): cv.positive_int, | ||||||
|  |     }, | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def validate_modbus_register(config): | ||||||
|  |     if CONF_CUSTOM_COMMAND not in config and CONF_ADDRESS not in config: | ||||||
|  |         raise cv.Invalid( | ||||||
|  |             f" {CONF_ADDRESS} is a required property if '{CONF_CUSTOM_COMMAND}:' isn't used" | ||||||
|  |         ) | ||||||
|  |     if CONF_CUSTOM_COMMAND in config and CONF_REGISTER_TYPE in config: | ||||||
|  |         raise cv.Invalid( | ||||||
|  |             f"can't use '{CONF_REGISTER_TYPE}:' together with '{CONF_CUSTOM_COMMAND}:'", | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |     if CONF_CUSTOM_COMMAND not in config and CONF_REGISTER_TYPE not in config: | ||||||
|  |         raise cv.Invalid( | ||||||
|  |             f" {CONF_REGISTER_TYPE} is a required property if '{CONF_CUSTOM_COMMAND}:' isn't used" | ||||||
|  |         ) | ||||||
|  |     return config | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def modbus_calc_properties(config): | ||||||
|  |     byte_offset = 0 | ||||||
|  |     reg_count = 0 | ||||||
|  |     if CONF_OFFSET in config: | ||||||
|  |         byte_offset = config[CONF_OFFSET] | ||||||
|  |     # A CONF_BYTE_OFFSET setting overrides CONF_OFFSET | ||||||
|  |     if CONF_BYTE_OFFSET in config: | ||||||
|  |         byte_offset = config[CONF_BYTE_OFFSET] | ||||||
|  |     if CONF_REGISTER_COUNT in config: | ||||||
|  |         reg_count = config[CONF_REGISTER_COUNT] | ||||||
|  |     if CONF_VALUE_TYPE in config: | ||||||
|  |         value_type = config[CONF_VALUE_TYPE] | ||||||
|  |         if reg_count == 0: | ||||||
|  |             reg_count = TYPE_REGISTER_MAP[value_type] | ||||||
|  |     if CONF_CUSTOM_COMMAND in config: | ||||||
|  |         if CONF_ADDRESS not in config: | ||||||
|  |             # generate a unique modbus address using the hash of the name | ||||||
|  |             # CONF_NAME set even if only CONF_ID is used. | ||||||
|  |             # a modbus register address is required to add the item to sensormap | ||||||
|  |             value = config[CONF_NAME] | ||||||
|  |             if isinstance(value, str): | ||||||
|  |                 value = value.encode() | ||||||
|  |             config[CONF_ADDRESS] = binascii.crc_hqx(value, 0) | ||||||
|  |         config[CONF_REGISTER_TYPE] = ModbusRegisterType.CUSTOM | ||||||
|  |         config[CONF_FORCE_NEW_RANGE] = True | ||||||
|  |     return byte_offset, reg_count | ||||||
|  |  | ||||||
|  |  | ||||||
|  | async def add_modbus_base_properties( | ||||||
|  |     var, config, sensor_type, lamdba_param_type=cg.float_, lamdba_return_type=float | ||||||
|  | ): | ||||||
|  |     if CONF_CUSTOM_COMMAND in config: | ||||||
|  |         cg.add(var.set_custom_data(config[CONF_CUSTOM_COMMAND])) | ||||||
|  |  | ||||||
|  |     if config[CONF_RESPONSE_SIZE] > 0: | ||||||
|  |         cg.add(var.set_register_size(config[CONF_RESPONSE_SIZE])) | ||||||
|  |  | ||||||
|  |     if CONF_LAMBDA in config: | ||||||
|  |         template_ = await cg.process_lambda( | ||||||
|  |             config[CONF_LAMBDA], | ||||||
|  |             [ | ||||||
|  |                 (sensor_type.operator("ptr"), "item"), | ||||||
|  |                 (lamdba_param_type, "x"), | ||||||
|  |                 ( | ||||||
|  |                     cg.std_vector.template(cg.uint8).operator("const").operator("ref"), | ||||||
|  |                     "data", | ||||||
|  |                 ), | ||||||
|  |             ], | ||||||
|  |             return_type=cg.optional.template(lamdba_return_type), | ||||||
|  |         ) | ||||||
|  |         cg.add(var.set_template(template_)) | ||||||
|  |  | ||||||
|  |  | ||||||
| async def to_code(config): | async def to_code(config): | ||||||
|     var = cg.new_Pvariable(config[CONF_ID], config[CONF_COMMAND_THROTTLE]) |     var = cg.new_Pvariable(config[CONF_ID], config[CONF_COMMAND_THROTTLE]) | ||||||
|     cg.add(var.set_command_throttle(config[CONF_COMMAND_THROTTLE])) |     cg.add(var.set_command_throttle(config[CONF_COMMAND_THROTTLE])) | ||||||
| @@ -119,11 +225,3 @@ def function_code_to_register(function_code): | |||||||
|         "write_multiple_registers": ModbusRegisterType.HOLDING, |         "write_multiple_registers": ModbusRegisterType.HOLDING, | ||||||
|     } |     } | ||||||
|     return FUNCTION_CODE_TYPE_MAP[function_code] |     return FUNCTION_CODE_TYPE_MAP[function_code] | ||||||
|  |  | ||||||
|  |  | ||||||
| def find_by_value(dict, find_value): |  | ||||||
|     for (key, value) in MODBUS_REGISTER_TYPE.items(): |  | ||||||
|         print(find_value, value) |  | ||||||
|         if find_value == value: |  | ||||||
|             return key |  | ||||||
|     return "not found" |  | ||||||
|   | |||||||
| @@ -2,16 +2,18 @@ from esphome.components import binary_sensor | |||||||
| import esphome.config_validation as cv | import esphome.config_validation as cv | ||||||
| import esphome.codegen as cg | import esphome.codegen as cg | ||||||
|  |  | ||||||
| from esphome.const import CONF_ADDRESS, CONF_ID, CONF_LAMBDA, CONF_OFFSET | from esphome.const import CONF_ADDRESS, CONF_ID | ||||||
| from .. import ( | from .. import ( | ||||||
|     SensorItem, |     add_modbus_base_properties, | ||||||
|     modbus_controller_ns, |     modbus_controller_ns, | ||||||
|     ModbusController, |     modbus_calc_properties, | ||||||
|  |     validate_modbus_register, | ||||||
|  |     ModbusItemBaseSchema, | ||||||
|  |     SensorItem, | ||||||
|     MODBUS_REGISTER_TYPE, |     MODBUS_REGISTER_TYPE, | ||||||
| ) | ) | ||||||
| from ..const import ( | from ..const import ( | ||||||
|     CONF_BITMASK, |     CONF_BITMASK, | ||||||
|     CONF_BYTE_OFFSET, |  | ||||||
|     CONF_FORCE_NEW_RANGE, |     CONF_FORCE_NEW_RANGE, | ||||||
|     CONF_MODBUS_CONTROLLER_ID, |     CONF_MODBUS_CONTROLLER_ID, | ||||||
|     CONF_REGISTER_TYPE, |     CONF_REGISTER_TYPE, | ||||||
| @@ -27,30 +29,20 @@ ModbusBinarySensor = modbus_controller_ns.class_( | |||||||
| ) | ) | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = cv.All( | CONFIG_SCHEMA = cv.All( | ||||||
|     binary_sensor.BINARY_SENSOR_SCHEMA.extend( |     binary_sensor.BINARY_SENSOR_SCHEMA.extend(cv.COMPONENT_SCHEMA) | ||||||
|  |     .extend(ModbusItemBaseSchema) | ||||||
|  |     .extend( | ||||||
|         { |         { | ||||||
|             cv.GenerateID(): cv.declare_id(ModbusBinarySensor), |             cv.GenerateID(): cv.declare_id(ModbusBinarySensor), | ||||||
|             cv.GenerateID(CONF_MODBUS_CONTROLLER_ID): cv.use_id(ModbusController), |             cv.Optional(CONF_REGISTER_TYPE): cv.enum(MODBUS_REGISTER_TYPE), | ||||||
|             cv.Required(CONF_ADDRESS): cv.positive_int, |  | ||||||
|             cv.Required(CONF_REGISTER_TYPE): cv.enum(MODBUS_REGISTER_TYPE), |  | ||||||
|             cv.Optional(CONF_OFFSET, default=0): cv.positive_int, |  | ||||||
|             cv.Optional(CONF_BYTE_OFFSET): cv.positive_int, |  | ||||||
|             cv.Optional(CONF_BITMASK, default=0x1): cv.hex_uint32_t, |  | ||||||
|             cv.Optional(CONF_SKIP_UPDATES, default=0): cv.positive_int, |  | ||||||
|             cv.Optional(CONF_FORCE_NEW_RANGE, default=False): cv.boolean, |  | ||||||
|             cv.Optional(CONF_LAMBDA): cv.returning_lambda, |  | ||||||
|         } |         } | ||||||
|     ).extend(cv.COMPONENT_SCHEMA), |     ), | ||||||
|  |     validate_modbus_register, | ||||||
| ) | ) | ||||||
|  |  | ||||||
|  |  | ||||||
| async def to_code(config): | async def to_code(config): | ||||||
|     byte_offset = 0 |     byte_offset, _ = modbus_calc_properties(config) | ||||||
|     if CONF_OFFSET in config: |  | ||||||
|         byte_offset = config[CONF_OFFSET] |  | ||||||
|     # A CONF_BYTE_OFFSET setting overrides CONF_OFFSET |  | ||||||
|     if CONF_BYTE_OFFSET in config: |  | ||||||
|         byte_offset = config[CONF_BYTE_OFFSET] |  | ||||||
|     var = cg.new_Pvariable( |     var = cg.new_Pvariable( | ||||||
|         config[CONF_ID], |         config[CONF_ID], | ||||||
|         config[CONF_REGISTER_TYPE], |         config[CONF_REGISTER_TYPE], | ||||||
| @@ -65,17 +57,4 @@ async def to_code(config): | |||||||
|  |  | ||||||
|     paren = await cg.get_variable(config[CONF_MODBUS_CONTROLLER_ID]) |     paren = await cg.get_variable(config[CONF_MODBUS_CONTROLLER_ID]) | ||||||
|     cg.add(paren.add_sensor_item(var)) |     cg.add(paren.add_sensor_item(var)) | ||||||
|     if CONF_LAMBDA in config: |     await add_modbus_base_properties(var, config, ModbusBinarySensor, cg.float_, bool) | ||||||
|         template_ = await cg.process_lambda( |  | ||||||
|             config[CONF_LAMBDA], |  | ||||||
|             [ |  | ||||||
|                 (ModbusBinarySensor.operator("ptr"), "item"), |  | ||||||
|                 (cg.float_, "x"), |  | ||||||
|                 ( |  | ||||||
|                     cg.std_vector.template(cg.uint8).operator("const").operator("ref"), |  | ||||||
|                     "data", |  | ||||||
|                 ), |  | ||||||
|             ], |  | ||||||
|             return_type=cg.optional.template(bool), |  | ||||||
|         ) |  | ||||||
|         cg.add(var.set_template(template_)) |  | ||||||
|   | |||||||
| @@ -13,8 +13,6 @@ void ModbusBinarySensor::parse_and_publish(const std::vector<uint8_t> &data) { | |||||||
|  |  | ||||||
|   switch (this->register_type) { |   switch (this->register_type) { | ||||||
|     case ModbusRegisterType::DISCRETE_INPUT: |     case ModbusRegisterType::DISCRETE_INPUT: | ||||||
|       value = coil_from_vector(this->offset, data); |  | ||||||
|       break; |  | ||||||
|     case ModbusRegisterType::COIL: |     case ModbusRegisterType::COIL: | ||||||
|       // offset for coil is the actual number of the coil not the byte offset |       // offset for coil is the actual number of the coil not the byte offset | ||||||
|       value = coil_from_vector(this->offset, data); |       value = coil_from_vector(this->offset, data); | ||||||
|   | |||||||
| @@ -1,6 +1,7 @@ | |||||||
| CONF_BITMASK = "bitmask" | CONF_BITMASK = "bitmask" | ||||||
| CONF_BYTE_OFFSET = "byte_offset" | CONF_BYTE_OFFSET = "byte_offset" | ||||||
| CONF_COMMAND_THROTTLE = "command_throttle" | CONF_COMMAND_THROTTLE = "command_throttle" | ||||||
|  | CONF_CUSTOM_COMMAND = "custom_command" | ||||||
| CONF_FORCE_NEW_RANGE = "force_new_range" | CONF_FORCE_NEW_RANGE = "force_new_range" | ||||||
| CONF_MODBUS_CONTROLLER_ID = "modbus_controller_id" | CONF_MODBUS_CONTROLLER_ID = "modbus_controller_id" | ||||||
| CONF_MODBUS_FUNCTIONCODE = "modbus_functioncode" | CONF_MODBUS_FUNCTIONCODE = "modbus_functioncode" | ||||||
|   | |||||||
| @@ -28,7 +28,10 @@ bool ModbusController::send_next_command_() { | |||||||
|              command->register_address, command->register_count); |              command->register_address, command->register_count); | ||||||
|     command->send(); |     command->send(); | ||||||
|     this->last_command_timestamp_ = millis(); |     this->last_command_timestamp_ = millis(); | ||||||
|     if (!command->on_data_func) {  // No handler remove from queue directly after sending |     // remove from queue if no handler is defined or command was sent too often | ||||||
|  |     if (!command->on_data_func || command->send_countdown < 1) { | ||||||
|  |       ESP_LOGD(TAG, "Modbus command to device=%d register=0x%02X countdown=%d removed from queue after send", | ||||||
|  |                this->address_, command->register_address, command->send_countdown); | ||||||
|       command_queue_.pop_front(); |       command_queue_.pop_front(); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| @@ -69,24 +72,30 @@ void ModbusController::on_modbus_error(uint8_t function_code, uint8_t exception_ | |||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| void ModbusController::on_register_data(ModbusRegisterType register_type, uint16_t start_address, | std::map<uint64_t, SensorItem *>::iterator ModbusController::find_register_(ModbusRegisterType register_type, | ||||||
|                                         const std::vector<uint8_t> &data) { |                                                                             uint16_t start_address) { | ||||||
|   ESP_LOGV(TAG, "data for register address : 0x%X : ", start_address); |  | ||||||
|  |  | ||||||
|   auto vec_it = find_if(begin(register_ranges_), end(register_ranges_), [=](RegisterRange const &r) { |   auto vec_it = find_if(begin(register_ranges_), end(register_ranges_), [=](RegisterRange const &r) { | ||||||
|     return (r.start_address == start_address && r.register_type == register_type); |     return (r.start_address == start_address && r.register_type == register_type); | ||||||
|   }); |   }); | ||||||
|  |  | ||||||
|   if (vec_it == register_ranges_.end()) { |   if (vec_it == register_ranges_.end()) { | ||||||
|     ESP_LOGE(TAG, "Handle incoming data : No matching range for sensor found - start_address :  0x%X", start_address); |     ESP_LOGE(TAG, "No matching range for sensor found - start_address :  0x%X", start_address); | ||||||
|     return; |   } else { | ||||||
|   } |  | ||||||
|     auto map_it = sensormap_.find(vec_it->first_sensorkey); |     auto map_it = sensormap_.find(vec_it->first_sensorkey); | ||||||
|     if (map_it == sensormap_.end()) { |     if (map_it == sensormap_.end()) { | ||||||
|     ESP_LOGE(TAG, "Handle incoming data : No sensor found in at start_address :  0x%X (0x%llX)", start_address, |       ESP_LOGE(TAG, "No sensor found in at start_address :  0x%X (0x%llX)", start_address, vec_it->first_sensorkey); | ||||||
|              vec_it->first_sensorkey); |     } else { | ||||||
|     return; |       return sensormap_.find(vec_it->first_sensorkey); | ||||||
|     } |     } | ||||||
|  |   } | ||||||
|  |   // not found | ||||||
|  |   return std::end(sensormap_); | ||||||
|  | } | ||||||
|  | void ModbusController::on_register_data(ModbusRegisterType register_type, uint16_t start_address, | ||||||
|  |                                         const std::vector<uint8_t> &data) { | ||||||
|  |   ESP_LOGV(TAG, "data for register address : 0x%X : ", start_address); | ||||||
|  |  | ||||||
|  |   auto map_it = find_register_(register_type, start_address); | ||||||
|   // loop through all sensors with the same start address |   // loop through all sensors with the same start address | ||||||
|   while (map_it != sensormap_.end() && map_it->second->start_address == start_address) { |   while (map_it != sensormap_.end() && map_it->second->start_address == start_address) { | ||||||
|     if (map_it->second->register_type == register_type) { |     if (map_it->second->register_type == register_type) { | ||||||
| @@ -116,9 +125,23 @@ void ModbusController::update_range_(RegisterRange &r) { | |||||||
|   ESP_LOGV(TAG, "Range : %X Size: %x (%d) skip: %d", r.start_address, r.register_count, (int) r.register_type, |   ESP_LOGV(TAG, "Range : %X Size: %x (%d) skip: %d", r.start_address, r.register_count, (int) r.register_type, | ||||||
|            r.skip_updates_counter); |            r.skip_updates_counter); | ||||||
|   if (r.skip_updates_counter == 0) { |   if (r.skip_updates_counter == 0) { | ||||||
|     ModbusCommandItem command_item = |     // if a custom command is used the user supplied custom_data is only available in the SensorItem. | ||||||
|         ModbusCommandItem::create_read_command(this, r.register_type, r.start_address, r.register_count); |     if (r.register_type == ModbusRegisterType::CUSTOM) { | ||||||
|  |       auto it = this->find_register_(r.register_type, r.start_address); | ||||||
|  |       if (it != sensormap_.end()) { | ||||||
|  |         auto command_item = ModbusCommandItem::create_custom_command( | ||||||
|  |             this, it->second->custom_data, | ||||||
|  |             [this](ModbusRegisterType register_type, uint16_t start_address, const std::vector<uint8_t> &data) { | ||||||
|  |               this->on_register_data(ModbusRegisterType::CUSTOM, start_address, data); | ||||||
|  |             }); | ||||||
|  |         command_item.register_address = it->second->start_address; | ||||||
|  |         command_item.register_count = it->second->register_count; | ||||||
|  |         command_item.function_code = ModbusFunctionCode::CUSTOM; | ||||||
|         queue_command(command_item); |         queue_command(command_item); | ||||||
|  |       } | ||||||
|  |     } else { | ||||||
|  |       queue_command(ModbusCommandItem::create_read_command(this, r.register_type, r.start_address, r.register_count)); | ||||||
|  |     } | ||||||
|     r.skip_updates_counter = r.skip_updates;  // reset counter to config value |     r.skip_updates_counter = r.skip_updates;  // reset counter to config value | ||||||
|   } else { |   } else { | ||||||
|     r.skip_updates_counter--; |     r.skip_updates_counter--; | ||||||
| @@ -422,6 +445,7 @@ bool ModbusCommandItem::send() { | |||||||
|     modbusdevice->send_raw(this->payload); |     modbusdevice->send_raw(this->payload); | ||||||
|   } |   } | ||||||
|   ESP_LOGV(TAG, "Command sent %d 0x%X %d", uint8_t(this->function_code), this->register_address, this->register_count); |   ESP_LOGV(TAG, "Command sent %d 0x%X %d", uint8_t(this->function_code), this->register_address, this->register_count); | ||||||
|  |   send_countdown--; | ||||||
|   return true; |   return true; | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -549,6 +573,9 @@ float payload_to_float(const std::vector<uint8_t> &data, SensorValueType sensor_ | |||||||
|       ESP_LOGD(TAG, "FP32_R = 0x%08X => %f", raw_to_float.raw, raw_to_float.float_value); |       ESP_LOGD(TAG, "FP32_R = 0x%08X => %f", raw_to_float.raw, raw_to_float.float_value); | ||||||
|       result = raw_to_float.float_value; |       result = raw_to_float.float_value; | ||||||
|     } break; |     } break; | ||||||
|  |     case SensorValueType::RAW: | ||||||
|  |       result = NAN; | ||||||
|  |       break; | ||||||
|     default: |     default: | ||||||
|       break; |       break; | ||||||
|   } |   } | ||||||
|   | |||||||
| @@ -102,8 +102,6 @@ inline ModbusFunctionCode modbus_register_write_function(ModbusRegisterType reg_ | |||||||
|       return ModbusFunctionCode::READ_WRITE_MULTIPLE_REGISTERS; |       return ModbusFunctionCode::READ_WRITE_MULTIPLE_REGISTERS; | ||||||
|       break; |       break; | ||||||
|     case ModbusRegisterType::READ: |     case ModbusRegisterType::READ: | ||||||
|       return ModbusFunctionCode::CUSTOM; |  | ||||||
|       break; |  | ||||||
|     default: |     default: | ||||||
|       return ModbusFunctionCode::CUSTOM; |       return ModbusFunctionCode::CUSTOM; | ||||||
|       break; |       break; | ||||||
| @@ -221,7 +219,7 @@ template<typename N> N mask_and_shift_by_rightbit(N data, uint32_t mask) { | |||||||
|   if (result == 0) { |   if (result == 0) { | ||||||
|     return result; |     return result; | ||||||
|   } |   } | ||||||
|   for (int pos = 0; pos < sizeof(N) << 3; pos++) { |   for (size_t pos = 0; pos < sizeof(N) << 3; pos++) { | ||||||
|     if ((mask & (1 << pos)) != 0) |     if ((mask & (1 << pos)) != 0) | ||||||
|       return result >> pos; |       return result >> pos; | ||||||
|   } |   } | ||||||
| @@ -247,29 +245,36 @@ float payload_to_float(const std::vector<uint8_t> &data, SensorValueType sensor_ | |||||||
|  |  | ||||||
| class ModbusController; | class ModbusController; | ||||||
|  |  | ||||||
| struct SensorItem { | class SensorItem { | ||||||
|  |  public: | ||||||
|  |   virtual void parse_and_publish(const std::vector<uint8_t> &data) = 0; | ||||||
|  |  | ||||||
|  |   void set_custom_data(const std::vector<uint8_t> &data) { custom_data = data; } | ||||||
|  |   uint64_t getkey() const { return calc_key(register_type, start_address, offset, bitmask); } | ||||||
|  |   size_t virtual get_register_size() const { | ||||||
|  |     if (register_type == ModbusRegisterType::COIL || register_type == ModbusRegisterType::DISCRETE_INPUT) | ||||||
|  |       return 1; | ||||||
|  |     else  // if CONF_RESPONSE_BYTES is used override the default | ||||||
|  |       return response_bytes > 0 ? response_bytes : register_count * 2; | ||||||
|  |   } | ||||||
|  |   // Override register size for modbus devices not using 1 register for one dword | ||||||
|  |   void set_register_size(uint8_t register_size) { response_bytes = register_size; } | ||||||
|   ModbusRegisterType register_type; |   ModbusRegisterType register_type; | ||||||
|   SensorValueType sensor_value_type; |   SensorValueType sensor_value_type; | ||||||
|   uint16_t start_address; |   uint16_t start_address; | ||||||
|   uint32_t bitmask; |   uint32_t bitmask; | ||||||
|   uint8_t offset; |   uint8_t offset; | ||||||
|   uint8_t register_count; |   uint8_t register_count; | ||||||
|  |   uint8_t response_bytes{0}; | ||||||
|   uint8_t skip_updates; |   uint8_t skip_updates; | ||||||
|  |   std::vector<uint8_t> custom_data{}; | ||||||
|   bool force_new_range{false}; |   bool force_new_range{false}; | ||||||
|  |  | ||||||
|   virtual void parse_and_publish(const std::vector<uint8_t> &data) = 0; |  | ||||||
|  |  | ||||||
|   uint64_t getkey() const { return calc_key(register_type, start_address, offset, bitmask); } |  | ||||||
|   size_t virtual get_register_size() const { |  | ||||||
|     if (register_type == ModbusRegisterType::COIL || register_type == ModbusRegisterType::DISCRETE_INPUT) |  | ||||||
|       return 1; |  | ||||||
|     else |  | ||||||
|       return register_count * 2; |  | ||||||
|   } |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
| struct ModbusCommandItem { | class ModbusCommandItem { | ||||||
|  |  public: | ||||||
|   static const size_t MAX_PAYLOAD_BYTES = 240; |   static const size_t MAX_PAYLOAD_BYTES = 240; | ||||||
|  |   static const uint8_t MAX_SEND_REPEATS = 5; | ||||||
|   ModbusController *modbusdevice; |   ModbusController *modbusdevice; | ||||||
|   uint16_t register_address; |   uint16_t register_address; | ||||||
|   uint16_t register_count; |   uint16_t register_count; | ||||||
| @@ -279,7 +284,9 @@ struct ModbusCommandItem { | |||||||
|       on_data_func; |       on_data_func; | ||||||
|   std::vector<uint8_t> payload = {}; |   std::vector<uint8_t> payload = {}; | ||||||
|   bool send(); |   bool send(); | ||||||
|  |   // wrong commands (esp. custom commands) can block the send queue | ||||||
|  |   // limit the number of repeats | ||||||
|  |   uint8_t send_countdown{MAX_SEND_REPEATS}; | ||||||
|   /// factory methods |   /// factory methods | ||||||
|   /** Create modbus read command |   /** Create modbus read command | ||||||
|    *  Function code 02-04 |    *  Function code 02-04 | ||||||
| @@ -392,6 +399,8 @@ class ModbusController : public PollingComponent, public modbus::ModbusDevice { | |||||||
|  protected: |  protected: | ||||||
|   /// parse sensormap_ and create range of sequential addresses |   /// parse sensormap_ and create range of sequential addresses | ||||||
|   size_t create_register_ranges_(); |   size_t create_register_ranges_(); | ||||||
|  |   // find register in sensormap. Returns iterator with all registers having the same start address | ||||||
|  |   std::map<uint64_t, SensorItem *>::iterator find_register_(ModbusRegisterType register_type, uint16_t start_address); | ||||||
|   /// submit the read command for the address range to the send queue |   /// submit the read command for the address range to the send queue | ||||||
|   void update_range_(RegisterRange &r); |   void update_range_(RegisterRange &r); | ||||||
|   /// parse incoming modbus data |   /// parse incoming modbus data | ||||||
|   | |||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user