mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 07:03:55 +00:00 
			
		
		
		
	Merge remote-tracking branch 'origin/dev' into nrf52
This commit is contained in:
		| @@ -7,8 +7,21 @@ | |||||||
|     "PIP_BREAK_SYSTEM_PACKAGES": "1", |     "PIP_BREAK_SYSTEM_PACKAGES": "1", | ||||||
|     "PIP_ROOT_USER_ACTION": "ignore" |     "PIP_ROOT_USER_ACTION": "ignore" | ||||||
|   }, |   }, | ||||||
|   "runArgs": ["--privileged", "-e", "ESPHOME_DASHBOARD_USE_PING=1"], |   "runArgs": [ | ||||||
|  |     "--privileged", | ||||||
|  |     "-e", | ||||||
|  |     "ESPHOME_DASHBOARD_USE_PING=1" | ||||||
|  |     // uncomment and edit the path in order to pass though local USB serial to the conatiner | ||||||
|  |     // , "--device=/dev/ttyACM0" | ||||||
|  |   ], | ||||||
|   "appPort": 6052, |   "appPort": 6052, | ||||||
|  |   // if you are using avahi in the host device, uncomment these to allow the | ||||||
|  |   // devcontainer to find devices via mdns | ||||||
|  |   //"mounts": [ | ||||||
|  |   //  "type=bind,source=/dev/bus/usb,target=/dev/bus/usb", | ||||||
|  |   //  "type=bind,source=/var/run/dbus,target=/var/run/dbus", | ||||||
|  |   //  "type=bind,source=/var/run/avahi-daemon/socket,target=/var/run/avahi-daemon/socket" | ||||||
|  |   //], | ||||||
|   "customizations": { |   "customizations": { | ||||||
|     "vscode": { |     "vscode": { | ||||||
|       "extensions": [ |       "extensions": [ | ||||||
|   | |||||||
							
								
								
									
										3
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							| @@ -11,6 +11,7 @@ on: | |||||||
|       - "**" |       - "**" | ||||||
|       - "!.github/workflows/*.yml" |       - "!.github/workflows/*.yml" | ||||||
|       - ".github/workflows/ci.yml" |       - ".github/workflows/ci.yml" | ||||||
|  |       - "!.yamllint" | ||||||
|   merge_group: |   merge_group: | ||||||
|  |  | ||||||
| permissions: | permissions: | ||||||
| @@ -218,7 +219,7 @@ jobs: | |||||||
|           . venv/bin/activate |           . venv/bin/activate | ||||||
|           pytest -vv --cov-report=xml --tb=native tests |           pytest -vv --cov-report=xml --tb=native tests | ||||||
|       - name: Upload coverage to Codecov |       - name: Upload coverage to Codecov | ||||||
|         uses: codecov/codecov-action@v3 |         uses: codecov/codecov-action@v4 | ||||||
|         with: |         with: | ||||||
|           token: ${{ secrets.CODECOV_TOKEN }} |           token: ${{ secrets.CODECOV_TOKEN }} | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										1
									
								
								.github/workflows/needs-docs.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.github/workflows/needs-docs.yml
									
									
									
									
										vendored
									
									
								
							| @@ -1,5 +1,6 @@ | |||||||
| name: Needs Docs | name: Needs Docs | ||||||
|  |  | ||||||
|  | # yamllint disable-line rule:truthy | ||||||
| on: | on: | ||||||
|   pull_request: |   pull_request: | ||||||
|     types: [labeled, unlabeled] |     types: [labeled, unlabeled] | ||||||
|   | |||||||
							
								
								
									
										3
									
								
								.github/workflows/sync-device-classes.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.github/workflows/sync-device-classes.yml
									
									
									
									
										vendored
									
									
								
							| @@ -1,6 +1,7 @@ | |||||||
| --- | --- | ||||||
| name: Synchronise Device Classes from Home Assistant | name: Synchronise Device Classes from Home Assistant | ||||||
|  |  | ||||||
|  | # yamllint disable-line rule:truthy | ||||||
| on: | on: | ||||||
|   workflow_dispatch: |   workflow_dispatch: | ||||||
|   schedule: |   schedule: | ||||||
| @@ -36,7 +37,7 @@ jobs: | |||||||
|           python ./script/sync-device_class.py |           python ./script/sync-device_class.py | ||||||
|  |  | ||||||
|       - name: Commit changes |       - name: Commit changes | ||||||
|         uses: peter-evans/create-pull-request@v5.0.2 |         uses: peter-evans/create-pull-request@v6.0.1 | ||||||
|         with: |         with: | ||||||
|           commit-message: "Synchronise Device Classes from Home Assistant" |           commit-message: "Synchronise Device Classes from Home Assistant" | ||||||
|           committer: esphomebot <esphome@nabucasa.com> |           committer: esphomebot <esphome@nabucasa.com> | ||||||
|   | |||||||
							
								
								
									
										6
									
								
								.github/workflows/yaml-lint.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								.github/workflows/yaml-lint.yml
									
									
									
									
										vendored
									
									
								
							| @@ -1,5 +1,7 @@ | |||||||
|  | --- | ||||||
| name: YAML lint | name: YAML lint | ||||||
|  |  | ||||||
|  | # yamllint disable-line rule:truthy | ||||||
| on: | on: | ||||||
|   push: |   push: | ||||||
|     branches: [dev, beta, release] |     branches: [dev, beta, release] | ||||||
| @@ -19,4 +21,6 @@ jobs: | |||||||
|       - name: Check out code from GitHub |       - name: Check out code from GitHub | ||||||
|         uses: actions/checkout@v4.1.1 |         uses: actions/checkout@v4.1.1 | ||||||
|       - name: Run yamllint |       - name: Run yamllint | ||||||
|         uses: frenck/action-yamllint@v1.4.2 |         uses: frenck/action-yamllint@v1.5.0 | ||||||
|  |         with: | ||||||
|  |           strict: true | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ | |||||||
| # See https://pre-commit.com/hooks.html for more hooks | # See https://pre-commit.com/hooks.html for more hooks | ||||||
| repos: | repos: | ||||||
|   - repo: https://github.com/psf/black-pre-commit-mirror |   - repo: https://github.com/psf/black-pre-commit-mirror | ||||||
|     rev: 23.12.1 |     rev: 24.2.0 | ||||||
|     hooks: |     hooks: | ||||||
|       - id: black |       - id: black | ||||||
|         args: |         args: | ||||||
| @@ -27,7 +27,7 @@ repos: | |||||||
|           - --branch=release |           - --branch=release | ||||||
|           - --branch=beta |           - --branch=beta | ||||||
|   - repo: https://github.com/asottile/pyupgrade |   - repo: https://github.com/asottile/pyupgrade | ||||||
|     rev: v3.15.0 |     rev: v3.15.1 | ||||||
|     hooks: |     hooks: | ||||||
|       - id: pyupgrade |       - id: pyupgrade | ||||||
|         args: [--py39-plus] |         args: [--py39-plus] | ||||||
|   | |||||||
							
								
								
									
										19
									
								
								.yamllint
									
									
									
									
									
								
							
							
						
						
									
										19
									
								
								.yamllint
									
									
									
									
									
								
							| @@ -1,3 +1,18 @@ | |||||||
| --- | --- | ||||||
| ignore: | | extends: default | ||||||
|   venv/ |  | ||||||
|  | ignore-from-file: .gitignore | ||||||
|  |  | ||||||
|  | rules: | ||||||
|  |   document-start: disable | ||||||
|  |   empty-lines: | ||||||
|  |     level: error | ||||||
|  |     max: 1 | ||||||
|  |     max-start: 0 | ||||||
|  |     max-end: 1 | ||||||
|  |   indentation: | ||||||
|  |     level: error | ||||||
|  |     spaces: 2 | ||||||
|  |     indent-sequences: true | ||||||
|  |     check-multi-line-strings: false | ||||||
|  |   line-length: disable | ||||||
|   | |||||||
| @@ -18,6 +18,7 @@ esphome/components/ac_dimmer/* @glmnet | |||||||
| esphome/components/adc/* @esphome/core | esphome/components/adc/* @esphome/core | ||||||
| esphome/components/adc128s102/* @DeerMaximum | esphome/components/adc128s102/* @DeerMaximum | ||||||
| esphome/components/addressable_light/* @justfalter | esphome/components/addressable_light/* @justfalter | ||||||
|  | esphome/components/ade7880/* @kpfleming | ||||||
| esphome/components/ade7953/* @angelnu | esphome/components/ade7953/* @angelnu | ||||||
| esphome/components/ade7953_i2c/* @angelnu | esphome/components/ade7953_i2c/* @angelnu | ||||||
| esphome/components/ade7953_spi/* @angelnu | esphome/components/ade7953_spi/* @angelnu | ||||||
| @@ -155,6 +156,7 @@ esphome/components/iaqcore/* @yozik04 | |||||||
| esphome/components/ili9xxx/* @clydebarrow @nielsnl68 | esphome/components/ili9xxx/* @clydebarrow @nielsnl68 | ||||||
| esphome/components/improv_base/* @esphome/core | esphome/components/improv_base/* @esphome/core | ||||||
| esphome/components/improv_serial/* @esphome/core | esphome/components/improv_serial/* @esphome/core | ||||||
|  | esphome/components/ina226/* @Sergio303 @latonita | ||||||
| esphome/components/ina260/* @mreditor97 | esphome/components/ina260/* @mreditor97 | ||||||
| esphome/components/inkbird_ibsth1_mini/* @fkirill | esphome/components/inkbird_ibsth1_mini/* @fkirill | ||||||
| esphome/components/inkplate6/* @jesserockz | esphome/components/inkplate6/* @jesserockz | ||||||
| @@ -199,6 +201,7 @@ esphome/components/mcp9808/* @k7hpn | |||||||
| esphome/components/md5/* @esphome/core | esphome/components/md5/* @esphome/core | ||||||
| esphome/components/mdns/* @esphome/core | esphome/components/mdns/* @esphome/core | ||||||
| esphome/components/media_player/* @jesserockz | esphome/components/media_player/* @jesserockz | ||||||
|  | esphome/components/micro_wake_word/* @jesserockz @kahrendt | ||||||
| esphome/components/micronova/* @jorre05 | esphome/components/micronova/* @jorre05 | ||||||
| esphome/components/microphone/* @jesserockz | esphome/components/microphone/* @jesserockz | ||||||
| esphome/components/mics_4514/* @jesserockz | esphome/components/mics_4514/* @jesserockz | ||||||
| @@ -222,6 +225,7 @@ esphome/components/mopeka_pro_check/* @spbrogan | |||||||
| esphome/components/mopeka_std_check/* @Fabian-Schmidt | esphome/components/mopeka_std_check/* @Fabian-Schmidt | ||||||
| esphome/components/mpl3115a2/* @kbickar | esphome/components/mpl3115a2/* @kbickar | ||||||
| esphome/components/mpu6886/* @fabaff | esphome/components/mpu6886/* @fabaff | ||||||
|  | esphome/components/ms8607/* @e28eta | ||||||
| esphome/components/network/* @esphome/core | esphome/components/network/* @esphome/core | ||||||
| esphome/components/nextion/* @senexcrenshaw | esphome/components/nextion/* @senexcrenshaw | ||||||
| esphome/components/nextion/binary_sensor/* @senexcrenshaw | esphome/components/nextion/binary_sensor/* @senexcrenshaw | ||||||
| @@ -362,6 +366,7 @@ esphome/components/uart/button/* @ssieb | |||||||
| esphome/components/ufire_ec/* @pvizeli | esphome/components/ufire_ec/* @pvizeli | ||||||
| esphome/components/ufire_ise/* @pvizeli | esphome/components/ufire_ise/* @pvizeli | ||||||
| esphome/components/ultrasonic/* @OttoWinter | esphome/components/ultrasonic/* @OttoWinter | ||||||
|  | esphome/components/uponor_smatrix/* @kroimon | ||||||
| esphome/components/vbus/* @ssieb | esphome/components/vbus/* @ssieb | ||||||
| esphome/components/veml3235/* @kbx81 | esphome/components/veml3235/* @kbx81 | ||||||
| esphome/components/version/* @esphome/core | esphome/components/version/* @esphome/core | ||||||
|   | |||||||
| @@ -35,7 +35,7 @@ RUN \ | |||||||
|         iputils-ping=3:20221126-1 \ |         iputils-ping=3:20221126-1 \ | ||||||
|         git=1:2.39.2-1.1 \ |         git=1:2.39.2-1.1 \ | ||||||
|         curl=7.88.1-10+deb12u5 \ |         curl=7.88.1-10+deb12u5 \ | ||||||
|         openssh-client=1:9.2p1-2+deb12u1 \ |         openssh-client=1:9.2p1-2+deb12u2 \ | ||||||
|         python3-cffi=1.15.1-5 \ |         python3-cffi=1.15.1-5 \ | ||||||
|         libcairo2=1.16.0-7 \ |         libcairo2=1.16.0-7 \ | ||||||
|         libmagic1=1:5.44-3 \ |         libmagic1=1:5.44-3 \ | ||||||
|   | |||||||
| @@ -21,4 +21,10 @@ export PLATFORMIO_PLATFORMS_DIR="${pio_cache_base}/platforms" | |||||||
| export PLATFORMIO_PACKAGES_DIR="${pio_cache_base}/packages" | export PLATFORMIO_PACKAGES_DIR="${pio_cache_base}/packages" | ||||||
| export PLATFORMIO_CACHE_DIR="${pio_cache_base}/cache" | export PLATFORMIO_CACHE_DIR="${pio_cache_base}/cache" | ||||||
|  |  | ||||||
|  | # If /build is mounted, use that as the build path | ||||||
|  | # otherwise use path in /config (so that builds aren't lost on container restart) | ||||||
|  | if [[ -d /build ]]; then | ||||||
|  |     export ESPHOME_BUILD_PATH=/build | ||||||
|  | fi | ||||||
|  |  | ||||||
| exec esphome "$@" | exec esphome "$@" | ||||||
|   | |||||||
							
								
								
									
										1
									
								
								esphome/components/ade7880/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								esphome/components/ade7880/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | |||||||
|  | CODEOWNERS = ["@kpfleming"] | ||||||
							
								
								
									
										302
									
								
								esphome/components/ade7880/ade7880.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										302
									
								
								esphome/components/ade7880/ade7880.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,302 @@ | |||||||
|  | // This component was developed using knowledge gathered by a number | ||||||
|  | // of people who reverse-engineered the Shelly 3EM: | ||||||
|  | // | ||||||
|  | // @AndreKR on GitHub | ||||||
|  | // Axel (@Axel830 on GitHub) | ||||||
|  | // Marko (@goodkiller on GitHub) | ||||||
|  | // Michaël Piron (@michaelpiron on GitHub) | ||||||
|  | // Theo Arends (@arendst on GitHub) | ||||||
|  |  | ||||||
|  | #include "ade7880.h" | ||||||
|  | #include "ade7880_registers.h" | ||||||
|  | #include "esphome/core/log.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace ade7880 { | ||||||
|  |  | ||||||
|  | static const char *const TAG = "ade7880"; | ||||||
|  |  | ||||||
|  | void IRAM_ATTR ADE7880Store::gpio_intr(ADE7880Store *arg) { arg->reset_done = true; } | ||||||
|  |  | ||||||
|  | void ADE7880::setup() { | ||||||
|  |   if (this->irq0_pin_ != nullptr) { | ||||||
|  |     this->irq0_pin_->setup(); | ||||||
|  |   } | ||||||
|  |   this->irq1_pin_->setup(); | ||||||
|  |   if (this->reset_pin_ != nullptr) { | ||||||
|  |     this->reset_pin_->setup(); | ||||||
|  |   } | ||||||
|  |   this->store_.irq1_pin = this->irq1_pin_->to_isr(); | ||||||
|  |   this->irq1_pin_->attach_interrupt(ADE7880Store::gpio_intr, &this->store_, gpio::INTERRUPT_FALLING_EDGE); | ||||||
|  |  | ||||||
|  |   // if IRQ1 is already asserted, the cause must be determined | ||||||
|  |   if (this->irq1_pin_->digital_read() == 0) { | ||||||
|  |     ESP_LOGD(TAG, "IRQ1 found asserted during setup()"); | ||||||
|  |     auto status1 = read_u32_register16_(STATUS1); | ||||||
|  |     if ((status1 & ~STATUS1_RSTDONE) != 0) { | ||||||
|  |       // not safe to proceed, must initiate reset | ||||||
|  |       ESP_LOGD(TAG, "IRQ1 asserted for !RSTDONE, resetting device"); | ||||||
|  |       this->reset_device_(); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     if ((status1 & STATUS1_RSTDONE) == STATUS1_RSTDONE) { | ||||||
|  |       // safe to proceed, device has just completed reset cycle | ||||||
|  |       ESP_LOGD(TAG, "Acknowledging RSTDONE"); | ||||||
|  |       this->write_u32_register16_(STATUS0, 0xFFFF); | ||||||
|  |       this->write_u32_register16_(STATUS1, 0xFFFF); | ||||||
|  |       this->init_device_(); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   this->reset_device_(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void ADE7880::loop() { | ||||||
|  |   // check for completion of a reset cycle | ||||||
|  |   if (!this->store_.reset_done) { | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   ESP_LOGD(TAG, "Acknowledging RSTDONE"); | ||||||
|  |   this->write_u32_register16_(STATUS0, 0xFFFF); | ||||||
|  |   this->write_u32_register16_(STATUS1, 0xFFFF); | ||||||
|  |   this->init_device_(); | ||||||
|  |   this->store_.reset_done = false; | ||||||
|  |   this->store_.reset_pending = false; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | template<typename F> | ||||||
|  | void ADE7880::update_sensor_from_s24zp_register16_(sensor::Sensor *sensor, uint16_t a_register, F &&f) { | ||||||
|  |   if (sensor == nullptr) { | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   float val = this->read_s24zp_register16_(a_register); | ||||||
|  |   sensor->publish_state(f(val)); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | template<typename F> | ||||||
|  | void ADE7880::update_sensor_from_s16_register16_(sensor::Sensor *sensor, uint16_t a_register, F &&f) { | ||||||
|  |   if (sensor == nullptr) { | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   float val = this->read_s16_register16_(a_register); | ||||||
|  |   sensor->publish_state(f(val)); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | template<typename F> | ||||||
|  | void ADE7880::update_sensor_from_s32_register16_(sensor::Sensor *sensor, uint16_t a_register, F &&f) { | ||||||
|  |   if (sensor == nullptr) { | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   float val = this->read_s32_register16_(a_register); | ||||||
|  |   sensor->publish_state(f(val)); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void ADE7880::update() { | ||||||
|  |   if (this->store_.reset_pending) { | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   auto start = millis(); | ||||||
|  |  | ||||||
|  |   if (this->channel_n_ != nullptr) { | ||||||
|  |     auto *chan = this->channel_n_; | ||||||
|  |     this->update_sensor_from_s24zp_register16_(chan->current, NIRMS, [](float val) { return val / 100000.0f; }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (this->channel_a_ != nullptr) { | ||||||
|  |     auto *chan = this->channel_a_; | ||||||
|  |     this->update_sensor_from_s24zp_register16_(chan->current, AIRMS, [](float val) { return val / 100000.0f; }); | ||||||
|  |     this->update_sensor_from_s24zp_register16_(chan->voltage, BVRMS, [](float val) { return val / 10000.0f; }); | ||||||
|  |     this->update_sensor_from_s24zp_register16_(chan->active_power, AWATT, [](float val) { return val / 100.0f; }); | ||||||
|  |     this->update_sensor_from_s24zp_register16_(chan->apparent_power, AVA, [](float val) { return val / 100.0f; }); | ||||||
|  |     this->update_sensor_from_s16_register16_(chan->power_factor, APF, | ||||||
|  |                                              [](float val) { return std::abs(val / -327.68f); }); | ||||||
|  |     this->update_sensor_from_s32_register16_(chan->forward_active_energy, AFWATTHR, [&chan](float val) { | ||||||
|  |       return chan->forward_active_energy_total += val / 14400.0f; | ||||||
|  |     }); | ||||||
|  |     this->update_sensor_from_s32_register16_(chan->reverse_active_energy, AFWATTHR, [&chan](float val) { | ||||||
|  |       return chan->reverse_active_energy_total += val / 14400.0f; | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (this->channel_b_ != nullptr) { | ||||||
|  |     auto *chan = this->channel_b_; | ||||||
|  |     this->update_sensor_from_s24zp_register16_(chan->current, BIRMS, [](float val) { return val / 100000.0f; }); | ||||||
|  |     this->update_sensor_from_s24zp_register16_(chan->voltage, BVRMS, [](float val) { return val / 10000.0f; }); | ||||||
|  |     this->update_sensor_from_s24zp_register16_(chan->active_power, BWATT, [](float val) { return val / 100.0f; }); | ||||||
|  |     this->update_sensor_from_s24zp_register16_(chan->apparent_power, BVA, [](float val) { return val / 100.0f; }); | ||||||
|  |     this->update_sensor_from_s16_register16_(chan->power_factor, BPF, | ||||||
|  |                                              [](float val) { return std::abs(val / -327.68f); }); | ||||||
|  |     this->update_sensor_from_s32_register16_(chan->forward_active_energy, BFWATTHR, [&chan](float val) { | ||||||
|  |       return chan->forward_active_energy_total += val / 14400.0f; | ||||||
|  |     }); | ||||||
|  |     this->update_sensor_from_s32_register16_(chan->reverse_active_energy, BFWATTHR, [&chan](float val) { | ||||||
|  |       return chan->reverse_active_energy_total += val / 14400.0f; | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (this->channel_c_ != nullptr) { | ||||||
|  |     auto *chan = this->channel_c_; | ||||||
|  |     this->update_sensor_from_s24zp_register16_(chan->current, CIRMS, [](float val) { return val / 100000.0f; }); | ||||||
|  |     this->update_sensor_from_s24zp_register16_(chan->voltage, CVRMS, [](float val) { return val / 10000.0f; }); | ||||||
|  |     this->update_sensor_from_s24zp_register16_(chan->active_power, CWATT, [](float val) { return val / 100.0f; }); | ||||||
|  |     this->update_sensor_from_s24zp_register16_(chan->apparent_power, CVA, [](float val) { return val / 100.0f; }); | ||||||
|  |     this->update_sensor_from_s16_register16_(chan->power_factor, CPF, | ||||||
|  |                                              [](float val) { return std::abs(val / -327.68f); }); | ||||||
|  |     this->update_sensor_from_s32_register16_(chan->forward_active_energy, CFWATTHR, [&chan](float val) { | ||||||
|  |       return chan->forward_active_energy_total += val / 14400.0f; | ||||||
|  |     }); | ||||||
|  |     this->update_sensor_from_s32_register16_(chan->reverse_active_energy, CFWATTHR, [&chan](float val) { | ||||||
|  |       return chan->reverse_active_energy_total += val / 14400.0f; | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   ESP_LOGD(TAG, "update took %u ms", millis() - start); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void ADE7880::dump_config() { | ||||||
|  |   ESP_LOGCONFIG(TAG, "ADE7880:"); | ||||||
|  |   LOG_PIN("  IRQ0  Pin: ", this->irq0_pin_); | ||||||
|  |   LOG_PIN("  IRQ1  Pin: ", this->irq1_pin_); | ||||||
|  |   LOG_PIN("  RESET Pin: ", this->reset_pin_); | ||||||
|  |   ESP_LOGCONFIG(TAG, "  Frequency: %.0f Hz", this->frequency_); | ||||||
|  |  | ||||||
|  |   if (this->channel_a_ != nullptr) { | ||||||
|  |     ESP_LOGCONFIG(TAG, "  Phase A:"); | ||||||
|  |     LOG_SENSOR("    ", "Current", this->channel_a_->current); | ||||||
|  |     LOG_SENSOR("    ", "Voltage", this->channel_a_->voltage); | ||||||
|  |     LOG_SENSOR("    ", "Active Power", this->channel_a_->active_power); | ||||||
|  |     LOG_SENSOR("    ", "Apparent Power", this->channel_a_->apparent_power); | ||||||
|  |     LOG_SENSOR("    ", "Power Factor", this->channel_a_->power_factor); | ||||||
|  |     LOG_SENSOR("    ", "Forward Active Energy", this->channel_a_->forward_active_energy); | ||||||
|  |     LOG_SENSOR("    ", "Reverse Active Energy", this->channel_a_->reverse_active_energy); | ||||||
|  |     ESP_LOGCONFIG(TAG, "    Calibration:"); | ||||||
|  |     ESP_LOGCONFIG(TAG, "     Current: %u", this->channel_a_->current_gain_calibration); | ||||||
|  |     ESP_LOGCONFIG(TAG, "     Voltage: %d", this->channel_a_->voltage_gain_calibration); | ||||||
|  |     ESP_LOGCONFIG(TAG, "     Power: %d", this->channel_a_->power_gain_calibration); | ||||||
|  |     ESP_LOGCONFIG(TAG, "     Phase Angle: %u", this->channel_a_->phase_angle_calibration); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (this->channel_b_ != nullptr) { | ||||||
|  |     ESP_LOGCONFIG(TAG, "  Phase B:"); | ||||||
|  |     LOG_SENSOR("    ", "Current", this->channel_b_->current); | ||||||
|  |     LOG_SENSOR("    ", "Voltage", this->channel_b_->voltage); | ||||||
|  |     LOG_SENSOR("    ", "Active Power", this->channel_b_->active_power); | ||||||
|  |     LOG_SENSOR("    ", "Apparent Power", this->channel_b_->apparent_power); | ||||||
|  |     LOG_SENSOR("    ", "Power Factor", this->channel_b_->power_factor); | ||||||
|  |     LOG_SENSOR("    ", "Forward Active Energy", this->channel_b_->forward_active_energy); | ||||||
|  |     LOG_SENSOR("    ", "Reverse Active Energy", this->channel_b_->reverse_active_energy); | ||||||
|  |     ESP_LOGCONFIG(TAG, "    Calibration:"); | ||||||
|  |     ESP_LOGCONFIG(TAG, "     Current: %u", this->channel_b_->current_gain_calibration); | ||||||
|  |     ESP_LOGCONFIG(TAG, "     Voltage: %d", this->channel_b_->voltage_gain_calibration); | ||||||
|  |     ESP_LOGCONFIG(TAG, "     Power: %d", this->channel_b_->power_gain_calibration); | ||||||
|  |     ESP_LOGCONFIG(TAG, "     Phase Angle: %u", this->channel_b_->phase_angle_calibration); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (this->channel_c_ != nullptr) { | ||||||
|  |     ESP_LOGCONFIG(TAG, "  Phase C:"); | ||||||
|  |     LOG_SENSOR("    ", "Current", this->channel_c_->current); | ||||||
|  |     LOG_SENSOR("    ", "Voltage", this->channel_c_->voltage); | ||||||
|  |     LOG_SENSOR("    ", "Active Power", this->channel_c_->active_power); | ||||||
|  |     LOG_SENSOR("    ", "Apparent Power", this->channel_c_->apparent_power); | ||||||
|  |     LOG_SENSOR("    ", "Power Factor", this->channel_c_->power_factor); | ||||||
|  |     LOG_SENSOR("    ", "Forward Active Energy", this->channel_c_->forward_active_energy); | ||||||
|  |     LOG_SENSOR("    ", "Reverse Active Energy", this->channel_c_->reverse_active_energy); | ||||||
|  |     ESP_LOGCONFIG(TAG, "    Calibration:"); | ||||||
|  |     ESP_LOGCONFIG(TAG, "     Current: %u", this->channel_c_->current_gain_calibration); | ||||||
|  |     ESP_LOGCONFIG(TAG, "     Voltage: %d", this->channel_c_->voltage_gain_calibration); | ||||||
|  |     ESP_LOGCONFIG(TAG, "     Power: %d", this->channel_c_->power_gain_calibration); | ||||||
|  |     ESP_LOGCONFIG(TAG, "     Phase Angle: %u", this->channel_c_->phase_angle_calibration); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (this->channel_n_ != nullptr) { | ||||||
|  |     ESP_LOGCONFIG(TAG, "  Neutral:"); | ||||||
|  |     LOG_SENSOR("    ", "Current", this->channel_n_->current); | ||||||
|  |     ESP_LOGCONFIG(TAG, "    Calibration:"); | ||||||
|  |     ESP_LOGCONFIG(TAG, "     Current: %u", this->channel_n_->current_gain_calibration); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   LOG_I2C_DEVICE(this); | ||||||
|  |   LOG_UPDATE_INTERVAL(this); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void ADE7880::calibrate_s10zp_reading_(uint16_t a_register, int16_t calibration) { | ||||||
|  |   if (calibration == 0) { | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   this->write_s10zp_register16_(a_register, calibration); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void ADE7880::calibrate_s24zpse_reading_(uint16_t a_register, int32_t calibration) { | ||||||
|  |   if (calibration == 0) { | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   this->write_s24zpse_register16_(a_register, calibration); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void ADE7880::init_device_() { | ||||||
|  |   this->write_u8_register16_(CONFIG2, CONFIG2_I2C_LOCK); | ||||||
|  |  | ||||||
|  |   this->write_u16_register16_(GAIN, 0); | ||||||
|  |  | ||||||
|  |   if (this->frequency_ > 55) { | ||||||
|  |     this->write_u16_register16_(COMPMODE, COMPMODE_DEFAULT | COMPMODE_SELFREQ); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (this->channel_n_ != nullptr) { | ||||||
|  |     this->calibrate_s24zpse_reading_(NIGAIN, this->channel_n_->current_gain_calibration); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (this->channel_a_ != nullptr) { | ||||||
|  |     this->calibrate_s24zpse_reading_(AIGAIN, this->channel_a_->current_gain_calibration); | ||||||
|  |     this->calibrate_s24zpse_reading_(AVGAIN, this->channel_a_->voltage_gain_calibration); | ||||||
|  |     this->calibrate_s24zpse_reading_(APGAIN, this->channel_a_->power_gain_calibration); | ||||||
|  |     this->calibrate_s10zp_reading_(APHCAL, this->channel_a_->phase_angle_calibration); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (this->channel_b_ != nullptr) { | ||||||
|  |     this->calibrate_s24zpse_reading_(BIGAIN, this->channel_b_->current_gain_calibration); | ||||||
|  |     this->calibrate_s24zpse_reading_(BVGAIN, this->channel_b_->voltage_gain_calibration); | ||||||
|  |     this->calibrate_s24zpse_reading_(BPGAIN, this->channel_b_->power_gain_calibration); | ||||||
|  |     this->calibrate_s10zp_reading_(BPHCAL, this->channel_b_->phase_angle_calibration); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (this->channel_c_ != nullptr) { | ||||||
|  |     this->calibrate_s24zpse_reading_(CIGAIN, this->channel_c_->current_gain_calibration); | ||||||
|  |     this->calibrate_s24zpse_reading_(CVGAIN, this->channel_c_->voltage_gain_calibration); | ||||||
|  |     this->calibrate_s24zpse_reading_(CPGAIN, this->channel_c_->power_gain_calibration); | ||||||
|  |     this->calibrate_s10zp_reading_(CPHCAL, this->channel_c_->phase_angle_calibration); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // write three default values to data memory RAM to flush the I2C write queue | ||||||
|  |   this->write_s32_register16_(VLEVEL, 0); | ||||||
|  |   this->write_s32_register16_(VLEVEL, 0); | ||||||
|  |   this->write_s32_register16_(VLEVEL, 0); | ||||||
|  |  | ||||||
|  |   this->write_u8_register16_(DSPWP_SEL, DSPWP_SEL_SET); | ||||||
|  |   this->write_u8_register16_(DSPWP_SET, DSPWP_SET_RO); | ||||||
|  |   this->write_u16_register16_(RUN, RUN_ENABLE); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void ADE7880::reset_device_() { | ||||||
|  |   if (this->reset_pin_ != nullptr) { | ||||||
|  |     ESP_LOGD(TAG, "Reset device using RESET pin"); | ||||||
|  |     this->reset_pin_->digital_write(false); | ||||||
|  |     delay(1); | ||||||
|  |     this->reset_pin_->digital_write(true); | ||||||
|  |   } else { | ||||||
|  |     ESP_LOGD(TAG, "Reset device using SWRST command"); | ||||||
|  |     this->write_u16_register16_(CONFIG, CONFIG_SWRST); | ||||||
|  |   } | ||||||
|  |   this->store_.reset_pending = true; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | }  // namespace ade7880 | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										131
									
								
								esphome/components/ade7880/ade7880.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										131
									
								
								esphome/components/ade7880/ade7880.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,131 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | // This component was developed using knowledge gathered by a number | ||||||
|  | // of people who reverse-engineered the Shelly 3EM: | ||||||
|  | // | ||||||
|  | // @AndreKR on GitHub | ||||||
|  | // Axel (@Axel830 on GitHub) | ||||||
|  | // Marko (@goodkiller on GitHub) | ||||||
|  | // Michaël Piron (@michaelpiron on GitHub) | ||||||
|  | // Theo Arends (@arendst on GitHub) | ||||||
|  |  | ||||||
|  | #include "esphome/core/component.h" | ||||||
|  | #include "esphome/core/hal.h" | ||||||
|  | #include "esphome/components/i2c/i2c.h" | ||||||
|  | #include "esphome/components/sensor/sensor.h" | ||||||
|  |  | ||||||
|  | #include "ade7880_registers.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace ade7880 { | ||||||
|  |  | ||||||
|  | struct NeutralChannel { | ||||||
|  |   void set_current(sensor::Sensor *sens) { this->current = sens; } | ||||||
|  |  | ||||||
|  |   void set_current_gain_calibration(int32_t val) { this->current_gain_calibration = val; } | ||||||
|  |  | ||||||
|  |   sensor::Sensor *current{nullptr}; | ||||||
|  |   int32_t current_gain_calibration{0}; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | struct PowerChannel { | ||||||
|  |   void set_current(sensor::Sensor *sens) { this->current = sens; } | ||||||
|  |   void set_voltage(sensor::Sensor *sens) { this->voltage = sens; } | ||||||
|  |   void set_active_power(sensor::Sensor *sens) { this->active_power = sens; } | ||||||
|  |   void set_apparent_power(sensor::Sensor *sens) { this->apparent_power = sens; } | ||||||
|  |   void set_power_factor(sensor::Sensor *sens) { this->power_factor = sens; } | ||||||
|  |   void set_forward_active_energy(sensor::Sensor *sens) { this->forward_active_energy = sens; } | ||||||
|  |   void set_reverse_active_energy(sensor::Sensor *sens) { this->reverse_active_energy = sens; } | ||||||
|  |  | ||||||
|  |   void set_current_gain_calibration(int32_t val) { this->current_gain_calibration = val; } | ||||||
|  |   void set_voltage_gain_calibration(int32_t val) { this->voltage_gain_calibration = val; } | ||||||
|  |   void set_power_gain_calibration(int32_t val) { this->power_gain_calibration = val; } | ||||||
|  |   void set_phase_angle_calibration(int32_t val) { this->phase_angle_calibration = val; } | ||||||
|  |  | ||||||
|  |   sensor::Sensor *current{nullptr}; | ||||||
|  |   sensor::Sensor *voltage{nullptr}; | ||||||
|  |   sensor::Sensor *active_power{nullptr}; | ||||||
|  |   sensor::Sensor *apparent_power{nullptr}; | ||||||
|  |   sensor::Sensor *power_factor{nullptr}; | ||||||
|  |   sensor::Sensor *forward_active_energy{nullptr}; | ||||||
|  |   sensor::Sensor *reverse_active_energy{nullptr}; | ||||||
|  |   int32_t current_gain_calibration{0}; | ||||||
|  |   int32_t voltage_gain_calibration{0}; | ||||||
|  |   int32_t power_gain_calibration{0}; | ||||||
|  |   uint16_t phase_angle_calibration{0}; | ||||||
|  |   float forward_active_energy_total{0}; | ||||||
|  |   float reverse_active_energy_total{0}; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | // Store data in a class that doesn't use multiple-inheritance (no vtables in flash!) | ||||||
|  | struct ADE7880Store { | ||||||
|  |   volatile bool reset_done{false}; | ||||||
|  |   bool reset_pending{false}; | ||||||
|  |   ISRInternalGPIOPin irq1_pin; | ||||||
|  |  | ||||||
|  |   static void gpio_intr(ADE7880Store *arg); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | class ADE7880 : public i2c::I2CDevice, public PollingComponent { | ||||||
|  |  public: | ||||||
|  |   void set_irq0_pin(InternalGPIOPin *pin) { this->irq0_pin_ = pin; } | ||||||
|  |   void set_irq1_pin(InternalGPIOPin *pin) { this->irq1_pin_ = pin; } | ||||||
|  |   void set_reset_pin(InternalGPIOPin *pin) { this->reset_pin_ = pin; } | ||||||
|  |   void set_frequency(float frequency) { this->frequency_ = frequency; } | ||||||
|  |   void set_channel_n(NeutralChannel *channel) { this->channel_n_ = channel; } | ||||||
|  |   void set_channel_a(PowerChannel *channel) { this->channel_a_ = channel; } | ||||||
|  |   void set_channel_b(PowerChannel *channel) { this->channel_b_ = channel; } | ||||||
|  |   void set_channel_c(PowerChannel *channel) { this->channel_c_ = channel; } | ||||||
|  |  | ||||||
|  |   void setup() override; | ||||||
|  |  | ||||||
|  |   void loop() override; | ||||||
|  |  | ||||||
|  |   void update() override; | ||||||
|  |  | ||||||
|  |   void dump_config() override; | ||||||
|  |  | ||||||
|  |   float get_setup_priority() const override { return setup_priority::DATA; } | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   ADE7880Store store_{}; | ||||||
|  |   InternalGPIOPin *irq0_pin_{nullptr}; | ||||||
|  |   InternalGPIOPin *irq1_pin_{nullptr}; | ||||||
|  |   InternalGPIOPin *reset_pin_{nullptr}; | ||||||
|  |   float frequency_; | ||||||
|  |   NeutralChannel *channel_n_{nullptr}; | ||||||
|  |   PowerChannel *channel_a_{nullptr}; | ||||||
|  |   PowerChannel *channel_b_{nullptr}; | ||||||
|  |   PowerChannel *channel_c_{nullptr}; | ||||||
|  |  | ||||||
|  |   void calibrate_s10zp_reading_(uint16_t a_register, int16_t calibration); | ||||||
|  |   void calibrate_s24zpse_reading_(uint16_t a_register, int32_t calibration); | ||||||
|  |  | ||||||
|  |   void init_device_(); | ||||||
|  |  | ||||||
|  |   // each of these functions allow the caller to pass in a lambda (or any other callable) | ||||||
|  |   // which modifies the value read from the register before it is passed to the sensor | ||||||
|  |   // the callable will be passed a 'float' value and is expected to return a 'float' | ||||||
|  |   template<typename F> void update_sensor_from_s24zp_register16_(sensor::Sensor *sensor, uint16_t a_register, F &&f); | ||||||
|  |   template<typename F> void update_sensor_from_s16_register16_(sensor::Sensor *sensor, uint16_t a_register, F &&f); | ||||||
|  |   template<typename F> void update_sensor_from_s32_register16_(sensor::Sensor *sensor, uint16_t a_register, F &&f); | ||||||
|  |  | ||||||
|  |   void reset_device_(); | ||||||
|  |  | ||||||
|  |   uint8_t read_u8_register16_(uint16_t a_register); | ||||||
|  |   int16_t read_s16_register16_(uint16_t a_register); | ||||||
|  |   uint16_t read_u16_register16_(uint16_t a_register); | ||||||
|  |   int32_t read_s24zp_register16_(uint16_t a_register); | ||||||
|  |   int32_t read_s32_register16_(uint16_t a_register); | ||||||
|  |   uint32_t read_u32_register16_(uint16_t a_register); | ||||||
|  |  | ||||||
|  |   void write_u8_register16_(uint16_t a_register, uint8_t value); | ||||||
|  |   void write_s10zp_register16_(uint16_t a_register, int16_t value); | ||||||
|  |   void write_u16_register16_(uint16_t a_register, uint16_t value); | ||||||
|  |   void write_s24zpse_register16_(uint16_t a_register, int32_t value); | ||||||
|  |   void write_s32_register16_(uint16_t a_register, int32_t value); | ||||||
|  |   void write_u32_register16_(uint16_t a_register, uint32_t value); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | }  // namespace ade7880 | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										101
									
								
								esphome/components/ade7880/ade7880_i2c.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										101
									
								
								esphome/components/ade7880/ade7880_i2c.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,101 @@ | |||||||
|  | // This component was developed using knowledge gathered by a number | ||||||
|  | // of people who reverse-engineered the Shelly 3EM: | ||||||
|  | // | ||||||
|  | // @AndreKR on GitHub | ||||||
|  | // Axel (@Axel830 on GitHub) | ||||||
|  | // Marko (@goodkiller on GitHub) | ||||||
|  | // Michaël Piron (@michaelpiron on GitHub) | ||||||
|  | // Theo Arends (@arendst on GitHub) | ||||||
|  |  | ||||||
|  | #include "ade7880.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace ade7880 { | ||||||
|  |  | ||||||
|  | // adapted from https://stackoverflow.com/a/55912127/1886371 | ||||||
|  | template<size_t Bits, typename T> inline T sign_extend(const T &v) noexcept { | ||||||
|  |   using S = struct { signed Val : Bits; }; | ||||||
|  |   return reinterpret_cast<const S *>(&v)->Val; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Register types | ||||||
|  | // unsigned 8-bit (uint8_t) | ||||||
|  | // signed 10-bit - 16-bit ZP on wire (int16_t, needs sign extension) | ||||||
|  | // unsigned 16-bit (uint16_t) | ||||||
|  | // unsigned 20-bit - 32-bit ZP on wire (uint32_t) | ||||||
|  | // signed 24-bit - 32-bit ZPSE on wire (int32_t, needs sign extension) | ||||||
|  | // signed 24-bit - 32-bit ZP on wire (int32_t, needs sign extension) | ||||||
|  | // signed 24-bit - 32-bit SE on wire (int32_t) | ||||||
|  | // signed 28-bit - 32-bit ZP on wire (int32_t, needs sign extension) | ||||||
|  | // unsigned 32-bit (uint32_t) | ||||||
|  | // signed 32-bit (int32_t) | ||||||
|  |  | ||||||
|  | uint8_t ADE7880::read_u8_register16_(uint16_t a_register) { | ||||||
|  |   uint8_t in; | ||||||
|  |   this->read_register16(a_register, &in, sizeof(in)); | ||||||
|  |   return in; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | int16_t ADE7880::read_s16_register16_(uint16_t a_register) { | ||||||
|  |   int16_t in; | ||||||
|  |   this->read_register16(a_register, reinterpret_cast<uint8_t *>(&in), sizeof(in)); | ||||||
|  |   return convert_big_endian(in); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | uint16_t ADE7880::read_u16_register16_(uint16_t a_register) { | ||||||
|  |   uint16_t in; | ||||||
|  |   this->read_register16(a_register, reinterpret_cast<uint8_t *>(&in), sizeof(in)); | ||||||
|  |   return convert_big_endian(in); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | int32_t ADE7880::read_s24zp_register16_(uint16_t a_register) { | ||||||
|  |   // s24zp means 24 bit signed value in the lower 24 bits of a 32-bit register | ||||||
|  |   int32_t in; | ||||||
|  |   this->read_register16(a_register, reinterpret_cast<uint8_t *>(&in), sizeof(in)); | ||||||
|  |   return sign_extend<24>(convert_big_endian(in)); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | int32_t ADE7880::read_s32_register16_(uint16_t a_register) { | ||||||
|  |   int32_t in; | ||||||
|  |   this->read_register16(a_register, reinterpret_cast<uint8_t *>(&in), sizeof(in)); | ||||||
|  |   return convert_big_endian(in); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | uint32_t ADE7880::read_u32_register16_(uint16_t a_register) { | ||||||
|  |   uint32_t in; | ||||||
|  |   this->read_register16(a_register, reinterpret_cast<uint8_t *>(&in), sizeof(in)); | ||||||
|  |   return convert_big_endian(in); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void ADE7880::write_u8_register16_(uint16_t a_register, uint8_t value) { | ||||||
|  |   this->write_register16(a_register, &value, sizeof(value)); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void ADE7880::write_s10zp_register16_(uint16_t a_register, int16_t value) { | ||||||
|  |   int16_t out = convert_big_endian(value & 0x03FF); | ||||||
|  |   this->write_register16(a_register, reinterpret_cast<uint8_t *>(&out), sizeof(out)); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void ADE7880::write_u16_register16_(uint16_t a_register, uint16_t value) { | ||||||
|  |   uint16_t out = convert_big_endian(value); | ||||||
|  |   this->write_register16(a_register, reinterpret_cast<uint8_t *>(&out), sizeof(out)); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void ADE7880::write_s24zpse_register16_(uint16_t a_register, int32_t value) { | ||||||
|  |   // s24zpse means a 24-bit signed value, sign-extended to 28 bits, in the lower 28 bits of a 32-bit register | ||||||
|  |   int32_t out = convert_big_endian(value & 0x0FFFFFFF); | ||||||
|  |   this->write_register16(a_register, reinterpret_cast<uint8_t *>(&out), sizeof(out)); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void ADE7880::write_s32_register16_(uint16_t a_register, int32_t value) { | ||||||
|  |   int32_t out = convert_big_endian(value); | ||||||
|  |   this->write_register16(a_register, reinterpret_cast<uint8_t *>(&out), sizeof(out)); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void ADE7880::write_u32_register16_(uint16_t a_register, uint32_t value) { | ||||||
|  |   uint32_t out = convert_big_endian(value); | ||||||
|  |   this->write_register16(a_register, reinterpret_cast<uint8_t *>(&out), sizeof(out)); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | }  // namespace ade7880 | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										243
									
								
								esphome/components/ade7880/ade7880_registers.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										243
									
								
								esphome/components/ade7880/ade7880_registers.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,243 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | // This file is a modified version of the one created by Michaël Piron (@michaelpiron on GitHub) | ||||||
|  |  | ||||||
|  | // Source: https://www.analog.com/media/en/technical-documentation/application-notes/AN-1127.pdf | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace ade7880 { | ||||||
|  |  | ||||||
|  | // DSP Data Memory RAM registers | ||||||
|  | constexpr uint16_t AIGAIN = 0x4380; | ||||||
|  | constexpr uint16_t AVGAIN = 0x4381; | ||||||
|  | constexpr uint16_t BIGAIN = 0x4382; | ||||||
|  | constexpr uint16_t BVGAIN = 0x4383; | ||||||
|  | constexpr uint16_t CIGAIN = 0x4384; | ||||||
|  | constexpr uint16_t CVGAIN = 0x4385; | ||||||
|  | constexpr uint16_t NIGAIN = 0x4386; | ||||||
|  |  | ||||||
|  | constexpr uint16_t DICOEFF = 0x4388; | ||||||
|  |  | ||||||
|  | constexpr uint16_t APGAIN = 0x4389; | ||||||
|  | constexpr uint16_t AWATTOS = 0x438A; | ||||||
|  | constexpr uint16_t BPGAIN = 0x438B; | ||||||
|  | constexpr uint16_t BWATTOS = 0x438C; | ||||||
|  | constexpr uint16_t CPGAIN = 0x438D; | ||||||
|  | constexpr uint16_t CWATTOS = 0x438E; | ||||||
|  | constexpr uint16_t AIRMSOS = 0x438F; | ||||||
|  | constexpr uint16_t AVRMSOS = 0x4390; | ||||||
|  | constexpr uint16_t BIRMSOS = 0x4391; | ||||||
|  | constexpr uint16_t BVRMSOS = 0x4392; | ||||||
|  | constexpr uint16_t CIRMSOS = 0x4393; | ||||||
|  | constexpr uint16_t CVRMSOS = 0x4394; | ||||||
|  | constexpr uint16_t NIRMSOS = 0x4395; | ||||||
|  | constexpr uint16_t HPGAIN = 0x4398; | ||||||
|  | constexpr uint16_t ISUMLVL = 0x4399; | ||||||
|  |  | ||||||
|  | constexpr uint16_t VLEVEL = 0x439F; | ||||||
|  |  | ||||||
|  | constexpr uint16_t AFWATTOS = 0x43A2; | ||||||
|  | constexpr uint16_t BFWATTOS = 0x43A3; | ||||||
|  | constexpr uint16_t CFWATTOS = 0x43A4; | ||||||
|  |  | ||||||
|  | constexpr uint16_t AFVAROS = 0x43A5; | ||||||
|  | constexpr uint16_t BFVAROS = 0x43A6; | ||||||
|  | constexpr uint16_t CFVAROS = 0x43A7; | ||||||
|  |  | ||||||
|  | constexpr uint16_t AFIRMSOS = 0x43A8; | ||||||
|  | constexpr uint16_t BFIRMSOS = 0x43A9; | ||||||
|  | constexpr uint16_t CFIRMSOS = 0x43AA; | ||||||
|  |  | ||||||
|  | constexpr uint16_t AFVRMSOS = 0x43AB; | ||||||
|  | constexpr uint16_t BFVRMSOS = 0x43AC; | ||||||
|  | constexpr uint16_t CFVRMSOS = 0x43AD; | ||||||
|  |  | ||||||
|  | constexpr uint16_t HXWATTOS = 0x43AE; | ||||||
|  | constexpr uint16_t HYWATTOS = 0x43AF; | ||||||
|  | constexpr uint16_t HZWATTOS = 0x43B0; | ||||||
|  | constexpr uint16_t HXVAROS = 0x43B1; | ||||||
|  | constexpr uint16_t HYVAROS = 0x43B2; | ||||||
|  | constexpr uint16_t HZVAROS = 0x43B3; | ||||||
|  |  | ||||||
|  | constexpr uint16_t HXIRMSOS = 0x43B4; | ||||||
|  | constexpr uint16_t HYIRMSOS = 0x43B5; | ||||||
|  | constexpr uint16_t HZIRMSOS = 0x43B6; | ||||||
|  | constexpr uint16_t HXVRMSOS = 0x43B7; | ||||||
|  | constexpr uint16_t HYVRMSOS = 0x43B8; | ||||||
|  | constexpr uint16_t HZVRMSOS = 0x43B9; | ||||||
|  |  | ||||||
|  | constexpr uint16_t AIRMS = 0x43C0; | ||||||
|  | constexpr uint16_t AVRMS = 0x43C1; | ||||||
|  | constexpr uint16_t BIRMS = 0x43C2; | ||||||
|  | constexpr uint16_t BVRMS = 0x43C3; | ||||||
|  | constexpr uint16_t CIRMS = 0x43C4; | ||||||
|  | constexpr uint16_t CVRMS = 0x43C5; | ||||||
|  | constexpr uint16_t NIRMS = 0x43C6; | ||||||
|  |  | ||||||
|  | constexpr uint16_t ISUM = 0x43C7; | ||||||
|  |  | ||||||
|  | // Internal DSP Memory RAM registers | ||||||
|  | constexpr uint16_t RUN = 0xE228; | ||||||
|  |  | ||||||
|  | constexpr uint16_t AWATTHR = 0xE400; | ||||||
|  | constexpr uint16_t BWATTHR = 0xE401; | ||||||
|  | constexpr uint16_t CWATTHR = 0xE402; | ||||||
|  | constexpr uint16_t AFWATTHR = 0xE403; | ||||||
|  | constexpr uint16_t BFWATTHR = 0xE404; | ||||||
|  | constexpr uint16_t CFWATTHR = 0xE405; | ||||||
|  | constexpr uint16_t AFVARHR = 0xE409; | ||||||
|  | constexpr uint16_t BFVARHR = 0xE40A; | ||||||
|  | constexpr uint16_t CFVARHR = 0xE40B; | ||||||
|  |  | ||||||
|  | constexpr uint16_t AVAHR = 0xE40C; | ||||||
|  | constexpr uint16_t BVAHR = 0xE40D; | ||||||
|  | constexpr uint16_t CVAHR = 0xE40E; | ||||||
|  |  | ||||||
|  | constexpr uint16_t IPEAK = 0xE500; | ||||||
|  | constexpr uint16_t VPEAK = 0xE501; | ||||||
|  |  | ||||||
|  | constexpr uint16_t STATUS0 = 0xE502; | ||||||
|  | constexpr uint16_t STATUS1 = 0xE503; | ||||||
|  |  | ||||||
|  | constexpr uint16_t AIMAV = 0xE504; | ||||||
|  | constexpr uint16_t BIMAV = 0xE505; | ||||||
|  | constexpr uint16_t CIMAV = 0xE506; | ||||||
|  |  | ||||||
|  | constexpr uint16_t OILVL = 0xE507; | ||||||
|  | constexpr uint16_t OVLVL = 0xE508; | ||||||
|  | constexpr uint16_t SAGLVL = 0xE509; | ||||||
|  | constexpr uint16_t MASK0 = 0xE50A; | ||||||
|  | constexpr uint16_t MASK1 = 0xE50B; | ||||||
|  |  | ||||||
|  | constexpr uint16_t IAWV = 0xE50C; | ||||||
|  | constexpr uint16_t IBWV = 0xE50D; | ||||||
|  | constexpr uint16_t ICWV = 0xE50E; | ||||||
|  | constexpr uint16_t INWV = 0xE50F; | ||||||
|  | constexpr uint16_t VAWV = 0xE510; | ||||||
|  | constexpr uint16_t VBWV = 0xE511; | ||||||
|  | constexpr uint16_t VCWV = 0xE512; | ||||||
|  |  | ||||||
|  | constexpr uint16_t AWATT = 0xE513; | ||||||
|  | constexpr uint16_t BWATT = 0xE514; | ||||||
|  | constexpr uint16_t CWATT = 0xE515; | ||||||
|  |  | ||||||
|  | constexpr uint16_t AFVAR = 0xE516; | ||||||
|  | constexpr uint16_t BFVAR = 0xE517; | ||||||
|  | constexpr uint16_t CFVAR = 0xE518; | ||||||
|  |  | ||||||
|  | constexpr uint16_t AVA = 0xE519; | ||||||
|  | constexpr uint16_t BVA = 0xE51A; | ||||||
|  | constexpr uint16_t CVA = 0xE51B; | ||||||
|  |  | ||||||
|  | constexpr uint16_t CHECKSUM = 0xE51F; | ||||||
|  | constexpr uint16_t VNOM = 0xE520; | ||||||
|  | constexpr uint16_t LAST_RWDATA_24BIT = 0xE5FF; | ||||||
|  | constexpr uint16_t PHSTATUS = 0xE600; | ||||||
|  | constexpr uint16_t ANGLE0 = 0xE601; | ||||||
|  | constexpr uint16_t ANGLE1 = 0xE602; | ||||||
|  | constexpr uint16_t ANGLE2 = 0xE603; | ||||||
|  | constexpr uint16_t PHNOLOAD = 0xE608; | ||||||
|  | constexpr uint16_t LINECYC = 0xE60C; | ||||||
|  | constexpr uint16_t ZXTOUT = 0xE60D; | ||||||
|  | constexpr uint16_t COMPMODE = 0xE60E; | ||||||
|  | constexpr uint16_t GAIN = 0xE60F; | ||||||
|  | constexpr uint16_t CFMODE = 0xE610; | ||||||
|  | constexpr uint16_t CF1DEN = 0xE611; | ||||||
|  | constexpr uint16_t CF2DEN = 0xE612; | ||||||
|  | constexpr uint16_t CF3DEN = 0xE613; | ||||||
|  | constexpr uint16_t APHCAL = 0xE614; | ||||||
|  | constexpr uint16_t BPHCAL = 0xE615; | ||||||
|  | constexpr uint16_t CPHCAL = 0xE616; | ||||||
|  | constexpr uint16_t PHSIGN = 0xE617; | ||||||
|  | constexpr uint16_t CONFIG = 0xE618; | ||||||
|  | constexpr uint16_t MMODE = 0xE700; | ||||||
|  | constexpr uint16_t ACCMODE = 0xE701; | ||||||
|  | constexpr uint16_t LCYCMODE = 0xE702; | ||||||
|  | constexpr uint16_t PEAKCYC = 0xE703; | ||||||
|  | constexpr uint16_t SAGCYC = 0xE704; | ||||||
|  | constexpr uint16_t CFCYC = 0xE705; | ||||||
|  | constexpr uint16_t HSDC_CFG = 0xE706; | ||||||
|  | constexpr uint16_t VERSION = 0xE707; | ||||||
|  | constexpr uint16_t DSPWP_SET = 0xE7E3; | ||||||
|  | constexpr uint16_t LAST_RWDATA_8BIT = 0xE7FD; | ||||||
|  | constexpr uint16_t DSPWP_SEL = 0xE7FE; | ||||||
|  | constexpr uint16_t FVRMS = 0xE880; | ||||||
|  | constexpr uint16_t FIRMS = 0xE881; | ||||||
|  | constexpr uint16_t FWATT = 0xE882; | ||||||
|  | constexpr uint16_t FVAR = 0xE883; | ||||||
|  | constexpr uint16_t FVA = 0xE884; | ||||||
|  | constexpr uint16_t FPF = 0xE885; | ||||||
|  | constexpr uint16_t VTHDN = 0xE886; | ||||||
|  | constexpr uint16_t ITHDN = 0xE887; | ||||||
|  | constexpr uint16_t HXVRMS = 0xE888; | ||||||
|  | constexpr uint16_t HXIRMS = 0xE889; | ||||||
|  | constexpr uint16_t HXWATT = 0xE88A; | ||||||
|  | constexpr uint16_t HXVAR = 0xE88B; | ||||||
|  | constexpr uint16_t HXVA = 0xE88C; | ||||||
|  | constexpr uint16_t HXPF = 0xE88D; | ||||||
|  | constexpr uint16_t HXVHD = 0xE88E; | ||||||
|  | constexpr uint16_t HXIHD = 0xE88F; | ||||||
|  | constexpr uint16_t HYVRMS = 0xE890; | ||||||
|  | constexpr uint16_t HYIRMS = 0xE891; | ||||||
|  | constexpr uint16_t HYWATT = 0xE892; | ||||||
|  | constexpr uint16_t HYVAR = 0xE893; | ||||||
|  | constexpr uint16_t HYVA = 0xE894; | ||||||
|  | constexpr uint16_t HYPF = 0xE895; | ||||||
|  | constexpr uint16_t HYVHD = 0xE896; | ||||||
|  | constexpr uint16_t HYIHD = 0xE897; | ||||||
|  | constexpr uint16_t HZVRMS = 0xE898; | ||||||
|  | constexpr uint16_t HZIRMS = 0xE899; | ||||||
|  | constexpr uint16_t HZWATT = 0xE89A; | ||||||
|  | constexpr uint16_t HZVAR = 0xE89B; | ||||||
|  | constexpr uint16_t HZVA = 0xE89C; | ||||||
|  | constexpr uint16_t HZPF = 0xE89D; | ||||||
|  | constexpr uint16_t HZVHD = 0xE89E; | ||||||
|  | constexpr uint16_t HZIHD = 0xE89F; | ||||||
|  | constexpr uint16_t HCONFIG = 0xE900; | ||||||
|  | constexpr uint16_t APF = 0xE902; | ||||||
|  | constexpr uint16_t BPF = 0xE903; | ||||||
|  | constexpr uint16_t CPF = 0xE904; | ||||||
|  | constexpr uint16_t APERIOD = 0xE905; | ||||||
|  | constexpr uint16_t BPERIOD = 0xE906; | ||||||
|  | constexpr uint16_t CPERIOD = 0xE907; | ||||||
|  | constexpr uint16_t APNOLOAD = 0xE908; | ||||||
|  | constexpr uint16_t VARNOLOAD = 0xE909; | ||||||
|  | constexpr uint16_t VANOLOAD = 0xE90A; | ||||||
|  | constexpr uint16_t LAST_ADD = 0xE9FE; | ||||||
|  | constexpr uint16_t LAST_RWDATA_16BIT = 0xE9FF; | ||||||
|  | constexpr uint16_t CONFIG3 = 0xEA00; | ||||||
|  | constexpr uint16_t LAST_OP = 0xEA01; | ||||||
|  | constexpr uint16_t WTHR = 0xEA02; | ||||||
|  | constexpr uint16_t VARTHR = 0xEA03; | ||||||
|  | constexpr uint16_t VATHR = 0xEA04; | ||||||
|  |  | ||||||
|  | constexpr uint16_t HX_REG = 0xEA08; | ||||||
|  | constexpr uint16_t HY_REG = 0xEA09; | ||||||
|  | constexpr uint16_t HZ_REG = 0xEA0A; | ||||||
|  | constexpr uint16_t LPOILVL = 0xEC00; | ||||||
|  | constexpr uint16_t CONFIG2 = 0xEC01; | ||||||
|  |  | ||||||
|  | // STATUS1 Register Bits | ||||||
|  | constexpr uint32_t STATUS1_RSTDONE = (1 << 15); | ||||||
|  |  | ||||||
|  | // CONFIG Register Bits | ||||||
|  | constexpr uint16_t CONFIG_SWRST = (1 << 7); | ||||||
|  |  | ||||||
|  | // CONFIG2 Register Bits | ||||||
|  | constexpr uint8_t CONFIG2_I2C_LOCK = (1 << 1); | ||||||
|  |  | ||||||
|  | // COMPMODE Register Bits | ||||||
|  | constexpr uint16_t COMPMODE_DEFAULT = 0x01FF; | ||||||
|  | constexpr uint16_t COMPMODE_SELFREQ = (1 << 14); | ||||||
|  |  | ||||||
|  | // RUN Register Bits | ||||||
|  | constexpr uint16_t RUN_ENABLE = (1 << 0); | ||||||
|  |  | ||||||
|  | // DSPWP_SET Register Bits | ||||||
|  | constexpr uint8_t DSPWP_SET_RO = (1 << 7); | ||||||
|  |  | ||||||
|  | // DSPWP_SEL Register Bits | ||||||
|  | constexpr uint8_t DSPWP_SEL_SET = 0xAD; | ||||||
|  |  | ||||||
|  | }  // namespace ade7880 | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										290
									
								
								esphome/components/ade7880/sensor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										290
									
								
								esphome/components/ade7880/sensor.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,290 @@ | |||||||
|  | import esphome.codegen as cg | ||||||
|  | import esphome.config_validation as cv | ||||||
|  | from esphome.components import sensor, i2c | ||||||
|  | from esphome import pins | ||||||
|  | from esphome.const import ( | ||||||
|  |     CONF_ACTIVE_POWER, | ||||||
|  |     CONF_APPARENT_POWER, | ||||||
|  |     CONF_CALIBRATION, | ||||||
|  |     CONF_CURRENT, | ||||||
|  |     CONF_FORWARD_ACTIVE_ENERGY, | ||||||
|  |     CONF_FREQUENCY, | ||||||
|  |     CONF_ID, | ||||||
|  |     CONF_NAME, | ||||||
|  |     CONF_PHASE_A, | ||||||
|  |     CONF_PHASE_ANGLE, | ||||||
|  |     CONF_PHASE_B, | ||||||
|  |     CONF_PHASE_C, | ||||||
|  |     CONF_POWER_FACTOR, | ||||||
|  |     CONF_RESET_PIN, | ||||||
|  |     CONF_REVERSE_ACTIVE_ENERGY, | ||||||
|  |     CONF_VOLTAGE, | ||||||
|  |     DEVICE_CLASS_APPARENT_POWER, | ||||||
|  |     DEVICE_CLASS_CURRENT, | ||||||
|  |     DEVICE_CLASS_ENERGY, | ||||||
|  |     DEVICE_CLASS_POWER, | ||||||
|  |     DEVICE_CLASS_POWER_FACTOR, | ||||||
|  |     DEVICE_CLASS_VOLTAGE, | ||||||
|  |     STATE_CLASS_MEASUREMENT, | ||||||
|  |     STATE_CLASS_TOTAL_INCREASING, | ||||||
|  |     UNIT_AMPERE, | ||||||
|  |     UNIT_PERCENT, | ||||||
|  |     UNIT_VOLT, | ||||||
|  |     UNIT_VOLT_AMPS, | ||||||
|  |     UNIT_VOLT_AMPS_REACTIVE_HOURS, | ||||||
|  |     UNIT_WATT, | ||||||
|  |     UNIT_WATT_HOURS, | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | DEPENDENCIES = ["i2c"] | ||||||
|  |  | ||||||
|  | ade7880_ns = cg.esphome_ns.namespace("ade7880") | ||||||
|  | ADE7880 = ade7880_ns.class_("ADE7880", cg.PollingComponent, i2c.I2CDevice) | ||||||
|  | NeutralChannel = ade7880_ns.struct("NeutralChannel") | ||||||
|  | PowerChannel = ade7880_ns.struct("PowerChannel") | ||||||
|  |  | ||||||
|  | CONF_CURRENT_GAIN = "current_gain" | ||||||
|  | CONF_IRQ0_PIN = "irq0_pin" | ||||||
|  | CONF_IRQ1_PIN = "irq1_pin" | ||||||
|  | CONF_POWER_GAIN = "power_gain" | ||||||
|  | CONF_VOLTAGE_GAIN = "voltage_gain" | ||||||
|  |  | ||||||
|  | CONF_NEUTRAL = "neutral" | ||||||
|  |  | ||||||
|  | NEUTRAL_CHANNEL_SCHEMA = cv.Schema( | ||||||
|  |     { | ||||||
|  |         cv.GenerateID(): cv.declare_id(NeutralChannel), | ||||||
|  |         cv.Optional(CONF_NAME): cv.string_strict, | ||||||
|  |         cv.Required(CONF_CURRENT): cv.maybe_simple_value( | ||||||
|  |             sensor.sensor_schema( | ||||||
|  |                 unit_of_measurement=UNIT_AMPERE, | ||||||
|  |                 accuracy_decimals=2, | ||||||
|  |                 device_class=DEVICE_CLASS_CURRENT, | ||||||
|  |                 state_class=STATE_CLASS_MEASUREMENT, | ||||||
|  |             ), | ||||||
|  |             key=CONF_NAME, | ||||||
|  |         ), | ||||||
|  |         cv.Required(CONF_CALIBRATION): cv.Schema( | ||||||
|  |             { | ||||||
|  |                 cv.Required(CONF_CURRENT_GAIN): cv.int_, | ||||||
|  |             }, | ||||||
|  |         ), | ||||||
|  |     } | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | POWER_CHANNEL_SCHEMA = cv.Schema( | ||||||
|  |     { | ||||||
|  |         cv.GenerateID(): cv.declare_id(PowerChannel), | ||||||
|  |         cv.Optional(CONF_NAME): cv.string_strict, | ||||||
|  |         cv.Optional(CONF_VOLTAGE): cv.maybe_simple_value( | ||||||
|  |             sensor.sensor_schema( | ||||||
|  |                 unit_of_measurement=UNIT_VOLT, | ||||||
|  |                 accuracy_decimals=1, | ||||||
|  |                 device_class=DEVICE_CLASS_VOLTAGE, | ||||||
|  |                 state_class=STATE_CLASS_MEASUREMENT, | ||||||
|  |             ), | ||||||
|  |             key=CONF_NAME, | ||||||
|  |         ), | ||||||
|  |         cv.Optional(CONF_CURRENT): cv.maybe_simple_value( | ||||||
|  |             sensor.sensor_schema( | ||||||
|  |                 unit_of_measurement=UNIT_AMPERE, | ||||||
|  |                 accuracy_decimals=2, | ||||||
|  |                 device_class=DEVICE_CLASS_CURRENT, | ||||||
|  |                 state_class=STATE_CLASS_MEASUREMENT, | ||||||
|  |             ), | ||||||
|  |             key=CONF_NAME, | ||||||
|  |         ), | ||||||
|  |         cv.Optional(CONF_ACTIVE_POWER): cv.maybe_simple_value( | ||||||
|  |             sensor.sensor_schema( | ||||||
|  |                 unit_of_measurement=UNIT_WATT, | ||||||
|  |                 accuracy_decimals=1, | ||||||
|  |                 device_class=DEVICE_CLASS_POWER, | ||||||
|  |                 state_class=STATE_CLASS_MEASUREMENT, | ||||||
|  |             ), | ||||||
|  |             key=CONF_NAME, | ||||||
|  |         ), | ||||||
|  |         cv.Optional(CONF_APPARENT_POWER): cv.maybe_simple_value( | ||||||
|  |             sensor.sensor_schema( | ||||||
|  |                 unit_of_measurement=UNIT_VOLT_AMPS, | ||||||
|  |                 accuracy_decimals=1, | ||||||
|  |                 device_class=DEVICE_CLASS_APPARENT_POWER, | ||||||
|  |                 state_class=STATE_CLASS_MEASUREMENT, | ||||||
|  |             ), | ||||||
|  |             key=CONF_NAME, | ||||||
|  |         ), | ||||||
|  |         cv.Optional(CONF_POWER_FACTOR): cv.maybe_simple_value( | ||||||
|  |             sensor.sensor_schema( | ||||||
|  |                 unit_of_measurement=UNIT_PERCENT, | ||||||
|  |                 accuracy_decimals=0, | ||||||
|  |                 device_class=DEVICE_CLASS_POWER_FACTOR, | ||||||
|  |                 state_class=STATE_CLASS_MEASUREMENT, | ||||||
|  |             ), | ||||||
|  |             key=CONF_NAME, | ||||||
|  |         ), | ||||||
|  |         cv.Optional(CONF_FORWARD_ACTIVE_ENERGY): cv.maybe_simple_value( | ||||||
|  |             sensor.sensor_schema( | ||||||
|  |                 unit_of_measurement=UNIT_WATT_HOURS, | ||||||
|  |                 accuracy_decimals=2, | ||||||
|  |                 device_class=DEVICE_CLASS_ENERGY, | ||||||
|  |                 state_class=STATE_CLASS_TOTAL_INCREASING, | ||||||
|  |             ), | ||||||
|  |             key=CONF_NAME, | ||||||
|  |         ), | ||||||
|  |         cv.Optional(CONF_REVERSE_ACTIVE_ENERGY): cv.maybe_simple_value( | ||||||
|  |             sensor.sensor_schema( | ||||||
|  |                 unit_of_measurement=UNIT_VOLT_AMPS_REACTIVE_HOURS, | ||||||
|  |                 accuracy_decimals=2, | ||||||
|  |                 device_class=DEVICE_CLASS_ENERGY, | ||||||
|  |                 state_class=STATE_CLASS_TOTAL_INCREASING, | ||||||
|  |             ), | ||||||
|  |             key=CONF_NAME, | ||||||
|  |         ), | ||||||
|  |         cv.Required(CONF_CALIBRATION): cv.Schema( | ||||||
|  |             { | ||||||
|  |                 cv.Required(CONF_CURRENT_GAIN): cv.int_, | ||||||
|  |                 cv.Required(CONF_VOLTAGE_GAIN): cv.int_, | ||||||
|  |                 cv.Required(CONF_POWER_GAIN): cv.int_, | ||||||
|  |                 cv.Required(CONF_PHASE_ANGLE): cv.int_, | ||||||
|  |             }, | ||||||
|  |         ), | ||||||
|  |     } | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | CONFIG_SCHEMA = ( | ||||||
|  |     cv.Schema( | ||||||
|  |         { | ||||||
|  |             cv.GenerateID(): cv.declare_id(ADE7880), | ||||||
|  |             cv.Optional(CONF_FREQUENCY, default="50Hz"): cv.All( | ||||||
|  |                 cv.frequency, cv.Range(min=45.0, max=66.0) | ||||||
|  |             ), | ||||||
|  |             cv.Optional(CONF_IRQ0_PIN): pins.internal_gpio_input_pin_schema, | ||||||
|  |             cv.Required(CONF_IRQ1_PIN): pins.internal_gpio_input_pin_schema, | ||||||
|  |             cv.Optional(CONF_RESET_PIN): pins.internal_gpio_output_pin_schema, | ||||||
|  |             cv.Optional(CONF_PHASE_A): POWER_CHANNEL_SCHEMA, | ||||||
|  |             cv.Optional(CONF_PHASE_B): POWER_CHANNEL_SCHEMA, | ||||||
|  |             cv.Optional(CONF_PHASE_C): POWER_CHANNEL_SCHEMA, | ||||||
|  |             cv.Optional(CONF_NEUTRAL): NEUTRAL_CHANNEL_SCHEMA, | ||||||
|  |         } | ||||||
|  |     ) | ||||||
|  |     .extend(cv.polling_component_schema("60s")) | ||||||
|  |     .extend(i2c.i2c_device_schema(0x38)) | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | async def neutral_channel(config): | ||||||
|  |     var = cg.new_Pvariable(config[CONF_ID]) | ||||||
|  |  | ||||||
|  |     current = config[CONF_CURRENT] | ||||||
|  |     sens = await sensor.new_sensor(current) | ||||||
|  |     cg.add(var.set_current(sens)) | ||||||
|  |  | ||||||
|  |     cg.add( | ||||||
|  |         var.set_current_gain_calibration(config[CONF_CALIBRATION][CONF_CURRENT_GAIN]) | ||||||
|  |     ) | ||||||
|  |  | ||||||
|  |     return var | ||||||
|  |  | ||||||
|  |  | ||||||
|  | async def power_channel(config): | ||||||
|  |     var = cg.new_Pvariable(config[CONF_ID]) | ||||||
|  |  | ||||||
|  |     for sensor_type in [ | ||||||
|  |         CONF_CURRENT, | ||||||
|  |         CONF_VOLTAGE, | ||||||
|  |         CONF_ACTIVE_POWER, | ||||||
|  |         CONF_APPARENT_POWER, | ||||||
|  |         CONF_POWER_FACTOR, | ||||||
|  |         CONF_FORWARD_ACTIVE_ENERGY, | ||||||
|  |         CONF_REVERSE_ACTIVE_ENERGY, | ||||||
|  |     ]: | ||||||
|  |         if conf := config.get(sensor_type): | ||||||
|  |             sens = await sensor.new_sensor(conf) | ||||||
|  |             cg.add(getattr(var, f"set_{sensor_type}")(sens)) | ||||||
|  |  | ||||||
|  |     for calib_type in [ | ||||||
|  |         CONF_CURRENT_GAIN, | ||||||
|  |         CONF_VOLTAGE_GAIN, | ||||||
|  |         CONF_POWER_GAIN, | ||||||
|  |         CONF_PHASE_ANGLE, | ||||||
|  |     ]: | ||||||
|  |         cg.add( | ||||||
|  |             getattr(var, f"set_{calib_type}_calibration")( | ||||||
|  |                 config[CONF_CALIBRATION][calib_type] | ||||||
|  |             ) | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |     return var | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def final_validate(config): | ||||||
|  |     for channel in [CONF_PHASE_A, CONF_PHASE_B, CONF_PHASE_C]: | ||||||
|  |         if channel := config.get(channel): | ||||||
|  |             channel_name = channel.get(CONF_NAME) | ||||||
|  |  | ||||||
|  |             for sensor_type in [ | ||||||
|  |                 CONF_CURRENT, | ||||||
|  |                 CONF_VOLTAGE, | ||||||
|  |                 CONF_ACTIVE_POWER, | ||||||
|  |                 CONF_APPARENT_POWER, | ||||||
|  |                 CONF_POWER_FACTOR, | ||||||
|  |                 CONF_FORWARD_ACTIVE_ENERGY, | ||||||
|  |                 CONF_REVERSE_ACTIVE_ENERGY, | ||||||
|  |             ]: | ||||||
|  |                 if conf := channel.get(sensor_type): | ||||||
|  |                     sensor_name = conf.get(CONF_NAME) | ||||||
|  |                     if ( | ||||||
|  |                         sensor_name | ||||||
|  |                         and channel_name | ||||||
|  |                         and not sensor_name.startswith(channel_name) | ||||||
|  |                     ): | ||||||
|  |                         conf[CONF_NAME] = f"{channel_name} {sensor_name}" | ||||||
|  |  | ||||||
|  |     if channel := config.get(CONF_NEUTRAL): | ||||||
|  |         channel_name = channel.get(CONF_NAME) | ||||||
|  |         if conf := channel.get(CONF_CURRENT): | ||||||
|  |             sensor_name = conf.get(CONF_NAME) | ||||||
|  |             if ( | ||||||
|  |                 sensor_name | ||||||
|  |                 and channel_name | ||||||
|  |                 and not sensor_name.startswith(channel_name) | ||||||
|  |             ): | ||||||
|  |                 conf[CONF_NAME] = f"{channel_name} {sensor_name}" | ||||||
|  |  | ||||||
|  |  | ||||||
|  | FINAL_VALIDATE_SCHEMA = final_validate | ||||||
|  |  | ||||||
|  |  | ||||||
|  | async def to_code(config): | ||||||
|  |     var = cg.new_Pvariable(config[CONF_ID]) | ||||||
|  |     await cg.register_component(var, config) | ||||||
|  |     await i2c.register_i2c_device(var, config) | ||||||
|  |  | ||||||
|  |     if irq0_pin := config.get(CONF_IRQ0_PIN): | ||||||
|  |         pin = await cg.gpio_pin_expression(irq0_pin) | ||||||
|  |         cg.add(var.set_irq0_pin(pin)) | ||||||
|  |  | ||||||
|  |     pin = await cg.gpio_pin_expression(config[CONF_IRQ1_PIN]) | ||||||
|  |     cg.add(var.set_irq1_pin(pin)) | ||||||
|  |  | ||||||
|  |     if reset_pin := config.get(CONF_RESET_PIN): | ||||||
|  |         pin = await cg.gpio_pin_expression(reset_pin) | ||||||
|  |         cg.add(var.set_reset_pin(pin)) | ||||||
|  |  | ||||||
|  |     if frequency := config.get(CONF_FREQUENCY): | ||||||
|  |         cg.add(var.set_frequency(frequency)) | ||||||
|  |  | ||||||
|  |     if channel := config.get(CONF_PHASE_A): | ||||||
|  |         chan = await power_channel(channel) | ||||||
|  |         cg.add(var.set_channel_a(chan)) | ||||||
|  |  | ||||||
|  |     if channel := config.get(CONF_PHASE_B): | ||||||
|  |         chan = await power_channel(channel) | ||||||
|  |         cg.add(var.set_channel_b(chan)) | ||||||
|  |  | ||||||
|  |     if channel := config.get(CONF_PHASE_C): | ||||||
|  |         chan = await power_channel(channel) | ||||||
|  |         cg.add(var.set_channel_c(chan)) | ||||||
|  |  | ||||||
|  |     if channel := config.get(CONF_NEUTRAL): | ||||||
|  |         chan = await neutral_channel(channel) | ||||||
|  |         cg.add(var.set_channel_n(chan)) | ||||||
| @@ -21,30 +21,39 @@ namespace esphome { | |||||||
| namespace aht10 { | namespace aht10 { | ||||||
|  |  | ||||||
| static const char *const TAG = "aht10"; | static const char *const TAG = "aht10"; | ||||||
| static const size_t SIZE_CALIBRATE_CMD = 3; | static const uint8_t AHT10_INITIALIZE_CMD[] = {0xE1, 0x08, 0x00}; | ||||||
| static const uint8_t AHT10_CALIBRATE_CMD[] = {0xE1, 0x08, 0x00}; | static const uint8_t AHT20_INITIALIZE_CMD[] = {0xBE, 0x08, 0x00}; | ||||||
| static const uint8_t AHT20_CALIBRATE_CMD[] = {0xBE, 0x08, 0x00}; |  | ||||||
| static const uint8_t AHT10_MEASURE_CMD[] = {0xAC, 0x33, 0x00}; | static const uint8_t AHT10_MEASURE_CMD[] = {0xAC, 0x33, 0x00}; | ||||||
| static const uint8_t AHT10_DEFAULT_DELAY = 5;    // ms, for calibration and temperature measurement | static const uint8_t AHT10_SOFTRESET_CMD[] = {0xBA}; | ||||||
| static const uint8_t AHT10_HUMIDITY_DELAY = 30;  // ms |  | ||||||
| static const uint8_t AHT10_ATTEMPTS = 3;         // safety margin, normally 3 attempts are enough: 3*30=90ms | static const uint8_t AHT10_DEFAULT_DELAY = 5;     // ms, for initialization and temperature measurement | ||||||
| static const uint8_t AHT10_CAL_ATTEMPTS = 10; | static const uint8_t AHT10_HUMIDITY_DELAY = 30;   // ms | ||||||
|  | static const uint8_t AHT10_SOFTRESET_DELAY = 30;  // ms | ||||||
|  |  | ||||||
|  | static const uint8_t AHT10_ATTEMPTS = 3;  // safety margin, normally 3 attempts are enough: 3*30=90ms | ||||||
|  | static const uint8_t AHT10_INIT_ATTEMPTS = 10; | ||||||
|  |  | ||||||
| static const uint8_t AHT10_STATUS_BUSY = 0x80; | static const uint8_t AHT10_STATUS_BUSY = 0x80; | ||||||
|  |  | ||||||
| void AHT10Component::setup() { | void AHT10Component::setup() { | ||||||
|   const uint8_t *calibrate_cmd; |   if (this->write(AHT10_SOFTRESET_CMD, sizeof(AHT10_SOFTRESET_CMD)) != i2c::ERROR_OK) { | ||||||
|  |     ESP_LOGE(TAG, "Reset AHT10 failed!"); | ||||||
|  |   } | ||||||
|  |   delay(AHT10_SOFTRESET_DELAY); | ||||||
|  |  | ||||||
|  |   const uint8_t *init_cmd; | ||||||
|   switch (this->variant_) { |   switch (this->variant_) { | ||||||
|     case AHT10Variant::AHT20: |     case AHT10Variant::AHT20: | ||||||
|       calibrate_cmd = AHT20_CALIBRATE_CMD; |       init_cmd = AHT20_INITIALIZE_CMD; | ||||||
|       ESP_LOGCONFIG(TAG, "Setting up AHT20"); |       ESP_LOGCONFIG(TAG, "Setting up AHT20"); | ||||||
|       break; |       break; | ||||||
|     case AHT10Variant::AHT10: |     case AHT10Variant::AHT10: | ||||||
|     default: |     default: | ||||||
|       calibrate_cmd = AHT10_CALIBRATE_CMD; |       init_cmd = AHT10_INITIALIZE_CMD; | ||||||
|       ESP_LOGCONFIG(TAG, "Setting up AHT10"); |       ESP_LOGCONFIG(TAG, "Setting up AHT10"); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   if (this->write(calibrate_cmd, SIZE_CALIBRATE_CMD) != i2c::ERROR_OK) { |   if (this->write(init_cmd, sizeof(init_cmd)) != i2c::ERROR_OK) { | ||||||
|     ESP_LOGE(TAG, "Communication with AHT10 failed!"); |     ESP_LOGE(TAG, "Communication with AHT10 failed!"); | ||||||
|     this->mark_failed(); |     this->mark_failed(); | ||||||
|     return; |     return; | ||||||
| @@ -59,19 +68,19 @@ void AHT10Component::setup() { | |||||||
|       return; |       return; | ||||||
|     } |     } | ||||||
|     ++cal_attempts; |     ++cal_attempts; | ||||||
|     if (cal_attempts > AHT10_CAL_ATTEMPTS) { |     if (cal_attempts > AHT10_INIT_ATTEMPTS) { | ||||||
|       ESP_LOGE(TAG, "AHT10 calibration timed out!"); |       ESP_LOGE(TAG, "AHT10 initialization timed out!"); | ||||||
|       this->mark_failed(); |       this->mark_failed(); | ||||||
|       return; |       return; | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|   if ((data & 0x68) != 0x08) {  // Bit[6:5] = 0b00, NORMAL mode and Bit[3] = 0b1, CALIBRATED |   if ((data & 0x68) != 0x08) {  // Bit[6:5] = 0b00, NORMAL mode and Bit[3] = 0b1, CALIBRATED | ||||||
|     ESP_LOGE(TAG, "AHT10 calibration failed!"); |     ESP_LOGE(TAG, "AHT10 initialization failed!"); | ||||||
|     this->mark_failed(); |     this->mark_failed(); | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   ESP_LOGV(TAG, "AHT10 calibrated"); |   ESP_LOGV(TAG, "AHT10 initialization"); | ||||||
| } | } | ||||||
|  |  | ||||||
| void AHT10Component::update() { | void AHT10Component::update() { | ||||||
|   | |||||||
| @@ -600,6 +600,7 @@ message ListEntitiesTextSensorResponse { | |||||||
|   string icon = 5; |   string icon = 5; | ||||||
|   bool disabled_by_default = 6; |   bool disabled_by_default = 6; | ||||||
|   EntityCategory entity_category = 7; |   EntityCategory entity_category = 7; | ||||||
|  |   string device_class = 8; | ||||||
| } | } | ||||||
| message TextSensorStateResponse { | message TextSensorStateResponse { | ||||||
|   option (id) = 27; |   option (id) = 27; | ||||||
| @@ -1449,6 +1450,7 @@ message VoiceAssistantRequest { | |||||||
|   string conversation_id = 2; |   string conversation_id = 2; | ||||||
|   uint32 flags = 3; |   uint32 flags = 3; | ||||||
|   VoiceAssistantAudioSettings audio_settings = 4; |   VoiceAssistantAudioSettings audio_settings = 4; | ||||||
|  |   string wake_word_phrase = 5; | ||||||
| } | } | ||||||
|  |  | ||||||
| message VoiceAssistantResponse { | message VoiceAssistantResponse { | ||||||
|   | |||||||
| @@ -543,6 +543,7 @@ bool APIConnection::send_text_sensor_info(text_sensor::TextSensor *text_sensor) | |||||||
|   msg.icon = text_sensor->get_icon(); |   msg.icon = text_sensor->get_icon(); | ||||||
|   msg.disabled_by_default = text_sensor->is_disabled_by_default(); |   msg.disabled_by_default = text_sensor->is_disabled_by_default(); | ||||||
|   msg.entity_category = static_cast<enums::EntityCategory>(text_sensor->get_entity_category()); |   msg.entity_category = static_cast<enums::EntityCategory>(text_sensor->get_entity_category()); | ||||||
|  |   msg.device_class = text_sensor->get_device_class(); | ||||||
|   return this->send_list_entities_text_sensor_response(msg); |   return this->send_list_entities_text_sensor_response(msg); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
|   | |||||||
| @@ -2721,6 +2721,10 @@ bool ListEntitiesTextSensorResponse::decode_length(uint32_t field_id, ProtoLengt | |||||||
|       this->icon = value.as_string(); |       this->icon = value.as_string(); | ||||||
|       return true; |       return true; | ||||||
|     } |     } | ||||||
|  |     case 8: { | ||||||
|  |       this->device_class = value.as_string(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|     default: |     default: | ||||||
|       return false; |       return false; | ||||||
|   } |   } | ||||||
| @@ -2743,6 +2747,7 @@ void ListEntitiesTextSensorResponse::encode(ProtoWriteBuffer buffer) const { | |||||||
|   buffer.encode_string(5, this->icon); |   buffer.encode_string(5, this->icon); | ||||||
|   buffer.encode_bool(6, this->disabled_by_default); |   buffer.encode_bool(6, this->disabled_by_default); | ||||||
|   buffer.encode_enum<enums::EntityCategory>(7, this->entity_category); |   buffer.encode_enum<enums::EntityCategory>(7, this->entity_category); | ||||||
|  |   buffer.encode_string(8, this->device_class); | ||||||
| } | } | ||||||
| #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 { | ||||||
| @@ -2776,6 +2781,10 @@ void ListEntitiesTextSensorResponse::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("  device_class: "); | ||||||
|  |   out.append("'").append(this->device_class).append("'"); | ||||||
|  |   out.append("\n"); | ||||||
|   out.append("}"); |   out.append("}"); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| @@ -6594,6 +6603,10 @@ bool VoiceAssistantRequest::decode_length(uint32_t field_id, ProtoLengthDelimite | |||||||
|       this->audio_settings = value.as_message<VoiceAssistantAudioSettings>(); |       this->audio_settings = value.as_message<VoiceAssistantAudioSettings>(); | ||||||
|       return true; |       return true; | ||||||
|     } |     } | ||||||
|  |     case 5: { | ||||||
|  |       this->wake_word_phrase = value.as_string(); | ||||||
|  |       return true; | ||||||
|  |     } | ||||||
|     default: |     default: | ||||||
|       return false; |       return false; | ||||||
|   } |   } | ||||||
| @@ -6603,6 +6616,7 @@ void VoiceAssistantRequest::encode(ProtoWriteBuffer buffer) const { | |||||||
|   buffer.encode_string(2, this->conversation_id); |   buffer.encode_string(2, this->conversation_id); | ||||||
|   buffer.encode_uint32(3, this->flags); |   buffer.encode_uint32(3, this->flags); | ||||||
|   buffer.encode_message<VoiceAssistantAudioSettings>(4, this->audio_settings); |   buffer.encode_message<VoiceAssistantAudioSettings>(4, this->audio_settings); | ||||||
|  |   buffer.encode_string(5, this->wake_word_phrase); | ||||||
| } | } | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
| void VoiceAssistantRequest::dump_to(std::string &out) const { | void VoiceAssistantRequest::dump_to(std::string &out) const { | ||||||
| @@ -6624,6 +6638,10 @@ void VoiceAssistantRequest::dump_to(std::string &out) const { | |||||||
|   out.append("  audio_settings: "); |   out.append("  audio_settings: "); | ||||||
|   this->audio_settings.dump_to(out); |   this->audio_settings.dump_to(out); | ||||||
|   out.append("\n"); |   out.append("\n"); | ||||||
|  |  | ||||||
|  |   out.append("  wake_word_phrase: "); | ||||||
|  |   out.append("'").append(this->wake_word_phrase).append("'"); | ||||||
|  |   out.append("\n"); | ||||||
|   out.append("}"); |   out.append("}"); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
|   | |||||||
| @@ -713,6 +713,7 @@ class ListEntitiesTextSensorResponse : public ProtoMessage { | |||||||
|   std::string icon{}; |   std::string icon{}; | ||||||
|   bool disabled_by_default{false}; |   bool disabled_by_default{false}; | ||||||
|   enums::EntityCategory entity_category{}; |   enums::EntityCategory entity_category{}; | ||||||
|  |   std::string device_class{}; | ||||||
|   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; | ||||||
| @@ -1701,6 +1702,7 @@ class VoiceAssistantRequest : public ProtoMessage { | |||||||
|   std::string conversation_id{}; |   std::string conversation_id{}; | ||||||
|   uint32_t flags{0}; |   uint32_t flags{0}; | ||||||
|   VoiceAssistantAudioSettings audio_settings{}; |   VoiceAssistantAudioSettings audio_settings{}; | ||||||
|  |   std::string wake_word_phrase{}; | ||||||
|   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; | ||||||
|   | |||||||
| @@ -22,7 +22,7 @@ CONFIG_SCHEMA = cv.All( | |||||||
| async def to_code(config): | async def to_code(config): | ||||||
|     if CORE.is_esp32 or CORE.is_libretiny: |     if CORE.is_esp32 or CORE.is_libretiny: | ||||||
|         # https://github.com/esphome/AsyncTCP/blob/master/library.json |         # https://github.com/esphome/AsyncTCP/blob/master/library.json | ||||||
|         cg.add_library("esphome/AsyncTCP-esphome", "2.0.1") |         cg.add_library("esphome/AsyncTCP-esphome", "2.1.3") | ||||||
|     elif CORE.is_esp8266: |     elif CORE.is_esp8266: | ||||||
|         # https://github.com/esphome/ESPAsyncTCP |         # https://github.com/esphome/ESPAsyncTCP | ||||||
|         cg.add_library("esphome/ESPAsyncTCP-esphome", "2.0.0") |         cg.add_library("esphome/ESPAsyncTCP-esphome", "2.0.0") | ||||||
|   | |||||||
| @@ -4,6 +4,7 @@ from esphome.components import sensor, uart | |||||||
| from esphome.const import ( | from esphome.const import ( | ||||||
|     CONF_CURRENT, |     CONF_CURRENT, | ||||||
|     CONF_ENERGY, |     CONF_ENERGY, | ||||||
|  |     CONF_EXTERNAL_TEMPERATURE, | ||||||
|     CONF_ID, |     CONF_ID, | ||||||
|     CONF_POWER, |     CONF_POWER, | ||||||
|     CONF_VOLTAGE, |     CONF_VOLTAGE, | ||||||
| @@ -24,7 +25,6 @@ from esphome.const import ( | |||||||
| DEPENDENCIES = ["uart"] | DEPENDENCIES = ["uart"] | ||||||
|  |  | ||||||
| CONF_INTERNAL_TEMPERATURE = "internal_temperature" | CONF_INTERNAL_TEMPERATURE = "internal_temperature" | ||||||
| CONF_EXTERNAL_TEMPERATURE = "external_temperature" |  | ||||||
|  |  | ||||||
| bl0940_ns = cg.esphome_ns.namespace("bl0940") | bl0940_ns = cg.esphome_ns.namespace("bl0940") | ||||||
| BL0940 = bl0940_ns.class_("BL0940", cg.PollingComponent, uart.UARTDevice) | BL0940 = bl0940_ns.class_("BL0940", cg.PollingComponent, uart.UARTDevice) | ||||||
|   | |||||||
| @@ -11,6 +11,7 @@ MULTI_CONF = True | |||||||
| CONF_BME680_BSEC_ID = "bme680_bsec_id" | CONF_BME680_BSEC_ID = "bme680_bsec_id" | ||||||
| CONF_TEMPERATURE_OFFSET = "temperature_offset" | CONF_TEMPERATURE_OFFSET = "temperature_offset" | ||||||
| CONF_IAQ_MODE = "iaq_mode" | CONF_IAQ_MODE = "iaq_mode" | ||||||
|  | CONF_SUPPLY_VOLTAGE = "supply_voltage" | ||||||
| CONF_SAMPLE_RATE = "sample_rate" | CONF_SAMPLE_RATE = "sample_rate" | ||||||
| CONF_STATE_SAVE_INTERVAL = "state_save_interval" | CONF_STATE_SAVE_INTERVAL = "state_save_interval" | ||||||
|  |  | ||||||
| @@ -22,6 +23,12 @@ IAQ_MODE_OPTIONS = { | |||||||
|     "MOBILE": IAQMode.IAQ_MODE_MOBILE, |     "MOBILE": IAQMode.IAQ_MODE_MOBILE, | ||||||
| } | } | ||||||
|  |  | ||||||
|  | SupplyVoltage = bme680_bsec_ns.enum("SupplyVoltage") | ||||||
|  | SUPPLY_VOLTAGE_OPTIONS = { | ||||||
|  |     "1.8V": SupplyVoltage.SUPPLY_VOLTAGE_1V8, | ||||||
|  |     "3.3V": SupplyVoltage.SUPPLY_VOLTAGE_3V3, | ||||||
|  | } | ||||||
|  |  | ||||||
| SampleRate = bme680_bsec_ns.enum("SampleRate") | SampleRate = bme680_bsec_ns.enum("SampleRate") | ||||||
| SAMPLE_RATE_OPTIONS = { | SAMPLE_RATE_OPTIONS = { | ||||||
|     "LP": SampleRate.SAMPLE_RATE_LP, |     "LP": SampleRate.SAMPLE_RATE_LP, | ||||||
| @@ -40,6 +47,9 @@ CONFIG_SCHEMA = cv.All( | |||||||
|             cv.Optional(CONF_IAQ_MODE, default="STATIC"): cv.enum( |             cv.Optional(CONF_IAQ_MODE, default="STATIC"): cv.enum( | ||||||
|                 IAQ_MODE_OPTIONS, upper=True |                 IAQ_MODE_OPTIONS, upper=True | ||||||
|             ), |             ), | ||||||
|  |             cv.Optional(CONF_SUPPLY_VOLTAGE, default="3.3V"): cv.enum( | ||||||
|  |                 SUPPLY_VOLTAGE_OPTIONS, upper=True | ||||||
|  |             ), | ||||||
|             cv.Optional(CONF_SAMPLE_RATE, default="LP"): cv.enum( |             cv.Optional(CONF_SAMPLE_RATE, default="LP"): cv.enum( | ||||||
|                 SAMPLE_RATE_OPTIONS, upper=True |                 SAMPLE_RATE_OPTIONS, upper=True | ||||||
|             ), |             ), | ||||||
| @@ -67,6 +77,7 @@ async def to_code(config): | |||||||
|     cg.add(var.set_device_id(str(config[CONF_ID]))) |     cg.add(var.set_device_id(str(config[CONF_ID]))) | ||||||
|     cg.add(var.set_temperature_offset(config[CONF_TEMPERATURE_OFFSET])) |     cg.add(var.set_temperature_offset(config[CONF_TEMPERATURE_OFFSET])) | ||||||
|     cg.add(var.set_iaq_mode(config[CONF_IAQ_MODE])) |     cg.add(var.set_iaq_mode(config[CONF_IAQ_MODE])) | ||||||
|  |     cg.add(var.set_supply_voltage(config[CONF_SUPPLY_VOLTAGE])) | ||||||
|     cg.add(var.set_sample_rate(config[CONF_SAMPLE_RATE])) |     cg.add(var.set_sample_rate(config[CONF_SAMPLE_RATE])) | ||||||
|     cg.add( |     cg.add( | ||||||
|         var.set_state_save_interval(config[CONF_STATE_SAVE_INTERVAL].total_milliseconds) |         var.set_state_save_interval(config[CONF_STATE_SAVE_INTERVAL].total_milliseconds) | ||||||
|   | |||||||
| @@ -52,17 +52,33 @@ void BME680BSECComponent::setup() { | |||||||
|  |  | ||||||
| void BME680BSECComponent::set_config_() { | void BME680BSECComponent::set_config_() { | ||||||
|   if (this->sample_rate_ == SAMPLE_RATE_ULP) { |   if (this->sample_rate_ == SAMPLE_RATE_ULP) { | ||||||
|     const uint8_t config[] = { |     if (this->supply_voltage_ == SUPPLY_VOLTAGE_3V3) { | ||||||
|  |       const uint8_t config[] = { | ||||||
| #include "config/generic_33v_300s_28d/bsec_iaq.txt" | #include "config/generic_33v_300s_28d/bsec_iaq.txt" | ||||||
|     }; |       }; | ||||||
|     this->bsec_status_ = |       this->bsec_status_ = | ||||||
|         bsec_set_configuration(config, BSEC_MAX_PROPERTY_BLOB_SIZE, this->work_buffer_, sizeof(this->work_buffer_)); |           bsec_set_configuration(config, BSEC_MAX_PROPERTY_BLOB_SIZE, this->work_buffer_, sizeof(this->work_buffer_)); | ||||||
|   } else { |     } else {  // SUPPLY_VOLTAGE_1V8 | ||||||
|     const uint8_t config[] = { |       const uint8_t config[] = { | ||||||
|  | #include "config/generic_18v_300s_28d/bsec_iaq.txt" | ||||||
|  |       }; | ||||||
|  |       this->bsec_status_ = | ||||||
|  |           bsec_set_configuration(config, BSEC_MAX_PROPERTY_BLOB_SIZE, this->work_buffer_, sizeof(this->work_buffer_)); | ||||||
|  |     } | ||||||
|  |   } else {  // SAMPLE_RATE_LP | ||||||
|  |     if (this->supply_voltage_ == SUPPLY_VOLTAGE_3V3) { | ||||||
|  |       const uint8_t config[] = { | ||||||
| #include "config/generic_33v_3s_28d/bsec_iaq.txt" | #include "config/generic_33v_3s_28d/bsec_iaq.txt" | ||||||
|     }; |       }; | ||||||
|     this->bsec_status_ = |       this->bsec_status_ = | ||||||
|         bsec_set_configuration(config, BSEC_MAX_PROPERTY_BLOB_SIZE, this->work_buffer_, sizeof(this->work_buffer_)); |           bsec_set_configuration(config, BSEC_MAX_PROPERTY_BLOB_SIZE, this->work_buffer_, sizeof(this->work_buffer_)); | ||||||
|  |     } else {  // SUPPLY_VOLTAGE_1V8 | ||||||
|  |       const uint8_t config[] = { | ||||||
|  | #include "config/generic_18v_3s_28d/bsec_iaq.txt" | ||||||
|  |       }; | ||||||
|  |       this->bsec_status_ = | ||||||
|  |           bsec_set_configuration(config, BSEC_MAX_PROPERTY_BLOB_SIZE, this->work_buffer_, sizeof(this->work_buffer_)); | ||||||
|  |     } | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -145,6 +161,7 @@ void BME680BSECComponent::dump_config() { | |||||||
|  |  | ||||||
|   ESP_LOGCONFIG(TAG, "  Temperature Offset: %.2f", this->temperature_offset_); |   ESP_LOGCONFIG(TAG, "  Temperature Offset: %.2f", this->temperature_offset_); | ||||||
|   ESP_LOGCONFIG(TAG, "  IAQ Mode: %s", this->iaq_mode_ == IAQ_MODE_STATIC ? "Static" : "Mobile"); |   ESP_LOGCONFIG(TAG, "  IAQ Mode: %s", this->iaq_mode_ == IAQ_MODE_STATIC ? "Static" : "Mobile"); | ||||||
|  |   ESP_LOGCONFIG(TAG, "  Supply Voltage: %sV", this->supply_voltage_ == SUPPLY_VOLTAGE_3V3 ? "3.3" : "1.8"); | ||||||
|   ESP_LOGCONFIG(TAG, "  Sample Rate: %s", BME680_BSEC_SAMPLE_RATE_LOG(this->sample_rate_)); |   ESP_LOGCONFIG(TAG, "  Sample Rate: %s", BME680_BSEC_SAMPLE_RATE_LOG(this->sample_rate_)); | ||||||
|   ESP_LOGCONFIG(TAG, "  State Save Interval: %ims", this->state_save_interval_ms_); |   ESP_LOGCONFIG(TAG, "  State Save Interval: %ims", this->state_save_interval_ms_); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -21,6 +21,11 @@ enum IAQMode { | |||||||
|   IAQ_MODE_MOBILE = 1, |   IAQ_MODE_MOBILE = 1, | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | enum SupplyVoltage { | ||||||
|  |   SUPPLY_VOLTAGE_3V3 = 0, | ||||||
|  |   SUPPLY_VOLTAGE_1V8 = 1, | ||||||
|  | }; | ||||||
|  |  | ||||||
| enum SampleRate { | enum SampleRate { | ||||||
|   SAMPLE_RATE_LP = 0, |   SAMPLE_RATE_LP = 0, | ||||||
|   SAMPLE_RATE_ULP = 1, |   SAMPLE_RATE_ULP = 1, | ||||||
| @@ -35,6 +40,7 @@ class BME680BSECComponent : public Component, public i2c::I2CDevice { | |||||||
|   void set_temperature_offset(float offset) { this->temperature_offset_ = offset; } |   void set_temperature_offset(float offset) { this->temperature_offset_ = offset; } | ||||||
|   void set_iaq_mode(IAQMode iaq_mode) { this->iaq_mode_ = iaq_mode; } |   void set_iaq_mode(IAQMode iaq_mode) { this->iaq_mode_ = iaq_mode; } | ||||||
|   void set_state_save_interval(uint32_t interval) { this->state_save_interval_ms_ = interval; } |   void set_state_save_interval(uint32_t interval) { this->state_save_interval_ms_ = interval; } | ||||||
|  |   void set_supply_voltage(SupplyVoltage supply_voltage) { this->supply_voltage_ = supply_voltage; } | ||||||
|  |  | ||||||
|   void set_sample_rate(SampleRate sample_rate) { this->sample_rate_ = sample_rate; } |   void set_sample_rate(SampleRate sample_rate) { this->sample_rate_ = sample_rate; } | ||||||
|   void set_temperature_sample_rate(SampleRate sample_rate) { this->temperature_sample_rate_ = sample_rate; } |   void set_temperature_sample_rate(SampleRate sample_rate) { this->temperature_sample_rate_ = sample_rate; } | ||||||
| @@ -109,6 +115,7 @@ class BME680BSECComponent : public Component, public i2c::I2CDevice { | |||||||
|   std::string device_id_; |   std::string device_id_; | ||||||
|   float temperature_offset_{0}; |   float temperature_offset_{0}; | ||||||
|   IAQMode iaq_mode_{IAQ_MODE_STATIC}; |   IAQMode iaq_mode_{IAQ_MODE_STATIC}; | ||||||
|  |   SupplyVoltage supply_voltage_; | ||||||
|  |  | ||||||
|   SampleRate sample_rate_{SAMPLE_RATE_LP};  // Core/gas sample rate |   SampleRate sample_rate_{SAMPLE_RATE_LP};  // Core/gas sample rate | ||||||
|   SampleRate temperature_sample_rate_{SAMPLE_RATE_DEFAULT}; |   SampleRate temperature_sample_rate_{SAMPLE_RATE_DEFAULT}; | ||||||
|   | |||||||
| @@ -1,6 +1,8 @@ | |||||||
| #include "cse7766.h" | #include "cse7766.h" | ||||||
| #include "esphome/core/log.h" | #include "esphome/core/log.h" | ||||||
| #include <cinttypes> | #include <cinttypes> | ||||||
|  | #include <iomanip> | ||||||
|  | #include <sstream> | ||||||
|  |  | ||||||
| namespace esphome { | namespace esphome { | ||||||
| namespace cse7766 { | namespace cse7766 { | ||||||
| @@ -68,20 +70,26 @@ bool CSE7766Component::check_byte_() { | |||||||
|   return true; |   return true; | ||||||
| } | } | ||||||
| void CSE7766Component::parse_data_() { | void CSE7766Component::parse_data_() { | ||||||
|   ESP_LOGVV(TAG, "CSE7766 Data: "); | #if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE | ||||||
|   for (uint8_t i = 0; i < 23; i++) { |   { | ||||||
|     ESP_LOGVV(TAG, "  %u: 0b" BYTE_TO_BINARY_PATTERN " (0x%02X)", i + 1, BYTE_TO_BINARY(this->raw_data_[i]), |     std::stringstream ss; | ||||||
|               this->raw_data_[i]); |     ss << "Raw data:" << std::hex << std::uppercase << std::setfill('0'); | ||||||
|  |     for (uint8_t i = 0; i < 23; i++) { | ||||||
|  |       ss << ' ' << std::setw(2) << static_cast<unsigned>(this->raw_data_[i]); | ||||||
|  |     } | ||||||
|  |     ESP_LOGVV(TAG, "%s", ss.str().c_str()); | ||||||
|   } |   } | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  |   // Parse header | ||||||
|   uint8_t header1 = this->raw_data_[0]; |   uint8_t header1 = this->raw_data_[0]; | ||||||
|  |  | ||||||
|   if (header1 == 0xAA) { |   if (header1 == 0xAA) { | ||||||
|     ESP_LOGE(TAG, "CSE7766 not calibrated!"); |     ESP_LOGE(TAG, "CSE7766 not calibrated!"); | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   bool power_cycle_exceeds_range = false; |   bool power_cycle_exceeds_range = false; | ||||||
|  |  | ||||||
|   if ((header1 & 0xF0) == 0xF0) { |   if ((header1 & 0xF0) == 0xF0) { | ||||||
|     if (header1 & 0xD) { |     if (header1 & 0xD) { | ||||||
|       ESP_LOGE(TAG, "CSE7766 reports abnormal external circuit or chip damage: (0x%02X)", header1); |       ESP_LOGE(TAG, "CSE7766 reports abnormal external circuit or chip damage: (0x%02X)", header1); | ||||||
| @@ -94,74 +102,122 @@ void CSE7766Component::parse_data_() { | |||||||
|       if (header1 & (1 << 0)) { |       if (header1 & (1 << 0)) { | ||||||
|         ESP_LOGE(TAG, "  Coefficient storage area is abnormal."); |         ESP_LOGE(TAG, "  Coefficient storage area is abnormal."); | ||||||
|       } |       } | ||||||
|  |  | ||||||
|  |       // Datasheet: voltage or current cycle exceeding range means invalid values | ||||||
|       return; |       return; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     power_cycle_exceeds_range = header1 & (1 << 1); |     power_cycle_exceeds_range = header1 & (1 << 1); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   uint32_t voltage_calib = this->get_24_bit_uint_(2); |   // Parse data frame | ||||||
|  |   uint32_t voltage_coeff = this->get_24_bit_uint_(2); | ||||||
|   uint32_t voltage_cycle = this->get_24_bit_uint_(5); |   uint32_t voltage_cycle = this->get_24_bit_uint_(5); | ||||||
|   uint32_t current_calib = this->get_24_bit_uint_(8); |   uint32_t current_coeff = this->get_24_bit_uint_(8); | ||||||
|   uint32_t current_cycle = this->get_24_bit_uint_(11); |   uint32_t current_cycle = this->get_24_bit_uint_(11); | ||||||
|   uint32_t power_calib = this->get_24_bit_uint_(14); |   uint32_t power_coeff = this->get_24_bit_uint_(14); | ||||||
|   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]; |   uint16_t cf_pulses = (this->raw_data_[21] << 8) + this->raw_data_[22]; | ||||||
|  |  | ||||||
|   bool have_voltage = adj & 0x40; |  | ||||||
|   if (have_voltage) { |  | ||||||
|     // voltage cycle of serial port outputted is a complete cycle; |  | ||||||
|     float voltage = voltage_calib / float(voltage_cycle); |  | ||||||
|     if (this->voltage_sensor_ != nullptr) |  | ||||||
|       this->voltage_sensor_->publish_state(voltage); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   bool have_power = adj & 0x10; |   bool have_power = adj & 0x10; | ||||||
|   float power = 0.0f; |   bool have_current = adj & 0x20; | ||||||
|  |   bool have_voltage = adj & 0x40; | ||||||
|  |  | ||||||
|   if (have_power) { |   float voltage = 0.0f; | ||||||
|     // power cycle of serial port outputted is a complete cycle; |   if (have_voltage) { | ||||||
|     // According to the user manual, power cycle exceeding range means the measured power is 0 |     voltage = voltage_coeff / float(voltage_cycle); | ||||||
|     if (!power_cycle_exceeds_range) { |     if (this->voltage_sensor_ != nullptr) { | ||||||
|       power = power_calib / float(power_cycle); |       this->voltage_sensor_->publish_state(voltage); | ||||||
|     } |     } | ||||||
|     if (this->power_sensor_ != nullptr) |   } | ||||||
|       this->power_sensor_->publish_state(power); |  | ||||||
|  |  | ||||||
|     uint32_t difference; |   float energy = 0.0; | ||||||
|     if (this->cf_pulses_last_ == 0) { |   if (this->energy_sensor_ != nullptr) { | ||||||
|  |     if (this->cf_pulses_last_ == 0 && !this->energy_sensor_->has_state()) { | ||||||
|       this->cf_pulses_last_ = cf_pulses; |       this->cf_pulses_last_ = cf_pulses; | ||||||
|     } |     } | ||||||
|  |     uint16_t cf_diff = cf_pulses - this->cf_pulses_last_; | ||||||
|     if (cf_pulses < this->cf_pulses_last_) { |     this->cf_pulses_total_ += cf_diff; | ||||||
|       difference = cf_pulses + (0x10000 - this->cf_pulses_last_); |  | ||||||
|     } else { |  | ||||||
|       difference = cf_pulses - this->cf_pulses_last_; |  | ||||||
|     } |  | ||||||
|     this->cf_pulses_last_ = cf_pulses; |     this->cf_pulses_last_ = cf_pulses; | ||||||
|     this->energy_total_ += difference * float(power_calib) / 1000000.0f / 3600.0f; |     energy = this->cf_pulses_total_ * float(power_coeff) / 1000000.0f / 3600.0f; | ||||||
|     if (this->energy_sensor_ != nullptr) |     this->energy_sensor_->publish_state(energy); | ||||||
|       this->energy_sensor_->publish_state(this->energy_total_); |  | ||||||
|   } else if ((this->energy_sensor_ != nullptr) && !this->energy_sensor_->has_state()) { |  | ||||||
|     this->energy_sensor_->publish_state(0); |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   if (adj & 0x20) { |   float power = 0.0f; | ||||||
|     // indicates current cycle of serial port outputted is a complete cycle; |   if (power_cycle_exceeds_range) { | ||||||
|     float current = 0.0f; |     // Datasheet: power cycle exceeding range means active power is 0 | ||||||
|     if (have_voltage && !have_power) { |     if (this->power_sensor_ != nullptr) { | ||||||
|       // Testing has shown that when we have voltage and current but not power, that means the power is 0. |       this->power_sensor_->publish_state(0.0f); | ||||||
|       // We report a power of 0, which in turn means we should report a current of 0. |     } | ||||||
|       if (this->power_sensor_ != nullptr) |   } else if (have_power) { | ||||||
|         this->power_sensor_->publish_state(0); |     power = power_coeff / float(power_cycle); | ||||||
|     } else if (power != 0.0f) { |     if (this->power_sensor_ != nullptr) { | ||||||
|       current = current_calib / float(current_cycle); |       this->power_sensor_->publish_state(power); | ||||||
|     } |     } | ||||||
|     if (this->current_sensor_ != nullptr) |  | ||||||
|       this->current_sensor_->publish_state(current); |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   float current = 0.0f; | ||||||
|  |   float calculated_current = 0.0f; | ||||||
|  |   if (have_current) { | ||||||
|  |     // Assumption: if we don't have power measurement, then current is likely below 50mA | ||||||
|  |     if (have_power && voltage > 1.0f) { | ||||||
|  |       calculated_current = power / voltage; | ||||||
|  |     } | ||||||
|  |     // Datasheet: minimum measured current is 50mA | ||||||
|  |     if (calculated_current > 0.05f) { | ||||||
|  |       current = current_coeff / float(current_cycle); | ||||||
|  |     } | ||||||
|  |     if (this->current_sensor_ != nullptr) { | ||||||
|  |       this->current_sensor_->publish_state(current); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (have_voltage && have_current) { | ||||||
|  |     const float apparent_power = voltage * current; | ||||||
|  |     if (this->apparent_power_sensor_ != nullptr) { | ||||||
|  |       this->apparent_power_sensor_->publish_state(apparent_power); | ||||||
|  |     } | ||||||
|  |     if (this->power_factor_sensor_ != nullptr && (have_power || power_cycle_exceeds_range)) { | ||||||
|  |       float pf = NAN; | ||||||
|  |       if (apparent_power > 0) { | ||||||
|  |         pf = power / apparent_power; | ||||||
|  |         if (pf < 0 || pf > 1) { | ||||||
|  |           ESP_LOGD(TAG, "Impossible power factor: %.4f not in interval [0, 1]", pf); | ||||||
|  |           pf = NAN; | ||||||
|  |         } | ||||||
|  |       } else if (apparent_power == 0 && power == 0) { | ||||||
|  |         // No load, report ideal power factor | ||||||
|  |         pf = 1.0f; | ||||||
|  |       } else if (current == 0 && calculated_current <= 0.05f) { | ||||||
|  |         // Datasheet: minimum measured current is 50mA | ||||||
|  |         ESP_LOGV(TAG, "Can't calculate power factor (current below minimum for CSE7766)"); | ||||||
|  |       } else { | ||||||
|  |         ESP_LOGW(TAG, "Can't calculate power factor from P = %.4f W, S = %.4f VA", power, apparent_power); | ||||||
|  |       } | ||||||
|  |       this->power_factor_sensor_->publish_state(pf); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  | #if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE | ||||||
|  |   { | ||||||
|  |     std::stringstream ss; | ||||||
|  |     ss << "Parsed:"; | ||||||
|  |     if (have_voltage) { | ||||||
|  |       ss << " V=" << voltage << "V"; | ||||||
|  |     } | ||||||
|  |     if (have_current) { | ||||||
|  |       ss << " I=" << current * 1000.0f << "mA (~" << calculated_current * 1000.0f << "mA)"; | ||||||
|  |     } | ||||||
|  |     if (have_power) { | ||||||
|  |       ss << " P=" << power << "W"; | ||||||
|  |     } | ||||||
|  |     if (energy != 0.0f) { | ||||||
|  |       ss << " E=" << energy << "kWh (" << cf_pulses << ")"; | ||||||
|  |     } | ||||||
|  |     ESP_LOGVV(TAG, "%s", ss.str().c_str()); | ||||||
|  |   } | ||||||
|  | #endif | ||||||
| } | } | ||||||
|  |  | ||||||
| uint32_t CSE7766Component::get_24_bit_uint_(uint8_t start_index) { | uint32_t CSE7766Component::get_24_bit_uint_(uint8_t start_index) { | ||||||
| @@ -175,6 +231,8 @@ void CSE7766Component::dump_config() { | |||||||
|   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_); |   LOG_SENSOR("  ", "Energy", this->energy_sensor_); | ||||||
|  |   LOG_SENSOR("  ", "Apparent Power", this->apparent_power_sensor_); | ||||||
|  |   LOG_SENSOR("  ", "Power Factor", this->power_factor_sensor_); | ||||||
|   this->check_uart_settings(4800); |   this->check_uart_settings(4800); | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -13,6 +13,10 @@ class CSE7766Component : public Component, public uart::UARTDevice { | |||||||
|   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 set_energy_sensor(sensor::Sensor *energy_sensor) { energy_sensor_ = energy_sensor; } | ||||||
|  |   void set_apparent_power_sensor(sensor::Sensor *apparent_power_sensor) { | ||||||
|  |     apparent_power_sensor_ = apparent_power_sensor; | ||||||
|  |   } | ||||||
|  |   void set_power_factor_sensor(sensor::Sensor *power_factor_sensor) { power_factor_sensor_ = power_factor_sensor; } | ||||||
|  |  | ||||||
|   void loop() override; |   void loop() override; | ||||||
|   float get_setup_priority() const override; |   float get_setup_priority() const override; | ||||||
| @@ -30,8 +34,10 @@ class CSE7766Component : public Component, public uart::UARTDevice { | |||||||
|   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}; |   sensor::Sensor *energy_sensor_{nullptr}; | ||||||
|   float energy_total_{0.0f}; |   sensor::Sensor *apparent_power_sensor_{nullptr}; | ||||||
|   uint32_t cf_pulses_last_{0}; |   sensor::Sensor *power_factor_sensor_{nullptr}; | ||||||
|  |   uint32_t cf_pulses_total_{0}; | ||||||
|  |   uint16_t cf_pulses_last_{0}; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| }  // namespace cse7766 | }  // namespace cse7766 | ||||||
|   | |||||||
| @@ -2,19 +2,24 @@ import esphome.codegen as cg | |||||||
| import esphome.config_validation as cv | 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_APPARENT_POWER, | ||||||
|     CONF_CURRENT, |     CONF_CURRENT, | ||||||
|     CONF_ENERGY, |     CONF_ENERGY, | ||||||
|     CONF_ID, |     CONF_ID, | ||||||
|     CONF_POWER, |     CONF_POWER, | ||||||
|  |     CONF_POWER_FACTOR, | ||||||
|     CONF_VOLTAGE, |     CONF_VOLTAGE, | ||||||
|  |     DEVICE_CLASS_APPARENT_POWER, | ||||||
|     DEVICE_CLASS_CURRENT, |     DEVICE_CLASS_CURRENT, | ||||||
|     DEVICE_CLASS_ENERGY, |     DEVICE_CLASS_ENERGY, | ||||||
|     DEVICE_CLASS_POWER, |     DEVICE_CLASS_POWER, | ||||||
|  |     DEVICE_CLASS_POWER_FACTOR, | ||||||
|     DEVICE_CLASS_VOLTAGE, |     DEVICE_CLASS_VOLTAGE, | ||||||
|     STATE_CLASS_MEASUREMENT, |     STATE_CLASS_MEASUREMENT, | ||||||
|     STATE_CLASS_TOTAL_INCREASING, |     STATE_CLASS_TOTAL_INCREASING, | ||||||
|     UNIT_VOLT, |  | ||||||
|     UNIT_AMPERE, |     UNIT_AMPERE, | ||||||
|  |     UNIT_VOLT, | ||||||
|  |     UNIT_VOLT_AMPS, | ||||||
|     UNIT_WATT, |     UNIT_WATT, | ||||||
|     UNIT_WATT_HOURS, |     UNIT_WATT_HOURS, | ||||||
| ) | ) | ||||||
| @@ -51,6 +56,17 @@ CONFIG_SCHEMA = cv.Schema( | |||||||
|             device_class=DEVICE_CLASS_ENERGY, |             device_class=DEVICE_CLASS_ENERGY, | ||||||
|             state_class=STATE_CLASS_TOTAL_INCREASING, |             state_class=STATE_CLASS_TOTAL_INCREASING, | ||||||
|         ), |         ), | ||||||
|  |         cv.Optional(CONF_APPARENT_POWER): sensor.sensor_schema( | ||||||
|  |             unit_of_measurement=UNIT_VOLT_AMPS, | ||||||
|  |             accuracy_decimals=1, | ||||||
|  |             device_class=DEVICE_CLASS_APPARENT_POWER, | ||||||
|  |             state_class=STATE_CLASS_MEASUREMENT, | ||||||
|  |         ), | ||||||
|  |         cv.Optional(CONF_POWER_FACTOR): sensor.sensor_schema( | ||||||
|  |             accuracy_decimals=2, | ||||||
|  |             device_class=DEVICE_CLASS_POWER_FACTOR, | ||||||
|  |             state_class=STATE_CLASS_MEASUREMENT, | ||||||
|  |         ), | ||||||
|     } |     } | ||||||
| ).extend(uart.UART_DEVICE_SCHEMA) | ).extend(uart.UART_DEVICE_SCHEMA) | ||||||
| FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema( | FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema( | ||||||
| @@ -75,3 +91,9 @@ async def to_code(config): | |||||||
|     if energy_config := config.get(CONF_ENERGY): |     if energy_config := config.get(CONF_ENERGY): | ||||||
|         sens = await sensor.new_sensor(energy_config) |         sens = await sensor.new_sensor(energy_config) | ||||||
|         cg.add(var.set_energy_sensor(sens)) |         cg.add(var.set_energy_sensor(sens)) | ||||||
|  |     if apparent_power_config := config.get(CONF_APPARENT_POWER): | ||||||
|  |         sens = await sensor.new_sensor(apparent_power_config) | ||||||
|  |         cg.add(var.set_apparent_power_sensor(sens)) | ||||||
|  |     if power_factor_config := config.get(CONF_POWER_FACTOR): | ||||||
|  |         sens = await sensor.new_sensor(power_factor_config) | ||||||
|  |         cg.add(var.set_power_factor_sensor(sens)) | ||||||
|   | |||||||
| @@ -168,10 +168,6 @@ bool IRAM_ATTR DallasTemperatureSensor::read_scratch_pad() { | |||||||
|     if (!wire->reset()) { |     if (!wire->reset()) { | ||||||
|       return false; |       return false; | ||||||
|     } |     } | ||||||
|   } |  | ||||||
|  |  | ||||||
|   { |  | ||||||
|     InterruptLock lock; |  | ||||||
|  |  | ||||||
|     wire->select(this->address_); |     wire->select(this->address_); | ||||||
|     wire->write8(DALLAS_COMMAND_READ_SCRATCH_PAD); |     wire->write8(DALLAS_COMMAND_READ_SCRATCH_PAD); | ||||||
|   | |||||||
| @@ -1,7 +1,6 @@ | |||||||
| import esphome.codegen as cg | import esphome.codegen as cg | ||||||
| import esphome.config_validation as cv | import esphome.config_validation as cv | ||||||
| from esphome import automation | from esphome import automation | ||||||
| from esphome import core |  | ||||||
| from esphome.automation import maybe_simple_id | from esphome.automation import maybe_simple_id | ||||||
| from esphome.const import CONF_ID | from esphome.const import CONF_ID | ||||||
| from esphome.components import uart | from esphome.components import uart | ||||||
| @@ -101,7 +100,7 @@ def range_segment_list(input): | |||||||
|  |  | ||||||
|     largest_distance = -1 |     largest_distance = -1 | ||||||
|     for distance in input: |     for distance in input: | ||||||
|         if isinstance(distance, core.Lambda): |         if isinstance(distance, cv.Lambda): | ||||||
|             continue |             continue | ||||||
|         m = cv.distance(distance) |         m = cv.distance(distance) | ||||||
|         if m > 9: |         if m > 9: | ||||||
| @@ -128,14 +127,14 @@ MMWAVE_SETTINGS_SCHEMA = cv.Schema( | |||||||
|         cv.Optional(CONF_OUTPUT_LATENCY): { |         cv.Optional(CONF_OUTPUT_LATENCY): { | ||||||
|             cv.Required(CONF_DELAY_AFTER_DETECT): cv.templatable( |             cv.Required(CONF_DELAY_AFTER_DETECT): cv.templatable( | ||||||
|                 cv.All( |                 cv.All( | ||||||
|                     cv.positive_time_period, |                     cv.positive_time_period_milliseconds, | ||||||
|                     cv.Range(max=core.TimePeriod(seconds=1638.375)), |                     cv.Range(max=cv.TimePeriod(seconds=1638.375)), | ||||||
|                 ) |                 ) | ||||||
|             ), |             ), | ||||||
|             cv.Required(CONF_DELAY_AFTER_DISAPPEAR): cv.templatable( |             cv.Required(CONF_DELAY_AFTER_DISAPPEAR): cv.templatable( | ||||||
|                 cv.All( |                 cv.All( | ||||||
|                     cv.positive_time_period, |                     cv.positive_time_period_milliseconds, | ||||||
|                     cv.Range(max=core.TimePeriod(seconds=1638.375)), |                     cv.Range(max=cv.TimePeriod(seconds=1638.375)), | ||||||
|                 ) |                 ) | ||||||
|             ), |             ), | ||||||
|         }, |         }, | ||||||
|   | |||||||
| @@ -50,7 +50,7 @@ class DfrobotSen0395SettingsAction : public Action<Ts...>, public Parented<Dfrob | |||||||
|       float detect = this->delay_after_detect_.value(x...); |       float detect = this->delay_after_detect_.value(x...); | ||||||
|       float disappear = this->delay_after_disappear_.value(x...); |       float disappear = this->delay_after_disappear_.value(x...); | ||||||
|       if (detect >= 0 && disappear >= 0) { |       if (detect >= 0 && disappear >= 0) { | ||||||
|         this->parent_->enqueue(make_unique<OutputLatencyCommand>(detect, disappear)); |         this->parent_->enqueue(make_unique<SetLatencyCommand>(detect, disappear)); | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|     if (this->start_after_power_on_.has_value()) { |     if (this->start_after_power_on_.has_value()) { | ||||||
|   | |||||||
| @@ -1,5 +1,7 @@ | |||||||
| #include "commands.h" | #include "commands.h" | ||||||
|  |  | ||||||
|  | #include <cmath> | ||||||
|  |  | ||||||
| #include "esphome/core/log.h" | #include "esphome/core/log.h" | ||||||
|  |  | ||||||
| #include "dfrobot_sen0395.h" | #include "dfrobot_sen0395.h" | ||||||
| @@ -194,32 +196,22 @@ uint8_t DetRangeCfgCommand::on_message(std::string &message) { | |||||||
|   return 0;  // Command not done yet. |   return 0;  // Command not done yet. | ||||||
| } | } | ||||||
|  |  | ||||||
| OutputLatencyCommand::OutputLatencyCommand(float delay_after_detection, float delay_after_disappear) { | SetLatencyCommand::SetLatencyCommand(float delay_after_detection, float delay_after_disappear) { | ||||||
|   delay_after_detection = round(delay_after_detection / 0.025) * 0.025; |   delay_after_detection = std::round(delay_after_detection / 0.025f) * 0.025f; | ||||||
|   delay_after_disappear = round(delay_after_disappear / 0.025) * 0.025; |   delay_after_disappear = std::round(delay_after_disappear / 0.025f) * 0.025f; | ||||||
|   if (delay_after_detection < 0) |   this->delay_after_detection_ = clamp(delay_after_detection, 0.0f, 1638.375f); | ||||||
|     delay_after_detection = 0; |   this->delay_after_disappear_ = clamp(delay_after_disappear, 0.0f, 1638.375f); | ||||||
|   if (delay_after_detection > 1638.375) |   this->cmd_ = str_sprintf("setLatency %.03f %.03f", this->delay_after_detection_, this->delay_after_disappear_); | ||||||
|     delay_after_detection = 1638.375; |  | ||||||
|   if (delay_after_disappear < 0) |  | ||||||
|     delay_after_disappear = 0; |  | ||||||
|   if (delay_after_disappear > 1638.375) |  | ||||||
|     delay_after_disappear = 1638.375; |  | ||||||
|  |  | ||||||
|   this->delay_after_detection_ = delay_after_detection; |  | ||||||
|   this->delay_after_disappear_ = delay_after_disappear; |  | ||||||
|  |  | ||||||
|   this->cmd_ = str_sprintf("outputLatency -1 %.0f %.0f", delay_after_detection / 0.025, delay_after_disappear / 0.025); |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
| uint8_t OutputLatencyCommand::on_message(std::string &message) { | uint8_t SetLatencyCommand::on_message(std::string &message) { | ||||||
|   if (message == "sensor is not stopped") { |   if (message == "sensor is not stopped") { | ||||||
|     ESP_LOGE(TAG, "Cannot configure output latency. Sensor is not stopped!"); |     ESP_LOGE(TAG, "Cannot configure output latency. Sensor is not stopped!"); | ||||||
|     return 1;  // Command done |     return 1;  // Command done | ||||||
|   } else if (message == "Done") { |   } else if (message == "Done") { | ||||||
|     ESP_LOGI(TAG, "Updated output latency config:"); |     ESP_LOGI(TAG, "Updated output latency config:"); | ||||||
|     ESP_LOGI(TAG, "Signal that someone was detected is delayed by %.02fs.", this->delay_after_detection_); |     ESP_LOGI(TAG, "Signal that someone was detected is delayed by %.03f s.", this->delay_after_detection_); | ||||||
|     ESP_LOGI(TAG, "Signal that nobody is detected anymore is delayed by %.02fs.", this->delay_after_disappear_); |     ESP_LOGI(TAG, "Signal that nobody is detected anymore is delayed by %.03f s.", this->delay_after_disappear_); | ||||||
|     ESP_LOGD(TAG, "Used command: %s", this->cmd_.c_str()); |     ESP_LOGD(TAG, "Used command: %s", this->cmd_.c_str()); | ||||||
|     return 1;  // Command done |     return 1;  // Command done | ||||||
|   } |   } | ||||||
|   | |||||||
| @@ -62,9 +62,9 @@ class DetRangeCfgCommand : public Command { | |||||||
|   // TODO: Set min max values in component, so they can be published as sensor. |   // TODO: Set min max values in component, so they can be published as sensor. | ||||||
| }; | }; | ||||||
|  |  | ||||||
| class OutputLatencyCommand : public Command { | class SetLatencyCommand : public Command { | ||||||
|  public: |  public: | ||||||
|   OutputLatencyCommand(float delay_after_detection, float delay_after_disappear); |   SetLatencyCommand(float delay_after_detection, float delay_after_disappear); | ||||||
|   uint8_t on_message(std::string &message) override; |   uint8_t on_message(std::string &message) override; | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   | |||||||
| @@ -257,6 +257,67 @@ void Display::filled_triangle(int x1, int y1, int x2, int y2, int x3, int y3, Co | |||||||
|     this->filled_flat_side_triangle_(x3, y3, x2, y2, x_temp, y_temp, color); |     this->filled_flat_side_triangle_(x3, y3, x2, y2, x_temp, y_temp, color); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  | void HOT Display::get_regular_polygon_vertex(int vertex_id, int *vertex_x, int *vertex_y, int center_x, int center_y, | ||||||
|  |                                              int radius, int edges, RegularPolygonVariation variation, | ||||||
|  |                                              float rotation_degrees) { | ||||||
|  |   if (edges >= 2) { | ||||||
|  |     // Given the orientation of the display component, an angle is measured clockwise from the x axis. | ||||||
|  |     // For a regular polygon, the human reference would be the top of the polygon, | ||||||
|  |     // hence we rotate the shape by 270° to orient the polygon up. | ||||||
|  |     rotation_degrees += ROTATION_270_DEGREES; | ||||||
|  |     // Convert the rotation to radians, easier to use in trigonometrical calculations | ||||||
|  |     float rotation_radians = rotation_degrees * PI / 180; | ||||||
|  |     // A pointy top variation means the first vertex of the polygon is at the top center of the shape, this requires no | ||||||
|  |     // additional rotation of the shape. | ||||||
|  |     // A flat top variation means the first point of the polygon has to be rotated so that the first edge is horizontal, | ||||||
|  |     // this requires to rotate the shape by π/edges radians counter-clockwise so that the first point is located on the | ||||||
|  |     // left side of the first horizontal edge. | ||||||
|  |     rotation_radians -= (variation == VARIATION_FLAT_TOP) ? PI / edges : 0.0; | ||||||
|  |  | ||||||
|  |     float vertex_angle = ((float) vertex_id) / edges * 2 * PI + rotation_radians; | ||||||
|  |     *vertex_x = (int) round(cos(vertex_angle) * radius) + center_x; | ||||||
|  |     *vertex_y = (int) round(sin(vertex_angle) * radius) + center_y; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void HOT Display::regular_polygon(int x, int y, int radius, int edges, RegularPolygonVariation variation, | ||||||
|  |                                   float rotation_degrees, Color color, RegularPolygonDrawing drawing) { | ||||||
|  |   if (edges >= 2) { | ||||||
|  |     int previous_vertex_x, previous_vertex_y; | ||||||
|  |     for (int current_vertex_id = 0; current_vertex_id <= edges; current_vertex_id++) { | ||||||
|  |       int current_vertex_x, current_vertex_y; | ||||||
|  |       get_regular_polygon_vertex(current_vertex_id, ¤t_vertex_x, ¤t_vertex_y, x, y, radius, edges, | ||||||
|  |                                  variation, rotation_degrees); | ||||||
|  |       if (current_vertex_id > 0) {  // Start drawing after the 2nd vertex coordinates has been calculated | ||||||
|  |         if (drawing == DRAWING_FILLED) { | ||||||
|  |           this->filled_triangle(x, y, previous_vertex_x, previous_vertex_y, current_vertex_x, current_vertex_y, color); | ||||||
|  |         } else if (drawing == DRAWING_OUTLINE) { | ||||||
|  |           this->line(previous_vertex_x, previous_vertex_y, current_vertex_x, current_vertex_y, color); | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |       previous_vertex_x = current_vertex_x; | ||||||
|  |       previous_vertex_y = current_vertex_y; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | void HOT Display::regular_polygon(int x, int y, int radius, int edges, RegularPolygonVariation variation, Color color, | ||||||
|  |                                   RegularPolygonDrawing drawing) { | ||||||
|  |   regular_polygon(x, y, radius, edges, variation, ROTATION_0_DEGREES, color, drawing); | ||||||
|  | } | ||||||
|  | void HOT Display::regular_polygon(int x, int y, int radius, int edges, Color color, RegularPolygonDrawing drawing) { | ||||||
|  |   regular_polygon(x, y, radius, edges, VARIATION_POINTY_TOP, ROTATION_0_DEGREES, color, drawing); | ||||||
|  | } | ||||||
|  | void Display::filled_regular_polygon(int x, int y, int radius, int edges, RegularPolygonVariation variation, | ||||||
|  |                                      float rotation_degrees, Color color) { | ||||||
|  |   regular_polygon(x, y, radius, edges, variation, rotation_degrees, color, DRAWING_FILLED); | ||||||
|  | } | ||||||
|  | void Display::filled_regular_polygon(int x, int y, int radius, int edges, RegularPolygonVariation variation, | ||||||
|  |                                      Color color) { | ||||||
|  |   regular_polygon(x, y, radius, edges, variation, ROTATION_0_DEGREES, color, DRAWING_FILLED); | ||||||
|  | } | ||||||
|  | void Display::filled_regular_polygon(int x, int y, int radius, int edges, Color color) { | ||||||
|  |   regular_polygon(x, y, radius, edges, VARIATION_POINTY_TOP, ROTATION_0_DEGREES, color, DRAWING_FILLED); | ||||||
|  | } | ||||||
|  |  | ||||||
| void Display::print(int x, int y, BaseFont *font, Color color, TextAlign align, const char *text) { | void Display::print(int x, int y, BaseFont *font, Color color, TextAlign align, const char *text) { | ||||||
|   int x_start, y_start; |   int x_start, y_start; | ||||||
|   | |||||||
| @@ -137,6 +137,42 @@ enum DisplayRotation { | |||||||
|   DISPLAY_ROTATION_270_DEGREES = 270, |   DISPLAY_ROTATION_270_DEGREES = 270, | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | #define PI 3.1415926535897932384626433832795 | ||||||
|  |  | ||||||
|  | const int EDGES_TRIGON = 3; | ||||||
|  | const int EDGES_TRIANGLE = 3; | ||||||
|  | const int EDGES_TETRAGON = 4; | ||||||
|  | const int EDGES_QUADRILATERAL = 4; | ||||||
|  | const int EDGES_PENTAGON = 5; | ||||||
|  | const int EDGES_HEXAGON = 6; | ||||||
|  | const int EDGES_HEPTAGON = 7; | ||||||
|  | const int EDGES_OCTAGON = 8; | ||||||
|  | const int EDGES_NONAGON = 9; | ||||||
|  | const int EDGES_ENNEAGON = 9; | ||||||
|  | const int EDGES_DECAGON = 10; | ||||||
|  | const int EDGES_HENDECAGON = 11; | ||||||
|  | const int EDGES_DODECAGON = 12; | ||||||
|  | const int EDGES_TRIDECAGON = 13; | ||||||
|  | const int EDGES_TETRADECAGON = 14; | ||||||
|  | const int EDGES_PENTADECAGON = 15; | ||||||
|  | const int EDGES_HEXADECAGON = 16; | ||||||
|  |  | ||||||
|  | const float ROTATION_0_DEGREES = 0.0; | ||||||
|  | const float ROTATION_45_DEGREES = 45.0; | ||||||
|  | const float ROTATION_90_DEGREES = 90.0; | ||||||
|  | const float ROTATION_180_DEGREES = 180.0; | ||||||
|  | const float ROTATION_270_DEGREES = 270.0; | ||||||
|  |  | ||||||
|  | enum RegularPolygonVariation { | ||||||
|  |   VARIATION_POINTY_TOP = 0, | ||||||
|  |   VARIATION_FLAT_TOP = 1, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | enum RegularPolygonDrawing { | ||||||
|  |   DRAWING_OUTLINE = 0, | ||||||
|  |   DRAWING_FILLED = 1, | ||||||
|  | }; | ||||||
|  |  | ||||||
| class Display; | class Display; | ||||||
| class DisplayPage; | class DisplayPage; | ||||||
| class DisplayOnPageChangeTrigger; | class DisplayOnPageChangeTrigger; | ||||||
| @@ -175,10 +211,15 @@ class Display : public PollingComponent { | |||||||
|   /// Clear the entire screen by filling it with OFF pixels. |   /// Clear the entire screen by filling it with OFF pixels. | ||||||
|   void clear(); |   void clear(); | ||||||
|  |  | ||||||
|   /// Get the width of the image in pixels with rotation applied. |   /// Get the calculated width of the display in pixels with rotation applied. | ||||||
|   virtual int get_width() = 0; |   virtual int get_width() { return this->get_width_internal(); } | ||||||
|   /// Get the height of the image in pixels with rotation applied. |   /// Get the calculated height of the display in pixels with rotation applied. | ||||||
|   virtual int get_height() = 0; |   virtual int get_height() { return this->get_height_internal(); } | ||||||
|  |  | ||||||
|  |   /// Get the native (original) width of the display in pixels. | ||||||
|  |   int get_native_width() { return this->get_width_internal(); } | ||||||
|  |   /// Get the native (original) height of the display in pixels. | ||||||
|  |   int get_native_height() { return this->get_height_internal(); } | ||||||
|  |  | ||||||
|   /// Set a single pixel at the specified coordinates to default color. |   /// Set a single pixel at the specified coordinates to default color. | ||||||
|   inline void draw_pixel_at(int x, int y) { this->draw_pixel_at(x, y, COLOR_ON); } |   inline void draw_pixel_at(int x, int y) { this->draw_pixel_at(x, y, COLOR_ON); } | ||||||
| @@ -242,6 +283,42 @@ class Display : public PollingComponent { | |||||||
|   /// Fill a triangle contained between the points [x1,y1], [x2,y2] and [x3,y3] with the given color. |   /// Fill a triangle contained between the points [x1,y1], [x2,y2] and [x3,y3] with the given color. | ||||||
|   void filled_triangle(int x1, int y1, int x2, int y2, int x3, int y3, Color color = COLOR_ON); |   void filled_triangle(int x1, int y1, int x2, int y2, int x3, int y3, Color color = COLOR_ON); | ||||||
|  |  | ||||||
|  |   /// Get the specified vertex (x,y) coordinates for the regular polygon inscribed in the circle centered on | ||||||
|  |   /// [center_x,center_y] with the given radius. Vertex id are 0-indexed and rotate clockwise. In a pointy-topped | ||||||
|  |   /// variation of a polygon with a 0° rotation, the vertex #0 is located at the top of the polygon. In a flat-topped | ||||||
|  |   /// variation of a polygon with a 0° rotation, the vertex #0 is located on the left-side of the horizontal top | ||||||
|  |   /// edge, and the vertex #1 is located on the right-side of the horizontal top edge. | ||||||
|  |   /// Use the edges constants (e.g.: EDGES_HEXAGON) or any integer to specify the number of edges of the polygon. | ||||||
|  |   /// Use the variation to switch between the flat-topped or the pointy-topped variation of the polygon. | ||||||
|  |   /// Use the rotation in degrees to rotate the shape clockwise. | ||||||
|  |   void get_regular_polygon_vertex(int vertex_id, int *vertex_x, int *vertex_y, int center_x, int center_y, int radius, | ||||||
|  |                                   int edges, RegularPolygonVariation variation = VARIATION_POINTY_TOP, | ||||||
|  |                                   float rotation_degrees = ROTATION_0_DEGREES); | ||||||
|  |  | ||||||
|  |   /// Draw the outline of a regular polygon inscribed in the circle centered on [x,y] with the given | ||||||
|  |   /// radius and color. | ||||||
|  |   /// Use the edges constants (e.g.: EDGES_HEXAGON) or any integer to specify the number of edges of the polygon. | ||||||
|  |   /// Use the variation to switch between the flat-topped or the pointy-topped variation of the polygon. | ||||||
|  |   /// Use the rotation in degrees to rotate the shape clockwise. | ||||||
|  |   /// Use the drawing to switch between outlining or filling the polygon. | ||||||
|  |   void regular_polygon(int x, int y, int radius, int edges, RegularPolygonVariation variation = VARIATION_POINTY_TOP, | ||||||
|  |                        float rotation_degrees = ROTATION_0_DEGREES, Color color = COLOR_ON, | ||||||
|  |                        RegularPolygonDrawing drawing = DRAWING_OUTLINE); | ||||||
|  |   void regular_polygon(int x, int y, int radius, int edges, RegularPolygonVariation variation, Color color, | ||||||
|  |                        RegularPolygonDrawing drawing = DRAWING_OUTLINE); | ||||||
|  |   void regular_polygon(int x, int y, int radius, int edges, Color color, | ||||||
|  |                        RegularPolygonDrawing drawing = DRAWING_OUTLINE); | ||||||
|  |  | ||||||
|  |   /// Fill a regular polygon inscribed in the circle centered on [x,y] with the given radius and color. | ||||||
|  |   /// Use the edges constants (e.g.: EDGES_HEXAGON) or any integer to specify the number of edges of the polygon. | ||||||
|  |   /// Use the variation to switch between the flat-topped or the pointy-topped variation of the polygon. | ||||||
|  |   /// Use the rotation in degrees to rotate the shape clockwise. | ||||||
|  |   void filled_regular_polygon(int x, int y, int radius, int edges, | ||||||
|  |                               RegularPolygonVariation variation = VARIATION_POINTY_TOP, | ||||||
|  |                               float rotation_degrees = ROTATION_0_DEGREES, Color color = COLOR_ON); | ||||||
|  |   void filled_regular_polygon(int x, int y, int radius, int edges, RegularPolygonVariation variation, Color color); | ||||||
|  |   void filled_regular_polygon(int x, int y, int radius, int edges, Color color); | ||||||
|  |  | ||||||
|   /** Print `text` with the anchor point at [x,y] with `font`. |   /** Print `text` with the anchor point at [x,y] with `font`. | ||||||
|    * |    * | ||||||
|    * @param x The x coordinate of the text alignment anchor point. |    * @param x The x coordinate of the text alignment anchor point. | ||||||
| @@ -538,6 +615,9 @@ class Display : public PollingComponent { | |||||||
|   void do_update_(); |   void do_update_(); | ||||||
|   void clear_clipping_(); |   void clear_clipping_(); | ||||||
|  |  | ||||||
|  |   virtual int get_height_internal() = 0; | ||||||
|  |   virtual int get_width_internal() = 0; | ||||||
|  |  | ||||||
|   /** |   /** | ||||||
|    * This method fills a triangle using only integer variables by using a |    * This method fills a triangle using only integer variables by using a | ||||||
|    * modified bresenham algorithm. |    * modified bresenham algorithm. | ||||||
|   | |||||||
| @@ -22,9 +22,6 @@ class DisplayBuffer : public Display { | |||||||
|   /// Set a single pixel at the specified coordinates to the given color. |   /// Set a single pixel at the specified coordinates to the given color. | ||||||
|   void draw_pixel_at(int x, int y, Color color) override; |   void draw_pixel_at(int x, int y, Color color) override; | ||||||
|  |  | ||||||
|   virtual int get_height_internal() = 0; |  | ||||||
|   virtual int get_width_internal() = 0; |  | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   virtual void draw_absolute_pixel_internal(int x, int y, Color color) = 0; |   virtual void draw_absolute_pixel_internal(int x, int y, Color color) = 0; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -34,24 +34,27 @@ void EKTF2232Touchscreen::setup() { | |||||||
|  |  | ||||||
|   // Get touch resolution |   // Get touch resolution | ||||||
|   uint8_t received[4]; |   uint8_t received[4]; | ||||||
|   this->write(GET_X_RES, 4); |   if (this->x_raw_max_ == this->x_raw_min_) { | ||||||
|   if (this->read(received, 4)) { |     this->write(GET_X_RES, 4); | ||||||
|     ESP_LOGE(TAG, "Failed to read X resolution!"); |     if (this->read(received, 4)) { | ||||||
|     this->interrupt_pin_->detach_interrupt(); |       ESP_LOGE(TAG, "Failed to read X resolution!"); | ||||||
|     this->mark_failed(); |       this->interrupt_pin_->detach_interrupt(); | ||||||
|     return; |       this->mark_failed(); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     this->x_raw_max_ = ((received[2])) | ((received[3] & 0xf0) << 4); | ||||||
|   } |   } | ||||||
|   this->x_raw_max_ = ((received[2])) | ((received[3] & 0xf0) << 4); |  | ||||||
|  |  | ||||||
|   this->write(GET_Y_RES, 4); |   if (this->y_raw_max_ == this->y_raw_min_) { | ||||||
|   if (this->read(received, 4)) { |     this->write(GET_Y_RES, 4); | ||||||
|     ESP_LOGE(TAG, "Failed to read Y resolution!"); |     if (this->read(received, 4)) { | ||||||
|     this->interrupt_pin_->detach_interrupt(); |       ESP_LOGE(TAG, "Failed to read Y resolution!"); | ||||||
|     this->mark_failed(); |       this->interrupt_pin_->detach_interrupt(); | ||||||
|     return; |       this->mark_failed(); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     this->y_raw_max_ = ((received[2])) | ((received[3] & 0xf0) << 4); | ||||||
|   } |   } | ||||||
|   this->y_raw_max_ = ((received[2])) | ((received[3] & 0xf0) << 4); |  | ||||||
|  |  | ||||||
|   this->set_power_state(true); |   this->set_power_state(true); | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -7,6 +7,8 @@ CODEOWNERS = ["@ellull"] | |||||||
|  |  | ||||||
| DEPENDENCIES = ["i2c"] | DEPENDENCIES = ["i2c"] | ||||||
|  |  | ||||||
|  | MULTI_CONF = True | ||||||
|  |  | ||||||
| CONF_PWM = "pwm" | CONF_PWM = "pwm" | ||||||
| CONF_DIVIDER = "divider" | CONF_DIVIDER = "divider" | ||||||
| CONF_DAC = "dac" | CONF_DAC = "dac" | ||||||
|   | |||||||
| @@ -2,6 +2,7 @@ import esphome.codegen as cg | |||||||
| import esphome.config_validation as cv | import esphome.config_validation as cv | ||||||
| from esphome.components import sensor | from esphome.components import sensor | ||||||
| from esphome.const import ( | from esphome.const import ( | ||||||
|  |     CONF_EXTERNAL_TEMPERATURE, | ||||||
|     CONF_ID, |     CONF_ID, | ||||||
|     CONF_SPEED, |     CONF_SPEED, | ||||||
|     DEVICE_CLASS_TEMPERATURE, |     DEVICE_CLASS_TEMPERATURE, | ||||||
| @@ -16,7 +17,6 @@ from .. import EMC2101_COMPONENT_SCHEMA, CONF_EMC2101_ID, emc2101_ns | |||||||
| DEPENDENCIES = ["emc2101"] | DEPENDENCIES = ["emc2101"] | ||||||
|  |  | ||||||
| CONF_INTERNAL_TEMPERATURE = "internal_temperature" | CONF_INTERNAL_TEMPERATURE = "internal_temperature" | ||||||
| CONF_EXTERNAL_TEMPERATURE = "external_temperature" |  | ||||||
| CONF_DUTY_CYCLE = "duty_cycle" | CONF_DUTY_CYCLE = "duty_cycle" | ||||||
|  |  | ||||||
| EMC2101Sensor = emc2101_ns.class_("EMC2101Sensor", cg.PollingComponent) | EMC2101Sensor = emc2101_ns.class_("EMC2101Sensor", cg.PollingComponent) | ||||||
|   | |||||||
| @@ -141,9 +141,13 @@ void ESP32ImprovComponent::loop() { | |||||||
|  |  | ||||||
|         std::vector<std::string> urls = {ESPHOME_MY_LINK}; |         std::vector<std::string> urls = {ESPHOME_MY_LINK}; | ||||||
| #ifdef USE_WEBSERVER | #ifdef USE_WEBSERVER | ||||||
|         auto ip = wifi::global_wifi_component->wifi_sta_ip(); |         for (auto &ip : wifi::global_wifi_component->wifi_sta_ip_addresses()) { | ||||||
|         std::string webserver_url = "http://" + ip.str() + ":" + to_string(USE_WEBSERVER_PORT); |           if (ip.is_ip4()) { | ||||||
|         urls.push_back(webserver_url); |             std::string webserver_url = "http://" + ip.str() + ":" + to_string(USE_WEBSERVER_PORT); | ||||||
|  |             urls.push_back(webserver_url); | ||||||
|  |             break; | ||||||
|  |           } | ||||||
|  |         } | ||||||
| #endif | #endif | ||||||
|         std::vector<uint8_t> data = improv::build_rpc_response(improv::WIFI_SETTINGS, urls); |         std::vector<uint8_t> data = improv::build_rpc_response(improv::WIFI_SETTINGS, urls); | ||||||
|         this->send_response_(data); |         this->send_response_(data); | ||||||
| @@ -289,7 +293,7 @@ void ESP32ImprovComponent::process_incoming_data_() { | |||||||
|         this->connecting_sta_ = sta; |         this->connecting_sta_ = sta; | ||||||
|  |  | ||||||
|         wifi::global_wifi_component->set_sta(sta); |         wifi::global_wifi_component->set_sta(sta); | ||||||
|         wifi::global_wifi_component->start_scanning(); |         wifi::global_wifi_component->start_connecting(sta, false); | ||||||
|         this->set_state_(improv::STATE_PROVISIONING); |         this->set_state_(improv::STATE_PROVISIONING); | ||||||
|         ESP_LOGD(TAG, "Received Improv wifi settings ssid=%s, password=" LOG_SECRET("%s"), command.ssid.c_str(), |         ESP_LOGD(TAG, "Received Improv wifi settings ssid=%s, password=" LOG_SECRET("%s"), command.ssid.c_str(), | ||||||
|                  command.password.c_str()); |                  command.password.c_str()); | ||||||
|   | |||||||
| @@ -160,11 +160,13 @@ light::ESPColorView ESP32RMTLEDStripLightOutput::get_view_internal(int32_t index | |||||||
|       b = 0; |       b = 0; | ||||||
|       break; |       break; | ||||||
|   } |   } | ||||||
|   uint8_t multiplier = this->is_rgbw_ ? 4 : 3; |   uint8_t multiplier = this->is_rgbw_ || this->is_wrgb_ ? 4 : 3; | ||||||
|   return {this->buf_ + (index * multiplier) + r, |   uint8_t white = this->is_wrgb_ ? 0 : 3; | ||||||
|           this->buf_ + (index * multiplier) + g, |  | ||||||
|           this->buf_ + (index * multiplier) + b, |   return {this->buf_ + (index * multiplier) + r + this->is_wrgb_, | ||||||
|           this->is_rgbw_ ? this->buf_ + (index * multiplier) + 3 : nullptr, |           this->buf_ + (index * multiplier) + g + this->is_wrgb_, | ||||||
|  |           this->buf_ + (index * multiplier) + b + this->is_wrgb_, | ||||||
|  |           this->is_rgbw_ || this->is_wrgb_ ? this->buf_ + (index * multiplier) + white : nullptr, | ||||||
|           &this->effect_data_[index], |           &this->effect_data_[index], | ||||||
|           &this->correction_}; |           &this->correction_}; | ||||||
| } | } | ||||||
|   | |||||||
| @@ -33,7 +33,7 @@ class ESP32RMTLEDStripLightOutput : public light::AddressableLight { | |||||||
|   int32_t size() const override { return this->num_leds_; } |   int32_t size() const override { return this->num_leds_; } | ||||||
|   light::LightTraits get_traits() override { |   light::LightTraits get_traits() override { | ||||||
|     auto traits = light::LightTraits(); |     auto traits = light::LightTraits(); | ||||||
|     if (this->is_rgbw_) { |     if (this->is_rgbw_ || this->is_wrgb_) { | ||||||
|       traits.set_supported_color_modes({light::ColorMode::RGB_WHITE, light::ColorMode::WHITE}); |       traits.set_supported_color_modes({light::ColorMode::RGB_WHITE, light::ColorMode::WHITE}); | ||||||
|     } else { |     } else { | ||||||
|       traits.set_supported_color_modes({light::ColorMode::RGB}); |       traits.set_supported_color_modes({light::ColorMode::RGB}); | ||||||
| @@ -44,6 +44,7 @@ class ESP32RMTLEDStripLightOutput : public light::AddressableLight { | |||||||
|   void set_pin(uint8_t pin) { this->pin_ = pin; } |   void set_pin(uint8_t pin) { this->pin_ = pin; } | ||||||
|   void set_num_leds(uint16_t num_leds) { this->num_leds_ = num_leds; } |   void set_num_leds(uint16_t num_leds) { this->num_leds_ = num_leds; } | ||||||
|   void set_is_rgbw(bool is_rgbw) { this->is_rgbw_ = is_rgbw; } |   void set_is_rgbw(bool is_rgbw) { this->is_rgbw_ = is_rgbw; } | ||||||
|  |   void set_is_wrgb(bool is_wrgb) { this->is_wrgb_ = is_wrgb; } | ||||||
|  |  | ||||||
|   /// Set a maximum refresh rate in µs as some lights do not like being updated too often. |   /// Set a maximum refresh rate in µs as some lights do not like being updated too often. | ||||||
|   void set_max_refresh_rate(uint32_t interval_us) { this->max_refresh_rate_ = interval_us; } |   void set_max_refresh_rate(uint32_t interval_us) { this->max_refresh_rate_ = interval_us; } | ||||||
| @@ -72,6 +73,7 @@ class ESP32RMTLEDStripLightOutput : public light::AddressableLight { | |||||||
|   uint8_t pin_; |   uint8_t pin_; | ||||||
|   uint16_t num_leds_; |   uint16_t num_leds_; | ||||||
|   bool is_rgbw_; |   bool is_rgbw_; | ||||||
|  |   bool is_wrgb_; | ||||||
|  |  | ||||||
|   rmt_item32_t bit0_, bit1_; |   rmt_item32_t bit0_, bit1_; | ||||||
|   RGBOrder rgb_order_; |   RGBOrder rgb_order_; | ||||||
|   | |||||||
| @@ -52,6 +52,7 @@ CHIPSETS = { | |||||||
|  |  | ||||||
|  |  | ||||||
| CONF_IS_RGBW = "is_rgbw" | CONF_IS_RGBW = "is_rgbw" | ||||||
|  | CONF_IS_WRGB = "is_wrgb" | ||||||
| CONF_BIT0_HIGH = "bit0_high" | CONF_BIT0_HIGH = "bit0_high" | ||||||
| CONF_BIT0_LOW = "bit0_low" | CONF_BIT0_LOW = "bit0_low" | ||||||
| CONF_BIT1_HIGH = "bit1_high" | CONF_BIT1_HIGH = "bit1_high" | ||||||
| @@ -90,6 +91,7 @@ CONFIG_SCHEMA = cv.All( | |||||||
|             cv.Optional(CONF_MAX_REFRESH_RATE): cv.positive_time_period_microseconds, |             cv.Optional(CONF_MAX_REFRESH_RATE): cv.positive_time_period_microseconds, | ||||||
|             cv.Optional(CONF_CHIPSET): cv.one_of(*CHIPSETS, upper=True), |             cv.Optional(CONF_CHIPSET): cv.one_of(*CHIPSETS, upper=True), | ||||||
|             cv.Optional(CONF_IS_RGBW, default=False): cv.boolean, |             cv.Optional(CONF_IS_RGBW, default=False): cv.boolean, | ||||||
|  |             cv.Optional(CONF_IS_WRGB, default=False): cv.boolean, | ||||||
|             cv.Inclusive( |             cv.Inclusive( | ||||||
|                 CONF_BIT0_HIGH, |                 CONF_BIT0_HIGH, | ||||||
|                 "custom", |                 "custom", | ||||||
| @@ -145,6 +147,7 @@ async def to_code(config): | |||||||
|  |  | ||||||
|     cg.add(var.set_rgb_order(config[CONF_RGB_ORDER])) |     cg.add(var.set_rgb_order(config[CONF_RGB_ORDER])) | ||||||
|     cg.add(var.set_is_rgbw(config[CONF_IS_RGBW])) |     cg.add(var.set_is_rgbw(config[CONF_IS_RGBW])) | ||||||
|  |     cg.add(var.set_is_wrgb(config[CONF_IS_WRGB])) | ||||||
|  |  | ||||||
|     cg.add( |     cg.add( | ||||||
|         var.set_rmt_channel( |         var.set_rmt_channel( | ||||||
|   | |||||||
| @@ -1,6 +1,13 @@ | |||||||
| from esphome import pins | from esphome import pins | ||||||
| import esphome.config_validation as cv | import esphome.config_validation as cv | ||||||
|  | import esphome.final_validate as fv | ||||||
| import esphome.codegen as cg | import esphome.codegen as cg | ||||||
|  | from esphome.components.esp32 import add_idf_sdkconfig_option, get_esp32_variant | ||||||
|  | from esphome.components.esp32.const import ( | ||||||
|  |     VARIANT_ESP32C3, | ||||||
|  |     VARIANT_ESP32S2, | ||||||
|  |     VARIANT_ESP32S3, | ||||||
|  | ) | ||||||
| from esphome.const import ( | from esphome.const import ( | ||||||
|     CONF_DOMAIN, |     CONF_DOMAIN, | ||||||
|     CONF_ID, |     CONF_ID, | ||||||
| @@ -12,9 +19,17 @@ from esphome.const import ( | |||||||
|     CONF_SUBNET, |     CONF_SUBNET, | ||||||
|     CONF_DNS1, |     CONF_DNS1, | ||||||
|     CONF_DNS2, |     CONF_DNS2, | ||||||
|  |     CONF_CLK_PIN, | ||||||
|  |     CONF_MISO_PIN, | ||||||
|  |     CONF_MOSI_PIN, | ||||||
|  |     CONF_CS_PIN, | ||||||
|  |     CONF_INTERRUPT_PIN, | ||||||
|  |     CONF_RESET_PIN, | ||||||
|  |     CONF_SPI, | ||||||
| ) | ) | ||||||
| from esphome.core import CORE, coroutine_with_priority | from esphome.core import CORE, coroutine_with_priority | ||||||
| from esphome.components.network import IPAddress | from esphome.components.network import IPAddress | ||||||
|  | from esphome.components.spi import get_spi_interface, CONF_INTERFACE_INDEX | ||||||
|  |  | ||||||
| CONFLICTS_WITH = ["wifi"] | CONFLICTS_WITH = ["wifi"] | ||||||
| DEPENDENCIES = ["esp32"] | DEPENDENCIES = ["esp32"] | ||||||
| @@ -27,6 +42,8 @@ CONF_MDIO_PIN = "mdio_pin" | |||||||
| CONF_CLK_MODE = "clk_mode" | CONF_CLK_MODE = "clk_mode" | ||||||
| CONF_POWER_PIN = "power_pin" | CONF_POWER_PIN = "power_pin" | ||||||
|  |  | ||||||
|  | CONF_CLOCK_SPEED = "clock_speed" | ||||||
|  |  | ||||||
| EthernetType = ethernet_ns.enum("EthernetType") | EthernetType = ethernet_ns.enum("EthernetType") | ||||||
| ETHERNET_TYPES = { | ETHERNET_TYPES = { | ||||||
|     "LAN8720": EthernetType.ETHERNET_TYPE_LAN8720, |     "LAN8720": EthernetType.ETHERNET_TYPE_LAN8720, | ||||||
| @@ -36,8 +53,11 @@ ETHERNET_TYPES = { | |||||||
|     "JL1101": EthernetType.ETHERNET_TYPE_JL1101, |     "JL1101": EthernetType.ETHERNET_TYPE_JL1101, | ||||||
|     "KSZ8081": EthernetType.ETHERNET_TYPE_KSZ8081, |     "KSZ8081": EthernetType.ETHERNET_TYPE_KSZ8081, | ||||||
|     "KSZ8081RNA": EthernetType.ETHERNET_TYPE_KSZ8081RNA, |     "KSZ8081RNA": EthernetType.ETHERNET_TYPE_KSZ8081RNA, | ||||||
|  |     "W5500": EthernetType.ETHERNET_TYPE_W5500, | ||||||
| } | } | ||||||
|  |  | ||||||
|  | SPI_ETHERNET_TYPES = ["W5500"] | ||||||
|  |  | ||||||
| emac_rmii_clock_mode_t = cg.global_ns.enum("emac_rmii_clock_mode_t") | emac_rmii_clock_mode_t = cg.global_ns.enum("emac_rmii_clock_mode_t") | ||||||
| emac_rmii_clock_gpio_t = cg.global_ns.enum("emac_rmii_clock_gpio_t") | emac_rmii_clock_gpio_t = cg.global_ns.enum("emac_rmii_clock_gpio_t") | ||||||
| CLK_MODES = { | CLK_MODES = { | ||||||
| @@ -84,11 +104,22 @@ def _validate(config): | |||||||
|     return config |     return config | ||||||
|  |  | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = cv.All( | BASE_SCHEMA = cv.Schema( | ||||||
|  |     { | ||||||
|  |         cv.GenerateID(): cv.declare_id(EthernetComponent), | ||||||
|  |         cv.Optional(CONF_MANUAL_IP): MANUAL_IP_SCHEMA, | ||||||
|  |         cv.Optional(CONF_DOMAIN, default=".local"): cv.domain_name, | ||||||
|  |         cv.Optional(CONF_USE_ADDRESS): cv.string_strict, | ||||||
|  |         cv.Optional("enable_mdns"): cv.invalid( | ||||||
|  |             "This option has been removed. Please use the [disabled] option under the " | ||||||
|  |             "new mdns component instead." | ||||||
|  |         ), | ||||||
|  |     } | ||||||
|  | ).extend(cv.COMPONENT_SCHEMA) | ||||||
|  |  | ||||||
|  | RMII_SCHEMA = BASE_SCHEMA.extend( | ||||||
|     cv.Schema( |     cv.Schema( | ||||||
|         { |         { | ||||||
|             cv.GenerateID(): cv.declare_id(EthernetComponent), |  | ||||||
|             cv.Required(CONF_TYPE): cv.enum(ETHERNET_TYPES, upper=True), |  | ||||||
|             cv.Required(CONF_MDC_PIN): pins.internal_gpio_output_pin_number, |             cv.Required(CONF_MDC_PIN): pins.internal_gpio_output_pin_number, | ||||||
|             cv.Required(CONF_MDIO_PIN): pins.internal_gpio_output_pin_number, |             cv.Required(CONF_MDIO_PIN): pins.internal_gpio_output_pin_number, | ||||||
|             cv.Optional(CONF_CLK_MODE, default="GPIO0_IN"): cv.enum( |             cv.Optional(CONF_CLK_MODE, default="GPIO0_IN"): cv.enum( | ||||||
| @@ -96,19 +127,64 @@ CONFIG_SCHEMA = cv.All( | |||||||
|             ), |             ), | ||||||
|             cv.Optional(CONF_PHY_ADDR, default=0): cv.int_range(min=0, max=31), |             cv.Optional(CONF_PHY_ADDR, default=0): cv.int_range(min=0, max=31), | ||||||
|             cv.Optional(CONF_POWER_PIN): pins.internal_gpio_output_pin_number, |             cv.Optional(CONF_POWER_PIN): pins.internal_gpio_output_pin_number, | ||||||
|             cv.Optional(CONF_MANUAL_IP): MANUAL_IP_SCHEMA, |         } | ||||||
|             cv.Optional(CONF_DOMAIN, default=".local"): cv.domain_name, |     ) | ||||||
|             cv.Optional(CONF_USE_ADDRESS): cv.string_strict, | ) | ||||||
|             cv.Optional("enable_mdns"): cv.invalid( |  | ||||||
|                 "This option has been removed. Please use the [disabled] option under the " | SPI_SCHEMA = BASE_SCHEMA.extend( | ||||||
|                 "new mdns component instead." |     cv.Schema( | ||||||
|  |         { | ||||||
|  |             cv.Required(CONF_CLK_PIN): pins.internal_gpio_output_pin_number, | ||||||
|  |             cv.Required(CONF_MISO_PIN): pins.internal_gpio_input_pin_number, | ||||||
|  |             cv.Required(CONF_MOSI_PIN): pins.internal_gpio_output_pin_number, | ||||||
|  |             cv.Required(CONF_CS_PIN): pins.internal_gpio_output_pin_number, | ||||||
|  |             cv.Optional(CONF_INTERRUPT_PIN): pins.internal_gpio_input_pin_number, | ||||||
|  |             cv.Optional(CONF_RESET_PIN): pins.internal_gpio_output_pin_number, | ||||||
|  |             cv.Optional(CONF_CLOCK_SPEED, default="26.67MHz"): cv.All( | ||||||
|  |                 cv.frequency, cv.int_range(int(8e6), int(80e6)) | ||||||
|             ), |             ), | ||||||
|         } |         } | ||||||
|     ).extend(cv.COMPONENT_SCHEMA), |     ), | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | CONFIG_SCHEMA = cv.All( | ||||||
|  |     cv.typed_schema( | ||||||
|  |         { | ||||||
|  |             "LAN8720": RMII_SCHEMA, | ||||||
|  |             "RTL8201": RMII_SCHEMA, | ||||||
|  |             "DP83848": RMII_SCHEMA, | ||||||
|  |             "IP101": RMII_SCHEMA, | ||||||
|  |             "JL1101": RMII_SCHEMA, | ||||||
|  |             "W5500": SPI_SCHEMA, | ||||||
|  |         }, | ||||||
|  |         upper=True, | ||||||
|  |     ), | ||||||
|     _validate, |     _validate, | ||||||
| ) | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def _final_validate(config): | ||||||
|  |     if config[CONF_TYPE] not in SPI_ETHERNET_TYPES: | ||||||
|  |         return | ||||||
|  |     if spi_configs := fv.full_config.get().get(CONF_SPI): | ||||||
|  |         variant = get_esp32_variant() | ||||||
|  |         if variant in (VARIANT_ESP32C3, VARIANT_ESP32S2, VARIANT_ESP32S3): | ||||||
|  |             spi_host = "SPI2_HOST" | ||||||
|  |         else: | ||||||
|  |             spi_host = "SPI3_HOST" | ||||||
|  |         for spi_conf in spi_configs: | ||||||
|  |             if (index := spi_conf.get(CONF_INTERFACE_INDEX)) is not None: | ||||||
|  |                 interface = get_spi_interface(index) | ||||||
|  |                 if interface == spi_host: | ||||||
|  |                     raise cv.Invalid( | ||||||
|  |                         f"`spi` component is using interface '{interface}'. " | ||||||
|  |                         f"To use {config[CONF_TYPE]}, you must change the `interface` on the `spi` component.", | ||||||
|  |                     ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | FINAL_VALIDATE_SCHEMA = _final_validate | ||||||
|  |  | ||||||
|  |  | ||||||
| def manual_ip(config): | def manual_ip(config): | ||||||
|     return cg.StructInitializer( |     return cg.StructInitializer( | ||||||
|         ManualIP, |         ManualIP, | ||||||
| @@ -125,15 +201,31 @@ 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(var.set_phy_addr(config[CONF_PHY_ADDR])) |     if config[CONF_TYPE] == "W5500": | ||||||
|     cg.add(var.set_mdc_pin(config[CONF_MDC_PIN])) |         cg.add(var.set_clk_pin(config[CONF_CLK_PIN])) | ||||||
|     cg.add(var.set_mdio_pin(config[CONF_MDIO_PIN])) |         cg.add(var.set_miso_pin(config[CONF_MISO_PIN])) | ||||||
|     cg.add(var.set_type(config[CONF_TYPE])) |         cg.add(var.set_mosi_pin(config[CONF_MOSI_PIN])) | ||||||
|     cg.add(var.set_clk_mode(*CLK_MODES[config[CONF_CLK_MODE]])) |         cg.add(var.set_cs_pin(config[CONF_CS_PIN])) | ||||||
|     cg.add(var.set_use_address(config[CONF_USE_ADDRESS])) |         if CONF_INTERRUPT_PIN in config: | ||||||
|  |             cg.add(var.set_interrupt_pin(config[CONF_INTERRUPT_PIN])) | ||||||
|  |         if CONF_RESET_PIN in config: | ||||||
|  |             cg.add(var.set_reset_pin(config[CONF_RESET_PIN])) | ||||||
|  |         cg.add(var.set_clock_speed(config[CONF_CLOCK_SPEED])) | ||||||
|  |  | ||||||
|     if CONF_POWER_PIN in config: |         cg.add_define("USE_ETHERNET_SPI") | ||||||
|         cg.add(var.set_power_pin(config[CONF_POWER_PIN])) |         if CORE.using_esp_idf: | ||||||
|  |             add_idf_sdkconfig_option("CONFIG_ETH_USE_SPI_ETHERNET", True) | ||||||
|  |             add_idf_sdkconfig_option("CONFIG_ETH_SPI_ETHERNET_W5500", True) | ||||||
|  |     else: | ||||||
|  |         cg.add(var.set_phy_addr(config[CONF_PHY_ADDR])) | ||||||
|  |         cg.add(var.set_mdc_pin(config[CONF_MDC_PIN])) | ||||||
|  |         cg.add(var.set_mdio_pin(config[CONF_MDIO_PIN])) | ||||||
|  |         cg.add(var.set_clk_mode(*CLK_MODES[config[CONF_CLK_MODE]])) | ||||||
|  |         if CONF_POWER_PIN in config: | ||||||
|  |             cg.add(var.set_power_pin(config[CONF_POWER_PIN])) | ||||||
|  |  | ||||||
|  |     cg.add(var.set_type(ETHERNET_TYPES[config[CONF_TYPE]])) | ||||||
|  |     cg.add(var.set_use_address(config[CONF_USE_ADDRESS])) | ||||||
|  |  | ||||||
|     if CONF_MANUAL_IP in config: |     if CONF_MANUAL_IP in config: | ||||||
|         cg.add(var.set_manual_ip(manual_ip(config[CONF_MANUAL_IP]))) |         cg.add(var.set_manual_ip(manual_ip(config[CONF_MANUAL_IP]))) | ||||||
|   | |||||||
| @@ -9,6 +9,11 @@ | |||||||
| #include <lwip/dns.h> | #include <lwip/dns.h> | ||||||
| #include "esp_event.h" | #include "esp_event.h" | ||||||
|  |  | ||||||
|  | #ifdef USE_ETHERNET_SPI | ||||||
|  | #include <driver/gpio.h> | ||||||
|  | #include <driver/spi_master.h> | ||||||
|  | #endif | ||||||
|  |  | ||||||
| namespace esphome { | namespace esphome { | ||||||
| namespace ethernet { | namespace ethernet { | ||||||
|  |  | ||||||
| @@ -33,6 +38,36 @@ void EthernetComponent::setup() { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   esp_err_t err; |   esp_err_t err; | ||||||
|  |  | ||||||
|  | #ifdef USE_ETHERNET_SPI | ||||||
|  |   // Install GPIO ISR handler to be able to service SPI Eth modules interrupts | ||||||
|  |   gpio_install_isr_service(0); | ||||||
|  |  | ||||||
|  |   spi_bus_config_t buscfg = { | ||||||
|  |       .mosi_io_num = this->mosi_pin_, | ||||||
|  |       .miso_io_num = this->miso_pin_, | ||||||
|  |       .sclk_io_num = this->clk_pin_, | ||||||
|  |       .quadwp_io_num = -1, | ||||||
|  |       .quadhd_io_num = -1, | ||||||
|  |       .data4_io_num = -1, | ||||||
|  |       .data5_io_num = -1, | ||||||
|  |       .data6_io_num = -1, | ||||||
|  |       .data7_io_num = -1, | ||||||
|  |       .max_transfer_sz = 0, | ||||||
|  |       .flags = 0, | ||||||
|  |       .intr_flags = 0, | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  | #if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) | ||||||
|  |   auto host = SPI2_HOST; | ||||||
|  | #else | ||||||
|  |   auto host = SPI3_HOST; | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  |   err = spi_bus_initialize(host, &buscfg, SPI_DMA_CH_AUTO); | ||||||
|  |   ESPHL_ERROR_CHECK(err, "SPI bus initialize error"); | ||||||
|  | #endif | ||||||
|  |  | ||||||
|   err = esp_netif_init(); |   err = esp_netif_init(); | ||||||
|   ESPHL_ERROR_CHECK(err, "ETH netif init error"); |   ESPHL_ERROR_CHECK(err, "ETH netif init error"); | ||||||
|   err = esp_event_loop_create_default(); |   err = esp_event_loop_create_default(); | ||||||
| @@ -43,10 +78,40 @@ void EthernetComponent::setup() { | |||||||
|  |  | ||||||
|   // Init MAC and PHY configs to default |   // Init MAC and PHY configs to default | ||||||
|   eth_phy_config_t phy_config = ETH_PHY_DEFAULT_CONFIG(); |   eth_phy_config_t phy_config = ETH_PHY_DEFAULT_CONFIG(); | ||||||
|  |   eth_mac_config_t mac_config = ETH_MAC_DEFAULT_CONFIG(); | ||||||
|  |  | ||||||
|  | #ifdef USE_ETHERNET_SPI  // Configure SPI interface and Ethernet driver for specific SPI module | ||||||
|  |   spi_device_interface_config_t devcfg = { | ||||||
|  |       .command_bits = 16,  // Actually it's the address phase in W5500 SPI frame | ||||||
|  |       .address_bits = 8,   // Actually it's the control phase in W5500 SPI frame | ||||||
|  |       .dummy_bits = 0, | ||||||
|  |       .mode = 0, | ||||||
|  |       .duty_cycle_pos = 0, | ||||||
|  |       .cs_ena_pretrans = 0, | ||||||
|  |       .cs_ena_posttrans = 0, | ||||||
|  |       .clock_speed_hz = this->clock_speed_, | ||||||
|  |       .input_delay_ns = 0, | ||||||
|  |       .spics_io_num = this->cs_pin_, | ||||||
|  |       .flags = 0, | ||||||
|  |       .queue_size = 20, | ||||||
|  |       .pre_cb = nullptr, | ||||||
|  |       .post_cb = nullptr, | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   spi_device_handle_t spi_handle = nullptr; | ||||||
|  |   err = spi_bus_add_device(host, &devcfg, &spi_handle); | ||||||
|  |   ESPHL_ERROR_CHECK(err, "SPI bus add device error"); | ||||||
|  |  | ||||||
|  |   eth_w5500_config_t w5500_config = ETH_W5500_DEFAULT_CONFIG(spi_handle); | ||||||
|  |   w5500_config.int_gpio_num = this->interrupt_pin_; | ||||||
|  |   phy_config.phy_addr = this->phy_addr_spi_; | ||||||
|  |   phy_config.reset_gpio_num = this->reset_pin_; | ||||||
|  |  | ||||||
|  |   esp_eth_mac_t *mac = esp_eth_mac_new_w5500(&w5500_config, &mac_config); | ||||||
|  | #else | ||||||
|   phy_config.phy_addr = this->phy_addr_; |   phy_config.phy_addr = this->phy_addr_; | ||||||
|   phy_config.reset_gpio_num = this->power_pin_; |   phy_config.reset_gpio_num = this->power_pin_; | ||||||
|  |  | ||||||
|   eth_mac_config_t mac_config = ETH_MAC_DEFAULT_CONFIG(); |  | ||||||
| #if ESP_IDF_VERSION_MAJOR >= 5 | #if ESP_IDF_VERSION_MAJOR >= 5 | ||||||
|   eth_esp32_emac_config_t esp32_emac_config = ETH_ESP32_EMAC_DEFAULT_CONFIG(); |   eth_esp32_emac_config_t esp32_emac_config = ETH_ESP32_EMAC_DEFAULT_CONFIG(); | ||||||
|   esp32_emac_config.smi_mdc_gpio_num = this->mdc_pin_; |   esp32_emac_config.smi_mdc_gpio_num = this->mdc_pin_; | ||||||
| @@ -62,9 +127,11 @@ void EthernetComponent::setup() { | |||||||
|   mac_config.clock_config.rmii.clock_gpio = this->clk_gpio_; |   mac_config.clock_config.rmii.clock_gpio = this->clk_gpio_; | ||||||
|  |  | ||||||
|   esp_eth_mac_t *mac = esp_eth_mac_new_esp32(&mac_config); |   esp_eth_mac_t *mac = esp_eth_mac_new_esp32(&mac_config); | ||||||
|  | #endif | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
|   switch (this->type_) { |   switch (this->type_) { | ||||||
|  | #if CONFIG_ETH_USE_ESP32_EMAC | ||||||
|     case ETHERNET_TYPE_LAN8720: { |     case ETHERNET_TYPE_LAN8720: { | ||||||
|       this->phy_ = esp_eth_phy_new_lan87xx(&phy_config); |       this->phy_ = esp_eth_phy_new_lan87xx(&phy_config); | ||||||
|       break; |       break; | ||||||
| @@ -94,6 +161,13 @@ void EthernetComponent::setup() { | |||||||
| #endif | #endif | ||||||
|       break; |       break; | ||||||
|     } |     } | ||||||
|  | #endif | ||||||
|  | #ifdef USE_ETHERNET_SPI | ||||||
|  |     case ETHERNET_TYPE_W5500: { | ||||||
|  |       this->phy_ = esp_eth_phy_new_w5500(&phy_config); | ||||||
|  |       break; | ||||||
|  |     } | ||||||
|  | #endif | ||||||
|     default: { |     default: { | ||||||
|       this->mark_failed(); |       this->mark_failed(); | ||||||
|       return; |       return; | ||||||
| @@ -105,10 +179,18 @@ void EthernetComponent::setup() { | |||||||
|   err = esp_eth_driver_install(ð_config, &this->eth_handle_); |   err = esp_eth_driver_install(ð_config, &this->eth_handle_); | ||||||
|   ESPHL_ERROR_CHECK(err, "ETH driver install error"); |   ESPHL_ERROR_CHECK(err, "ETH driver install error"); | ||||||
|  |  | ||||||
|  | #ifndef USE_ETHERNET_SPI | ||||||
|   if (this->type_ == ETHERNET_TYPE_KSZ8081RNA && this->clk_mode_ == EMAC_CLK_OUT) { |   if (this->type_ == ETHERNET_TYPE_KSZ8081RNA && this->clk_mode_ == EMAC_CLK_OUT) { | ||||||
|     // KSZ8081RNA default is incorrect. It expects a 25MHz clock instead of the 50MHz we provide. |     // KSZ8081RNA default is incorrect. It expects a 25MHz clock instead of the 50MHz we provide. | ||||||
|     this->ksz8081_set_clock_reference_(mac); |     this->ksz8081_set_clock_reference_(mac); | ||||||
|   } |   } | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  |   // use ESP internal eth mac | ||||||
|  |   uint8_t mac_addr[6]; | ||||||
|  |   esp_read_mac(mac_addr, ESP_MAC_ETH); | ||||||
|  |   err = esp_eth_ioctl(this->eth_handle_, ETH_CMD_S_MAC_ADDR, mac_addr); | ||||||
|  |   ESPHL_ERROR_CHECK(err, "set mac address error"); | ||||||
|  |  | ||||||
|   /* attach Ethernet driver to TCP/IP stack */ |   /* attach Ethernet driver to TCP/IP stack */ | ||||||
|   err = esp_netif_attach(this->eth_netif_, esp_eth_new_netif_glue(this->eth_handle_)); |   err = esp_netif_attach(this->eth_netif_, esp_eth_new_netif_glue(this->eth_handle_)); | ||||||
| @@ -119,10 +201,10 @@ void EthernetComponent::setup() { | |||||||
|   ESPHL_ERROR_CHECK(err, "ETH event handler register error"); |   ESPHL_ERROR_CHECK(err, "ETH event handler register error"); | ||||||
|   err = esp_event_handler_register(IP_EVENT, IP_EVENT_ETH_GOT_IP, &EthernetComponent::got_ip_event_handler, nullptr); |   err = esp_event_handler_register(IP_EVENT, IP_EVENT_ETH_GOT_IP, &EthernetComponent::got_ip_event_handler, nullptr); | ||||||
|   ESPHL_ERROR_CHECK(err, "GOT IP event handler register error"); |   ESPHL_ERROR_CHECK(err, "GOT IP event handler register error"); | ||||||
| #if ENABLE_IPV6 | #if USE_NETWORK_IPV6 | ||||||
|   err = esp_event_handler_register(IP_EVENT, IP_EVENT_GOT_IP6, &EthernetComponent::got_ip6_event_handler, nullptr); |   err = esp_event_handler_register(IP_EVENT, IP_EVENT_GOT_IP6, &EthernetComponent::got_ip6_event_handler, nullptr); | ||||||
|   ESPHL_ERROR_CHECK(err, "GOT IP6 event handler register error"); |   ESPHL_ERROR_CHECK(err, "GOT IPv6 event handler register error"); | ||||||
| #endif /* ENABLE_IPV6 */ | #endif /* USE_NETWORK_IPV6 */ | ||||||
|  |  | ||||||
|   /* start Ethernet driver state machine */ |   /* start Ethernet driver state machine */ | ||||||
|   err = esp_eth_start(this->eth_handle_); |   err = esp_eth_start(this->eth_handle_); | ||||||
| @@ -165,20 +247,6 @@ void EthernetComponent::loop() { | |||||||
|         this->state_ = EthernetComponentState::CONNECTING; |         this->state_ = EthernetComponentState::CONNECTING; | ||||||
|         this->start_connect_(); |         this->start_connect_(); | ||||||
|       } |       } | ||||||
| #if ENABLE_IPV6 |  | ||||||
|       else if (this->got_ipv6_) { |  | ||||||
|         esp_ip6_addr_t ip6_addr; |  | ||||||
|         if (esp_netif_get_ip6_global(this->eth_netif_, &ip6_addr) == 0 && |  | ||||||
|             esp_netif_ip6_get_addr_type(&ip6_addr) == ESP_IP6_ADDR_IS_GLOBAL) { |  | ||||||
|           ESP_LOGCONFIG(TAG, "IPv6 Addr (Global): " IPV6STR, IPV62STR(ip6_addr)); |  | ||||||
|         } else { |  | ||||||
|           esp_netif_get_ip6_linklocal(this->eth_netif_, &ip6_addr); |  | ||||||
|           ESP_LOGCONFIG(TAG, " IPv6: " IPV6STR, IPV62STR(ip6_addr)); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         this->got_ipv6_ = false; |  | ||||||
|       } |  | ||||||
| #endif /* ENABLE_IPV6 */ |  | ||||||
|       break; |       break; | ||||||
|   } |   } | ||||||
| } | } | ||||||
| @@ -214,6 +282,10 @@ void EthernetComponent::dump_config() { | |||||||
|       eth_type = "KSZ8081RNA"; |       eth_type = "KSZ8081RNA"; | ||||||
|       break; |       break; | ||||||
|  |  | ||||||
|  |     case ETHERNET_TYPE_W5500: | ||||||
|  |       eth_type = "W5500"; | ||||||
|  |       break; | ||||||
|  |  | ||||||
|     default: |     default: | ||||||
|       eth_type = "Unknown"; |       eth_type = "Unknown"; | ||||||
|       break; |       break; | ||||||
| @@ -221,23 +293,51 @@ void EthernetComponent::dump_config() { | |||||||
|  |  | ||||||
|   ESP_LOGCONFIG(TAG, "Ethernet:"); |   ESP_LOGCONFIG(TAG, "Ethernet:"); | ||||||
|   this->dump_connect_params_(); |   this->dump_connect_params_(); | ||||||
|  | #ifdef USE_ETHERNET_SPI | ||||||
|  |   ESP_LOGCONFIG(TAG, "  CLK Pin: %u", this->clk_pin_); | ||||||
|  |   ESP_LOGCONFIG(TAG, "  MISO Pin: %u", this->miso_pin_); | ||||||
|  |   ESP_LOGCONFIG(TAG, "  MOSI Pin: %u", this->mosi_pin_); | ||||||
|  |   ESP_LOGCONFIG(TAG, "  CS Pin: %u", this->cs_pin_); | ||||||
|  |   ESP_LOGCONFIG(TAG, "  IRQ Pin: %u", this->interrupt_pin_); | ||||||
|  |   ESP_LOGCONFIG(TAG, "  Reset Pin: %d", this->reset_pin_); | ||||||
|  |   ESP_LOGCONFIG(TAG, "  Clock Speed: %d MHz", this->clock_speed_ / 1000000); | ||||||
|  | #else | ||||||
|   if (this->power_pin_ != -1) { |   if (this->power_pin_ != -1) { | ||||||
|     ESP_LOGCONFIG(TAG, "  Power Pin: %u", this->power_pin_); |     ESP_LOGCONFIG(TAG, "  Power Pin: %u", this->power_pin_); | ||||||
|   } |   } | ||||||
|   ESP_LOGCONFIG(TAG, "  MDC Pin: %u", this->mdc_pin_); |   ESP_LOGCONFIG(TAG, "  MDC Pin: %u", this->mdc_pin_); | ||||||
|   ESP_LOGCONFIG(TAG, "  MDIO Pin: %u", this->mdio_pin_); |   ESP_LOGCONFIG(TAG, "  MDIO Pin: %u", this->mdio_pin_); | ||||||
|   ESP_LOGCONFIG(TAG, "  Type: %s", eth_type); |  | ||||||
|   ESP_LOGCONFIG(TAG, "  PHY addr: %u", this->phy_addr_); |   ESP_LOGCONFIG(TAG, "  PHY addr: %u", this->phy_addr_); | ||||||
|  | #endif | ||||||
|  |   ESP_LOGCONFIG(TAG, "  Type: %s", eth_type); | ||||||
| } | } | ||||||
|  |  | ||||||
| float EthernetComponent::get_setup_priority() const { return setup_priority::WIFI; } | float EthernetComponent::get_setup_priority() const { return setup_priority::WIFI; } | ||||||
|  |  | ||||||
| bool EthernetComponent::can_proceed() { return this->is_connected(); } | bool EthernetComponent::can_proceed() { return this->is_connected(); } | ||||||
|  |  | ||||||
| network::IPAddress EthernetComponent::get_ip_address() { | network::IPAddresses EthernetComponent::get_ip_addresses() { | ||||||
|  |   network::IPAddresses addresses; | ||||||
|   esp_netif_ip_info_t ip; |   esp_netif_ip_info_t ip; | ||||||
|   esp_netif_get_ip_info(this->eth_netif_, &ip); |   esp_err_t err = esp_netif_get_ip_info(this->eth_netif_, &ip); | ||||||
|   return network::IPAddress(&ip.ip); |   if (err != ESP_OK) { | ||||||
|  |     ESP_LOGV(TAG, "esp_netif_get_ip_info failed: %s", esp_err_to_name(err)); | ||||||
|  |     // TODO: do something smarter | ||||||
|  |     // return false; | ||||||
|  |   } else { | ||||||
|  |     addresses[0] = network::IPAddress(&ip.ip); | ||||||
|  |   } | ||||||
|  | #if USE_NETWORK_IPV6 | ||||||
|  |   struct esp_ip6_addr if_ip6s[CONFIG_LWIP_IPV6_NUM_ADDRESSES]; | ||||||
|  |   uint8_t count = 0; | ||||||
|  |   count = esp_netif_get_all_ip6(this->eth_netif_, if_ip6s); | ||||||
|  |   assert(count <= CONFIG_LWIP_IPV6_NUM_ADDRESSES); | ||||||
|  |   for (int i = 0; i < count; i++) { | ||||||
|  |     addresses[i + 1] = network::IPAddress(&if_ip6s[i]); | ||||||
|  |   } | ||||||
|  | #endif /* USE_NETWORK_IPV6 */ | ||||||
|  |  | ||||||
|  |   return addresses; | ||||||
| } | } | ||||||
|  |  | ||||||
| void EthernetComponent::eth_event_handler(void *arg, esp_event_base_t event_base, int32_t event, void *event_data) { | void EthernetComponent::eth_event_handler(void *arg, esp_event_base_t event_base, int32_t event, void *event_data) { | ||||||
| @@ -269,20 +369,33 @@ void EthernetComponent::eth_event_handler(void *arg, esp_event_base_t event_base | |||||||
|  |  | ||||||
| void EthernetComponent::got_ip_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, | void EthernetComponent::got_ip_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, | ||||||
|                                              void *event_data) { |                                              void *event_data) { | ||||||
|  |   ip_event_got_ip_t *event = (ip_event_got_ip_t *) event_data; | ||||||
|  |   const esp_netif_ip_info_t *ip_info = &event->ip_info; | ||||||
|  |   ESP_LOGV(TAG, "[Ethernet event] ETH Got IP " IPSTR, IP2STR(&ip_info->ip)); | ||||||
|  |   global_eth_component->got_ipv4_address_ = true; | ||||||
|  | #if USE_NETWORK_IPV6 | ||||||
|  |   global_eth_component->connected_ = global_eth_component->ipv6_count_ >= USE_NETWORK_MIN_IPV6_ADDR_COUNT; | ||||||
|  | #else | ||||||
|   global_eth_component->connected_ = true; |   global_eth_component->connected_ = true; | ||||||
|   ESP_LOGV(TAG, "[Ethernet event] ETH Got IP (num=%" PRId32 ")", event_id); | #endif /* USE_NETWORK_IPV6 */ | ||||||
| } | } | ||||||
|  |  | ||||||
| #if ENABLE_IPV6 | #if USE_NETWORK_IPV6 | ||||||
| void EthernetComponent::got_ip6_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, | void EthernetComponent::got_ip6_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, | ||||||
|                                               void *event_data) { |                                               void *event_data) { | ||||||
|   ESP_LOGV(TAG, "[Ethernet event] ETH Got IP6 (num=%" PRId32 ")", event_id); |   ip_event_got_ip6_t *event = (ip_event_got_ip6_t *) event_data; | ||||||
|   global_eth_component->got_ipv6_ = true; |   ESP_LOGV(TAG, "[Ethernet event] ETH Got IPv6: " IPV6STR, IPV62STR(event->ip6_info.ip)); | ||||||
|   global_eth_component->ipv6_count_ += 1; |   global_eth_component->ipv6_count_ += 1; | ||||||
|  |   global_eth_component->connected_ = | ||||||
|  |       global_eth_component->got_ipv4_address_ && (global_eth_component->ipv6_count_ >= USE_NETWORK_MIN_IPV6_ADDR_COUNT); | ||||||
| } | } | ||||||
| #endif /* ENABLE_IPV6 */ | #endif /* USE_NETWORK_IPV6 */ | ||||||
|  |  | ||||||
| void EthernetComponent::start_connect_() { | void EthernetComponent::start_connect_() { | ||||||
|  |   global_eth_component->got_ipv4_address_ = false; | ||||||
|  | #if USE_NETWORK_IPV6 | ||||||
|  |   global_eth_component->ipv6_count_ = 0; | ||||||
|  | #endif /* USE_NETWORK_IPV6 */ | ||||||
|   this->connect_begin_ = millis(); |   this->connect_begin_ = millis(); | ||||||
|   this->status_set_warning(); |   this->status_set_warning(); | ||||||
|  |  | ||||||
| @@ -334,12 +447,12 @@ void EthernetComponent::start_connect_() { | |||||||
|     if (err != ESP_ERR_ESP_NETIF_DHCP_ALREADY_STARTED) { |     if (err != ESP_ERR_ESP_NETIF_DHCP_ALREADY_STARTED) { | ||||||
|       ESPHL_ERROR_CHECK(err, "DHCPC start error"); |       ESPHL_ERROR_CHECK(err, "DHCPC start error"); | ||||||
|     } |     } | ||||||
| #if ENABLE_IPV6 | #if USE_NETWORK_IPV6 | ||||||
|     err = esp_netif_create_ip6_linklocal(this->eth_netif_); |     err = esp_netif_create_ip6_linklocal(this->eth_netif_); | ||||||
|     if (err != ESP_OK) { |     if (err != ESP_OK) { | ||||||
|       ESPHL_ERROR_CHECK(err, "IPv6 local failed"); |       ESPHL_ERROR_CHECK(err, "Enable IPv6 link local failed"); | ||||||
|     } |     } | ||||||
| #endif /* ENABLE_IPV6 */ | #endif /* USE_NETWORK_IPV6 */ | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   this->connect_begin_ = millis(); |   this->connect_begin_ = millis(); | ||||||
| @@ -362,18 +475,15 @@ void EthernetComponent::dump_connect_params_() { | |||||||
|   ESP_LOGCONFIG(TAG, "  DNS1: %s", network::IPAddress(dns_ip1).str().c_str()); |   ESP_LOGCONFIG(TAG, "  DNS1: %s", network::IPAddress(dns_ip1).str().c_str()); | ||||||
|   ESP_LOGCONFIG(TAG, "  DNS2: %s", network::IPAddress(dns_ip2).str().c_str()); |   ESP_LOGCONFIG(TAG, "  DNS2: %s", network::IPAddress(dns_ip2).str().c_str()); | ||||||
|  |  | ||||||
| #if ENABLE_IPV6 | #if USE_NETWORK_IPV6 | ||||||
|   if (this->ipv6_count_ > 0) { |   struct esp_ip6_addr if_ip6s[CONFIG_LWIP_IPV6_NUM_ADDRESSES]; | ||||||
|     esp_ip6_addr_t ip6_addr; |   uint8_t count = 0; | ||||||
|     esp_netif_get_ip6_linklocal(this->eth_netif_, &ip6_addr); |   count = esp_netif_get_all_ip6(this->eth_netif_, if_ip6s); | ||||||
|     ESP_LOGCONFIG(TAG, " IPv6: " IPV6STR, IPV62STR(ip6_addr)); |   assert(count <= CONFIG_LWIP_IPV6_NUM_ADDRESSES); | ||||||
|  |   for (int i = 0; i < count; i++) { | ||||||
|     if (esp_netif_get_ip6_global(this->eth_netif_, &ip6_addr) == 0 && |     ESP_LOGCONFIG(TAG, "  IPv6: " IPV6STR, IPV62STR(if_ip6s[i])); | ||||||
|         esp_netif_ip6_get_addr_type(&ip6_addr) == ESP_IP6_ADDR_IS_GLOBAL) { |  | ||||||
|       ESP_LOGCONFIG(TAG, "IPv6 Addr (Global): " IPV6STR, IPV62STR(ip6_addr)); |  | ||||||
|     } |  | ||||||
|   } |   } | ||||||
| #endif /* ENABLE_IPV6 */ | #endif /* USE_NETWORK_IPV6 */ | ||||||
|  |  | ||||||
|   esp_err_t err; |   esp_err_t err; | ||||||
|  |  | ||||||
| @@ -393,15 +503,25 @@ void EthernetComponent::dump_connect_params_() { | |||||||
|   ESP_LOGCONFIG(TAG, "  Link Speed: %u", speed == ETH_SPEED_100M ? 100 : 10); |   ESP_LOGCONFIG(TAG, "  Link Speed: %u", speed == ETH_SPEED_100M ? 100 : 10); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | #ifdef USE_ETHERNET_SPI | ||||||
|  | void EthernetComponent::set_clk_pin(uint8_t clk_pin) { this->clk_pin_ = clk_pin; } | ||||||
|  | void EthernetComponent::set_miso_pin(uint8_t miso_pin) { this->miso_pin_ = miso_pin; } | ||||||
|  | void EthernetComponent::set_mosi_pin(uint8_t mosi_pin) { this->mosi_pin_ = mosi_pin; } | ||||||
|  | void EthernetComponent::set_cs_pin(uint8_t cs_pin) { this->cs_pin_ = cs_pin; } | ||||||
|  | void EthernetComponent::set_interrupt_pin(uint8_t interrupt_pin) { this->interrupt_pin_ = interrupt_pin; } | ||||||
|  | void EthernetComponent::set_reset_pin(uint8_t reset_pin) { this->reset_pin_ = reset_pin; } | ||||||
|  | void EthernetComponent::set_clock_speed(int clock_speed) { this->clock_speed_ = clock_speed; } | ||||||
|  | #else | ||||||
| void EthernetComponent::set_phy_addr(uint8_t phy_addr) { this->phy_addr_ = phy_addr; } | void EthernetComponent::set_phy_addr(uint8_t phy_addr) { this->phy_addr_ = phy_addr; } | ||||||
| void EthernetComponent::set_power_pin(int power_pin) { this->power_pin_ = power_pin; } | void EthernetComponent::set_power_pin(int power_pin) { this->power_pin_ = power_pin; } | ||||||
| void EthernetComponent::set_mdc_pin(uint8_t mdc_pin) { this->mdc_pin_ = mdc_pin; } | void EthernetComponent::set_mdc_pin(uint8_t mdc_pin) { this->mdc_pin_ = mdc_pin; } | ||||||
| void EthernetComponent::set_mdio_pin(uint8_t mdio_pin) { this->mdio_pin_ = mdio_pin; } | void EthernetComponent::set_mdio_pin(uint8_t mdio_pin) { this->mdio_pin_ = mdio_pin; } | ||||||
| void EthernetComponent::set_type(EthernetType type) { this->type_ = type; } |  | ||||||
| void EthernetComponent::set_clk_mode(emac_rmii_clock_mode_t clk_mode, emac_rmii_clock_gpio_t clk_gpio) { | void EthernetComponent::set_clk_mode(emac_rmii_clock_mode_t clk_mode, emac_rmii_clock_gpio_t clk_gpio) { | ||||||
|   this->clk_mode_ = clk_mode; |   this->clk_mode_ = clk_mode; | ||||||
|   this->clk_gpio_ = clk_gpio; |   this->clk_gpio_ = clk_gpio; | ||||||
| } | } | ||||||
|  | #endif | ||||||
|  | void EthernetComponent::set_type(EthernetType type) { this->type_ = type; } | ||||||
| void EthernetComponent::set_manual_ip(const ManualIP &manual_ip) { this->manual_ip_ = manual_ip; } | void EthernetComponent::set_manual_ip(const ManualIP &manual_ip) { this->manual_ip_ = manual_ip; } | ||||||
|  |  | ||||||
| std::string EthernetComponent::get_use_address() const { | std::string EthernetComponent::get_use_address() const { | ||||||
| @@ -428,6 +548,7 @@ bool EthernetComponent::powerdown() { | |||||||
|   return true; |   return true; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | #ifndef USE_ETHERNET_SPI | ||||||
| void EthernetComponent::ksz8081_set_clock_reference_(esp_eth_mac_t *mac) { | void EthernetComponent::ksz8081_set_clock_reference_(esp_eth_mac_t *mac) { | ||||||
| #define KSZ80XX_PC2R_REG_ADDR (0x1F) | #define KSZ80XX_PC2R_REG_ADDR (0x1F) | ||||||
|  |  | ||||||
| @@ -458,6 +579,7 @@ void EthernetComponent::ksz8081_set_clock_reference_(esp_eth_mac_t *mac) { | |||||||
|  |  | ||||||
| #undef KSZ80XX_PC2R_REG_ADDR | #undef KSZ80XX_PC2R_REG_ADDR | ||||||
| } | } | ||||||
|  | #endif | ||||||
|  |  | ||||||
| }  // namespace ethernet | }  // namespace ethernet | ||||||
| }  // namespace esphome | }  // namespace esphome | ||||||
|   | |||||||
| @@ -23,6 +23,7 @@ enum EthernetType { | |||||||
|   ETHERNET_TYPE_JL1101, |   ETHERNET_TYPE_JL1101, | ||||||
|   ETHERNET_TYPE_KSZ8081, |   ETHERNET_TYPE_KSZ8081, | ||||||
|   ETHERNET_TYPE_KSZ8081RNA, |   ETHERNET_TYPE_KSZ8081RNA, | ||||||
|  |   ETHERNET_TYPE_W5500, | ||||||
| }; | }; | ||||||
|  |  | ||||||
| struct ManualIP { | struct ManualIP { | ||||||
| @@ -50,15 +51,25 @@ class EthernetComponent : public Component { | |||||||
|   void on_shutdown() override { powerdown(); } |   void on_shutdown() override { powerdown(); } | ||||||
|   bool is_connected(); |   bool is_connected(); | ||||||
|  |  | ||||||
|  | #ifdef USE_ETHERNET_SPI | ||||||
|  |   void set_clk_pin(uint8_t clk_pin); | ||||||
|  |   void set_miso_pin(uint8_t miso_pin); | ||||||
|  |   void set_mosi_pin(uint8_t mosi_pin); | ||||||
|  |   void set_cs_pin(uint8_t cs_pin); | ||||||
|  |   void set_interrupt_pin(uint8_t interrupt_pin); | ||||||
|  |   void set_reset_pin(uint8_t reset_pin); | ||||||
|  |   void set_clock_speed(int clock_speed); | ||||||
|  | #else | ||||||
|   void set_phy_addr(uint8_t phy_addr); |   void set_phy_addr(uint8_t phy_addr); | ||||||
|   void set_power_pin(int power_pin); |   void set_power_pin(int power_pin); | ||||||
|   void set_mdc_pin(uint8_t mdc_pin); |   void set_mdc_pin(uint8_t mdc_pin); | ||||||
|   void set_mdio_pin(uint8_t mdio_pin); |   void set_mdio_pin(uint8_t mdio_pin); | ||||||
|   void set_type(EthernetType type); |  | ||||||
|   void set_clk_mode(emac_rmii_clock_mode_t clk_mode, emac_rmii_clock_gpio_t clk_gpio); |   void set_clk_mode(emac_rmii_clock_mode_t clk_mode, emac_rmii_clock_gpio_t clk_gpio); | ||||||
|  | #endif | ||||||
|  |   void set_type(EthernetType type); | ||||||
|   void set_manual_ip(const ManualIP &manual_ip); |   void set_manual_ip(const ManualIP &manual_ip); | ||||||
|  |  | ||||||
|   network::IPAddress get_ip_address(); |   network::IPAddresses get_ip_addresses(); | ||||||
|   std::string get_use_address() const; |   std::string get_use_address() const; | ||||||
|   void set_use_address(const std::string &use_address); |   void set_use_address(const std::string &use_address); | ||||||
|   bool powerdown(); |   bool powerdown(); | ||||||
| @@ -76,19 +87,30 @@ class EthernetComponent : public Component { | |||||||
|   void ksz8081_set_clock_reference_(esp_eth_mac_t *mac); |   void ksz8081_set_clock_reference_(esp_eth_mac_t *mac); | ||||||
|  |  | ||||||
|   std::string use_address_; |   std::string use_address_; | ||||||
|  | #ifdef USE_ETHERNET_SPI | ||||||
|  |   uint8_t clk_pin_; | ||||||
|  |   uint8_t miso_pin_; | ||||||
|  |   uint8_t mosi_pin_; | ||||||
|  |   uint8_t cs_pin_; | ||||||
|  |   uint8_t interrupt_pin_; | ||||||
|  |   int reset_pin_{-1}; | ||||||
|  |   int phy_addr_spi_{-1}; | ||||||
|  |   int clock_speed_; | ||||||
|  | #else | ||||||
|   uint8_t phy_addr_{0}; |   uint8_t phy_addr_{0}; | ||||||
|   int power_pin_{-1}; |   int power_pin_{-1}; | ||||||
|   uint8_t mdc_pin_{23}; |   uint8_t mdc_pin_{23}; | ||||||
|   uint8_t mdio_pin_{18}; |   uint8_t mdio_pin_{18}; | ||||||
|   EthernetType type_{ETHERNET_TYPE_UNKNOWN}; |  | ||||||
|   emac_rmii_clock_mode_t clk_mode_{EMAC_CLK_EXT_IN}; |   emac_rmii_clock_mode_t clk_mode_{EMAC_CLK_EXT_IN}; | ||||||
|   emac_rmii_clock_gpio_t clk_gpio_{EMAC_CLK_IN_GPIO}; |   emac_rmii_clock_gpio_t clk_gpio_{EMAC_CLK_IN_GPIO}; | ||||||
|  | #endif | ||||||
|  |   EthernetType type_{ETHERNET_TYPE_UNKNOWN}; | ||||||
|   optional<ManualIP> manual_ip_{}; |   optional<ManualIP> manual_ip_{}; | ||||||
|  |  | ||||||
|   bool started_{false}; |   bool started_{false}; | ||||||
|   bool connected_{false}; |   bool connected_{false}; | ||||||
|  |   bool got_ipv4_address_{false}; | ||||||
| #if LWIP_IPV6 | #if LWIP_IPV6 | ||||||
|   bool got_ipv6_{false}; |  | ||||||
|   uint8_t ipv6_count_{0}; |   uint8_t ipv6_count_{0}; | ||||||
| #endif /* LWIP_IPV6 */ | #endif /* LWIP_IPV6 */ | ||||||
|   EthernetComponentState state_{EthernetComponentState::STOPPED}; |   EthernetComponentState state_{EthernetComponentState::STOPPED}; | ||||||
|   | |||||||
| @@ -12,19 +12,30 @@ namespace ethernet_info { | |||||||
| class IPAddressEthernetInfo : public PollingComponent, public text_sensor::TextSensor { | class IPAddressEthernetInfo : public PollingComponent, public text_sensor::TextSensor { | ||||||
|  public: |  public: | ||||||
|   void update() override { |   void update() override { | ||||||
|     auto ip = ethernet::global_eth_component->get_ip_address(); |     auto ips = ethernet::global_eth_component->get_ip_addresses(); | ||||||
|     if (ip != this->last_ip_) { |     if (ips != this->last_ips_) { | ||||||
|       this->last_ip_ = ip; |       this->last_ips_ = ips; | ||||||
|       this->publish_state(network::IPAddress(ip).str()); |       this->publish_state(ips[0].str()); | ||||||
|  |       uint8_t sensor = 0; | ||||||
|  |       for (auto &ip : ips) { | ||||||
|  |         if (ip.is_set()) { | ||||||
|  |           if (this->ip_sensors_[sensor] != nullptr) { | ||||||
|  |             this->ip_sensors_[sensor]->publish_state(ip.str()); | ||||||
|  |           } | ||||||
|  |           sensor++; | ||||||
|  |         } | ||||||
|  |       } | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   float get_setup_priority() const override { return setup_priority::ETHERNET; } |   float get_setup_priority() const override { return setup_priority::ETHERNET; } | ||||||
|   std::string unique_id() override { return get_mac_address() + "-ethernetinfo"; } |   std::string unique_id() override { return get_mac_address() + "-ethernetinfo"; } | ||||||
|   void dump_config() override; |   void dump_config() override; | ||||||
|  |   void add_ip_sensors(uint8_t index, text_sensor::TextSensor *s) { this->ip_sensors_[index] = s; } | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   network::IPAddress last_ip_; |   network::IPAddresses last_ips_; | ||||||
|  |   std::array<text_sensor::TextSensor *, 5> ip_sensors_; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| }  // namespace ethernet_info | }  // namespace ethernet_info | ||||||
|   | |||||||
| @@ -18,17 +18,25 @@ CONFIG_SCHEMA = cv.Schema( | |||||||
|     { |     { | ||||||
|         cv.Optional(CONF_IP_ADDRESS): text_sensor.text_sensor_schema( |         cv.Optional(CONF_IP_ADDRESS): text_sensor.text_sensor_schema( | ||||||
|             IPAddressEsthernetInfo, entity_category=ENTITY_CATEGORY_DIAGNOSTIC |             IPAddressEsthernetInfo, entity_category=ENTITY_CATEGORY_DIAGNOSTIC | ||||||
|         ).extend(cv.polling_component_schema("1s")) |         ) | ||||||
|  |         .extend(cv.polling_component_schema("1s")) | ||||||
|  |         .extend( | ||||||
|  |             { | ||||||
|  |                 cv.Optional(f"address_{x}"): text_sensor.text_sensor_schema( | ||||||
|  |                     entity_category=ENTITY_CATEGORY_DIAGNOSTIC, | ||||||
|  |                 ) | ||||||
|  |                 for x in range(5) | ||||||
|  |             } | ||||||
|  |         ) | ||||||
|     } |     } | ||||||
| ) | ) | ||||||
|  |  | ||||||
|  |  | ||||||
| async def setup_conf(config, key): |  | ||||||
|     if key in config: |  | ||||||
|         conf = config[key] |  | ||||||
|         var = await text_sensor.new_text_sensor(conf) |  | ||||||
|         await cg.register_component(var, conf) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| async def to_code(config): | async def to_code(config): | ||||||
|     await setup_conf(config, CONF_IP_ADDRESS) |     if conf := config.get(CONF_IP_ADDRESS): | ||||||
|  |         ip_info = await text_sensor.new_text_sensor(config[CONF_IP_ADDRESS]) | ||||||
|  |         await cg.register_component(ip_info, config[CONF_IP_ADDRESS]) | ||||||
|  |         for x in range(5): | ||||||
|  |             if sensor_conf := conf.get(f"address_{x}"): | ||||||
|  |                 sens = await text_sensor.new_text_sensor(sensor_conf) | ||||||
|  |                 cg.add(ip_info.add_ip_sensors(x, sens)) | ||||||
|   | |||||||
| @@ -336,6 +336,8 @@ void FingerprintGrowComponent::aura_led_control(uint8_t state, uint8_t speed, ui | |||||||
| } | } | ||||||
|  |  | ||||||
| uint8_t FingerprintGrowComponent::send_command_() { | uint8_t FingerprintGrowComponent::send_command_() { | ||||||
|  |   while (this->available()) | ||||||
|  |     this->read(); | ||||||
|   this->write((uint8_t) (START_CODE >> 8)); |   this->write((uint8_t) (START_CODE >> 8)); | ||||||
|   this->write((uint8_t) (START_CODE & 0xFF)); |   this->write((uint8_t) (START_CODE & 0xFF)); | ||||||
|   this->write(this->address_[0]); |   this->write(this->address_[0]); | ||||||
|   | |||||||
| @@ -66,8 +66,14 @@ class FT5x06Touchscreen : public touchscreen::Touchscreen, public i2c::I2CDevice | |||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
|     // reading the chip registers to get max x/y does not seem to work. |     // reading the chip registers to get max x/y does not seem to work. | ||||||
|     this->x_raw_max_ = this->display_->get_width(); |     if (this->display_ != nullptr) { | ||||||
|     this->y_raw_max_ = this->display_->get_height(); |       if (this->x_raw_max_ == this->x_raw_min_) { | ||||||
|  |         this->x_raw_max_ = this->display_->get_native_width(); | ||||||
|  |       } | ||||||
|  |       if (this->y_raw_max_ == this->y_raw_min_) { | ||||||
|  |         this->x_raw_max_ = this->display_->get_native_height(); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|     esph_log_config(TAG, "FT5x06 Touchscreen setup complete"); |     esph_log_config(TAG, "FT5x06 Touchscreen setup complete"); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -12,21 +12,23 @@ | |||||||
| // Reference: https://focuslcds.com/content/FT6236.pdf | // Reference: https://focuslcds.com/content/FT6236.pdf | ||||||
| namespace esphome { | namespace esphome { | ||||||
| namespace ft63x6 { | namespace ft63x6 { | ||||||
|  | static const uint8_t FT6X36_ADDR_DEVICE_MODE = 0x00; | ||||||
|  |  | ||||||
|  | static const uint8_t FT63X6_ADDR_TD_STATUS = 0x02; | ||||||
| static const uint8_t FT63X6_ADDR_TOUCH1_STATE = 0x03; | static const uint8_t FT63X6_ADDR_TOUCH1_STATE = 0x03; | ||||||
| static const uint8_t FT63X6_ADDR_TOUCH1_X = 0x03; | static const uint8_t FT63X6_ADDR_TOUCH1_X = 0x03; | ||||||
| static const uint8_t FT63X6_ADDR_TOUCH1_ID = 0x05; | static const uint8_t FT63X6_ADDR_TOUCH1_ID = 0x05; | ||||||
| static const uint8_t FT63X6_ADDR_TOUCH1_Y = 0x05; | static const uint8_t FT63X6_ADDR_TOUCH1_Y = 0x05; | ||||||
|  | static const uint8_t FT63X6_ADDR_TOUCH1_WEIGHT = 0x07; | ||||||
|  | static const uint8_t FT63X6_ADDR_TOUCH1_MISC = 0x08; | ||||||
|  | static const uint8_t FT6X36_ADDR_THRESHHOLD = 0x80; | ||||||
|  | static const uint8_t FT6X36_ADDR_TOUCHRATE_ACTIVE = 0x88; | ||||||
|  | static const uint8_t FT63X6_ADDR_CHIP_ID = 0xA3; | ||||||
|  |  | ||||||
| static const uint8_t FT63X6_ADDR_TOUCH2_STATE = 0x09; | static const char *const TAG = "FT63X6"; | ||||||
| static const uint8_t FT63X6_ADDR_TOUCH2_X = 0x09; |  | ||||||
| static const uint8_t FT63X6_ADDR_TOUCH2_ID = 0x0B; |  | ||||||
| static const uint8_t FT63X6_ADDR_TOUCH2_Y = 0x0B; |  | ||||||
|  |  | ||||||
| static const char *const TAG = "FT63X6Touchscreen"; |  | ||||||
|  |  | ||||||
| void FT63X6Touchscreen::setup() { | void FT63X6Touchscreen::setup() { | ||||||
|   ESP_LOGCONFIG(TAG, "Setting up FT63X6Touchscreen Touchscreen..."); |   ESP_LOGCONFIG(TAG, "Setting up FT63X6 Touchscreen..."); | ||||||
|   if (this->interrupt_pin_ != nullptr) { |   if (this->interrupt_pin_ != nullptr) { | ||||||
|     this->interrupt_pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP); |     this->interrupt_pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP); | ||||||
|     this->interrupt_pin_->setup(); |     this->interrupt_pin_->setup(); | ||||||
| @@ -35,10 +37,9 @@ void FT63X6Touchscreen::setup() { | |||||||
|  |  | ||||||
|   if (this->reset_pin_ != nullptr) { |   if (this->reset_pin_ != nullptr) { | ||||||
|     this->reset_pin_->setup(); |     this->reset_pin_->setup(); | ||||||
|  |     this->hard_reset_(); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   this->hard_reset_(); |  | ||||||
|  |  | ||||||
|   // Get touch resolution |   // Get touch resolution | ||||||
|   if (this->x_raw_max_ == this->x_raw_min_) { |   if (this->x_raw_max_ == this->x_raw_min_) { | ||||||
|     this->x_raw_max_ = 320; |     this->x_raw_max_ = 320; | ||||||
| @@ -46,6 +47,15 @@ void FT63X6Touchscreen::setup() { | |||||||
|   if (this->y_raw_max_ == this->y_raw_min_) { |   if (this->y_raw_max_ == this->y_raw_min_) { | ||||||
|     this->y_raw_max_ = 480; |     this->y_raw_max_ = 480; | ||||||
|   } |   } | ||||||
|  |   uint8_t chip_id = this->read_byte_(FT63X6_ADDR_CHIP_ID); | ||||||
|  |   if (chip_id != 0) { | ||||||
|  |     ESP_LOGI(TAG, "FT6336U touch driver started chipid: %d", chip_id); | ||||||
|  |   } else { | ||||||
|  |     ESP_LOGE(TAG, "FT6336U touch driver failed to start"); | ||||||
|  |   } | ||||||
|  |   this->write_byte(FT6X36_ADDR_DEVICE_MODE, 0x00); | ||||||
|  |   this->write_byte(FT6X36_ADDR_THRESHHOLD, this->threshold_); | ||||||
|  |   this->write_byte(FT6X36_ADDR_TOUCHRATE_ACTIVE, 0x0E); | ||||||
| } | } | ||||||
|  |  | ||||||
| void FT63X6Touchscreen::hard_reset_() { | void FT63X6Touchscreen::hard_reset_() { | ||||||
| @@ -65,28 +75,61 @@ void FT63X6Touchscreen::dump_config() { | |||||||
| } | } | ||||||
|  |  | ||||||
| void FT63X6Touchscreen::update_touches() { | void FT63X6Touchscreen::update_touches() { | ||||||
|   uint8_t data[15]; |  | ||||||
|   uint16_t touch_id, x, y; |   uint16_t touch_id, x, y; | ||||||
|  |  | ||||||
|   if (!this->read_bytes(0x00, (uint8_t *) data, 15)) { |   uint8_t touches = this->read_touch_number_(); | ||||||
|     ESP_LOGE(TAG, "Failed to read touch data"); |   if ((touches == 0x00) || (touches == 0xff)) { | ||||||
|     this->skip_update_ = true; |     // ESP_LOGD(TAG, "No touches detected"); | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   if (((data[FT63X6_ADDR_TOUCH1_STATE] >> 6) & 0x01) == 0) { |   ESP_LOGV(TAG, "Touches found: %d", touches); | ||||||
|     touch_id = data[FT63X6_ADDR_TOUCH1_ID] >> 4;  // id1 = 0 or 1 |  | ||||||
|     x = encode_uint16(data[FT63X6_ADDR_TOUCH1_X] & 0x0F, data[FT63X6_ADDR_TOUCH1_X + 1]); |   for (auto point = 0; point < touches; point++) { | ||||||
|     y = encode_uint16(data[FT63X6_ADDR_TOUCH1_Y] & 0x0F, data[FT63X6_ADDR_TOUCH1_Y + 1]); |     if (((this->read_touch_event_(point)) & 0x01) == 0) {  // checking event flag bit 6 if it is null | ||||||
|     this->add_raw_touch_position_(touch_id, x, y); |       touch_id = this->read_touch_id_(point);              // id1 = 0 or 1 | ||||||
|   } |       x = this->read_touch_x_(point); | ||||||
|   if (((data[FT63X6_ADDR_TOUCH2_STATE] >> 6) & 0x01) == 0) { |       y = this->read_touch_y_(point); | ||||||
|     touch_id = data[FT63X6_ADDR_TOUCH2_ID] >> 4;  // id1 = 0 or 1 |       if ((x == 0) && (y == 0)) { | ||||||
|     x = encode_uint16(data[FT63X6_ADDR_TOUCH2_X] & 0x0F, data[FT63X6_ADDR_TOUCH2_X + 1]); |         ESP_LOGW(TAG, "Reporting a (0,0) touch on %d", touch_id); | ||||||
|     y = encode_uint16(data[FT63X6_ADDR_TOUCH2_Y] & 0x0F, data[FT63X6_ADDR_TOUCH2_Y + 1]); |       } | ||||||
|     this->add_raw_touch_position_(touch_id, x, y); |       this->add_raw_touch_position_(touch_id, x, y, this->read_touch_weight_(point)); | ||||||
|  |     } | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | uint8_t FT63X6Touchscreen::read_touch_number_() { return this->read_byte_(FT63X6_ADDR_TD_STATUS) & 0x0F; } | ||||||
|  | // Touch 1 functions | ||||||
|  | uint16_t FT63X6Touchscreen::read_touch_x_(uint8_t touch) { | ||||||
|  |   uint8_t read_buf[2]; | ||||||
|  |   read_buf[0] = this->read_byte_(FT63X6_ADDR_TOUCH1_X + (touch * 6)); | ||||||
|  |   read_buf[1] = this->read_byte_(FT63X6_ADDR_TOUCH1_X + 1 + (touch * 6)); | ||||||
|  |   return ((read_buf[0] & 0x0f) << 8) | read_buf[1]; | ||||||
|  | } | ||||||
|  | uint16_t FT63X6Touchscreen::read_touch_y_(uint8_t touch) { | ||||||
|  |   uint8_t read_buf[2]; | ||||||
|  |   read_buf[0] = this->read_byte_(FT63X6_ADDR_TOUCH1_Y + (touch * 6)); | ||||||
|  |   read_buf[1] = this->read_byte_(FT63X6_ADDR_TOUCH1_Y + 1 + (touch * 6)); | ||||||
|  |   return ((read_buf[0] & 0x0f) << 8) | read_buf[1]; | ||||||
|  | } | ||||||
|  | uint8_t FT63X6Touchscreen::read_touch_event_(uint8_t touch) { | ||||||
|  |   return this->read_byte_(FT63X6_ADDR_TOUCH1_X + (touch * 6)) >> 6; | ||||||
|  | } | ||||||
|  | uint8_t FT63X6Touchscreen::read_touch_id_(uint8_t touch) { | ||||||
|  |   return this->read_byte_(FT63X6_ADDR_TOUCH1_ID + (touch * 6)) >> 4; | ||||||
|  | } | ||||||
|  | uint8_t FT63X6Touchscreen::read_touch_weight_(uint8_t touch) { | ||||||
|  |   return this->read_byte_(FT63X6_ADDR_TOUCH1_WEIGHT + (touch * 6)); | ||||||
|  | } | ||||||
|  | uint8_t FT63X6Touchscreen::read_touch_misc_(uint8_t touch) { | ||||||
|  |   return this->read_byte_(FT63X6_ADDR_TOUCH1_MISC + (touch * 6)) >> 4; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | uint8_t FT63X6Touchscreen::read_byte_(uint8_t addr) { | ||||||
|  |   uint8_t byte = 0; | ||||||
|  |   this->read_byte(addr, &byte); | ||||||
|  |   return byte; | ||||||
|  | } | ||||||
|  |  | ||||||
| }  // namespace ft63x6 | }  // namespace ft63x6 | ||||||
| }  // namespace esphome | }  // namespace esphome | ||||||
|   | |||||||
| @@ -16,6 +16,8 @@ namespace ft63x6 { | |||||||
|  |  | ||||||
| using namespace touchscreen; | using namespace touchscreen; | ||||||
|  |  | ||||||
|  | static const uint8_t FT6X36_DEFAULT_THRESHOLD = 22; | ||||||
|  |  | ||||||
| class FT63X6Touchscreen : public Touchscreen, public i2c::I2CDevice { | class FT63X6Touchscreen : public Touchscreen, public i2c::I2CDevice { | ||||||
|  public: |  public: | ||||||
|   void setup() override; |   void setup() override; | ||||||
| @@ -23,18 +25,26 @@ class FT63X6Touchscreen : public Touchscreen, public i2c::I2CDevice { | |||||||
|  |  | ||||||
|   void set_interrupt_pin(InternalGPIOPin *pin) { this->interrupt_pin_ = pin; } |   void set_interrupt_pin(InternalGPIOPin *pin) { this->interrupt_pin_ = pin; } | ||||||
|   void set_reset_pin(GPIOPin *pin) { this->reset_pin_ = pin; } |   void set_reset_pin(GPIOPin *pin) { this->reset_pin_ = pin; } | ||||||
|  |   void set_threshold(uint8_t threshold) { this->threshold_ = threshold; } | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   void hard_reset_(); |   void hard_reset_(); | ||||||
|   uint8_t read_byte_(uint8_t addr); |  | ||||||
|   void update_touches() override; |   void update_touches() override; | ||||||
|  |  | ||||||
|   InternalGPIOPin *interrupt_pin_{nullptr}; |   InternalGPIOPin *interrupt_pin_{nullptr}; | ||||||
|   GPIOPin *reset_pin_{nullptr}; |   GPIOPin *reset_pin_{nullptr}; | ||||||
|  |   uint8_t threshold_{FT6X36_DEFAULT_THRESHOLD}; | ||||||
|  |  | ||||||
|   uint8_t read_touch_count_(); |   uint8_t read_touch_number_(); | ||||||
|   uint16_t read_touch_coordinate_(uint8_t coordinate); |  | ||||||
|   uint8_t read_touch_id_(uint8_t id_address); |   uint16_t read_touch_x_(uint8_t touch); | ||||||
|  |   uint16_t read_touch_y_(uint8_t touch); | ||||||
|  |   uint8_t read_touch_event_(uint8_t touch); | ||||||
|  |   uint8_t read_touch_id_(uint8_t touch); | ||||||
|  |   uint8_t read_touch_weight_(uint8_t touch); | ||||||
|  |   uint8_t read_touch_misc_(uint8_t touch); | ||||||
|  |  | ||||||
|  |   uint8_t read_byte_(uint8_t addr); | ||||||
| }; | }; | ||||||
|  |  | ||||||
| }  // namespace ft63x6 | }  // namespace ft63x6 | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ import esphome.config_validation as cv | |||||||
|  |  | ||||||
| from esphome import pins | from esphome import pins | ||||||
| from esphome.components import i2c, touchscreen | from esphome.components import i2c, touchscreen | ||||||
| from esphome.const import CONF_ID, CONF_INTERRUPT_PIN, CONF_RESET_PIN | from esphome.const import CONF_ID, CONF_INTERRUPT_PIN, CONF_RESET_PIN, CONF_THRESHOLD | ||||||
|  |  | ||||||
| CODEOWNERS = ["@gpambrozio"] | CODEOWNERS = ["@gpambrozio"] | ||||||
| DEPENDENCIES = ["i2c"] | DEPENDENCIES = ["i2c"] | ||||||
| @@ -26,6 +26,7 @@ CONFIG_SCHEMA = touchscreen.TOUCHSCREEN_SCHEMA.extend( | |||||||
|                 pins.internal_gpio_input_pin_schema |                 pins.internal_gpio_input_pin_schema | ||||||
|             ), |             ), | ||||||
|             cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema, |             cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema, | ||||||
|  |             cv.Optional(CONF_THRESHOLD): cv.uint8_t, | ||||||
|         } |         } | ||||||
|     ).extend(i2c.i2c_device_schema(0x38)) |     ).extend(i2c.i2c_device_schema(0x38)) | ||||||
| ) | ) | ||||||
|   | |||||||
| @@ -48,9 +48,13 @@ void GT911Touchscreen::setup() { | |||||||
|     if (err == i2c::ERROR_OK) { |     if (err == i2c::ERROR_OK) { | ||||||
|       err = this->read(data, sizeof(data)); |       err = this->read(data, sizeof(data)); | ||||||
|       if (err == i2c::ERROR_OK) { |       if (err == i2c::ERROR_OK) { | ||||||
|         this->x_raw_max_ = encode_uint16(data[1], data[0]); |         if (this->x_raw_max_ == this->x_raw_min_) { | ||||||
|         this->y_raw_max_ = encode_uint16(data[3], data[2]); |           this->x_raw_max_ = encode_uint16(data[1], data[0]); | ||||||
|         esph_log_d(TAG, "Read max_x/max_y %d/%d", this->x_raw_max_, this->y_raw_max_); |         } | ||||||
|  |         if (this->y_raw_max_ == this->y_raw_min_) { | ||||||
|  |           this->y_raw_max_ = encode_uint16(data[3], data[2]); | ||||||
|  |         } | ||||||
|  |         esph_log_d(TAG, "calibration max_x/max_y %d/%d", this->x_raw_max_, this->y_raw_max_); | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|   | |||||||
							
								
								
									
										70
									
								
								esphome/components/haier/binary_sensor/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								esphome/components/haier/binary_sensor/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,70 @@ | |||||||
|  | import esphome.codegen as cg | ||||||
|  | import esphome.config_validation as cv | ||||||
|  | from esphome.components import binary_sensor | ||||||
|  | from esphome.const import ( | ||||||
|  |     ENTITY_CATEGORY_DIAGNOSTIC, | ||||||
|  |     ICON_FAN, | ||||||
|  |     ICON_RADIATOR, | ||||||
|  | ) | ||||||
|  | from ..climate import ( | ||||||
|  |     CONF_HAIER_ID, | ||||||
|  |     HonClimate, | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | BinarySensorTypeEnum = HonClimate.enum("SubBinarySensorType", True) | ||||||
|  |  | ||||||
|  | # Haier sensors | ||||||
|  | CONF_OUTDOOR_FAN_STATUS = "outdoor_fan_status" | ||||||
|  | CONF_DEFROST_STATUS = "defrost_status" | ||||||
|  | CONF_COMPRESSOR_STATUS = "compressor_status" | ||||||
|  | CONF_INDOOR_FAN_STATUS = "indoor_fan_status" | ||||||
|  | CONF_FOUR_WAY_VALVE_STATUS = "four_way_valve_status" | ||||||
|  | CONF_INDOOR_ELECTRIC_HEATING_STATUS = "indoor_electric_heating_status" | ||||||
|  |  | ||||||
|  | # Additional icons | ||||||
|  | ICON_SNOWFLAKE_THERMOMETER = "mdi:snowflake-thermometer" | ||||||
|  | ICON_HVAC = "mdi:hvac" | ||||||
|  | ICON_VALVE = "mdi:valve" | ||||||
|  |  | ||||||
|  | SENSOR_TYPES = { | ||||||
|  |     CONF_OUTDOOR_FAN_STATUS: binary_sensor.binary_sensor_schema( | ||||||
|  |         icon=ICON_FAN, | ||||||
|  |         entity_category=ENTITY_CATEGORY_DIAGNOSTIC, | ||||||
|  |     ), | ||||||
|  |     CONF_DEFROST_STATUS: binary_sensor.binary_sensor_schema( | ||||||
|  |         icon=ICON_SNOWFLAKE_THERMOMETER, | ||||||
|  |         entity_category=ENTITY_CATEGORY_DIAGNOSTIC, | ||||||
|  |     ), | ||||||
|  |     CONF_COMPRESSOR_STATUS: binary_sensor.binary_sensor_schema( | ||||||
|  |         icon=ICON_HVAC, | ||||||
|  |         entity_category=ENTITY_CATEGORY_DIAGNOSTIC, | ||||||
|  |     ), | ||||||
|  |     CONF_INDOOR_FAN_STATUS: binary_sensor.binary_sensor_schema( | ||||||
|  |         icon=ICON_FAN, | ||||||
|  |         entity_category=ENTITY_CATEGORY_DIAGNOSTIC, | ||||||
|  |     ), | ||||||
|  |     CONF_FOUR_WAY_VALVE_STATUS: binary_sensor.binary_sensor_schema( | ||||||
|  |         icon=ICON_VALVE, | ||||||
|  |         entity_category=ENTITY_CATEGORY_DIAGNOSTIC, | ||||||
|  |     ), | ||||||
|  |     CONF_INDOOR_ELECTRIC_HEATING_STATUS: binary_sensor.binary_sensor_schema( | ||||||
|  |         icon=ICON_RADIATOR, | ||||||
|  |         entity_category=ENTITY_CATEGORY_DIAGNOSTIC, | ||||||
|  |     ), | ||||||
|  | } | ||||||
|  |  | ||||||
|  | CONFIG_SCHEMA = cv.Schema( | ||||||
|  |     { | ||||||
|  |         cv.Required(CONF_HAIER_ID): cv.use_id(HonClimate), | ||||||
|  |     } | ||||||
|  | ).extend({cv.Optional(type): schema for type, schema in SENSOR_TYPES.items()}) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | async def to_code(config): | ||||||
|  |     paren = await cg.get_variable(config[CONF_HAIER_ID]) | ||||||
|  |  | ||||||
|  |     for type, _ in SENSOR_TYPES.items(): | ||||||
|  |         if conf := config.get(type): | ||||||
|  |             sens = await binary_sensor.new_binary_sensor(conf) | ||||||
|  |             binary_sensor_type = getattr(BinarySensorTypeEnum, type.upper()) | ||||||
|  |             cg.add(paren.set_sub_binary_sensor(binary_sensor_type, sens)) | ||||||
| @@ -2,7 +2,7 @@ | |||||||
| import esphome.codegen as cg | import esphome.codegen as cg | ||||||
| import esphome.config_validation as cv | import esphome.config_validation as cv | ||||||
| import esphome.final_validate as fv | import esphome.final_validate as fv | ||||||
| from esphome.components import uart, sensor, climate, logger | from esphome.components import uart, climate, logger | ||||||
| from esphome import automation | from esphome import automation | ||||||
| from esphome.const import ( | from esphome.const import ( | ||||||
|     CONF_BEEPER, |     CONF_BEEPER, | ||||||
| @@ -21,10 +21,6 @@ from esphome.const import ( | |||||||
|     CONF_TRIGGER_ID, |     CONF_TRIGGER_ID, | ||||||
|     CONF_VISUAL, |     CONF_VISUAL, | ||||||
|     CONF_WIFI, |     CONF_WIFI, | ||||||
|     DEVICE_CLASS_TEMPERATURE, |  | ||||||
|     ICON_THERMOMETER, |  | ||||||
|     STATE_CLASS_MEASUREMENT, |  | ||||||
|     UNIT_CELSIUS, |  | ||||||
| ) | ) | ||||||
| from esphome.components.climate import ( | from esphome.components.climate import ( | ||||||
|     ClimateMode, |     ClimateMode, | ||||||
| @@ -42,7 +38,6 @@ PROTOCOL_CURRENT_TEMPERATURE_STEP = 0.5 | |||||||
| PROTOCOL_CONTROL_PACKET_SIZE = 10 | PROTOCOL_CONTROL_PACKET_SIZE = 10 | ||||||
|  |  | ||||||
| CODEOWNERS = ["@paveldn"] | CODEOWNERS = ["@paveldn"] | ||||||
| AUTO_LOAD = ["sensor"] |  | ||||||
| DEPENDENCIES = ["climate", "uart"] | DEPENDENCIES = ["climate", "uart"] | ||||||
| CONF_ALTERNATIVE_SWING_CONTROL = "alternative_swing_control" | CONF_ALTERNATIVE_SWING_CONTROL = "alternative_swing_control" | ||||||
| CONF_ANSWER_TIMEOUT = "answer_timeout" | CONF_ANSWER_TIMEOUT = "answer_timeout" | ||||||
| @@ -58,7 +53,6 @@ CONF_WIFI_SIGNAL = "wifi_signal" | |||||||
|  |  | ||||||
| PROTOCOL_HON = "HON" | PROTOCOL_HON = "HON" | ||||||
| PROTOCOL_SMARTAIR2 = "SMARTAIR2" | PROTOCOL_SMARTAIR2 = "SMARTAIR2" | ||||||
| PROTOCOLS_SUPPORTED = [PROTOCOL_HON, PROTOCOL_SMARTAIR2] |  | ||||||
|  |  | ||||||
| haier_ns = cg.esphome_ns.namespace("haier") | haier_ns = cg.esphome_ns.namespace("haier") | ||||||
| HaierClimateBase = haier_ns.class_( | HaierClimateBase = haier_ns.class_( | ||||||
| @@ -67,6 +61,7 @@ HaierClimateBase = haier_ns.class_( | |||||||
| HonClimate = haier_ns.class_("HonClimate", HaierClimateBase) | HonClimate = haier_ns.class_("HonClimate", HaierClimateBase) | ||||||
| Smartair2Climate = haier_ns.class_("Smartair2Climate", HaierClimateBase) | Smartair2Climate = haier_ns.class_("Smartair2Climate", HaierClimateBase) | ||||||
|  |  | ||||||
|  | CONF_HAIER_ID = "haier_id" | ||||||
|  |  | ||||||
| AirflowVerticalDirection = haier_ns.enum("AirflowVerticalDirection", True) | AirflowVerticalDirection = haier_ns.enum("AirflowVerticalDirection", True) | ||||||
| AIRFLOW_VERTICAL_DIRECTION_OPTIONS = { | AIRFLOW_VERTICAL_DIRECTION_OPTIONS = { | ||||||
| @@ -239,12 +234,8 @@ CONFIG_SCHEMA = cv.All( | |||||||
|                     ): cv.ensure_list( |                     ): cv.ensure_list( | ||||||
|                         cv.enum(SUPPORTED_CLIMATE_PRESETS_HON_OPTIONS, upper=True) |                         cv.enum(SUPPORTED_CLIMATE_PRESETS_HON_OPTIONS, upper=True) | ||||||
|                     ), |                     ), | ||||||
|                     cv.Optional(CONF_OUTDOOR_TEMPERATURE): sensor.sensor_schema( |                     cv.Optional(CONF_OUTDOOR_TEMPERATURE): cv.invalid( | ||||||
|                         unit_of_measurement=UNIT_CELSIUS, |                         f"The {CONF_OUTDOOR_TEMPERATURE} option is deprecated, use a sensor for a haier platform instead" | ||||||
|                         icon=ICON_THERMOMETER, |  | ||||||
|                         accuracy_decimals=0, |  | ||||||
|                         device_class=DEVICE_CLASS_TEMPERATURE, |  | ||||||
|                         state_class=STATE_CLASS_MEASUREMENT, |  | ||||||
|                     ), |                     ), | ||||||
|                     cv.Optional(CONF_ON_ALARM_START): automation.validate_automation( |                     cv.Optional(CONF_ON_ALARM_START): automation.validate_automation( | ||||||
|                         { |                         { | ||||||
| @@ -463,9 +454,6 @@ async def to_code(config): | |||||||
|         cg.add(var.set_beeper_state(config[CONF_BEEPER])) |         cg.add(var.set_beeper_state(config[CONF_BEEPER])) | ||||||
|     if CONF_DISPLAY in config: |     if CONF_DISPLAY in config: | ||||||
|         cg.add(var.set_display_state(config[CONF_DISPLAY])) |         cg.add(var.set_display_state(config[CONF_DISPLAY])) | ||||||
|     if CONF_OUTDOOR_TEMPERATURE in config: |  | ||||||
|         sens = await sensor.new_sensor(config[CONF_OUTDOOR_TEMPERATURE]) |  | ||||||
|         cg.add(var.set_outdoor_temperature_sensor(sens)) |  | ||||||
|     if CONF_SUPPORTED_MODES in config: |     if CONF_SUPPORTED_MODES in config: | ||||||
|         cg.add(var.set_supported_modes(config[CONF_SUPPORTED_MODES])) |         cg.add(var.set_supported_modes(config[CONF_SUPPORTED_MODES])) | ||||||
|     if CONF_SUPPORTED_SWING_MODES in config: |     if CONF_SUPPORTED_SWING_MODES in config: | ||||||
|   | |||||||
| @@ -2,6 +2,7 @@ | |||||||
| #include <string> | #include <string> | ||||||
| #include "esphome/components/climate/climate.h" | #include "esphome/components/climate/climate.h" | ||||||
| #include "esphome/components/uart/uart.h" | #include "esphome/components/uart/uart.h" | ||||||
|  | #include "esphome/core/helpers.h" | ||||||
| #include "hon_climate.h" | #include "hon_climate.h" | ||||||
| #include "hon_packet.h" | #include "hon_packet.h" | ||||||
|  |  | ||||||
| @@ -51,10 +52,9 @@ hon_protocol::HorizontalSwingMode get_horizontal_swing_mode(AirflowHorizontalDir | |||||||
| } | } | ||||||
|  |  | ||||||
| HonClimate::HonClimate() | HonClimate::HonClimate() | ||||||
|     : cleaning_status_(CleaningState::NO_CLEANING), |     : cleaning_status_(CleaningState::NO_CLEANING), got_valid_outdoor_temp_(false), active_alarms_{0x00, 0x00, 0x00, | ||||||
|       got_valid_outdoor_temp_(false), |                                                                                                    0x00, 0x00, 0x00, | ||||||
|       active_alarms_{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, |                                                                                                    0x00, 0x00} { | ||||||
|       outdoor_sensor_(nullptr) { |  | ||||||
|   last_status_message_ = std::unique_ptr<uint8_t[]>(new uint8_t[sizeof(hon_protocol::HaierPacketControl)]); |   last_status_message_ = std::unique_ptr<uint8_t[]>(new uint8_t[sizeof(hon_protocol::HaierPacketControl)]); | ||||||
|   this->fan_mode_speed_ = (uint8_t) hon_protocol::FanMode::FAN_MID; |   this->fan_mode_speed_ = (uint8_t) hon_protocol::FanMode::FAN_MID; | ||||||
|   this->other_modes_fan_speed_ = (uint8_t) hon_protocol::FanMode::FAN_AUTO; |   this->other_modes_fan_speed_ = (uint8_t) hon_protocol::FanMode::FAN_AUTO; | ||||||
| @@ -66,8 +66,6 @@ void HonClimate::set_beeper_state(bool state) { this->beeper_status_ = state; } | |||||||
|  |  | ||||||
| bool HonClimate::get_beeper_state() const { return this->beeper_status_; } | bool HonClimate::get_beeper_state() const { return this->beeper_status_; } | ||||||
|  |  | ||||||
| void HonClimate::set_outdoor_temperature_sensor(esphome::sensor::Sensor *sensor) { this->outdoor_sensor_ = sensor; } |  | ||||||
|  |  | ||||||
| AirflowVerticalDirection HonClimate::get_vertical_airflow() const { return this->vertical_direction_; }; | AirflowVerticalDirection HonClimate::get_vertical_airflow() const { return this->vertical_direction_; }; | ||||||
|  |  | ||||||
| void HonClimate::set_vertical_airflow(AirflowVerticalDirection direction) { | void HonClimate::set_vertical_airflow(AirflowVerticalDirection direction) { | ||||||
| @@ -368,7 +366,14 @@ void HonClimate::process_phase(std::chrono::steady_clock::time_point now) { | |||||||
|       if (this->can_send_message() && this->is_message_interval_exceeded_(now)) { |       if (this->can_send_message() && this->is_message_interval_exceeded_(now)) { | ||||||
|         static const haier_protocol::HaierMessage STATUS_REQUEST( |         static const haier_protocol::HaierMessage STATUS_REQUEST( | ||||||
|             haier_protocol::FrameType::CONTROL, (uint16_t) hon_protocol::SubcommandsControl::GET_USER_DATA); |             haier_protocol::FrameType::CONTROL, (uint16_t) hon_protocol::SubcommandsControl::GET_USER_DATA); | ||||||
|         this->send_message_(STATUS_REQUEST, this->use_crc_); |         static const haier_protocol::HaierMessage BIG_DATA_REQUEST( | ||||||
|  |             haier_protocol::FrameType::CONTROL, (uint16_t) hon_protocol::SubcommandsControl::GET_BIG_DATA); | ||||||
|  |         if ((this->protocol_phase_ == ProtocolPhases::SENDING_FIRST_STATUS_REQUEST) || | ||||||
|  |             (!this->should_get_big_data_())) { | ||||||
|  |           this->send_message_(STATUS_REQUEST, this->use_crc_); | ||||||
|  |         } else { | ||||||
|  |           this->send_message_(BIG_DATA_REQUEST, this->use_crc_); | ||||||
|  |         } | ||||||
|         this->last_status_request_ = now; |         this->last_status_request_ = now; | ||||||
|       } |       } | ||||||
|       break; |       break; | ||||||
| @@ -685,9 +690,87 @@ void HonClimate::process_alarm_message_(const uint8_t *packet, uint8_t size, boo | |||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | #ifdef USE_SENSOR | ||||||
|  | void HonClimate::set_sub_sensor(SubSensorType type, sensor::Sensor *sens) { | ||||||
|  |   if (type < SubSensorType::SUB_SENSOR_TYPE_COUNT) { | ||||||
|  |     if (type >= SubSensorType::BIG_DATA_FRAME_SUB_SENSORS) { | ||||||
|  |       if ((this->sub_sensors_[(size_t) type] != nullptr) && (sens == nullptr)) { | ||||||
|  |         this->big_data_sensors_--; | ||||||
|  |       } else if ((this->sub_sensors_[(size_t) type] == nullptr) && (sens != nullptr)) { | ||||||
|  |         this->big_data_sensors_++; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     this->sub_sensors_[(size_t) type] = sens; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void HonClimate::update_sub_sensor_(SubSensorType type, float value) { | ||||||
|  |   if (type < SubSensorType::SUB_SENSOR_TYPE_COUNT) { | ||||||
|  |     size_t index = (size_t) type; | ||||||
|  |     if ((this->sub_sensors_[index] != nullptr) && | ||||||
|  |         ((!this->sub_sensors_[index]->has_state()) || (this->sub_sensors_[index]->raw_state != value))) | ||||||
|  |       this->sub_sensors_[index]->publish_state(value); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | #endif  // USE_SENSOR | ||||||
|  |  | ||||||
|  | #ifdef USE_BINARY_SENSOR | ||||||
|  | void HonClimate::set_sub_binary_sensor(SubBinarySensorType type, binary_sensor::BinarySensor *sens) { | ||||||
|  |   if (type < SubBinarySensorType::SUB_BINARY_SENSOR_TYPE_COUNT) { | ||||||
|  |     if ((this->sub_binary_sensors_[(size_t) type] != nullptr) && (sens == nullptr)) { | ||||||
|  |       this->big_data_sensors_--; | ||||||
|  |     } else if ((this->sub_binary_sensors_[(size_t) type] == nullptr) && (sens != nullptr)) { | ||||||
|  |       this->big_data_sensors_++; | ||||||
|  |     } | ||||||
|  |     this->sub_binary_sensors_[(size_t) type] = sens; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void HonClimate::update_sub_binary_sensor_(SubBinarySensorType type, uint8_t value) { | ||||||
|  |   if (value < 2) { | ||||||
|  |     bool converted_value = value == 1; | ||||||
|  |     size_t index = (size_t) type; | ||||||
|  |     if ((this->sub_binary_sensors_[index] != nullptr) && ((!this->sub_binary_sensors_[index]->has_state()) || | ||||||
|  |                                                           (this->sub_binary_sensors_[index]->state != converted_value))) | ||||||
|  |       this->sub_binary_sensors_[index]->publish_state(converted_value); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | #endif  // USE_BINARY_SENSOR | ||||||
|  |  | ||||||
| haier_protocol::HandlerError HonClimate::process_status_message_(const uint8_t *packet_buffer, uint8_t size) { | haier_protocol::HandlerError HonClimate::process_status_message_(const uint8_t *packet_buffer, uint8_t size) { | ||||||
|   if (size < hon_protocol::HAIER_STATUS_FRAME_SIZE + this->extra_control_packet_bytes_) |   size_t expected_size = 2 + sizeof(hon_protocol::HaierPacketControl) + sizeof(hon_protocol::HaierPacketSensors) + | ||||||
|  |                          this->extra_control_packet_bytes_; | ||||||
|  |   if (size < expected_size) | ||||||
|     return haier_protocol::HandlerError::WRONG_MESSAGE_STRUCTURE; |     return haier_protocol::HandlerError::WRONG_MESSAGE_STRUCTURE; | ||||||
|  |   uint16_t subtype = (((uint16_t) packet_buffer[0]) << 8) + packet_buffer[1]; | ||||||
|  |   if ((subtype == 0x7D01) && (size >= expected_size + 4 + sizeof(hon_protocol::HaierPacketBigData))) { | ||||||
|  |     // Got BigData packet | ||||||
|  |     const hon_protocol::HaierPacketBigData *bd_packet = | ||||||
|  |         (const hon_protocol::HaierPacketBigData *) (&packet_buffer[expected_size + 4]); | ||||||
|  | #ifdef USE_SENSOR | ||||||
|  |     this->update_sub_sensor_(SubSensorType::INDOOR_COIL_TEMPERATURE, bd_packet->indoor_coil_temperature / 2.0 - 20); | ||||||
|  |     this->update_sub_sensor_(SubSensorType::OUTDOOR_COIL_TEMPERATURE, bd_packet->outdoor_coil_temperature - 64); | ||||||
|  |     this->update_sub_sensor_(SubSensorType::OUTDOOR_DEFROST_TEMPERATURE, bd_packet->outdoor_coil_temperature - 64); | ||||||
|  |     this->update_sub_sensor_(SubSensorType::OUTDOOR_IN_AIR_TEMPERATURE, bd_packet->outdoor_in_air_temperature - 64); | ||||||
|  |     this->update_sub_sensor_(SubSensorType::OUTDOOR_OUT_AIR_TEMPERATURE, bd_packet->outdoor_out_air_temperature - 64); | ||||||
|  |     this->update_sub_sensor_(SubSensorType::POWER, encode_uint16(bd_packet->power[0], bd_packet->power[1])); | ||||||
|  |     this->update_sub_sensor_(SubSensorType::COMPRESSOR_FREQUENCY, bd_packet->compressor_frequency); | ||||||
|  |     this->update_sub_sensor_(SubSensorType::COMPRESSOR_CURRENT, | ||||||
|  |                              encode_uint16(bd_packet->compressor_current[0], bd_packet->compressor_current[1]) / 10.0); | ||||||
|  |     this->update_sub_sensor_( | ||||||
|  |         SubSensorType::EXPANSION_VALVE_OPEN_DEGREE, | ||||||
|  |         encode_uint16(bd_packet->expansion_valve_open_degree[0], bd_packet->expansion_valve_open_degree[1]) / 4095.0); | ||||||
|  | #endif  // USE_SENSOR | ||||||
|  | #ifdef USE_BINARY_SENSOR | ||||||
|  |     this->update_sub_binary_sensor_(SubBinarySensorType::OUTDOOR_FAN_STATUS, bd_packet->outdoor_fan_status); | ||||||
|  |     this->update_sub_binary_sensor_(SubBinarySensorType::DEFROST_STATUS, bd_packet->defrost_status); | ||||||
|  |     this->update_sub_binary_sensor_(SubBinarySensorType::COMPRESSOR_STATUS, bd_packet->compressor_status); | ||||||
|  |     this->update_sub_binary_sensor_(SubBinarySensorType::INDOOR_FAN_STATUS, bd_packet->indoor_fan_status); | ||||||
|  |     this->update_sub_binary_sensor_(SubBinarySensorType::FOUR_WAY_VALVE_STATUS, bd_packet->four_way_valve_status); | ||||||
|  |     this->update_sub_binary_sensor_(SubBinarySensorType::INDOOR_ELECTRIC_HEATING_STATUS, | ||||||
|  |                                     bd_packet->indoor_electric_heating_status); | ||||||
|  | #endif  // USE_BINARY_SENSOR | ||||||
|  |   } | ||||||
|   struct { |   struct { | ||||||
|     hon_protocol::HaierPacketControl control; |     hon_protocol::HaierPacketControl control; | ||||||
|     hon_protocol::HaierPacketSensors sensors; |     hon_protocol::HaierPacketSensors sensors; | ||||||
| @@ -699,13 +782,17 @@ haier_protocol::HandlerError HonClimate::process_status_message_(const uint8_t * | |||||||
|   if (packet.sensors.error_status != 0) { |   if (packet.sensors.error_status != 0) { | ||||||
|     ESP_LOGW(TAG, "HVAC error, code=0x%02X", packet.sensors.error_status); |     ESP_LOGW(TAG, "HVAC error, code=0x%02X", packet.sensors.error_status); | ||||||
|   } |   } | ||||||
|   if ((this->outdoor_sensor_ != nullptr) && | #ifdef USE_SENSOR | ||||||
|  |   if ((this->sub_sensors_[(size_t) SubSensorType::OUTDOOR_TEMPERATURE] != nullptr) && | ||||||
|       (this->got_valid_outdoor_temp_ || (packet.sensors.outdoor_temperature > 0))) { |       (this->got_valid_outdoor_temp_ || (packet.sensors.outdoor_temperature > 0))) { | ||||||
|     this->got_valid_outdoor_temp_ = true; |     this->got_valid_outdoor_temp_ = true; | ||||||
|     float otemp = (float) (packet.sensors.outdoor_temperature + PROTOCOL_OUTDOOR_TEMPERATURE_OFFSET); |     this->update_sub_sensor_(SubSensorType::OUTDOOR_TEMPERATURE, | ||||||
|     if ((!this->outdoor_sensor_->has_state()) || (this->outdoor_sensor_->get_raw_state() != otemp)) |                              (float) (packet.sensors.outdoor_temperature + PROTOCOL_OUTDOOR_TEMPERATURE_OFFSET)); | ||||||
|       this->outdoor_sensor_->publish_state(otemp); |  | ||||||
|   } |   } | ||||||
|  |   if ((this->sub_sensors_[(size_t) SubSensorType::HUMIDITY] != nullptr) && (packet.sensors.room_humidity <= 100)) { | ||||||
|  |     this->update_sub_sensor_(SubSensorType::HUMIDITY, (float) packet.sensors.room_humidity); | ||||||
|  |   } | ||||||
|  | #endif  // USE_SENSOR | ||||||
|   bool should_publish = false; |   bool should_publish = false; | ||||||
|   { |   { | ||||||
|     // Extra modes/presets |     // Extra modes/presets | ||||||
| @@ -1009,21 +1096,22 @@ void HonClimate::fill_control_messages_queue_() { | |||||||
|           break; |           break; | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|     if (quiet_mode_buf[1] != 0xFF) { |     auto presets = this->traits_.get_supported_presets(); | ||||||
|  |     if ((quiet_mode_buf[1] != 0xFF) && ((presets.find(climate::ClimatePreset::CLIMATE_PRESET_ECO) != presets.end()))) { | ||||||
|       this->control_messages_queue_.push( |       this->control_messages_queue_.push( | ||||||
|           haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL, |           haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL, | ||||||
|                                        (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + |                                        (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + | ||||||
|                                            (uint8_t) hon_protocol::DataParameters::QUIET_MODE, |                                            (uint8_t) hon_protocol::DataParameters::QUIET_MODE, | ||||||
|                                        quiet_mode_buf, 2)); |                                        quiet_mode_buf, 2)); | ||||||
|     } |     } | ||||||
|     if (fast_mode_buf[1] != 0xFF) { |     if ((fast_mode_buf[1] != 0xFF) && ((presets.find(climate::ClimatePreset::CLIMATE_PRESET_BOOST) != presets.end()))) { | ||||||
|       this->control_messages_queue_.push( |       this->control_messages_queue_.push( | ||||||
|           haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL, |           haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL, | ||||||
|                                        (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + |                                        (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + | ||||||
|                                            (uint8_t) hon_protocol::DataParameters::FAST_MODE, |                                            (uint8_t) hon_protocol::DataParameters::FAST_MODE, | ||||||
|                                        fast_mode_buf, 2)); |                                        fast_mode_buf, 2)); | ||||||
|     } |     } | ||||||
|     if (away_mode_buf[1] != 0xFF) { |     if ((away_mode_buf[1] != 0xFF) && ((presets.find(climate::ClimatePreset::CLIMATE_PRESET_AWAY) != presets.end()))) { | ||||||
|       this->control_messages_queue_.push( |       this->control_messages_queue_.push( | ||||||
|           haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL, |           haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL, | ||||||
|                                        (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + |                                        (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + | ||||||
| @@ -1032,7 +1120,7 @@ void HonClimate::fill_control_messages_queue_() { | |||||||
|     } |     } | ||||||
|   } |   } | ||||||
|   // Target temperature |   // Target temperature | ||||||
|   if (climate_control.target_temperature.has_value()) { |   if (climate_control.target_temperature.has_value() && (this->mode != ClimateMode::CLIMATE_MODE_FAN_ONLY)) { | ||||||
|     uint8_t buffer[2] = {0x00, 0x00}; |     uint8_t buffer[2] = {0x00, 0x00}; | ||||||
|     buffer[1] = ((uint8_t) climate_control.target_temperature.value()) - 16; |     buffer[1] = ((uint8_t) climate_control.target_temperature.value()) - 16; | ||||||
|     this->control_messages_queue_.push( |     this->control_messages_queue_.push( | ||||||
| @@ -1119,12 +1207,24 @@ bool HonClimate::prepare_pending_action() { | |||||||
|  |  | ||||||
| void HonClimate::process_protocol_reset() { | void HonClimate::process_protocol_reset() { | ||||||
|   HaierClimateBase::process_protocol_reset(); |   HaierClimateBase::process_protocol_reset(); | ||||||
|   if (this->outdoor_sensor_ != nullptr) { | #ifdef USE_SENSOR | ||||||
|     this->outdoor_sensor_->publish_state(NAN); |   for (auto &sub_sensor : this->sub_sensors_) { | ||||||
|  |     if ((sub_sensor != nullptr) && sub_sensor->has_state()) | ||||||
|  |       sub_sensor->publish_state(NAN); | ||||||
|   } |   } | ||||||
|  | #endif  // USE_SENSOR | ||||||
|   this->got_valid_outdoor_temp_ = false; |   this->got_valid_outdoor_temp_ = false; | ||||||
|   this->hvac_hardware_info_.reset(); |   this->hvac_hardware_info_.reset(); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | bool HonClimate::should_get_big_data_() { | ||||||
|  |   if (this->big_data_sensors_ > 0) { | ||||||
|  |     static uint8_t counter = 0; | ||||||
|  |     counter = (counter + 1) % 3; | ||||||
|  |     return counter == 1; | ||||||
|  |   } | ||||||
|  |   return false; | ||||||
|  | } | ||||||
|  |  | ||||||
| }  // namespace haier | }  // namespace haier | ||||||
| }  // namespace esphome | }  // namespace esphome | ||||||
|   | |||||||
| @@ -1,7 +1,12 @@ | |||||||
| #pragma once | #pragma once | ||||||
|  |  | ||||||
| #include <chrono> | #include <chrono> | ||||||
|  | #ifdef USE_SENSOR | ||||||
| #include "esphome/components/sensor/sensor.h" | #include "esphome/components/sensor/sensor.h" | ||||||
|  | #endif | ||||||
|  | #ifdef USE_BINARY_SENSOR | ||||||
|  | #include "esphome/components/binary_sensor/binary_sensor.h" | ||||||
|  | #endif | ||||||
| #include "esphome/core/automation.h" | #include "esphome/core/automation.h" | ||||||
| #include "haier_base.h" | #include "haier_base.h" | ||||||
|  |  | ||||||
| @@ -34,6 +39,48 @@ enum class CleaningState : uint8_t { | |||||||
| enum class HonControlMethod { MONITOR_ONLY = 0, SET_GROUP_PARAMETERS, SET_SINGLE_PARAMETER }; | enum class HonControlMethod { MONITOR_ONLY = 0, SET_GROUP_PARAMETERS, SET_SINGLE_PARAMETER }; | ||||||
|  |  | ||||||
| class HonClimate : public HaierClimateBase { | class HonClimate : public HaierClimateBase { | ||||||
|  | #ifdef USE_SENSOR | ||||||
|  |  public: | ||||||
|  |   enum class SubSensorType { | ||||||
|  |     // Used data based sensors | ||||||
|  |     OUTDOOR_TEMPERATURE = 0, | ||||||
|  |     HUMIDITY, | ||||||
|  |     // Big data based sensors | ||||||
|  |     INDOOR_COIL_TEMPERATURE, | ||||||
|  |     OUTDOOR_COIL_TEMPERATURE, | ||||||
|  |     OUTDOOR_DEFROST_TEMPERATURE, | ||||||
|  |     OUTDOOR_IN_AIR_TEMPERATURE, | ||||||
|  |     OUTDOOR_OUT_AIR_TEMPERATURE, | ||||||
|  |     POWER, | ||||||
|  |     COMPRESSOR_FREQUENCY, | ||||||
|  |     COMPRESSOR_CURRENT, | ||||||
|  |     EXPANSION_VALVE_OPEN_DEGREE, | ||||||
|  |     SUB_SENSOR_TYPE_COUNT, | ||||||
|  |     BIG_DATA_FRAME_SUB_SENSORS = INDOOR_COIL_TEMPERATURE, | ||||||
|  |   }; | ||||||
|  |   void set_sub_sensor(SubSensorType type, sensor::Sensor *sens); | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   void update_sub_sensor_(SubSensorType type, float value); | ||||||
|  |   sensor::Sensor *sub_sensors_[(size_t) SubSensorType::SUB_SENSOR_TYPE_COUNT]{nullptr}; | ||||||
|  | #endif | ||||||
|  | #ifdef USE_BINARY_SENSOR | ||||||
|  |  public: | ||||||
|  |   enum class SubBinarySensorType { | ||||||
|  |     OUTDOOR_FAN_STATUS = 0, | ||||||
|  |     DEFROST_STATUS, | ||||||
|  |     COMPRESSOR_STATUS, | ||||||
|  |     INDOOR_FAN_STATUS, | ||||||
|  |     FOUR_WAY_VALVE_STATUS, | ||||||
|  |     INDOOR_ELECTRIC_HEATING_STATUS, | ||||||
|  |     SUB_BINARY_SENSOR_TYPE_COUNT, | ||||||
|  |   }; | ||||||
|  |   void set_sub_binary_sensor(SubBinarySensorType type, binary_sensor::BinarySensor *sens); | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   void update_sub_binary_sensor_(SubBinarySensorType type, uint8_t value); | ||||||
|  |   binary_sensor::BinarySensor *sub_binary_sensors_[(size_t) SubBinarySensorType::SUB_BINARY_SENSOR_TYPE_COUNT]{nullptr}; | ||||||
|  | #endif | ||||||
|  public: |  public: | ||||||
|   HonClimate(); |   HonClimate(); | ||||||
|   HonClimate(const HonClimate &) = delete; |   HonClimate(const HonClimate &) = delete; | ||||||
| @@ -42,7 +89,6 @@ class HonClimate : public HaierClimateBase { | |||||||
|   void dump_config() override; |   void dump_config() override; | ||||||
|   void set_beeper_state(bool state); |   void set_beeper_state(bool state); | ||||||
|   bool get_beeper_state() const; |   bool get_beeper_state() const; | ||||||
|   void set_outdoor_temperature_sensor(esphome::sensor::Sensor *sensor); |  | ||||||
|   AirflowVerticalDirection get_vertical_airflow() const; |   AirflowVerticalDirection get_vertical_airflow() const; | ||||||
|   void set_vertical_airflow(AirflowVerticalDirection direction); |   void set_vertical_airflow(AirflowVerticalDirection direction); | ||||||
|   AirflowHorizontalDirection get_horizontal_airflow() const; |   AirflowHorizontalDirection get_horizontal_airflow() const; | ||||||
| @@ -64,6 +110,7 @@ class HonClimate : public HaierClimateBase { | |||||||
|   haier_protocol::HaierMessage get_power_message(bool state) override; |   haier_protocol::HaierMessage get_power_message(bool state) override; | ||||||
|   bool prepare_pending_action() override; |   bool prepare_pending_action() override; | ||||||
|   void process_protocol_reset() override; |   void process_protocol_reset() override; | ||||||
|  |   bool should_get_big_data_(); | ||||||
|  |  | ||||||
|   // Answers handlers |   // Answers handlers | ||||||
|   haier_protocol::HandlerError get_device_version_answer_handler_(haier_protocol::FrameType request_type, |   haier_protocol::HandlerError get_device_version_answer_handler_(haier_protocol::FrameType request_type, | ||||||
| @@ -106,12 +153,12 @@ class HonClimate : public HaierClimateBase { | |||||||
|   uint8_t active_alarms_[8]; |   uint8_t active_alarms_[8]; | ||||||
|   int extra_control_packet_bytes_; |   int extra_control_packet_bytes_; | ||||||
|   HonControlMethod control_method_; |   HonControlMethod control_method_; | ||||||
|   esphome::sensor::Sensor *outdoor_sensor_; |  | ||||||
|   std::queue<haier_protocol::HaierMessage> control_messages_queue_; |   std::queue<haier_protocol::HaierMessage> control_messages_queue_; | ||||||
|   CallbackManager<void(uint8_t, const char *)> alarm_start_callback_{}; |   CallbackManager<void(uint8_t, const char *)> alarm_start_callback_{}; | ||||||
|   CallbackManager<void(uint8_t, const char *)> alarm_end_callback_{}; |   CallbackManager<void(uint8_t, const char *)> alarm_end_callback_{}; | ||||||
|   float active_alarm_count_{NAN}; |   float active_alarm_count_{NAN}; | ||||||
|   std::chrono::steady_clock::time_point last_alarm_request_; |   std::chrono::steady_clock::time_point last_alarm_request_; | ||||||
|  |   int big_data_sensors_{0}; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| class HaierAlarmStartTrigger : public Trigger<uint8_t, const char *> { | class HaierAlarmStartTrigger : public Trigger<uint8_t, const char *> { | ||||||
|   | |||||||
| @@ -55,18 +55,18 @@ enum class FanMode : uint8_t { FAN_HIGH = 0x01, FAN_MID = 0x02, FAN_LOW = 0x03, | |||||||
|  |  | ||||||
| struct HaierPacketControl { | struct HaierPacketControl { | ||||||
|   // Control bytes starts here |   // Control bytes starts here | ||||||
|   // 10 |   // 1 | ||||||
|   uint8_t set_point;  // Target temperature with 16°C offset (0x00 = 16°C) |   uint8_t set_point;  // Target temperature with 16°C offset (0x00 = 16°C) | ||||||
|   // 11 |   // 2 | ||||||
|   uint8_t vertical_swing_mode : 4;  // See enum VerticalSwingMode |   uint8_t vertical_swing_mode : 4;  // See enum VerticalSwingMode | ||||||
|   uint8_t : 0; |   uint8_t : 0; | ||||||
|   // 12 |   // 3 | ||||||
|   uint8_t fan_mode : 3;      // See enum FanMode |   uint8_t fan_mode : 3;      // See enum FanMode | ||||||
|   uint8_t special_mode : 2;  // See enum SpecialMode |   uint8_t special_mode : 2;  // See enum SpecialMode | ||||||
|   uint8_t ac_mode : 3;       // See enum ConditioningMode |   uint8_t ac_mode : 3;       // See enum ConditioningMode | ||||||
|   // 13 |   // 4 | ||||||
|   uint8_t : 8; |   uint8_t : 8; | ||||||
|   // 14 |   // 5 | ||||||
|   uint8_t ten_degree : 1;           // 10 degree status |   uint8_t ten_degree : 1;           // 10 degree status | ||||||
|   uint8_t display_status : 1;       // If 0 disables AC's display |   uint8_t display_status : 1;       // If 0 disables AC's display | ||||||
|   uint8_t half_degree : 1;          // Use half degree |   uint8_t half_degree : 1;          // Use half degree | ||||||
| @@ -75,7 +75,7 @@ struct HaierPacketControl { | |||||||
|   uint8_t use_fahrenheit : 1;       // Use Fahrenheit instead of Celsius |   uint8_t use_fahrenheit : 1;       // Use Fahrenheit instead of Celsius | ||||||
|   uint8_t : 1; |   uint8_t : 1; | ||||||
|   uint8_t steri_clean : 1; |   uint8_t steri_clean : 1; | ||||||
|   // 15 |   // 6 | ||||||
|   uint8_t ac_power : 1;                 // Is ac on or off |   uint8_t ac_power : 1;                 // Is ac on or off | ||||||
|   uint8_t health_mode : 1;              // Health mode (negative ions) on or off |   uint8_t health_mode : 1;              // Health mode (negative ions) on or off | ||||||
|   uint8_t electric_heating_status : 1;  // Electric heating status |   uint8_t electric_heating_status : 1;  // Electric heating status | ||||||
| @@ -84,16 +84,16 @@ struct HaierPacketControl { | |||||||
|   uint8_t sleep_mode : 1;               // Sleep mode |   uint8_t sleep_mode : 1;               // Sleep mode | ||||||
|   uint8_t lock_remote : 1;              // Disable remote |   uint8_t lock_remote : 1;              // Disable remote | ||||||
|   uint8_t beeper_status : 1;  // If 1 disables AC's command feedback beeper (need to be set on every control command) |   uint8_t beeper_status : 1;  // If 1 disables AC's command feedback beeper (need to be set on every control command) | ||||||
|   // 16 |   // 7 | ||||||
|   uint8_t target_humidity;  // Target humidity (0=30% .. 3C=90%, step = 1%) |   uint8_t target_humidity;  // Target humidity (0=30% .. 3C=90%, step = 1%) | ||||||
|   // 17 |   // 8 | ||||||
|   uint8_t horizontal_swing_mode : 3;  // See enum HorizontalSwingMode |   uint8_t horizontal_swing_mode : 3;  // See enum HorizontalSwingMode | ||||||
|   uint8_t : 3; |   uint8_t : 3; | ||||||
|   uint8_t human_sensing_status : 2;  // Human sensing status |   uint8_t human_sensing_status : 2;  // Human sensing status | ||||||
|   // 18 |   // 9 | ||||||
|   uint8_t change_filter : 1;  // Filter need replacement |   uint8_t change_filter : 1;  // Filter need replacement | ||||||
|   uint8_t : 0; |   uint8_t : 0; | ||||||
|   // 19 |   // 10 | ||||||
|   uint8_t fresh_air_status : 1;       // Fresh air status |   uint8_t fresh_air_status : 1;       // Fresh air status | ||||||
|   uint8_t humidification_status : 1;  // Humidification status |   uint8_t humidification_status : 1;  // Humidification status | ||||||
|   uint8_t pm2p5_cleaning_status : 1;  // PM2.5 cleaning status |   uint8_t pm2p5_cleaning_status : 1;  // PM2.5 cleaning status | ||||||
| @@ -105,40 +105,68 @@ struct HaierPacketControl { | |||||||
| }; | }; | ||||||
|  |  | ||||||
| struct HaierPacketSensors { | struct HaierPacketSensors { | ||||||
|   // 20 |   // 11 | ||||||
|   uint8_t room_temperature;  // 0.5°C step |   uint8_t room_temperature;  // 0.5°C step | ||||||
|   // 21 |   // 12 | ||||||
|   uint8_t room_humidity;  // 0%-100% with 1% step |   uint8_t room_humidity;  // 0%-100% with 1% step | ||||||
|   // 22 |   // 13 | ||||||
|   uint8_t outdoor_temperature;  // 1°C step, -64°C offset (0=-64°C) |   uint8_t outdoor_temperature;  // 1°C step, -64°C offset (0=-64°C) | ||||||
|   // 23 |   // 14 | ||||||
|   uint8_t pm2p5_level : 2;    // Indoor PM2.5 grade (00: Excellent, 01: good, 02: Medium, 03: Bad) |   uint8_t pm2p5_level : 2;    // Indoor PM2.5 grade (00: Excellent, 01: good, 02: Medium, 03: Bad) | ||||||
|   uint8_t air_quality : 2;    // Air quality grade (00: Excellent, 01: good, 02: Medium, 03: Bad) |   uint8_t air_quality : 2;    // Air quality grade (00: Excellent, 01: good, 02: Medium, 03: Bad) | ||||||
|   uint8_t human_sensing : 2;  // Human presence result (00: N/A, 01: not detected, 02: One, 03: Multiple) |   uint8_t human_sensing : 2;  // Human presence result (00: N/A, 01: not detected, 02: One, 03: Multiple) | ||||||
|   uint8_t : 1; |   uint8_t : 1; | ||||||
|   uint8_t ac_type : 1;  // 00 - Heat and cool, 01 - Cool only) |   uint8_t ac_type : 1;  // 00 - Heat and cool, 01 - Cool only) | ||||||
|   // 24 |   // 15 | ||||||
|   uint8_t error_status;  // See enum ErrorStatus |   uint8_t error_status;  // See enum ErrorStatus | ||||||
|   // 25 |   // 16 | ||||||
|   uint8_t operation_source : 2;   // who is controlling AC (00: Other, 01: Remote control, 02: Button, 03: ESP) |   uint8_t operation_source : 2;   // who is controlling AC (00: Other, 01: Remote control, 02: Button, 03: ESP) | ||||||
|   uint8_t operation_mode_hk : 2;  // Homekit only, operation mode (00: Cool, 01: Dry, 02: Heat, 03: Fan) |   uint8_t operation_mode_hk : 2;  // Homekit only, operation mode (00: Cool, 01: Dry, 02: Heat, 03: Fan) | ||||||
|   uint8_t : 3; |   uint8_t : 3; | ||||||
|   uint8_t err_confirmation : 1;  // If 1 clear error status |   uint8_t err_confirmation : 1;  // If 1 clear error status | ||||||
|   // 26 |   // 17 | ||||||
|   uint16_t total_cleaning_time;  // Cleaning cumulative time (1h step) |   uint16_t total_cleaning_time;  // Cleaning cumulative time (1h step) | ||||||
|   // 28 |   // 19 | ||||||
|   uint16_t indoor_pm2p5_value;  // Indoor PM2.5 value (0 ug/m3 -  4095 ug/m3, 1 ug/m3 step) |   uint16_t indoor_pm2p5_value;  // Indoor PM2.5 value (0 ug/m3 -  4095 ug/m3, 1 ug/m3 step) | ||||||
|   // 30 |   // 21 | ||||||
|   uint16_t outdoor_pm2p5_value;  // Outdoor PM2.5 value (0 ug/m3 -  4095 ug/m3, 1 ug/m3 step) |   uint16_t outdoor_pm2p5_value;  // Outdoor PM2.5 value (0 ug/m3 -  4095 ug/m3, 1 ug/m3 step) | ||||||
|   // 32 |   // 23 | ||||||
|   uint16_t ch2o_value;  // Formaldehyde value (0 ug/m3 -  10000 ug/m3, 1 ug/m3 step) |   uint16_t ch2o_value;  // Formaldehyde value (0 ug/m3 -  10000 ug/m3, 1 ug/m3 step) | ||||||
|   // 34 |   // 25 | ||||||
|   uint16_t voc_value;  // VOC value (Volatile Organic Compounds) (0 ug/m3 -  1023 ug/m3, 1 ug/m3 step) |   uint16_t voc_value;  // VOC value (Volatile Organic Compounds) (0 ug/m3 -  1023 ug/m3, 1 ug/m3 step) | ||||||
|   // 36 |   // 27 | ||||||
|   uint16_t co2_value;  // CO2 value (0 PPM -  10000 PPM, 1 PPM step) |   uint16_t co2_value;  // CO2 value (0 PPM -  10000 PPM, 1 PPM step) | ||||||
| }; | }; | ||||||
|  |  | ||||||
| constexpr size_t HAIER_STATUS_FRAME_SIZE = 2 + sizeof(HaierPacketControl) + sizeof(HaierPacketSensors); | struct HaierPacketBigData { | ||||||
|  |   // 29 | ||||||
|  |   uint8_t power[2];  // AC power consumption (0W - 65535W, 1W step) | ||||||
|  |   // 31 | ||||||
|  |   uint8_t indoor_coil_temperature;  // 0.5°C step, -20°C offset (0=-20°C) | ||||||
|  |   // 32 | ||||||
|  |   uint8_t outdoor_out_air_temperature;  // 1°C step, -64°C offset (0=-64°C) | ||||||
|  |   // 33 | ||||||
|  |   uint8_t outdoor_coil_temperature;  // 1°C step, -64°C offset (0=-64°C) | ||||||
|  |   // 34 | ||||||
|  |   uint8_t outdoor_in_air_temperature;  // 1°C step, -64°C offset (0=-64°C) | ||||||
|  |   // 35 | ||||||
|  |   uint8_t outdoor_defrost_temperature;  // 1°C step, -64°C offset (0=-64°C) | ||||||
|  |   // 36 | ||||||
|  |   uint8_t compressor_frequency;  // 1Hz step, 0Hz - 127Hz | ||||||
|  |   // 37 | ||||||
|  |   uint8_t compressor_current[2];  // 0.1A step, 0.0A - 51.1A (0x0000 - 0x01FF) | ||||||
|  |   // 39 | ||||||
|  |   uint8_t outdoor_fan_status : 2;  // 0 - off, 1 - on,  2 - information not available | ||||||
|  |   uint8_t defrost_status : 2;      // 0 - off, 1 - on,  2 - information not available | ||||||
|  |   uint8_t : 0; | ||||||
|  |   // 40 | ||||||
|  |   uint8_t compressor_status : 2;               // 0 - off, 1 - on,  2 - information not available | ||||||
|  |   uint8_t indoor_fan_status : 2;               // 0 - off, 1 - on,  2 - information not available | ||||||
|  |   uint8_t four_way_valve_status : 2;           // 0 - off, 1 - on,  2 - information not available | ||||||
|  |   uint8_t indoor_electric_heating_status : 2;  // 0 - off, 1 - on,  2 - information not available | ||||||
|  |   // 41 | ||||||
|  |   uint8_t expansion_valve_open_degree[2];  // 0 - 4095 | ||||||
|  | }; | ||||||
|  |  | ||||||
| struct DeviceVersionAnswer { | struct DeviceVersionAnswer { | ||||||
|   char protocol_version[8]; |   char protocol_version[8]; | ||||||
|   | |||||||
							
								
								
									
										151
									
								
								esphome/components/haier/sensor/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										151
									
								
								esphome/components/haier/sensor/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,151 @@ | |||||||
|  | import esphome.codegen as cg | ||||||
|  | import esphome.config_validation as cv | ||||||
|  | from esphome.components import sensor | ||||||
|  | from esphome.const import ( | ||||||
|  |     CONF_POWER, | ||||||
|  |     CONF_HUMIDITY, | ||||||
|  |     DEVICE_CLASS_CURRENT, | ||||||
|  |     DEVICE_CLASS_FREQUENCY, | ||||||
|  |     DEVICE_CLASS_HUMIDITY, | ||||||
|  |     DEVICE_CLASS_POWER, | ||||||
|  |     DEVICE_CLASS_TEMPERATURE, | ||||||
|  |     ENTITY_CATEGORY_DIAGNOSTIC, | ||||||
|  |     ICON_CURRENT_AC, | ||||||
|  |     ICON_FLASH, | ||||||
|  |     ICON_GAUGE, | ||||||
|  |     ICON_HEATING_COIL, | ||||||
|  |     ICON_PULSE, | ||||||
|  |     ICON_THERMOMETER, | ||||||
|  |     ICON_WATER_PERCENT, | ||||||
|  |     ICON_WEATHER_WINDY, | ||||||
|  |     STATE_CLASS_MEASUREMENT, | ||||||
|  |     UNIT_AMPERE, | ||||||
|  |     UNIT_CELSIUS, | ||||||
|  |     UNIT_HERTZ, | ||||||
|  |     UNIT_PERCENT, | ||||||
|  |     UNIT_WATT, | ||||||
|  | ) | ||||||
|  | from ..climate import ( | ||||||
|  |     CONF_HAIER_ID, | ||||||
|  |     HonClimate, | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | SensorTypeEnum = HonClimate.enum("SubSensorType", True) | ||||||
|  |  | ||||||
|  | # Haier sensors | ||||||
|  | CONF_COMPRESSOR_CURRENT = "compressor_current" | ||||||
|  | CONF_COMPRESSOR_FREQUENCY = "compressor_frequency" | ||||||
|  | CONF_EXPANSION_VALVE_OPEN_DEGREE = "expansion_valve_open_degree" | ||||||
|  | CONF_INDOOR_COIL_TEMPERATURE = "indoor_coil_temperature" | ||||||
|  | CONF_OUTDOOR_COIL_TEMPERATURE = "outdoor_coil_temperature" | ||||||
|  | CONF_OUTDOOR_DEFROST_TEMPERATURE = "outdoor_defrost_temperature" | ||||||
|  | CONF_OUTDOOR_IN_AIR_TEMPERATURE = "outdoor_in_air_temperature" | ||||||
|  | CONF_OUTDOOR_OUT_AIR_TEMPERATURE = "outdoor_out_air_temperature" | ||||||
|  | CONF_OUTDOOR_TEMPERATURE = "outdoor_temperature" | ||||||
|  |  | ||||||
|  | # Additional icons | ||||||
|  | ICON_SNOWFLAKE_THERMOMETER = "mdi:snowflake-thermometer" | ||||||
|  |  | ||||||
|  | SENSOR_TYPES = { | ||||||
|  |     CONF_COMPRESSOR_CURRENT: sensor.sensor_schema( | ||||||
|  |         unit_of_measurement=UNIT_AMPERE, | ||||||
|  |         icon=ICON_CURRENT_AC, | ||||||
|  |         accuracy_decimals=1, | ||||||
|  |         device_class=DEVICE_CLASS_CURRENT, | ||||||
|  |         state_class=STATE_CLASS_MEASUREMENT, | ||||||
|  |         entity_category=ENTITY_CATEGORY_DIAGNOSTIC, | ||||||
|  |     ), | ||||||
|  |     CONF_COMPRESSOR_FREQUENCY: sensor.sensor_schema( | ||||||
|  |         unit_of_measurement=UNIT_HERTZ, | ||||||
|  |         icon=ICON_PULSE, | ||||||
|  |         accuracy_decimals=0, | ||||||
|  |         device_class=DEVICE_CLASS_FREQUENCY, | ||||||
|  |         state_class=STATE_CLASS_MEASUREMENT, | ||||||
|  |         entity_category=ENTITY_CATEGORY_DIAGNOSTIC, | ||||||
|  |     ), | ||||||
|  |     CONF_EXPANSION_VALVE_OPEN_DEGREE: sensor.sensor_schema( | ||||||
|  |         unit_of_measurement=UNIT_PERCENT, | ||||||
|  |         icon=ICON_GAUGE, | ||||||
|  |         accuracy_decimals=2, | ||||||
|  |         state_class=STATE_CLASS_MEASUREMENT, | ||||||
|  |         entity_category=ENTITY_CATEGORY_DIAGNOSTIC, | ||||||
|  |     ), | ||||||
|  |     CONF_HUMIDITY: sensor.sensor_schema( | ||||||
|  |         unit_of_measurement=UNIT_PERCENT, | ||||||
|  |         icon=ICON_WATER_PERCENT, | ||||||
|  |         accuracy_decimals=0, | ||||||
|  |         device_class=DEVICE_CLASS_HUMIDITY, | ||||||
|  |         state_class=STATE_CLASS_MEASUREMENT, | ||||||
|  |     ), | ||||||
|  |     CONF_INDOOR_COIL_TEMPERATURE: sensor.sensor_schema( | ||||||
|  |         unit_of_measurement=UNIT_CELSIUS, | ||||||
|  |         icon=ICON_HEATING_COIL, | ||||||
|  |         accuracy_decimals=0, | ||||||
|  |         device_class=DEVICE_CLASS_TEMPERATURE, | ||||||
|  |         state_class=STATE_CLASS_MEASUREMENT, | ||||||
|  |         entity_category=ENTITY_CATEGORY_DIAGNOSTIC, | ||||||
|  |     ), | ||||||
|  |     CONF_OUTDOOR_COIL_TEMPERATURE: sensor.sensor_schema( | ||||||
|  |         unit_of_measurement=UNIT_CELSIUS, | ||||||
|  |         icon=ICON_HEATING_COIL, | ||||||
|  |         accuracy_decimals=0, | ||||||
|  |         device_class=DEVICE_CLASS_TEMPERATURE, | ||||||
|  |         state_class=STATE_CLASS_MEASUREMENT, | ||||||
|  |         entity_category=ENTITY_CATEGORY_DIAGNOSTIC, | ||||||
|  |     ), | ||||||
|  |     CONF_OUTDOOR_DEFROST_TEMPERATURE: sensor.sensor_schema( | ||||||
|  |         unit_of_measurement=UNIT_CELSIUS, | ||||||
|  |         icon=ICON_SNOWFLAKE_THERMOMETER, | ||||||
|  |         accuracy_decimals=0, | ||||||
|  |         device_class=DEVICE_CLASS_TEMPERATURE, | ||||||
|  |         state_class=STATE_CLASS_MEASUREMENT, | ||||||
|  |         entity_category=ENTITY_CATEGORY_DIAGNOSTIC, | ||||||
|  |     ), | ||||||
|  |     CONF_OUTDOOR_IN_AIR_TEMPERATURE: sensor.sensor_schema( | ||||||
|  |         unit_of_measurement=UNIT_CELSIUS, | ||||||
|  |         icon=ICON_WEATHER_WINDY, | ||||||
|  |         accuracy_decimals=0, | ||||||
|  |         device_class=DEVICE_CLASS_TEMPERATURE, | ||||||
|  |         state_class=STATE_CLASS_MEASUREMENT, | ||||||
|  |         entity_category=ENTITY_CATEGORY_DIAGNOSTIC, | ||||||
|  |     ), | ||||||
|  |     CONF_OUTDOOR_OUT_AIR_TEMPERATURE: sensor.sensor_schema( | ||||||
|  |         unit_of_measurement=UNIT_CELSIUS, | ||||||
|  |         icon=ICON_WEATHER_WINDY, | ||||||
|  |         accuracy_decimals=0, | ||||||
|  |         device_class=DEVICE_CLASS_TEMPERATURE, | ||||||
|  |         state_class=STATE_CLASS_MEASUREMENT, | ||||||
|  |         entity_category=ENTITY_CATEGORY_DIAGNOSTIC, | ||||||
|  |     ), | ||||||
|  |     CONF_OUTDOOR_TEMPERATURE: sensor.sensor_schema( | ||||||
|  |         unit_of_measurement=UNIT_CELSIUS, | ||||||
|  |         icon=ICON_THERMOMETER, | ||||||
|  |         accuracy_decimals=0, | ||||||
|  |         device_class=DEVICE_CLASS_TEMPERATURE, | ||||||
|  |         state_class=STATE_CLASS_MEASUREMENT, | ||||||
|  |     ), | ||||||
|  |     CONF_POWER: sensor.sensor_schema( | ||||||
|  |         unit_of_measurement=UNIT_WATT, | ||||||
|  |         icon=ICON_FLASH, | ||||||
|  |         accuracy_decimals=0, | ||||||
|  |         device_class=DEVICE_CLASS_POWER, | ||||||
|  |         state_class=STATE_CLASS_MEASUREMENT, | ||||||
|  |         entity_category=ENTITY_CATEGORY_DIAGNOSTIC, | ||||||
|  |     ), | ||||||
|  | } | ||||||
|  |  | ||||||
|  | CONFIG_SCHEMA = cv.Schema( | ||||||
|  |     { | ||||||
|  |         cv.Required(CONF_HAIER_ID): cv.use_id(HonClimate), | ||||||
|  |     } | ||||||
|  | ).extend({cv.Optional(type): schema for type, schema in SENSOR_TYPES.items()}) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | async def to_code(config): | ||||||
|  |     paren = await cg.get_variable(config[CONF_HAIER_ID]) | ||||||
|  |  | ||||||
|  |     for type, _ in SENSOR_TYPES.items(): | ||||||
|  |         if conf := config.get(type): | ||||||
|  |             sens = await sensor.new_sensor(conf) | ||||||
|  |             sensor_type = getattr(SensorTypeEnum, type.upper()) | ||||||
|  |             cg.add(paren.set_sub_sensor(sensor_type, sens)) | ||||||
| @@ -119,4 +119,4 @@ def to_code(config): | |||||||
|     cg.add_library("tonia/HeatpumpIR", "1.0.23") |     cg.add_library("tonia/HeatpumpIR", "1.0.23") | ||||||
|  |  | ||||||
|     if CORE.is_esp8266 or CORE.is_esp32: |     if CORE.is_esp8266 or CORE.is_esp32: | ||||||
|         cg.add_library("crankyoldgit/IRremoteESP8266", "2.7.12") |         cg.add_library("crankyoldgit/IRremoteESP8266", "2.8.4") | ||||||
|   | |||||||
| @@ -1,2 +1,3 @@ | |||||||
| """Support for Honeywell HumidIcon HIH""" | """Support for Honeywell HumidIcon HIH""" | ||||||
|  |  | ||||||
| CODEOWNERS = ["@Benichou34"] | CODEOWNERS = ["@Benichou34"] | ||||||
|   | |||||||
| @@ -1,2 +1,3 @@ | |||||||
| """Support for Honeywell ABP2""" | """Support for Honeywell ABP2""" | ||||||
|  |  | ||||||
| CODEOWNERS = ["@jpfaff"] | CODEOWNERS = ["@jpfaff"] | ||||||
|   | |||||||
| @@ -4,6 +4,7 @@ from esphome.const import ( | |||||||
|     KEY_TARGET_FRAMEWORK, |     KEY_TARGET_FRAMEWORK, | ||||||
|     KEY_TARGET_PLATFORM, |     KEY_TARGET_PLATFORM, | ||||||
|     PLATFORM_HOST, |     PLATFORM_HOST, | ||||||
|  |     CONF_MAC_ADDRESS, | ||||||
| ) | ) | ||||||
| from esphome.core import CORE | from esphome.core import CORE | ||||||
| from esphome.helpers import IS_MACOS | from esphome.helpers import IS_MACOS | ||||||
| @@ -28,13 +29,18 @@ def set_core_data(config): | |||||||
|  |  | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = cv.All( | CONFIG_SCHEMA = cv.All( | ||||||
|     cv.Schema({}), |     cv.Schema( | ||||||
|  |         { | ||||||
|  |             cv.Optional(CONF_MAC_ADDRESS, default="98:35:69:ab:f6:79"): cv.mac_address, | ||||||
|  |         } | ||||||
|  |     ), | ||||||
|     set_core_data, |     set_core_data, | ||||||
| ) | ) | ||||||
|  |  | ||||||
|  |  | ||||||
| async def to_code(config): | async def to_code(config): | ||||||
|     cg.add_build_flag("-DUSE_HOST") |     cg.add_build_flag("-DUSE_HOST") | ||||||
|  |     cg.add_define("USE_ESPHOME_HOST_MAC_ADDRESS", config[CONF_MAC_ADDRESS].parts) | ||||||
|     cg.add_build_flag("-std=c++17") |     cg.add_build_flag("-std=c++17") | ||||||
|     cg.add_build_flag("-lsodium") |     cg.add_build_flag("-lsodium") | ||||||
|     if IS_MACOS: |     if IS_MACOS: | ||||||
|   | |||||||
| @@ -24,7 +24,7 @@ void ArduinoI2CBus::setup() { | |||||||
|   } |   } | ||||||
|   next_bus_num++; |   next_bus_num++; | ||||||
| #elif defined(USE_ESP8266) | #elif defined(USE_ESP8266) | ||||||
|   wire_ = &Wire;  // NOLINT(cppcoreguidelines-prefer-member-initializer) |   wire_ = new TwoWire();  // NOLINT(cppcoreguidelines-owning-memory) | ||||||
| #elif defined(USE_RP2040) | #elif defined(USE_RP2040) | ||||||
|   static bool first = true; |   static bool first = true; | ||||||
|   if (first) { |   if (first) { | ||||||
| @@ -35,6 +35,16 @@ void ArduinoI2CBus::setup() { | |||||||
|   } |   } | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
|  |   this->set_pins_and_clock_(); | ||||||
|  |  | ||||||
|  |   this->initialized_ = true; | ||||||
|  |   if (this->scan_) { | ||||||
|  |     ESP_LOGV(TAG, "Scanning i2c bus for active devices..."); | ||||||
|  |     this->i2c_scan_(); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void ArduinoI2CBus::set_pins_and_clock_() { | ||||||
| #ifdef USE_RP2040 | #ifdef USE_RP2040 | ||||||
|   wire_->setSDA(this->sda_pin_); |   wire_->setSDA(this->sda_pin_); | ||||||
|   wire_->setSCL(this->scl_pin_); |   wire_->setSCL(this->scl_pin_); | ||||||
| @@ -43,12 +53,8 @@ void ArduinoI2CBus::setup() { | |||||||
|   wire_->begin(static_cast<int>(sda_pin_), static_cast<int>(scl_pin_)); |   wire_->begin(static_cast<int>(sda_pin_), static_cast<int>(scl_pin_)); | ||||||
| #endif | #endif | ||||||
|   wire_->setClock(frequency_); |   wire_->setClock(frequency_); | ||||||
|   initialized_ = true; |  | ||||||
|   if (this->scan_) { |  | ||||||
|     ESP_LOGV(TAG, "Scanning i2c bus for active devices..."); |  | ||||||
|     this->i2c_scan_(); |  | ||||||
|   } |  | ||||||
| } | } | ||||||
|  |  | ||||||
| void ArduinoI2CBus::dump_config() { | void ArduinoI2CBus::dump_config() { | ||||||
|   ESP_LOGCONFIG(TAG, "I2C Bus:"); |   ESP_LOGCONFIG(TAG, "I2C Bus:"); | ||||||
|   ESP_LOGCONFIG(TAG, "  SDA Pin: GPIO%u", this->sda_pin_); |   ESP_LOGCONFIG(TAG, "  SDA Pin: GPIO%u", this->sda_pin_); | ||||||
| @@ -82,6 +88,10 @@ void ArduinoI2CBus::dump_config() { | |||||||
| } | } | ||||||
|  |  | ||||||
| ErrorCode ArduinoI2CBus::readv(uint8_t address, ReadBuffer *buffers, size_t cnt) { | ErrorCode ArduinoI2CBus::readv(uint8_t address, ReadBuffer *buffers, size_t cnt) { | ||||||
|  | #if defined(USE_ESP8266) | ||||||
|  |   this->set_pins_and_clock_();  // reconfigure Wire global state in case there are multiple instances | ||||||
|  | #endif | ||||||
|  |  | ||||||
|   // logging is only enabled with vv level, if warnings are shown the caller |   // logging is only enabled with vv level, if warnings are shown the caller | ||||||
|   // should log them |   // should log them | ||||||
|   if (!initialized_) { |   if (!initialized_) { | ||||||
| @@ -120,6 +130,10 @@ ErrorCode ArduinoI2CBus::readv(uint8_t address, ReadBuffer *buffers, size_t cnt) | |||||||
|   return ERROR_OK; |   return ERROR_OK; | ||||||
| } | } | ||||||
| ErrorCode ArduinoI2CBus::writev(uint8_t address, WriteBuffer *buffers, size_t cnt, bool stop) { | ErrorCode ArduinoI2CBus::writev(uint8_t address, WriteBuffer *buffers, size_t cnt, bool stop) { | ||||||
|  | #if defined(USE_ESP8266) | ||||||
|  |   this->set_pins_and_clock_();  // reconfigure Wire global state in case there are multiple instances | ||||||
|  | #endif | ||||||
|  |  | ||||||
|   // logging is only enabled with vv level, if warnings are shown the caller |   // logging is only enabled with vv level, if warnings are shown the caller | ||||||
|   // should log them |   // should log them | ||||||
|   if (!initialized_) { |   if (!initialized_) { | ||||||
| @@ -164,7 +178,7 @@ ErrorCode ArduinoI2CBus::writev(uint8_t address, WriteBuffer *buffers, size_t cn | |||||||
|       return ERROR_UNKNOWN; |       return ERROR_UNKNOWN; | ||||||
|     case 2: |     case 2: | ||||||
|     case 3: |     case 3: | ||||||
|       ESP_LOGVV(TAG, "TX failed: not acknowledged"); |       ESP_LOGVV(TAG, "TX failed: not acknowledged: %d", status); | ||||||
|       return ERROR_NOT_ACKNOWLEDGED; |       return ERROR_NOT_ACKNOWLEDGED; | ||||||
|     case 5: |     case 5: | ||||||
|       ESP_LOGVV(TAG, "TX failed: timeout"); |       ESP_LOGVV(TAG, "TX failed: timeout"); | ||||||
|   | |||||||
| @@ -30,6 +30,7 @@ class ArduinoI2CBus : public I2CBus, public Component { | |||||||
|  |  | ||||||
|  private: |  private: | ||||||
|   void recover_(); |   void recover_(); | ||||||
|  |   void set_pins_and_clock_(); | ||||||
|   RecoveryCode recovery_result_; |   RecoveryCode recovery_result_; | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   | |||||||
| @@ -21,8 +21,13 @@ std::string ImprovBase::get_formatted_next_url_() { | |||||||
|   // Ip address |   // Ip address | ||||||
|   pos = this->next_url_.find("{{ip_address}}"); |   pos = this->next_url_.find("{{ip_address}}"); | ||||||
|   if (pos != std::string::npos) { |   if (pos != std::string::npos) { | ||||||
|     std::string ip = network::get_ip_address().str(); |     for (auto &ip : network::get_ip_addresses()) { | ||||||
|     copy.replace(pos, 14, ip); |       if (ip.is_ip4()) { | ||||||
|  |         std::string ipa = ip.str(); | ||||||
|  |         copy.replace(pos, 14, ipa); | ||||||
|  |         break; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   return copy; |   return copy; | ||||||
|   | |||||||
| @@ -155,9 +155,13 @@ std::vector<uint8_t> ImprovSerialComponent::build_rpc_settings_response_(improv: | |||||||
|     urls.push_back(this->get_formatted_next_url_()); |     urls.push_back(this->get_formatted_next_url_()); | ||||||
|   } |   } | ||||||
| #ifdef USE_WEBSERVER | #ifdef USE_WEBSERVER | ||||||
|   auto ip = wifi::global_wifi_component->wifi_sta_ip(); |   for (auto &ip : wifi::global_wifi_component->wifi_sta_ip_addresses()) { | ||||||
|   std::string webserver_url = "http://" + ip.str() + ":" + to_string(USE_WEBSERVER_PORT); |     if (ip.is_ip4()) { | ||||||
|   urls.push_back(webserver_url); |       std::string webserver_url = "http://" + ip.str() + ":" + to_string(USE_WEBSERVER_PORT); | ||||||
|  |       urls.push_back(webserver_url); | ||||||
|  |       break; | ||||||
|  |     } | ||||||
|  |   } | ||||||
| #endif | #endif | ||||||
|   std::vector<uint8_t> data = improv::build_rpc_response(command, urls, false); |   std::vector<uint8_t> data = improv::build_rpc_response(command, urls, false); | ||||||
|   return data; |   return data; | ||||||
| @@ -192,7 +196,7 @@ bool ImprovSerialComponent::parse_improv_payload_(improv::ImprovCommand &command | |||||||
|       this->connecting_sta_ = sta; |       this->connecting_sta_ = sta; | ||||||
|  |  | ||||||
|       wifi::global_wifi_component->set_sta(sta); |       wifi::global_wifi_component->set_sta(sta); | ||||||
|       wifi::global_wifi_component->start_scanning(); |       wifi::global_wifi_component->start_connecting(sta, false); | ||||||
|       this->set_state_(improv::STATE_PROVISIONING); |       this->set_state_(improv::STATE_PROVISIONING); | ||||||
|       ESP_LOGD(TAG, "Received Improv wifi settings ssid=%s, password=" LOG_SECRET("%s"), command.ssid.c_str(), |       ESP_LOGD(TAG, "Received Improv wifi settings ssid=%s, password=" LOG_SECRET("%s"), command.ssid.c_str(), | ||||||
|                command.password.c_str()); |                command.password.c_str()); | ||||||
|   | |||||||
| @@ -0,0 +1 @@ | |||||||
|  | CODEOWNERS = ["@Sergio303", "@latonita"] | ||||||
|   | |||||||
| @@ -33,31 +33,37 @@ static const uint8_t INA226_REGISTER_POWER = 0x03; | |||||||
| static const uint8_t INA226_REGISTER_CURRENT = 0x04; | static const uint8_t INA226_REGISTER_CURRENT = 0x04; | ||||||
| static const uint8_t INA226_REGISTER_CALIBRATION = 0x05; | static const uint8_t INA226_REGISTER_CALIBRATION = 0x05; | ||||||
|  |  | ||||||
|  | static const uint16_t INA226_ADC_TIMES[] = {140, 204, 332, 588, 1100, 2116, 4156, 8244}; | ||||||
|  | static const uint16_t INA226_ADC_AVG_SAMPLES[] = {1, 4, 16, 64, 128, 256, 512, 1024}; | ||||||
|  |  | ||||||
| void INA226Component::setup() { | void INA226Component::setup() { | ||||||
|   ESP_LOGCONFIG(TAG, "Setting up INA226..."); |   ESP_LOGCONFIG(TAG, "Setting up INA226..."); | ||||||
|   // Config Register |  | ||||||
|   // 0bx000000000000000 << 15 RESET Bit (1 -> trigger reset) |   ConfigurationRegister config; | ||||||
|   if (!this->write_byte_16(INA226_REGISTER_CONFIG, 0x8000)) { |  | ||||||
|  |   config.reset = 1; | ||||||
|  |   if (!this->write_byte_16(INA226_REGISTER_CONFIG, config.raw)) { | ||||||
|     this->mark_failed(); |     this->mark_failed(); | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|   delay(1); |   delay(1); | ||||||
|  |  | ||||||
|   uint16_t config = 0x0000; |   config.raw = 0; | ||||||
|  |   config.reserved = 0b100;  // as per datasheet | ||||||
|  |  | ||||||
|   // Averaging Mode AVG Bit Settings[11:9] (000 -> 1 sample, 001 -> 4 sample, 111 -> 1024 samples) |   // Averaging Mode AVG Bit Settings[11:9] (000 -> 1 sample, 001 -> 4 sample, 111 -> 1024 samples) | ||||||
|   config |= 0b0000001000000000; |   config.avg_samples = this->adc_avg_samples_; | ||||||
|  |  | ||||||
|   // Bus Voltage Conversion Time VBUSCT Bit Settings [8:6] (100 -> 1.1ms, 111 -> 8.244 ms) |   // Bus Voltage Conversion Time VBUSCT Bit Settings [8:6] (100 -> 1.1ms, 111 -> 8.244 ms) | ||||||
|   config |= 0b0000000100000000; |   config.bus_voltage_conversion_time = this->adc_time_; | ||||||
|  |  | ||||||
|   // Shunt Voltage Conversion Time VSHCT Bit Settings [5:3] (100 -> 1.1ms, 111 -> 8.244 ms) |   // Shunt Voltage Conversion Time VSHCT Bit Settings [5:3] (100 -> 1.1ms, 111 -> 8.244 ms) | ||||||
|   config |= 0b0000000000100000; |   config.shunt_voltage_conversion_time = this->adc_time_; | ||||||
|  |  | ||||||
|   // Mode Settings [2:0] Combinations (111 -> Shunt and Bus, Continuous) |   // Mode Settings [2:0] Combinations (111 -> Shunt and Bus, Continuous) | ||||||
|   config |= 0b0000000000000111; |   config.mode = 0b111; | ||||||
|  |  | ||||||
|   if (!this->write_byte_16(INA226_REGISTER_CONFIG, config)) { |   if (!this->write_byte_16(INA226_REGISTER_CONFIG, config.raw)) { | ||||||
|     this->mark_failed(); |     this->mark_failed(); | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
| @@ -87,6 +93,9 @@ void INA226Component::dump_config() { | |||||||
|   } |   } | ||||||
|   LOG_UPDATE_INTERVAL(this); |   LOG_UPDATE_INTERVAL(this); | ||||||
|  |  | ||||||
|  |   ESP_LOGCONFIG(TAG, "  ADC Conversion Time: %d", INA226_ADC_TIMES[this->adc_time_ & 0b111]); | ||||||
|  |   ESP_LOGCONFIG(TAG, "  ADC Averaging Samples: %d", INA226_ADC_AVG_SAMPLES[this->adc_avg_samples_ & 0b111]); | ||||||
|  |  | ||||||
|   LOG_SENSOR("  ", "Bus Voltage", this->bus_voltage_sensor_); |   LOG_SENSOR("  ", "Bus Voltage", this->bus_voltage_sensor_); | ||||||
|   LOG_SENSOR("  ", "Shunt Voltage", this->shunt_voltage_sensor_); |   LOG_SENSOR("  ", "Shunt Voltage", this->shunt_voltage_sensor_); | ||||||
|   LOG_SENSOR("  ", "Current", this->current_sensor_); |   LOG_SENSOR("  ", "Current", this->current_sensor_); | ||||||
| @@ -102,7 +111,9 @@ void INA226Component::update() { | |||||||
|       this->status_set_warning(); |       this->status_set_warning(); | ||||||
|       return; |       return; | ||||||
|     } |     } | ||||||
|     float bus_voltage_v = int16_t(raw_bus_voltage) * 0.00125f; |     // Convert for 2's compliment and signed value (though always positive) | ||||||
|  |     float bus_voltage_v = this->twos_complement_(raw_bus_voltage, 16); | ||||||
|  |     bus_voltage_v *= 0.00125f; | ||||||
|     this->bus_voltage_sensor_->publish_state(bus_voltage_v); |     this->bus_voltage_sensor_->publish_state(bus_voltage_v); | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @@ -112,7 +123,9 @@ void INA226Component::update() { | |||||||
|       this->status_set_warning(); |       this->status_set_warning(); | ||||||
|       return; |       return; | ||||||
|     } |     } | ||||||
|     float shunt_voltage_v = int16_t(raw_shunt_voltage) * 0.0000025f; |     // Convert for 2's compliment and signed value | ||||||
|  |     float shunt_voltage_v = this->twos_complement_(raw_shunt_voltage, 16); | ||||||
|  |     shunt_voltage_v *= 0.0000025f; | ||||||
|     this->shunt_voltage_sensor_->publish_state(shunt_voltage_v); |     this->shunt_voltage_sensor_->publish_state(shunt_voltage_v); | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @@ -122,7 +135,9 @@ void INA226Component::update() { | |||||||
|       this->status_set_warning(); |       this->status_set_warning(); | ||||||
|       return; |       return; | ||||||
|     } |     } | ||||||
|     float current_ma = int16_t(raw_current) * (this->calibration_lsb_ / 1000.0f); |     // Convert for 2's compliment and signed value | ||||||
|  |     float current_ma = this->twos_complement_(raw_current, 16); | ||||||
|  |     current_ma *= (this->calibration_lsb_ / 1000.0f); | ||||||
|     this->current_sensor_->publish_state(current_ma / 1000.0f); |     this->current_sensor_->publish_state(current_ma / 1000.0f); | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @@ -139,5 +154,12 @@ void INA226Component::update() { | |||||||
|   this->status_clear_warning(); |   this->status_clear_warning(); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | int32_t INA226Component::twos_complement_(int32_t val, uint8_t bits) { | ||||||
|  |   if (val & ((uint32_t) 1 << (bits - 1))) { | ||||||
|  |     val -= (uint32_t) 1 << bits; | ||||||
|  |   } | ||||||
|  |   return val; | ||||||
|  | } | ||||||
|  |  | ||||||
| }  // namespace ina226 | }  // namespace ina226 | ||||||
| }  // namespace esphome | }  // namespace esphome | ||||||
|   | |||||||
| @@ -7,6 +7,40 @@ | |||||||
| namespace esphome { | namespace esphome { | ||||||
| namespace ina226 { | namespace ina226 { | ||||||
|  |  | ||||||
|  | enum AdcTime : uint16_t { | ||||||
|  |   ADC_TIME_140US = 0, | ||||||
|  |   ADC_TIME_204US = 1, | ||||||
|  |   ADC_TIME_332US = 2, | ||||||
|  |   ADC_TIME_588US = 3, | ||||||
|  |   ADC_TIME_1100US = 4, | ||||||
|  |   ADC_TIME_2116US = 5, | ||||||
|  |   ADC_TIME_4156US = 6, | ||||||
|  |   ADC_TIME_8244US = 7 | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | enum AdcAvgSamples : uint16_t { | ||||||
|  |   ADC_AVG_SAMPLES_1 = 0, | ||||||
|  |   ADC_AVG_SAMPLES_4 = 1, | ||||||
|  |   ADC_AVG_SAMPLES_16 = 2, | ||||||
|  |   ADC_AVG_SAMPLES_64 = 3, | ||||||
|  |   ADC_AVG_SAMPLES_128 = 4, | ||||||
|  |   ADC_AVG_SAMPLES_256 = 5, | ||||||
|  |   ADC_AVG_SAMPLES_512 = 6, | ||||||
|  |   ADC_AVG_SAMPLES_1024 = 7 | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | union ConfigurationRegister { | ||||||
|  |   uint16_t raw; | ||||||
|  |   struct { | ||||||
|  |     uint16_t mode : 3; | ||||||
|  |     AdcTime shunt_voltage_conversion_time : 3; | ||||||
|  |     AdcTime bus_voltage_conversion_time : 3; | ||||||
|  |     AdcAvgSamples avg_samples : 3; | ||||||
|  |     uint16_t reserved : 3; | ||||||
|  |     uint16_t reset : 1; | ||||||
|  |   } __attribute__((packed)); | ||||||
|  | }; | ||||||
|  |  | ||||||
| class INA226Component : public PollingComponent, public i2c::I2CDevice { | class INA226Component : public PollingComponent, public i2c::I2CDevice { | ||||||
|  public: |  public: | ||||||
|   void setup() override; |   void setup() override; | ||||||
| @@ -16,6 +50,9 @@ class INA226Component : public PollingComponent, public i2c::I2CDevice { | |||||||
|  |  | ||||||
|   void set_shunt_resistance_ohm(float shunt_resistance_ohm) { shunt_resistance_ohm_ = shunt_resistance_ohm; } |   void set_shunt_resistance_ohm(float shunt_resistance_ohm) { shunt_resistance_ohm_ = shunt_resistance_ohm; } | ||||||
|   void set_max_current_a(float max_current_a) { max_current_a_ = max_current_a; } |   void set_max_current_a(float max_current_a) { max_current_a_ = max_current_a; } | ||||||
|  |   void set_adc_time(AdcTime time) { adc_time_ = time; } | ||||||
|  |   void set_adc_avg_samples(AdcAvgSamples samples) { adc_avg_samples_ = samples; } | ||||||
|  |  | ||||||
|   void set_bus_voltage_sensor(sensor::Sensor *bus_voltage_sensor) { bus_voltage_sensor_ = bus_voltage_sensor; } |   void set_bus_voltage_sensor(sensor::Sensor *bus_voltage_sensor) { bus_voltage_sensor_ = bus_voltage_sensor; } | ||||||
|   void set_shunt_voltage_sensor(sensor::Sensor *shunt_voltage_sensor) { shunt_voltage_sensor_ = shunt_voltage_sensor; } |   void set_shunt_voltage_sensor(sensor::Sensor *shunt_voltage_sensor) { shunt_voltage_sensor_ = shunt_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; } | ||||||
| @@ -24,11 +61,15 @@ class INA226Component : public PollingComponent, public i2c::I2CDevice { | |||||||
|  protected: |  protected: | ||||||
|   float shunt_resistance_ohm_; |   float shunt_resistance_ohm_; | ||||||
|   float max_current_a_; |   float max_current_a_; | ||||||
|  |   AdcTime adc_time_{AdcTime::ADC_TIME_1100US}; | ||||||
|  |   AdcAvgSamples adc_avg_samples_{AdcAvgSamples::ADC_AVG_SAMPLES_4}; | ||||||
|   uint32_t calibration_lsb_; |   uint32_t calibration_lsb_; | ||||||
|   sensor::Sensor *bus_voltage_sensor_{nullptr}; |   sensor::Sensor *bus_voltage_sensor_{nullptr}; | ||||||
|   sensor::Sensor *shunt_voltage_sensor_{nullptr}; |   sensor::Sensor *shunt_voltage_sensor_{nullptr}; | ||||||
|   sensor::Sensor *current_sensor_{nullptr}; |   sensor::Sensor *current_sensor_{nullptr}; | ||||||
|   sensor::Sensor *power_sensor_{nullptr}; |   sensor::Sensor *power_sensor_{nullptr}; | ||||||
|  |  | ||||||
|  |   int32_t twos_complement_(int32_t val, uint8_t bits); | ||||||
| }; | }; | ||||||
|  |  | ||||||
| }  // namespace ina226 | }  // namespace ina226 | ||||||
|   | |||||||
| @@ -20,11 +20,44 @@ from esphome.const import ( | |||||||
|  |  | ||||||
| DEPENDENCIES = ["i2c"] | DEPENDENCIES = ["i2c"] | ||||||
|  |  | ||||||
|  | CONF_ADC_AVERAGING = "adc_averaging" | ||||||
|  | CONF_ADC_TIME = "adc_time" | ||||||
|  |  | ||||||
| ina226_ns = cg.esphome_ns.namespace("ina226") | ina226_ns = cg.esphome_ns.namespace("ina226") | ||||||
| INA226Component = ina226_ns.class_( | INA226Component = ina226_ns.class_( | ||||||
|     "INA226Component", cg.PollingComponent, i2c.I2CDevice |     "INA226Component", cg.PollingComponent, i2c.I2CDevice | ||||||
| ) | ) | ||||||
|  |  | ||||||
|  | AdcTime = ina226_ns.enum("AdcTime") | ||||||
|  | ADC_TIMES = { | ||||||
|  |     140: AdcTime.ADC_TIME_140US, | ||||||
|  |     204: AdcTime.ADC_TIME_204US, | ||||||
|  |     332: AdcTime.ADC_TIME_332US, | ||||||
|  |     588: AdcTime.ADC_TIME_588US, | ||||||
|  |     1100: AdcTime.ADC_TIME_1100US, | ||||||
|  |     2116: AdcTime.ADC_TIME_2116US, | ||||||
|  |     4156: AdcTime.ADC_TIME_4156US, | ||||||
|  |     8244: AdcTime.ADC_TIME_8244US, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | AdcAvgSamples = ina226_ns.enum("AdcAvgSamples") | ||||||
|  | ADC_AVG_SAMPLES = { | ||||||
|  |     1: AdcAvgSamples.ADC_AVG_SAMPLES_1, | ||||||
|  |     4: AdcAvgSamples.ADC_AVG_SAMPLES_4, | ||||||
|  |     16: AdcAvgSamples.ADC_AVG_SAMPLES_16, | ||||||
|  |     64: AdcAvgSamples.ADC_AVG_SAMPLES_64, | ||||||
|  |     128: AdcAvgSamples.ADC_AVG_SAMPLES_128, | ||||||
|  |     256: AdcAvgSamples.ADC_AVG_SAMPLES_256, | ||||||
|  |     512: AdcAvgSamples.ADC_AVG_SAMPLES_512, | ||||||
|  |     1024: AdcAvgSamples.ADC_AVG_SAMPLES_1024, | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def validate_adc_time(value): | ||||||
|  |     value = cv.positive_time_period_microseconds(value).total_microseconds | ||||||
|  |     return cv.enum(ADC_TIMES, int=True)(value) | ||||||
|  |  | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = ( | CONFIG_SCHEMA = ( | ||||||
|     cv.Schema( |     cv.Schema( | ||||||
|         { |         { | ||||||
| @@ -59,6 +92,10 @@ CONFIG_SCHEMA = ( | |||||||
|             cv.Optional(CONF_MAX_CURRENT, default=3.2): cv.All( |             cv.Optional(CONF_MAX_CURRENT, default=3.2): cv.All( | ||||||
|                 cv.current, cv.Range(min=0.0) |                 cv.current, cv.Range(min=0.0) | ||||||
|             ), |             ), | ||||||
|  |             cv.Optional(CONF_ADC_TIME, default="1100 us"): validate_adc_time, | ||||||
|  |             cv.Optional(CONF_ADC_AVERAGING, default=4): cv.enum( | ||||||
|  |                 ADC_AVG_SAMPLES, int=True | ||||||
|  |             ), | ||||||
|         } |         } | ||||||
|     ) |     ) | ||||||
|     .extend(cv.polling_component_schema("60s")) |     .extend(cv.polling_component_schema("60s")) | ||||||
| @@ -72,8 +109,9 @@ async def to_code(config): | |||||||
|     await i2c.register_i2c_device(var, config) |     await i2c.register_i2c_device(var, config) | ||||||
|  |  | ||||||
|     cg.add(var.set_shunt_resistance_ohm(config[CONF_SHUNT_RESISTANCE])) |     cg.add(var.set_shunt_resistance_ohm(config[CONF_SHUNT_RESISTANCE])) | ||||||
|  |  | ||||||
|     cg.add(var.set_max_current_a(config[CONF_MAX_CURRENT])) |     cg.add(var.set_max_current_a(config[CONF_MAX_CURRENT])) | ||||||
|  |     cg.add(var.set_adc_time(config[CONF_ADC_TIME])) | ||||||
|  |     cg.add(var.set_adc_avg_samples(config[CONF_ADC_AVERAGING])) | ||||||
|  |  | ||||||
|     if CONF_BUS_VOLTAGE in config: |     if CONF_BUS_VOLTAGE in config: | ||||||
|         sens = await sensor.new_sensor(config[CONF_BUS_VOLTAGE]) |         sens = await sensor.new_sensor(config[CONF_BUS_VOLTAGE]) | ||||||
|   | |||||||
| @@ -3,6 +3,7 @@ import esphome.config_validation as cv | |||||||
| from esphome.components import sensor, esp32_ble_tracker | from esphome.components import sensor, esp32_ble_tracker | ||||||
| from esphome.const import ( | from esphome.const import ( | ||||||
|     CONF_BATTERY_LEVEL, |     CONF_BATTERY_LEVEL, | ||||||
|  |     CONF_EXTERNAL_TEMPERATURE, | ||||||
|     CONF_HUMIDITY, |     CONF_HUMIDITY, | ||||||
|     CONF_MAC_ADDRESS, |     CONF_MAC_ADDRESS, | ||||||
|     CONF_TEMPERATURE, |     CONF_TEMPERATURE, | ||||||
| @@ -19,8 +20,6 @@ from esphome.const import ( | |||||||
| CODEOWNERS = ["@fkirill"] | CODEOWNERS = ["@fkirill"] | ||||||
| DEPENDENCIES = ["esp32_ble_tracker"] | DEPENDENCIES = ["esp32_ble_tracker"] | ||||||
|  |  | ||||||
| CONF_EXTERNAL_TEMPERATURE = "external_temperature" |  | ||||||
|  |  | ||||||
| inkbird_ibsth1_mini_ns = cg.esphome_ns.namespace("inkbird_ibsth1_mini") | inkbird_ibsth1_mini_ns = cg.esphome_ns.namespace("inkbird_ibsth1_mini") | ||||||
| InkbirdIbstH1Mini = inkbird_ibsth1_mini_ns.class_( | InkbirdIbstH1Mini = inkbird_ibsth1_mini_ns.class_( | ||||||
|     "InkbirdIbstH1Mini", esp32_ble_tracker.ESPBTDeviceListener, cg.Component |     "InkbirdIbstH1Mini", esp32_ble_tracker.ESPBTDeviceListener, cg.Component | ||||||
|   | |||||||
| @@ -120,6 +120,7 @@ void LightState::loop() { | |||||||
|   // Apply transformer (if any) |   // Apply transformer (if any) | ||||||
|   if (this->transformer_ != nullptr) { |   if (this->transformer_ != nullptr) { | ||||||
|     auto values = this->transformer_->apply(); |     auto values = this->transformer_->apply(); | ||||||
|  |     this->is_transformer_active_ = true; | ||||||
|     if (values.has_value()) { |     if (values.has_value()) { | ||||||
|       this->current_values = *values; |       this->current_values = *values; | ||||||
|       this->output_->update_state(this); |       this->output_->update_state(this); | ||||||
| @@ -131,6 +132,7 @@ void LightState::loop() { | |||||||
|       this->current_values = this->transformer_->get_target_values(); |       this->current_values = this->transformer_->get_target_values(); | ||||||
|  |  | ||||||
|       this->transformer_->stop(); |       this->transformer_->stop(); | ||||||
|  |       this->is_transformer_active_ = false; | ||||||
|       this->transformer_ = nullptr; |       this->transformer_ = nullptr; | ||||||
|       this->target_state_reached_callback_.call(); |       this->target_state_reached_callback_.call(); | ||||||
|     } |     } | ||||||
| @@ -214,6 +216,8 @@ void LightState::current_values_as_ct(float *color_temperature, float *white_bri | |||||||
|                              this->gamma_correct_); |                              this->gamma_correct_); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | bool LightState::is_transformer_active() { return this->is_transformer_active_; } | ||||||
|  |  | ||||||
| void LightState::start_effect_(uint32_t effect_index) { | void LightState::start_effect_(uint32_t effect_index) { | ||||||
|   this->stop_effect_(); |   this->stop_effect_(); | ||||||
|   if (effect_index == 0) |   if (effect_index == 0) | ||||||
| @@ -263,6 +267,7 @@ void LightState::start_flash_(const LightColorValues &target, uint32_t length, b | |||||||
| } | } | ||||||
|  |  | ||||||
| void LightState::set_immediately_(const LightColorValues &target, bool set_remote_values) { | void LightState::set_immediately_(const LightColorValues &target, bool set_remote_values) { | ||||||
|  |   this->is_transformer_active_ = false; | ||||||
|   this->transformer_ = nullptr; |   this->transformer_ = nullptr; | ||||||
|   this->current_values = target; |   this->current_values = target; | ||||||
|   if (set_remote_values) { |   if (set_remote_values) { | ||||||
|   | |||||||
| @@ -144,6 +144,17 @@ class LightState : public EntityBase, public Component { | |||||||
|  |  | ||||||
|   void current_values_as_ct(float *color_temperature, float *white_brightness); |   void current_values_as_ct(float *color_temperature, float *white_brightness); | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |    * Indicator if a transformer (e.g. transition) is active. This is useful | ||||||
|  |    * for effects e.g. at the start of the apply() method, add a check like: | ||||||
|  |    * | ||||||
|  |    * if (this->state_->is_transformer_active()) { | ||||||
|  |    *   // Something is already running. | ||||||
|  |    *   return; | ||||||
|  |    * } | ||||||
|  |    */ | ||||||
|  |   bool is_transformer_active(); | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   friend LightOutput; |   friend LightOutput; | ||||||
|   friend LightCall; |   friend LightCall; | ||||||
| @@ -203,6 +214,9 @@ class LightState : public EntityBase, public Component { | |||||||
|   LightRestoreMode restore_mode_; |   LightRestoreMode restore_mode_; | ||||||
|   /// List of effects for this light. |   /// List of effects for this light. | ||||||
|   std::vector<LightEffect *> effects_; |   std::vector<LightEffect *> effects_; | ||||||
|  |  | ||||||
|  |   // for effects, true if a transformer (transition) is active. | ||||||
|  |   bool is_transformer_active_ = false; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| }  // namespace light | }  // namespace light | ||||||
|   | |||||||
| @@ -3,6 +3,8 @@ | |||||||
| #include "esphome/core/component.h" | #include "esphome/core/component.h" | ||||||
| #include "esphome/core/hal.h" | #include "esphome/core/hal.h" | ||||||
|  |  | ||||||
|  | #include <vector> | ||||||
|  |  | ||||||
| namespace esphome { | namespace esphome { | ||||||
| namespace lightwaverf { | namespace lightwaverf { | ||||||
|  |  | ||||||
|   | |||||||
| @@ -38,9 +38,14 @@ void LilygoT547Touchscreen::setup() { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   this->write_register(POWER_REGISTER, WAKEUP_CMD, 1); |   this->write_register(POWER_REGISTER, WAKEUP_CMD, 1); | ||||||
|  |   if (this->display_ != nullptr) { | ||||||
|   this->x_raw_max_ = this->get_width_(); |     if (this->x_raw_max_ == this->x_raw_min_) { | ||||||
|   this->y_raw_max_ = this->get_height_(); |       this->x_raw_max_ = this->display_->get_native_width(); | ||||||
|  |     } | ||||||
|  |     if (this->y_raw_max_ == this->y_raw_min_) { | ||||||
|  |       this->x_raw_max_ = this->display_->get_native_height(); | ||||||
|  |     } | ||||||
|  |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| void LilygoT547Touchscreen::update_touches() { | void LilygoT547Touchscreen::update_touches() { | ||||||
|   | |||||||
| @@ -140,7 +140,9 @@ def uart_selection(value): | |||||||
|             raise cv.Invalid(f"Arduino framework does not support {value}.") |             raise cv.Invalid(f"Arduino framework does not support {value}.") | ||||||
|         variant = get_esp32_variant() |         variant = get_esp32_variant() | ||||||
|         if CORE.using_esp_idf and variant == VARIANT_ESP32C3 and value == USB_CDC: |         if CORE.using_esp_idf and variant == VARIANT_ESP32C3 and value == USB_CDC: | ||||||
|             raise cv.Invalid(f"esp idf variant {variant} does not support {value}.") |             raise cv.Invalid( | ||||||
|  |                 f"{value} is not supported for variant {variant} when using ESP-IDF." | ||||||
|  |             ) | ||||||
|         if variant in UART_SELECTION_ESP32: |         if variant in UART_SELECTION_ESP32: | ||||||
|             return cv.one_of(*UART_SELECTION_ESP32[variant], upper=True)(value) |             return cv.one_of(*UART_SELECTION_ESP32[variant], upper=True)(value) | ||||||
|     if CORE.is_esp8266: |     if CORE.is_esp8266: | ||||||
| @@ -292,6 +294,16 @@ async def to_code(config): | |||||||
|             add_idf_sdkconfig_option("CONFIG_ESP_CONSOLE_USB_CDC", True) |             add_idf_sdkconfig_option("CONFIG_ESP_CONSOLE_USB_CDC", True) | ||||||
|         elif config[CONF_HARDWARE_UART] == USB_SERIAL_JTAG: |         elif config[CONF_HARDWARE_UART] == USB_SERIAL_JTAG: | ||||||
|             add_idf_sdkconfig_option("CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG", True) |             add_idf_sdkconfig_option("CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG", True) | ||||||
|  |     try: | ||||||
|  |         uart_selection(USB_SERIAL_JTAG) | ||||||
|  |         cg.add_define("USE_LOGGER_USB_SERIAL_JTAG") | ||||||
|  |     except cv.Invalid: | ||||||
|  |         pass | ||||||
|  |     try: | ||||||
|  |         uart_selection(USB_CDC) | ||||||
|  |         cg.add_define("USE_LOGGER_USB_CDC") | ||||||
|  |     except cv.Invalid: | ||||||
|  |         pass | ||||||
|  |  | ||||||
|     try: |     try: | ||||||
|         uart_selection(USB_SERIAL_JTAG) |         uart_selection(USB_SERIAL_JTAG) | ||||||
|   | |||||||
| @@ -161,26 +161,6 @@ Logger::Logger(uint32_t baud_rate, size_t tx_buffer_size) : baud_rate_(baud_rate | |||||||
| #endif | #endif | ||||||
| } | } | ||||||
|  |  | ||||||
| #ifdef USE_USB_CDC |  | ||||||
| #ifndef USE_ZEPHYR |  | ||||||
| void Logger::loop() { |  | ||||||
| #ifdef USE_ARDUINO |  | ||||||
|   if (this->uart_ != UART_SELECTION_USB_CDC) { |  | ||||||
|     return; |  | ||||||
|   } |  | ||||||
|   static bool opened = false; |  | ||||||
|   if (opened == Serial) { |  | ||||||
|     return; |  | ||||||
|   } |  | ||||||
|   if (false == opened) { |  | ||||||
|     App.schedule_dump_config(); |  | ||||||
|   } |  | ||||||
|   opened = !opened; |  | ||||||
| #endif |  | ||||||
| } |  | ||||||
| #endif |  | ||||||
| #endif |  | ||||||
|  |  | ||||||
| void Logger::set_baud_rate(uint32_t baud_rate) { this->baud_rate_ = baud_rate; } | void Logger::set_baud_rate(uint32_t baud_rate) { this->baud_rate_ = baud_rate; } | ||||||
| void Logger::set_log_level(const std::string &tag, int log_level) { | void Logger::set_log_level(const std::string &tag, int log_level) { | ||||||
|   this->log_levels_.push_back(LogLevelOverride{tag, log_level}); |   this->log_levels_.push_back(LogLevelOverride{tag, log_level}); | ||||||
|   | |||||||
| @@ -41,16 +41,14 @@ enum UARTSelection { | |||||||
| #else | #else | ||||||
|   UART_SELECTION_UART0 = 0, |   UART_SELECTION_UART0 = 0, | ||||||
| #endif | #endif | ||||||
| #ifndef USE_NRF52 |  | ||||||
|   UART_SELECTION_UART1, |   UART_SELECTION_UART1, | ||||||
| #endif |  | ||||||
| #if defined(USE_LIBRETINY) || defined(USE_ESP32_VARIANT_ESP32) | #if defined(USE_LIBRETINY) || defined(USE_ESP32_VARIANT_ESP32) | ||||||
|   UART_SELECTION_UART2, |   UART_SELECTION_UART2, | ||||||
| #endif | #endif | ||||||
| #ifdef USE_USB_CDC | #ifdef USE_LOGGER_USB_CDC | ||||||
|   UART_SELECTION_USB_CDC, |   UART_SELECTION_USB_CDC, | ||||||
| #endif | #endif | ||||||
| #ifdef USE_USB_SERIAL_JTAG | #ifdef USE_LOGGER_USB_SERIAL_JTAG | ||||||
|   UART_SELECTION_USB_SERIAL_JTAG, |   UART_SELECTION_USB_SERIAL_JTAG, | ||||||
| #endif | #endif | ||||||
| #ifdef USE_ESP8266 | #ifdef USE_ESP8266 | ||||||
|   | |||||||
| @@ -8,7 +8,7 @@ | |||||||
| #ifdef USE_ESP_IDF | #ifdef USE_ESP_IDF | ||||||
| #include <driver/uart.h> | #include <driver/uart.h> | ||||||
|  |  | ||||||
| #ifdef USE_USB_SERIAL_JTAG | #ifdef USE_LOGGER_USB_SERIAL_JTAG | ||||||
| #include <driver/usb_serial_jtag.h> | #include <driver/usb_serial_jtag.h> | ||||||
| #include <esp_vfs_dev.h> | #include <esp_vfs_dev.h> | ||||||
| #include <esp_vfs_usb_serial_jtag.h> | #include <esp_vfs_usb_serial_jtag.h> | ||||||
| @@ -32,7 +32,7 @@ static const char *const TAG = "logger"; | |||||||
|  |  | ||||||
| #ifdef USE_ESP_IDF | #ifdef USE_ESP_IDF | ||||||
|  |  | ||||||
| #ifdef USE_USB_SERIAL_JTAG | #ifdef USE_LOGGER_USB_SERIAL_JTAG | ||||||
| static void init_usb_serial_jtag_() { | static void init_usb_serial_jtag_() { | ||||||
|   setvbuf(stdin, NULL, _IONBF, 0);  // Disable buffering on stdin |   setvbuf(stdin, NULL, _IONBF, 0);  // Disable buffering on stdin | ||||||
|  |  | ||||||
| @@ -103,7 +103,7 @@ void Logger::pre_setup() { | |||||||
|         break; |         break; | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
| #ifdef USE_USB_CDC | #ifdef USE_LOGGER_USB_CDC | ||||||
|       case UART_SELECTION_USB_CDC: |       case UART_SELECTION_USB_CDC: | ||||||
|         this->hw_serial_ = &Serial; |         this->hw_serial_ = &Serial; | ||||||
| #if ARDUINO_USB_CDC_ON_BOOT | #if ARDUINO_USB_CDC_ON_BOOT | ||||||
| @@ -134,7 +134,7 @@ void Logger::pre_setup() { | |||||||
|         this->uart_num_ = -1; |         this->uart_num_ = -1; | ||||||
|         break; |         break; | ||||||
| #endif | #endif | ||||||
| #ifdef USE_USB_SERIAL_JTAG | #ifdef USE_LOGGER_USB_SERIAL_JTAG | ||||||
|       case UART_SELECTION_USB_SERIAL_JTAG: |       case UART_SELECTION_USB_SERIAL_JTAG: | ||||||
|         this->uart_num_ = -1; |         this->uart_num_ = -1; | ||||||
|         init_usb_serial_jtag_(); |         init_usb_serial_jtag_(); | ||||||
| @@ -177,6 +177,8 @@ void HOT Logger::write_msg_(const char *msg) { | |||||||
|     uart_write_bytes(this->uart_num_, "\n", 1); |     uart_write_bytes(this->uart_num_, "\n", 1); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  | #else | ||||||
|  | void HOT Logger::write_msg_(const char *msg) { this->hw_serial_->println(msg); } | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
| const char *const UART_SELECTIONS[] = { | const char *const UART_SELECTIONS[] = { | ||||||
| @@ -184,10 +186,10 @@ const char *const UART_SELECTIONS[] = { | |||||||
| #ifdef USE_ESP32_VARIANT_ESP32 | #ifdef USE_ESP32_VARIANT_ESP32 | ||||||
|     "UART2", |     "UART2", | ||||||
| #endif | #endif | ||||||
| #ifdef USE_USB_CDC | #ifdef USE_LOGGER_USB_CDC | ||||||
|     "USB_CDC", |     "USB_CDC", | ||||||
| #endif | #endif | ||||||
| #ifdef USE_USB_SERIAL_JTAG | #ifdef USE_LOGGER_USB_SERIAL_JTAG | ||||||
|     "USB_SERIAL_JTAG", |     "USB_SERIAL_JTAG", | ||||||
| #endif | #endif | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -1,7 +1,4 @@ | |||||||
| #ifdef USE_ESP8266 | #ifdef USE_ESP8266 | ||||||
| #ifndef USE_ARDUINO |  | ||||||
| #error "Only ARDUINO is supported" |  | ||||||
| #endif |  | ||||||
| #include "logger.h" | #include "logger.h" | ||||||
| #include "esphome/core/log.h" | #include "esphome/core/log.h" | ||||||
|  |  | ||||||
| @@ -37,6 +34,8 @@ void Logger::pre_setup() { | |||||||
|   ESP_LOGI(TAG, "Log initialized"); |   ESP_LOGI(TAG, "Log initialized"); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | void HOT Logger::write_msg_(const char *msg) { this->hw_serial_->println(msg); } | ||||||
|  |  | ||||||
| const char *const UART_SELECTIONS[] = {"UART0", "UART1", "UART0_SWAP"}; | const char *const UART_SELECTIONS[] = {"UART0", "UART1", "UART0_SWAP"}; | ||||||
|  |  | ||||||
| const char *Logger::get_uart_selection_() { return UART_SELECTIONS[this->uart_]; } | const char *Logger::get_uart_selection_() { return UART_SELECTIONS[this->uart_]; } | ||||||
|   | |||||||
							
								
								
									
										22
									
								
								esphome/components/logger/logger_host.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								esphome/components/logger/logger_host.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,22 @@ | |||||||
|  | #if defined(USE_HOST) | ||||||
|  | #include "logger.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace logger { | ||||||
|  |  | ||||||
|  | void HOT Logger::write_msg_(const char *msg) { | ||||||
|  |   time_t rawtime; | ||||||
|  |   struct tm *timeinfo; | ||||||
|  |   char buffer[80]; | ||||||
|  |  | ||||||
|  |   time(&rawtime); | ||||||
|  |   timeinfo = localtime(&rawtime); | ||||||
|  |   strftime(buffer, sizeof buffer, "[%H:%M:%S]", timeinfo); | ||||||
|  |   fputs(buffer, stdout); | ||||||
|  |   puts(msg); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | }  // namespace logger | ||||||
|  | }  // namespace esphome | ||||||
|  |  | ||||||
|  | #endif | ||||||
| @@ -50,6 +50,8 @@ void Logger::pre_setup() { | |||||||
|   ESP_LOGI(TAG, "Log initialized"); |   ESP_LOGI(TAG, "Log initialized"); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | void HOT Logger::write_msg_(const char *msg) { this->hw_serial_->println(msg); } | ||||||
|  |  | ||||||
| const char *const UART_SELECTIONS[] = {"DEFAULT", "UART0", "UART1", "UART2"}; | const char *const UART_SELECTIONS[] = {"DEFAULT", "UART0", "UART1", "UART2"}; | ||||||
|  |  | ||||||
| const char *Logger::get_uart_selection_() { return UART_SELECTIONS[this->uart_]; } | const char *Logger::get_uart_selection_() { return UART_SELECTIONS[this->uart_]; } | ||||||
|   | |||||||
| @@ -1,7 +1,4 @@ | |||||||
| #ifdef USE_RP2040 | #ifdef USE_RP2040 | ||||||
| #ifndef USE_ARDUINO |  | ||||||
| #error "Only ARDUINO is supported" |  | ||||||
| #endif |  | ||||||
| #include "logger.h" | #include "logger.h" | ||||||
| #include "esphome/core/log.h" | #include "esphome/core/log.h" | ||||||
|  |  | ||||||
| @@ -31,6 +28,8 @@ void Logger::pre_setup() { | |||||||
|   ESP_LOGI(TAG, "Log initialized"); |   ESP_LOGI(TAG, "Log initialized"); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | void HOT Logger::write_msg_(const char *msg) { this->hw_serial_->println(msg); } | ||||||
|  |  | ||||||
| const char *const UART_SELECTIONS[] = {"UART0", "UART1", "USB_CDC"}; | const char *const UART_SELECTIONS[] = {"UART0", "UART1", "USB_CDC"}; | ||||||
|  |  | ||||||
| const char *Logger::get_uart_selection_() { return UART_SELECTIONS[this->uart_]; } | const char *Logger::get_uart_selection_() { return UART_SELECTIONS[this->uart_]; } | ||||||
|   | |||||||
| @@ -8,10 +8,23 @@ namespace ltr390 { | |||||||
|  |  | ||||||
| static const char *const TAG = "ltr390"; | static const char *const TAG = "ltr390"; | ||||||
|  |  | ||||||
|  | static const uint8_t LTR390_MAIN_CTRL = 0x00; | ||||||
|  | static const uint8_t LTR390_MEAS_RATE = 0x04; | ||||||
|  | static const uint8_t LTR390_GAIN = 0x05; | ||||||
|  | static const uint8_t LTR390_PART_ID = 0x06; | ||||||
|  | static const uint8_t LTR390_MAIN_STATUS = 0x07; | ||||||
|  |  | ||||||
| static const float GAINVALUES[5] = {1.0, 3.0, 6.0, 9.0, 18.0}; | static const float GAINVALUES[5] = {1.0, 3.0, 6.0, 9.0, 18.0}; | ||||||
| static const float RESOLUTIONVALUE[6] = {4.0, 2.0, 1.0, 0.5, 0.25, 0.125}; | static const float RESOLUTIONVALUE[6] = {4.0, 2.0, 1.0, 0.5, 0.25, 0.125}; | ||||||
|  |  | ||||||
|  | // Request fastest measurement rate - will be slowed by device if conversion rate is slower. | ||||||
|  | static const float RESOLUTION_SETTING[6] = {0x00, 0x10, 0x20, 0x30, 0x40, 0x50}; | ||||||
| static const uint32_t MODEADDRESSES[2] = {0x0D, 0x10}; | static const uint32_t MODEADDRESSES[2] = {0x0D, 0x10}; | ||||||
|  |  | ||||||
|  | static const float SENSITIVITY_MAX = 2300; | ||||||
|  | static const float INTG_MAX = RESOLUTIONVALUE[0] * 100; | ||||||
|  | static const int GAIN_MAX = GAINVALUES[4]; | ||||||
|  |  | ||||||
| uint32_t little_endian_bytes_to_int(const uint8_t *buffer, uint8_t num_bytes) { | uint32_t little_endian_bytes_to_int(const uint8_t *buffer, uint8_t num_bytes) { | ||||||
|   uint32_t value = 0; |   uint32_t value = 0; | ||||||
|  |  | ||||||
| @@ -58,7 +71,7 @@ void LTR390Component::read_als_() { | |||||||
|   uint32_t als = *val; |   uint32_t als = *val; | ||||||
|  |  | ||||||
|   if (this->light_sensor_ != nullptr) { |   if (this->light_sensor_ != nullptr) { | ||||||
|     float lux = (0.6 * als) / (GAINVALUES[this->gain_] * RESOLUTIONVALUE[this->res_]) * this->wfac_; |     float lux = ((0.6 * als) / (GAINVALUES[this->gain_] * RESOLUTIONVALUE[this->res_])) * this->wfac_; | ||||||
|     this->light_sensor_->publish_state(lux); |     this->light_sensor_->publish_state(lux); | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @@ -74,7 +87,7 @@ void LTR390Component::read_uvs_() { | |||||||
|   uint32_t uv = *val; |   uint32_t uv = *val; | ||||||
|  |  | ||||||
|   if (this->uvi_sensor_ != nullptr) { |   if (this->uvi_sensor_ != nullptr) { | ||||||
|     this->uvi_sensor_->publish_state(uv / LTR390_SENSITIVITY * this->wfac_); |     this->uvi_sensor_->publish_state((uv / this->sensitivity_) * this->wfac_); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   if (this->uv_sensor_ != nullptr) { |   if (this->uv_sensor_ != nullptr) { | ||||||
| @@ -132,12 +145,13 @@ void LTR390Component::setup() { | |||||||
|   // Set gain |   // Set gain | ||||||
|   this->reg(LTR390_GAIN) = gain_; |   this->reg(LTR390_GAIN) = gain_; | ||||||
|  |  | ||||||
|   // Set resolution |   // Set resolution and measurement rate | ||||||
|   uint8_t res = this->reg(LTR390_MEAS_RATE).get(); |   this->reg(LTR390_MEAS_RATE) = RESOLUTION_SETTING[this->res_]; | ||||||
|   // resolution is in bits 5-7 |  | ||||||
|   res &= ~0b01110000; |   // Set sensitivity by linearly scaling against known value in the datasheet | ||||||
|   res |= res << 4; |   float gain_scale = GAINVALUES[this->gain_] / GAIN_MAX; | ||||||
|   this->reg(LTR390_MEAS_RATE) = res; |   float intg_scale = (RESOLUTIONVALUE[this->res_] * 100) / INTG_MAX; | ||||||
|  |   this->sensitivity_ = SENSITIVITY_MAX * gain_scale * intg_scale; | ||||||
|  |  | ||||||
|   // Set sensor read state |   // Set sensor read state | ||||||
|   this->reading_ = false; |   this->reading_ = false; | ||||||
|   | |||||||
| @@ -17,14 +17,6 @@ enum LTR390CTRL { | |||||||
| }; | }; | ||||||
|  |  | ||||||
| // enums from https://github.com/adafruit/Adafruit_LTR390/ | // enums from https://github.com/adafruit/Adafruit_LTR390/ | ||||||
|  |  | ||||||
| static const uint8_t LTR390_MAIN_CTRL = 0x00; |  | ||||||
| static const uint8_t LTR390_MEAS_RATE = 0x04; |  | ||||||
| static const uint8_t LTR390_GAIN = 0x05; |  | ||||||
| static const uint8_t LTR390_PART_ID = 0x06; |  | ||||||
| static const uint8_t LTR390_MAIN_STATUS = 0x07; |  | ||||||
| static const float LTR390_SENSITIVITY = 2300.0; |  | ||||||
|  |  | ||||||
| // Sensing modes | // Sensing modes | ||||||
| enum LTR390MODE { | enum LTR390MODE { | ||||||
|   LTR390_MODE_ALS, |   LTR390_MODE_ALS, | ||||||
| @@ -81,6 +73,7 @@ class LTR390Component : public PollingComponent, public i2c::I2CDevice { | |||||||
|  |  | ||||||
|   LTR390GAIN gain_; |   LTR390GAIN gain_; | ||||||
|   LTR390RESOLUTION res_; |   LTR390RESOLUTION res_; | ||||||
|  |   float sensitivity_; | ||||||
|   float wfac_; |   float wfac_; | ||||||
|  |  | ||||||
|   sensor::Sensor *light_sensor_{nullptr}; |   sensor::Sensor *light_sensor_{nullptr}; | ||||||
|   | |||||||
| @@ -8,6 +8,7 @@ from esphome.const import ( | |||||||
|     CONF_RESOLUTION, |     CONF_RESOLUTION, | ||||||
|     UNIT_LUX, |     UNIT_LUX, | ||||||
|     ICON_BRIGHTNESS_5, |     ICON_BRIGHTNESS_5, | ||||||
|  |     DEVICE_CLASS_EMPTY, | ||||||
|     DEVICE_CLASS_ILLUMINANCE, |     DEVICE_CLASS_ILLUMINANCE, | ||||||
| ) | ) | ||||||
|  |  | ||||||
| @@ -61,22 +62,22 @@ CONFIG_SCHEMA = cv.All( | |||||||
|                 unit_of_measurement=UNIT_COUNTS, |                 unit_of_measurement=UNIT_COUNTS, | ||||||
|                 icon=ICON_BRIGHTNESS_5, |                 icon=ICON_BRIGHTNESS_5, | ||||||
|                 accuracy_decimals=1, |                 accuracy_decimals=1, | ||||||
|                 device_class=DEVICE_CLASS_ILLUMINANCE, |                 device_class=DEVICE_CLASS_EMPTY, | ||||||
|             ), |             ), | ||||||
|             cv.Optional(CONF_UV_INDEX): sensor.sensor_schema( |             cv.Optional(CONF_UV_INDEX): sensor.sensor_schema( | ||||||
|                 unit_of_measurement=UNIT_UVI, |                 unit_of_measurement=UNIT_UVI, | ||||||
|                 icon=ICON_BRIGHTNESS_5, |                 icon=ICON_BRIGHTNESS_5, | ||||||
|                 accuracy_decimals=5, |                 accuracy_decimals=5, | ||||||
|                 device_class=DEVICE_CLASS_ILLUMINANCE, |                 device_class=DEVICE_CLASS_EMPTY, | ||||||
|             ), |             ), | ||||||
|             cv.Optional(CONF_UV): sensor.sensor_schema( |             cv.Optional(CONF_UV): sensor.sensor_schema( | ||||||
|                 unit_of_measurement=UNIT_COUNTS, |                 unit_of_measurement=UNIT_COUNTS, | ||||||
|                 icon=ICON_BRIGHTNESS_5, |                 icon=ICON_BRIGHTNESS_5, | ||||||
|                 accuracy_decimals=1, |                 accuracy_decimals=1, | ||||||
|                 device_class=DEVICE_CLASS_ILLUMINANCE, |                 device_class=DEVICE_CLASS_EMPTY, | ||||||
|             ), |             ), | ||||||
|             cv.Optional(CONF_GAIN, default="X3"): cv.enum(GAIN_OPTIONS), |             cv.Optional(CONF_GAIN, default="X18"): cv.enum(GAIN_OPTIONS), | ||||||
|             cv.Optional(CONF_RESOLUTION, default=18): cv.enum(RES_OPTIONS), |             cv.Optional(CONF_RESOLUTION, default=20): cv.enum(RES_OPTIONS), | ||||||
|             cv.Optional(CONF_WINDOW_CORRECTION_FACTOR, default=1.0): cv.float_range( |             cv.Optional(CONF_WINDOW_CORRECTION_FACTOR, default=1.0): cv.float_range( | ||||||
|                 min=1.0 |                 min=1.0 | ||||||
|             ), |             ), | ||||||
|   | |||||||
							
								
								
									
										367
									
								
								esphome/components/micro_wake_word/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										367
									
								
								esphome/components/micro_wake_word/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,367 @@ | |||||||
|  | import logging | ||||||
|  |  | ||||||
|  | import json | ||||||
|  | import hashlib | ||||||
|  | from urllib.parse import urljoin | ||||||
|  | from pathlib import Path | ||||||
|  | import requests | ||||||
|  |  | ||||||
|  | import esphome.config_validation as cv | ||||||
|  | import esphome.codegen as cg | ||||||
|  |  | ||||||
|  | from esphome.core import CORE, HexInt, EsphomeError | ||||||
|  |  | ||||||
|  | from esphome.components import esp32, microphone | ||||||
|  | from esphome import automation, git, external_files | ||||||
|  | from esphome.automation import register_action, register_condition | ||||||
|  |  | ||||||
|  |  | ||||||
|  | from esphome.const import ( | ||||||
|  |     __version__, | ||||||
|  |     CONF_ID, | ||||||
|  |     CONF_MICROPHONE, | ||||||
|  |     CONF_MODEL, | ||||||
|  |     CONF_URL, | ||||||
|  |     CONF_FILE, | ||||||
|  |     CONF_PATH, | ||||||
|  |     CONF_REF, | ||||||
|  |     CONF_REFRESH, | ||||||
|  |     CONF_TYPE, | ||||||
|  |     CONF_USERNAME, | ||||||
|  |     CONF_PASSWORD, | ||||||
|  |     CONF_RAW_DATA_ID, | ||||||
|  |     TYPE_GIT, | ||||||
|  |     TYPE_LOCAL, | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | _LOGGER = logging.getLogger(__name__) | ||||||
|  |  | ||||||
|  | CODEOWNERS = ["@kahrendt", "@jesserockz"] | ||||||
|  | DEPENDENCIES = ["microphone"] | ||||||
|  | DOMAIN = "micro_wake_word" | ||||||
|  |  | ||||||
|  | CONF_PROBABILITY_CUTOFF = "probability_cutoff" | ||||||
|  | CONF_SLIDING_WINDOW_AVERAGE_SIZE = "sliding_window_average_size" | ||||||
|  | CONF_ON_WAKE_WORD_DETECTED = "on_wake_word_detected" | ||||||
|  |  | ||||||
|  | TYPE_HTTP = "http" | ||||||
|  |  | ||||||
|  | micro_wake_word_ns = cg.esphome_ns.namespace("micro_wake_word") | ||||||
|  |  | ||||||
|  | MicroWakeWord = micro_wake_word_ns.class_("MicroWakeWord", cg.Component) | ||||||
|  |  | ||||||
|  | StartAction = micro_wake_word_ns.class_("StartAction", automation.Action) | ||||||
|  | StopAction = micro_wake_word_ns.class_("StopAction", automation.Action) | ||||||
|  |  | ||||||
|  | IsRunningCondition = micro_wake_word_ns.class_( | ||||||
|  |     "IsRunningCondition", automation.Condition | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def _validate_json_filename(value): | ||||||
|  |     value = cv.string(value) | ||||||
|  |     if not value.endswith(".json"): | ||||||
|  |         raise cv.Invalid("Manifest filename must end with .json") | ||||||
|  |     return value | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def _process_git_source(config): | ||||||
|  |     repo_dir, _ = git.clone_or_update( | ||||||
|  |         url=config[CONF_URL], | ||||||
|  |         ref=config.get(CONF_REF), | ||||||
|  |         refresh=config[CONF_REFRESH], | ||||||
|  |         domain=DOMAIN, | ||||||
|  |         username=config.get(CONF_USERNAME), | ||||||
|  |         password=config.get(CONF_PASSWORD), | ||||||
|  |     ) | ||||||
|  |  | ||||||
|  |     if not (repo_dir / config[CONF_FILE]).exists(): | ||||||
|  |         raise cv.Invalid("File does not exist in repository") | ||||||
|  |  | ||||||
|  |     return config | ||||||
|  |  | ||||||
|  |  | ||||||
|  | CV_GIT_SCHEMA = cv.GIT_SCHEMA | ||||||
|  | if isinstance(CV_GIT_SCHEMA, dict): | ||||||
|  |     CV_GIT_SCHEMA = cv.Schema(CV_GIT_SCHEMA) | ||||||
|  |  | ||||||
|  | GIT_SCHEMA = cv.All( | ||||||
|  |     CV_GIT_SCHEMA.extend( | ||||||
|  |         { | ||||||
|  |             cv.Required(CONF_FILE): _validate_json_filename, | ||||||
|  |             cv.Optional(CONF_REFRESH, default="1d"): cv.All( | ||||||
|  |                 cv.string, cv.source_refresh | ||||||
|  |             ), | ||||||
|  |         } | ||||||
|  |     ), | ||||||
|  |     _process_git_source, | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | KEY_WAKE_WORD = "wake_word" | ||||||
|  | KEY_AUTHOR = "author" | ||||||
|  | KEY_WEBSITE = "website" | ||||||
|  | KEY_VERSION = "version" | ||||||
|  | KEY_MICRO = "micro" | ||||||
|  | KEY_MINIMUM_ESPHOME_VERSION = "minimum_esphome_version" | ||||||
|  |  | ||||||
|  | MANIFEST_SCHEMA_V1 = cv.Schema( | ||||||
|  |     { | ||||||
|  |         cv.Required(CONF_TYPE): "micro", | ||||||
|  |         cv.Required(KEY_WAKE_WORD): cv.string, | ||||||
|  |         cv.Required(KEY_AUTHOR): cv.string, | ||||||
|  |         cv.Required(KEY_WEBSITE): cv.url, | ||||||
|  |         cv.Required(KEY_VERSION): cv.All(cv.int_, 1), | ||||||
|  |         cv.Required(CONF_MODEL): cv.string, | ||||||
|  |         cv.Required(KEY_MICRO): cv.Schema( | ||||||
|  |             { | ||||||
|  |                 cv.Required(CONF_PROBABILITY_CUTOFF): cv.float_, | ||||||
|  |                 cv.Required(CONF_SLIDING_WINDOW_AVERAGE_SIZE): cv.positive_int, | ||||||
|  |                 cv.Optional(KEY_MINIMUM_ESPHOME_VERSION): cv.All( | ||||||
|  |                     cv.version_number, cv.validate_esphome_version | ||||||
|  |                 ), | ||||||
|  |             } | ||||||
|  |         ), | ||||||
|  |     } | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def _compute_local_file_path(config: dict) -> Path: | ||||||
|  |     url = config[CONF_URL] | ||||||
|  |     h = hashlib.new("sha256") | ||||||
|  |     h.update(url.encode()) | ||||||
|  |     key = h.hexdigest()[:8] | ||||||
|  |     base_dir = external_files.compute_local_file_dir(DOMAIN) | ||||||
|  |     return base_dir / key | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def _download_file(url: str, path: Path) -> bytes: | ||||||
|  |     if not external_files.has_remote_file_changed(url, path): | ||||||
|  |         _LOGGER.debug("Remote file has not changed, skipping download") | ||||||
|  |         return path.read_bytes() | ||||||
|  |  | ||||||
|  |     try: | ||||||
|  |         req = requests.get( | ||||||
|  |             url, | ||||||
|  |             timeout=external_files.NETWORK_TIMEOUT, | ||||||
|  |             headers={"User-agent": f"ESPHome/{__version__} (https://esphome.io)"}, | ||||||
|  |         ) | ||||||
|  |         req.raise_for_status() | ||||||
|  |     except requests.exceptions.RequestException as e: | ||||||
|  |         raise cv.Invalid(f"Could not download file from {url}: {e}") from e | ||||||
|  |  | ||||||
|  |     path.parent.mkdir(parents=True, exist_ok=True) | ||||||
|  |     path.write_bytes(req.content) | ||||||
|  |     return req.content | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def _process_http_source(config): | ||||||
|  |     url = config[CONF_URL] | ||||||
|  |     path = _compute_local_file_path(config) | ||||||
|  |  | ||||||
|  |     json_path = path / "manifest.json" | ||||||
|  |  | ||||||
|  |     json_contents = _download_file(url, json_path) | ||||||
|  |  | ||||||
|  |     manifest_data = json.loads(json_contents) | ||||||
|  |     if not isinstance(manifest_data, dict): | ||||||
|  |         raise cv.Invalid("Manifest file must contain a JSON object") | ||||||
|  |  | ||||||
|  |     try: | ||||||
|  |         MANIFEST_SCHEMA_V1(manifest_data) | ||||||
|  |     except cv.Invalid as e: | ||||||
|  |         raise cv.Invalid(f"Invalid manifest file: {e}") from e | ||||||
|  |  | ||||||
|  |     model = manifest_data[CONF_MODEL] | ||||||
|  |     model_url = urljoin(url, model) | ||||||
|  |  | ||||||
|  |     model_path = path / model | ||||||
|  |  | ||||||
|  |     _download_file(str(model_url), model_path) | ||||||
|  |  | ||||||
|  |     return config | ||||||
|  |  | ||||||
|  |  | ||||||
|  | HTTP_SCHEMA = cv.All( | ||||||
|  |     { | ||||||
|  |         cv.Required(CONF_URL): cv.url, | ||||||
|  |     }, | ||||||
|  |     _process_http_source, | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | LOCAL_SCHEMA = cv.Schema( | ||||||
|  |     { | ||||||
|  |         cv.Required(CONF_PATH): cv.All(_validate_json_filename, cv.file_), | ||||||
|  |     } | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def _validate_source_model_name(value): | ||||||
|  |     if not isinstance(value, str): | ||||||
|  |         raise cv.Invalid("Model name must be a string") | ||||||
|  |  | ||||||
|  |     if value.endswith(".json"): | ||||||
|  |         raise cv.Invalid("Model name must not end with .json") | ||||||
|  |  | ||||||
|  |     return MODEL_SOURCE_SCHEMA( | ||||||
|  |         { | ||||||
|  |             CONF_TYPE: TYPE_HTTP, | ||||||
|  |             CONF_URL: f"https://github.com/esphome/micro-wake-word-models/raw/main/models/{value}.json", | ||||||
|  |         } | ||||||
|  |     ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def _validate_source_shorthand(value): | ||||||
|  |     if not isinstance(value, str): | ||||||
|  |         raise cv.Invalid("Shorthand only for strings") | ||||||
|  |  | ||||||
|  |     try:  # Test for model name | ||||||
|  |         return _validate_source_model_name(value) | ||||||
|  |     except cv.Invalid: | ||||||
|  |         pass | ||||||
|  |  | ||||||
|  |     try:  # Test for local path | ||||||
|  |         return MODEL_SOURCE_SCHEMA({CONF_TYPE: TYPE_LOCAL, CONF_PATH: value}) | ||||||
|  |     except cv.Invalid: | ||||||
|  |         pass | ||||||
|  |  | ||||||
|  |     try:  # Test for http url | ||||||
|  |         return MODEL_SOURCE_SCHEMA({CONF_TYPE: TYPE_HTTP, CONF_URL: value}) | ||||||
|  |     except cv.Invalid: | ||||||
|  |         pass | ||||||
|  |  | ||||||
|  |     git_file = git.GitFile.from_shorthand(value) | ||||||
|  |  | ||||||
|  |     conf = { | ||||||
|  |         CONF_TYPE: TYPE_GIT, | ||||||
|  |         CONF_URL: git_file.git_url, | ||||||
|  |         CONF_FILE: git_file.filename, | ||||||
|  |     } | ||||||
|  |     if git_file.ref: | ||||||
|  |         conf[CONF_REF] = git_file.ref | ||||||
|  |  | ||||||
|  |     try: | ||||||
|  |         return MODEL_SOURCE_SCHEMA(conf) | ||||||
|  |     except cv.Invalid as e: | ||||||
|  |         raise cv.Invalid( | ||||||
|  |             f"Could not find file '{git_file.filename}' in the repository. Please make sure it exists." | ||||||
|  |         ) from e | ||||||
|  |  | ||||||
|  |  | ||||||
|  | MODEL_SOURCE_SCHEMA = cv.Any( | ||||||
|  |     _validate_source_shorthand, | ||||||
|  |     cv.typed_schema( | ||||||
|  |         { | ||||||
|  |             TYPE_GIT: GIT_SCHEMA, | ||||||
|  |             TYPE_LOCAL: LOCAL_SCHEMA, | ||||||
|  |             TYPE_HTTP: HTTP_SCHEMA, | ||||||
|  |         } | ||||||
|  |     ), | ||||||
|  |     msg="Not a valid model name, local path, http(s) url, or github shorthand", | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | CONFIG_SCHEMA = cv.All( | ||||||
|  |     cv.Schema( | ||||||
|  |         { | ||||||
|  |             cv.GenerateID(): cv.declare_id(MicroWakeWord), | ||||||
|  |             cv.GenerateID(CONF_MICROPHONE): cv.use_id(microphone.Microphone), | ||||||
|  |             cv.Optional(CONF_PROBABILITY_CUTOFF): cv.percentage, | ||||||
|  |             cv.Optional(CONF_SLIDING_WINDOW_AVERAGE_SIZE): cv.positive_int, | ||||||
|  |             cv.Optional(CONF_ON_WAKE_WORD_DETECTED): automation.validate_automation( | ||||||
|  |                 single=True | ||||||
|  |             ), | ||||||
|  |             cv.Required(CONF_MODEL): MODEL_SOURCE_SCHEMA, | ||||||
|  |             cv.GenerateID(CONF_RAW_DATA_ID): cv.declare_id(cg.uint8), | ||||||
|  |         } | ||||||
|  |     ).extend(cv.COMPONENT_SCHEMA), | ||||||
|  |     cv.only_with_esp_idf, | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def _load_model_data(manifest_path: Path): | ||||||
|  |     with open(manifest_path, encoding="utf-8") as f: | ||||||
|  |         manifest = json.load(f) | ||||||
|  |  | ||||||
|  |     try: | ||||||
|  |         MANIFEST_SCHEMA_V1(manifest) | ||||||
|  |     except cv.Invalid as e: | ||||||
|  |         raise EsphomeError(f"Invalid manifest file: {e}") from e | ||||||
|  |  | ||||||
|  |     model_path = urljoin(str(manifest_path), manifest[CONF_MODEL]) | ||||||
|  |  | ||||||
|  |     with open(model_path, "rb") as f: | ||||||
|  |         model = f.read() | ||||||
|  |  | ||||||
|  |     return manifest, model | ||||||
|  |  | ||||||
|  |  | ||||||
|  | async def to_code(config): | ||||||
|  |     var = cg.new_Pvariable(config[CONF_ID]) | ||||||
|  |     await cg.register_component(var, config) | ||||||
|  |  | ||||||
|  |     mic = await cg.get_variable(config[CONF_MICROPHONE]) | ||||||
|  |     cg.add(var.set_microphone(mic)) | ||||||
|  |  | ||||||
|  |     if on_wake_word_detection_config := config.get(CONF_ON_WAKE_WORD_DETECTED): | ||||||
|  |         await automation.build_automation( | ||||||
|  |             var.get_wake_word_detected_trigger(), | ||||||
|  |             [(cg.std_string, "wake_word")], | ||||||
|  |             on_wake_word_detection_config, | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |     esp32.add_idf_component( | ||||||
|  |         name="esp-tflite-micro", | ||||||
|  |         repo="https://github.com/espressif/esp-tflite-micro", | ||||||
|  |     ) | ||||||
|  |  | ||||||
|  |     cg.add_build_flag("-DTF_LITE_STATIC_MEMORY") | ||||||
|  |     cg.add_build_flag("-DTF_LITE_DISABLE_X86_NEON") | ||||||
|  |     cg.add_build_flag("-DESP_NN") | ||||||
|  |  | ||||||
|  |     model_config = config.get(CONF_MODEL) | ||||||
|  |     data = [] | ||||||
|  |     if model_config[CONF_TYPE] == TYPE_GIT: | ||||||
|  |         # compute path to model file | ||||||
|  |         key = f"{model_config[CONF_URL]}@{model_config.get(CONF_REF)}" | ||||||
|  |         base_dir = Path(CORE.data_dir) / DOMAIN | ||||||
|  |         h = hashlib.new("sha256") | ||||||
|  |         h.update(key.encode()) | ||||||
|  |         file: Path = base_dir / h.hexdigest()[:8] / model_config[CONF_FILE] | ||||||
|  |  | ||||||
|  |     elif model_config[CONF_TYPE] == TYPE_LOCAL: | ||||||
|  |         file = model_config[CONF_PATH] | ||||||
|  |  | ||||||
|  |     elif model_config[CONF_TYPE] == TYPE_HTTP: | ||||||
|  |         file = _compute_local_file_path(model_config) / "manifest.json" | ||||||
|  |  | ||||||
|  |     manifest, data = _load_model_data(file) | ||||||
|  |  | ||||||
|  |     rhs = [HexInt(x) for x in data] | ||||||
|  |     prog_arr = cg.progmem_array(config[CONF_RAW_DATA_ID], rhs) | ||||||
|  |     cg.add(var.set_model_start(prog_arr)) | ||||||
|  |  | ||||||
|  |     probability_cutoff = config.get( | ||||||
|  |         CONF_PROBABILITY_CUTOFF, manifest[KEY_MICRO][CONF_PROBABILITY_CUTOFF] | ||||||
|  |     ) | ||||||
|  |     cg.add(var.set_probability_cutoff(probability_cutoff)) | ||||||
|  |     sliding_window_average_size = config.get( | ||||||
|  |         CONF_SLIDING_WINDOW_AVERAGE_SIZE, | ||||||
|  |         manifest[KEY_MICRO][CONF_SLIDING_WINDOW_AVERAGE_SIZE], | ||||||
|  |     ) | ||||||
|  |     cg.add(var.set_sliding_window_average_size(sliding_window_average_size)) | ||||||
|  |  | ||||||
|  |     cg.add(var.set_wake_word(manifest[KEY_WAKE_WORD])) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | MICRO_WAKE_WORD_ACTION_SCHEMA = cv.Schema({cv.GenerateID(): cv.use_id(MicroWakeWord)}) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @register_action("micro_wake_word.start", StartAction, MICRO_WAKE_WORD_ACTION_SCHEMA) | ||||||
|  | @register_action("micro_wake_word.stop", StopAction, MICRO_WAKE_WORD_ACTION_SCHEMA) | ||||||
|  | @register_condition( | ||||||
|  |     "micro_wake_word.is_running", IsRunningCondition, MICRO_WAKE_WORD_ACTION_SCHEMA | ||||||
|  | ) | ||||||
|  | async def micro_wake_word_action_to_code(config, action_id, template_arg, args): | ||||||
|  |     var = cg.new_Pvariable(action_id, template_arg) | ||||||
|  |     await cg.register_parented(var, config[CONF_ID]) | ||||||
|  |     return var | ||||||
| @@ -0,0 +1,493 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #ifdef USE_ESP_IDF | ||||||
|  |  | ||||||
|  | // Converted audio_preprocessor_int8.tflite | ||||||
|  | // From https://github.com/tensorflow/tflite-micro/tree/main/tensorflow/lite/micro/examples/micro_speech/models accessed | ||||||
|  | // January 2024 | ||||||
|  | // | ||||||
|  | // Copyright 2023 The TensorFlow Authors. All Rights Reserved. | ||||||
|  | // | ||||||
|  | // Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  | // you may not use this file except in compliance with the License. | ||||||
|  | // You may obtain a copy of the License at | ||||||
|  | // | ||||||
|  | //     http://www.apache.org/licenses/LICENSE-2.0 | ||||||
|  | // | ||||||
|  | // Unless required by applicable law or agreed to in writing, software | ||||||
|  | // distributed under the License is distributed on an "AS IS" BASIS, | ||||||
|  | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||
|  | // See the License for the specific language governing permissions and | ||||||
|  | // limitations under the License. | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace micro_wake_word { | ||||||
|  |  | ||||||
|  | const unsigned char G_AUDIO_PREPROCESSOR_INT8_TFLITE[] = { | ||||||
|  |     0x1c, 0x00, 0x00, 0x00, 0x54, 0x46, 0x4c, 0x33, 0x14, 0x00, 0x20, 0x00, 0x1c, 0x00, 0x18, 0x00, 0x14, 0x00, 0x10, | ||||||
|  |     0x00, 0x0c, 0x00, 0x00, 0x00, 0x08, 0x00, 0x04, 0x00, 0x14, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x88, 0x00, | ||||||
|  |     0x00, 0x00, 0xe0, 0x00, 0x00, 0x00, 0x80, 0x0e, 0x00, 0x00, 0x90, 0x0e, 0x00, 0x00, 0xcc, 0x1f, 0x00, 0x00, 0x03, | ||||||
|  |     0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0xe2, 0xeb, 0xff, 0xff, 0x0c, 0x00, 0x00, 0x00, | ||||||
|  |     0x1c, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x73, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, | ||||||
|  |     0x5f, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x94, 0xff, | ||||||
|  |     0xff, 0xff, 0x2a, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x6f, 0x75, 0x74, 0x70, 0x75, | ||||||
|  |     0x74, 0x5f, 0x30, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0xc2, 0xf5, 0xff, 0xff, | ||||||
|  |     0x04, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x61, 0x75, 0x64, 0x69, 0x6f, 0x5f, 0x66, 0x72, 0x61, 0x6d, 0x65, | ||||||
|  |     0x00, 0x02, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0xdc, 0xff, 0xff, 0xff, 0x2d, 0x00, | ||||||
|  |     0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x43, 0x4f, 0x4e, 0x56, 0x45, 0x52, 0x53, 0x49, 0x4f, | ||||||
|  |     0x4e, 0x5f, 0x4d, 0x45, 0x54, 0x41, 0x44, 0x41, 0x54, 0x41, 0x00, 0x08, 0x00, 0x0c, 0x00, 0x08, 0x00, 0x04, 0x00, | ||||||
|  |     0x08, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x6d, 0x69, 0x6e, | ||||||
|  |     0x5f, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x00, 0x2e, 0x00, | ||||||
|  |     0x00, 0x00, 0x9c, 0x0d, 0x00, 0x00, 0x94, 0x0d, 0x00, 0x00, 0xc4, 0x09, 0x00, 0x00, 0x6c, 0x09, 0x00, 0x00, 0x48, | ||||||
|  |     0x09, 0x00, 0x00, 0x34, 0x09, 0x00, 0x00, 0x20, 0x09, 0x00, 0x00, 0x0c, 0x09, 0x00, 0x00, 0xf8, 0x08, 0x00, 0x00, | ||||||
|  |     0xec, 0x07, 0x00, 0x00, 0x88, 0x07, 0x00, 0x00, 0x24, 0x07, 0x00, 0x00, 0xc0, 0x06, 0x00, 0x00, 0x38, 0x04, 0x00, | ||||||
|  |     0x00, 0xb0, 0x01, 0x00, 0x00, 0x9c, 0x01, 0x00, 0x00, 0x88, 0x01, 0x00, 0x00, 0x74, 0x01, 0x00, 0x00, 0x60, 0x01, | ||||||
|  |     0x00, 0x00, 0x4c, 0x01, 0x00, 0x00, 0x44, 0x01, 0x00, 0x00, 0x3c, 0x01, 0x00, 0x00, 0x34, 0x01, 0x00, 0x00, 0x2c, | ||||||
|  |     0x01, 0x00, 0x00, 0x24, 0x01, 0x00, 0x00, 0x1c, 0x01, 0x00, 0x00, 0x14, 0x01, 0x00, 0x00, 0x0c, 0x01, 0x00, 0x00, | ||||||
|  |     0x04, 0x01, 0x00, 0x00, 0xfc, 0x00, 0x00, 0x00, 0xf4, 0x00, 0x00, 0x00, 0xec, 0x00, 0x00, 0x00, 0xe4, 0x00, 0x00, | ||||||
|  |     0x00, 0xdc, 0x00, 0x00, 0x00, 0xd4, 0x00, 0x00, 0x00, 0xcc, 0x00, 0x00, 0x00, 0xc4, 0x00, 0x00, 0x00, 0xbc, 0x00, | ||||||
|  |     0x00, 0x00, 0xb4, 0x00, 0x00, 0x00, 0xac, 0x00, 0x00, 0x00, 0xa4, 0x00, 0x00, 0x00, 0x9c, 0x00, 0x00, 0x00, 0x94, | ||||||
|  |     0x00, 0x00, 0x00, 0x8c, 0x00, 0x00, 0x00, 0x6c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0xf2, 0xf6, 0xff, 0xff, | ||||||
|  |     0x04, 0x00, 0x00, 0x00, 0x58, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x08, 0x00, 0x0c, 0x00, 0x08, 0x00, 0x04, | ||||||
|  |     0x00, 0x08, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x08, 0x00, 0x0c, 0x00, 0x08, 0x00, | ||||||
|  |     0x07, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | ||||||
|  |     0x00, 0x0a, 0x00, 0x10, 0x00, 0x0c, 0x00, 0x08, 0x00, 0x04, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, | ||||||
|  |     0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x32, 0x2e, 0x31, 0x32, 0x2e, 0x30, 0x00, | ||||||
|  |     0x00, 0x56, 0xf7, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x32, 0x2e, 0x38, 0x2e, 0x30, 0x00, | ||||||
|  |     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd4, 0xe1, 0xff, 0xff, 0xd8, 0xe1, 0xff, 0xff, 0xdc, | ||||||
|  |     0xe1, 0xff, 0xff, 0xe0, 0xe1, 0xff, 0xff, 0xe4, 0xe1, 0xff, 0xff, 0xe8, 0xe1, 0xff, 0xff, 0xec, 0xe1, 0xff, 0xff, | ||||||
|  |     0xf0, 0xe1, 0xff, 0xff, 0xf4, 0xe1, 0xff, 0xff, 0xf8, 0xe1, 0xff, 0xff, 0xfc, 0xe1, 0xff, 0xff, 0x00, 0xe2, 0xff, | ||||||
|  |     0xff, 0x04, 0xe2, 0xff, 0xff, 0x08, 0xe2, 0xff, 0xff, 0x0c, 0xe2, 0xff, 0xff, 0x10, 0xe2, 0xff, 0xff, 0x14, 0xe2, | ||||||
|  |     0xff, 0xff, 0x18, 0xe2, 0xff, 0xff, 0x1c, 0xe2, 0xff, 0xff, 0x20, 0xe2, 0xff, 0xff, 0x24, 0xe2, 0xff, 0xff, 0x28, | ||||||
|  |     0xe2, 0xff, 0xff, 0x2c, 0xe2, 0xff, 0xff, 0x30, 0xe2, 0xff, 0xff, 0xd2, 0xf7, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00, | ||||||
|  |     0x04, 0x00, 0x00, 0x00, 0x9a, 0x02, 0x00, 0x00, 0xe2, 0xf7, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, | ||||||
|  |     0x00, 0x00, 0x01, 0x00, 0x00, 0xf2, 0xf7, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x80, 0xff, | ||||||
|  |     0xff, 0xff, 0x02, 0xf8, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x12, | ||||||
|  |     0xf8, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x00, 0x00, 0x22, 0xf8, 0xff, 0xff, | ||||||
|  |     0x04, 0x00, 0x00, 0x00, 0x78, 0x02, 0x00, 0x00, 0x00, 0x00, 0x61, 0x05, 0x00, 0x00, 0x00, 0x00, 0x23, 0x0b, 0x41, | ||||||
|  |     0x01, 0x00, 0x00, 0x00, 0x00, 0xb3, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x74, 0x0e, 0x80, 0x05, | ||||||
|  |     0x00, 0x00, 0x00, 0x00, 0xd1, 0x0c, 0x63, 0x04, 0x00, 0x00, 0x00, 0x00, 0x34, 0x0c, 0x3f, 0x04, 0x00, 0x00, 0x00, | ||||||
|  |     0x00, 0x81, 0x0c, 0xf7, 0x04, 0x00, 0x00, 0x00, 0x00, 0x9f, 0x0d, 0x77, 0x06, 0x00, 0x00, 0x00, 0x00, 0x7b, 0x0f, | ||||||
|  |     0xa9, 0x08, 0x01, 0x02, 0x7f, 0x0b, 0x22, 0x05, 0x00, 0x00, 0x00, 0x00, 0xe9, 0x0e, 0xd1, 0x08, 0xdb, 0x02, 0x00, | ||||||
|  |     0x00, 0x00, 0x00, 0x03, 0x0d, 0x4a, 0x07, 0xad, 0x01, 0x2c, 0x0c, 0xc6, 0x06, 0x79, 0x01, 0x00, 0x00, 0x00, 0x00, | ||||||
|  |     0x45, 0x0c, 0x29, 0x07, 0x23, 0x02, 0x34, 0x0d, 0x5b, 0x08, 0x96, 0x03, 0x00, 0x00, 0x00, 0x00, 0xe5, 0x0e, 0x48, | ||||||
|  |     0x0a, 0xbd, 0x05, 0x45, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xde, 0x0c, 0x88, 0x08, 0x43, 0x04, | ||||||
|  |     0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe9, 0x0b, 0xd3, 0x07, 0xcb, 0x03, 0xd2, 0x0f, 0xe7, | ||||||
|  |     0x0b, 0x09, 0x08, 0x39, 0x04, 0x76, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xbf, 0x0c, 0x14, 0x09, | ||||||
|  |     0x75, 0x05, 0xe2, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5a, 0x0e, 0xdd, 0x0a, 0x6b, 0x07, 0x03, | ||||||
|  |     0x04, 0xa6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x53, 0x0d, 0x09, 0x0a, 0xc9, 0x06, 0x93, 0x03, 0x65, 0x00, 0x00, 0x00, | ||||||
|  |     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x41, 0x0d, 0x25, 0x0a, 0x12, 0x07, 0x07, 0x04, 0x05, 0x01, 0x00, 0x00, 0x00, | ||||||
|  |     0x00, 0x0a, 0x0e, 0x17, 0x0b, 0x2c, 0x08, 0x49, 0x05, 0x6d, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | ||||||
|  |     0x98, 0x0f, 0xcb, 0x0c, 0x04, 0x0a, 0x44, 0x07, 0x8b, 0x04, 0xd8, 0x01, 0x00, 0x00, 0x00, 0x00, 0x2c, 0x0f, 0x87, | ||||||
|  |     0x0c, 0xe7, 0x09, 0x4e, 0x07, 0xba, 0x04, 0x2d, 0x02, 0x00, 0x00, 0x00, 0x00, 0xa5, 0x0f, 0x23, 0x0d, 0xa7, 0x0a, | ||||||
|  |     0x30, 0x08, 0xbe, 0x05, 0x52, 0x03, 0xeb, 0x00, 0x89, 0x0e, 0x2c, 0x0c, 0xd4, 0x09, 0x81, 0x07, 0x33, 0x05, 0xe9, | ||||||
|  |     0x02, 0xa5, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x0e, 0x29, 0x0c, 0xf1, 0x09, 0xbe, 0x07, 0x90, 0x05, 0x65, 0x03, | ||||||
|  |     0x3f, 0x01, 0x1d, 0x0f, 0xff, 0x0c, 0xe5, 0x0a, 0xcf, 0x08, 0xbc, 0x06, 0xae, 0x04, 0xa3, 0x02, 0x9c, 0x00, 0x99, | ||||||
|  |     0x0e, 0x99, 0x0c, 0x9d, 0x0a, 0xa4, 0x08, 0xaf, 0x06, 0xbd, 0x04, 0xcf, 0x02, 0xe4, 0x00, 0xfc, 0x0e, 0x17, 0x0d, | ||||||
|  |     0x36, 0x0b, 0x57, 0x09, 0x7c, 0x07, 0xa4, 0x05, 0xcf, 0x03, 0xfd, 0x01, 0x2e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | ||||||
|  |     0x00, 0x00, 0x00, 0x62, 0x0e, 0x98, 0x0c, 0xd2, 0x0a, 0x0e, 0x09, 0x4d, 0x07, 0x8f, 0x05, 0xd4, 0x03, 0x1b, 0x02, | ||||||
|  |     0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb1, 0x0e, 0x00, 0x0d, 0x52, 0x0b, 0xa6, 0x09, 0xfd, 0x07, 0x56, 0x06, 0xb1, | ||||||
|  |     0x04, 0x0f, 0x03, 0x6f, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd2, 0x0f, 0x37, 0x0e, 0x9e, 0x0c, | ||||||
|  |     0x08, 0x0b, 0x73, 0x09, 0xe1, 0x07, 0x52, 0x06, 0xc4, 0x04, 0x38, 0x03, 0xaf, 0x01, 0x28, 0x00, 0xa3, 0x0e, 0x1f, | ||||||
|  |     0x0d, 0x9e, 0x0b, 0x1f, 0x0a, 0xa2, 0x08, 0x27, 0x07, 0xae, 0x05, 0x37, 0x04, 0xc2, 0x02, 0x4e, 0x01, 0x00, 0x00, | ||||||
|  |     0x00, 0x00, 0xdd, 0x0f, 0x6d, 0x0e, 0xff, 0x0c, 0x93, 0x0b, 0x29, 0x0a, 0xc1, 0x08, 0x5a, 0x07, 0xf5, 0x05, 0x92, | ||||||
|  |     0x04, 0x30, 0x03, 0xd1, 0x01, 0x73, 0x00, 0x16, 0x0f, 0xbc, 0x0d, 0x62, 0x0c, 0x0b, 0x0b, 0xb5, 0x09, 0x61, 0x08, | ||||||
|  |     0x0e, 0x07, 0xbd, 0x05, 0x6d, 0x04, 0x1f, 0x03, 0xd3, 0x01, 0x88, 0x00, 0x3e, 0x0f, 0xf6, 0x0d, 0xaf, 0x0c, 0x6a, | ||||||
|  |     0x0b, 0x27, 0x0a, 0xe4, 0x08, 0xa3, 0x07, 0x64, 0x06, 0x26, 0x05, 0xe9, 0x03, 0xae, 0x02, 0x74, 0x01, 0x3b, 0x00, | ||||||
|  |     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x0f, 0xce, 0x0d, 0x99, 0x0c, 0x66, 0x0b, 0x34, 0x0a, 0x03, | ||||||
|  |     0x09, 0xd3, 0x07, 0xa5, 0x06, 0x78, 0x05, 0x4c, 0x04, 0x22, 0x03, 0xf8, 0x01, 0xd0, 0x00, 0x00, 0x00, 0x00, 0x00, | ||||||
|  |     0xa9, 0x0f, 0x83, 0x0e, 0x5f, 0x0d, 0x3b, 0x0c, 0x19, 0x0b, 0xf8, 0x09, 0xd8, 0x08, 0xb9, 0x07, 0x9b, 0x06, 0x7e, | ||||||
|  |     0x05, 0x63, 0x04, 0x48, 0x03, 0x2f, 0x02, 0x17, 0x01, 0x00, 0x00, 0x00, 0x00, 0xa6, 0xfa, 0xff, 0xff, 0x04, 0x00, | ||||||
|  |     0x00, 0x00, 0x78, 0x02, 0x00, 0x00, 0x00, 0x00, 0x9e, 0x0a, 0x00, 0x00, 0x00, 0x00, 0xdc, 0x04, 0xbe, 0x0e, 0x00, | ||||||
|  |     0x00, 0x00, 0x00, 0x4c, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8b, 0x01, 0x7f, 0x0a, 0x00, 0x00, | ||||||
|  |     0x00, 0x00, 0x2e, 0x03, 0x9c, 0x0b, 0x00, 0x00, 0x00, 0x00, 0xcb, 0x03, 0xc0, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x7e, | ||||||
|  |     0x03, 0x08, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x60, 0x02, 0x88, 0x09, 0x00, 0x00, 0x00, 0x00, 0x84, 0x00, 0x56, 0x07, | ||||||
|  |     0xfe, 0x0d, 0x80, 0x04, 0xdd, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x16, 0x01, 0x2e, 0x07, 0x24, 0x0d, 0x00, 0x00, 0x00, | ||||||
|  |     0x00, 0xfc, 0x02, 0xb5, 0x08, 0x52, 0x0e, 0xd3, 0x03, 0x39, 0x09, 0x86, 0x0e, 0x00, 0x00, 0x00, 0x00, 0xba, 0x03, | ||||||
|  |     0xd6, 0x08, 0xdc, 0x0d, 0xcb, 0x02, 0xa4, 0x07, 0x69, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x1a, 0x01, 0xb7, 0x05, 0x42, | ||||||
|  |     0x0a, 0xba, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x03, 0x77, 0x07, 0xbc, 0x0b, 0xf1, 0x0f, | ||||||
|  |     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, 0x04, 0x2c, 0x08, 0x34, 0x0c, 0x2d, 0x00, 0x18, 0x04, 0xf6, | ||||||
|  |     0x07, 0xc6, 0x0b, 0x89, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x03, 0xeb, 0x06, 0x8a, 0x0a, | ||||||
|  |     0x1d, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa5, 0x01, 0x22, 0x05, 0x94, 0x08, 0xfc, 0x0b, 0x59, | ||||||
|  |     0x0f, 0x00, 0x00, 0x00, 0x00, 0xac, 0x02, 0xf6, 0x05, 0x36, 0x09, 0x6c, 0x0c, 0x9a, 0x0f, 0x00, 0x00, 0x00, 0x00, | ||||||
|  |     0x00, 0x00, 0x00, 0x00, 0xbe, 0x02, 0xda, 0x05, 0xed, 0x08, 0xf8, 0x0b, 0xfa, 0x0e, 0x00, 0x00, 0x00, 0x00, 0xf5, | ||||||
|  |     0x01, 0xe8, 0x04, 0xd3, 0x07, 0xb6, 0x0a, 0x92, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x67, 0x00, | ||||||
|  |     0x34, 0x03, 0xfb, 0x05, 0xbb, 0x08, 0x74, 0x0b, 0x27, 0x0e, 0x00, 0x00, 0x00, 0x00, 0xd3, 0x00, 0x78, 0x03, 0x18, | ||||||
|  |     0x06, 0xb1, 0x08, 0x45, 0x0b, 0xd2, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x5a, 0x00, 0xdc, 0x02, 0x58, 0x05, 0xcf, 0x07, | ||||||
|  |     0x41, 0x0a, 0xad, 0x0c, 0x14, 0x0f, 0x76, 0x01, 0xd3, 0x03, 0x2b, 0x06, 0x7e, 0x08, 0xcc, 0x0a, 0x16, 0x0d, 0x5a, | ||||||
|  |     0x0f, 0x00, 0x00, 0x00, 0x00, 0x9b, 0x01, 0xd6, 0x03, 0x0e, 0x06, 0x41, 0x08, 0x6f, 0x0a, 0x9a, 0x0c, 0xc0, 0x0e, | ||||||
|  |     0xe2, 0x00, 0x00, 0x03, 0x1a, 0x05, 0x30, 0x07, 0x43, 0x09, 0x51, 0x0b, 0x5c, 0x0d, 0x63, 0x0f, 0x66, 0x01, 0x66, | ||||||
|  |     0x03, 0x62, 0x05, 0x5b, 0x07, 0x50, 0x09, 0x42, 0x0b, 0x30, 0x0d, 0x1b, 0x0f, 0x03, 0x01, 0xe8, 0x02, 0xc9, 0x04, | ||||||
|  |     0xa8, 0x06, 0x83, 0x08, 0x5b, 0x0a, 0x30, 0x0c, 0x02, 0x0e, 0xd1, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | ||||||
|  |     0x00, 0x9d, 0x01, 0x67, 0x03, 0x2d, 0x05, 0xf1, 0x06, 0xb2, 0x08, 0x70, 0x0a, 0x2b, 0x0c, 0xe4, 0x0d, 0x9a, 0x0f, | ||||||
|  |     0x00, 0x00, 0x00, 0x00, 0x4e, 0x01, 0xff, 0x02, 0xad, 0x04, 0x59, 0x06, 0x02, 0x08, 0xa9, 0x09, 0x4e, 0x0b, 0xf0, | ||||||
|  |     0x0c, 0x90, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2d, 0x00, 0xc8, 0x01, 0x61, 0x03, 0xf7, 0x04, | ||||||
|  |     0x8c, 0x06, 0x1e, 0x08, 0xad, 0x09, 0x3b, 0x0b, 0xc7, 0x0c, 0x50, 0x0e, 0xd7, 0x0f, 0x5c, 0x01, 0xe0, 0x02, 0x61, | ||||||
|  |     0x04, 0xe0, 0x05, 0x5d, 0x07, 0xd8, 0x08, 0x51, 0x0a, 0xc8, 0x0b, 0x3d, 0x0d, 0xb1, 0x0e, 0x00, 0x00, 0x00, 0x00, | ||||||
|  |     0x22, 0x00, 0x92, 0x01, 0x00, 0x03, 0x6c, 0x04, 0xd6, 0x05, 0x3e, 0x07, 0xa5, 0x08, 0x0a, 0x0a, 0x6d, 0x0b, 0xcf, | ||||||
|  |     0x0c, 0x2e, 0x0e, 0x8c, 0x0f, 0xe9, 0x00, 0x43, 0x02, 0x9d, 0x03, 0xf4, 0x04, 0x4a, 0x06, 0x9e, 0x07, 0xf1, 0x08, | ||||||
|  |     0x42, 0x0a, 0x92, 0x0b, 0xe0, 0x0c, 0x2c, 0x0e, 0x77, 0x0f, 0xc1, 0x00, 0x09, 0x02, 0x50, 0x03, 0x95, 0x04, 0xd8, | ||||||
|  |     0x05, 0x1b, 0x07, 0x5c, 0x08, 0x9b, 0x09, 0xd9, 0x0a, 0x16, 0x0c, 0x51, 0x0d, 0x8b, 0x0e, 0xc4, 0x0f, 0x00, 0x00, | ||||||
|  |     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfb, 0x00, 0x31, 0x02, 0x66, 0x03, 0x99, 0x04, 0xcb, 0x05, 0xfc, 0x06, 0x2c, | ||||||
|  |     0x08, 0x5a, 0x09, 0x87, 0x0a, 0xb3, 0x0b, 0xdd, 0x0c, 0x07, 0x0e, 0x2f, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x56, 0x00, | ||||||
|  |     0x7c, 0x01, 0xa0, 0x02, 0xc4, 0x03, 0xe6, 0x04, 0x07, 0x06, 0x27, 0x07, 0x46, 0x08, 0x64, 0x09, 0x81, 0x0a, 0x9c, | ||||||
|  |     0x0b, 0xb7, 0x0c, 0xd0, 0x0d, 0xe8, 0x0e, 0x00, 0x10, 0x00, 0x00, 0x2a, 0xfd, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00, | ||||||
|  |     0x52, 0x00, 0x00, 0x00, 0x04, 0x00, 0x06, 0x00, 0x08, 0x00, 0x08, 0x00, 0x0a, 0x00, 0x0c, 0x00, 0x0e, 0x00, 0x10, | ||||||
|  |     0x00, 0x12, 0x00, 0x16, 0x00, 0x18, 0x00, 0x1a, 0x00, 0x1e, 0x00, 0x20, 0x00, 0x24, 0x00, 0x26, 0x00, 0x2a, 0x00, | ||||||
|  |     0x2e, 0x00, 0x32, 0x00, 0x36, 0x00, 0x3a, 0x00, 0x40, 0x00, 0x44, 0x00, 0x4a, 0x00, 0x4e, 0x00, 0x54, 0x00, 0x5a, | ||||||
|  |     0x00, 0x62, 0x00, 0x68, 0x00, 0x70, 0x00, 0x78, 0x00, 0x80, 0x00, 0x88, 0x00, 0x92, 0x00, 0x9a, 0x00, 0xa6, 0x00, | ||||||
|  |     0xb0, 0x00, 0xbc, 0x00, 0xc8, 0x00, 0xd4, 0x00, 0xe2, 0x00, 0x00, 0x00, 0x8a, 0xfd, 0xff, 0xff, 0x04, 0x00, 0x00, | ||||||
|  |     0x00, 0x52, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x08, 0x00, 0x0c, 0x00, 0x10, 0x00, 0x14, 0x00, 0x18, 0x00, | ||||||
|  |     0x1c, 0x00, 0x20, 0x00, 0x24, 0x00, 0x28, 0x00, 0x2c, 0x00, 0x30, 0x00, 0x34, 0x00, 0x38, 0x00, 0x3c, 0x00, 0x44, | ||||||
|  |     0x00, 0x4c, 0x00, 0x50, 0x00, 0x58, 0x00, 0x60, 0x00, 0x68, 0x00, 0x70, 0x00, 0x78, 0x00, 0x80, 0x00, 0x88, 0x00, | ||||||
|  |     0x90, 0x00, 0x98, 0x00, 0xa0, 0x00, 0xa8, 0x00, 0xb0, 0x00, 0xb8, 0x00, 0xc4, 0x00, 0xd0, 0x00, 0xdc, 0x00, 0xe8, | ||||||
|  |     0x00, 0xf4, 0x00, 0x00, 0x01, 0x0c, 0x01, 0x1c, 0x01, 0x2c, 0x01, 0x00, 0x00, 0xea, 0xfd, 0xff, 0xff, 0x04, 0x00, | ||||||
|  |     0x00, 0x00, 0x52, 0x00, 0x00, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, | ||||||
|  |     0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x08, 0x00, | ||||||
|  |     0x08, 0x00, 0x04, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, | ||||||
|  |     0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x0c, 0x00, 0x0c, 0x00, 0x0c, 0x00, 0x0c, 0x00, | ||||||
|  |     0x0c, 0x00, 0x0c, 0x00, 0x0c, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x00, 0x00, 0x4a, 0xfe, 0xff, 0xff, 0x04, | ||||||
|  |     0x00, 0x00, 0x00, 0xfa, 0x00, 0x00, 0x00, 0x7c, 0x7f, 0x79, 0x7f, 0x76, 0x7f, 0xfa, 0xff, 0x00, 0x00, 0x00, 0x00, | ||||||
|  |     0x70, 0x7f, 0xf4, 0xff, 0x00, 0x00, 0x00, 0x00, 0x64, 0x7f, 0xe9, 0xff, 0xfe, 0xff, 0x00, 0x00, 0x4b, 0x7f, 0xd0, | ||||||
|  |     0xff, 0x00, 0x00, 0x00, 0x00, 0x1b, 0x7f, 0xa0, 0xff, 0x00, 0x00, 0x00, 0x00, 0xbb, 0x7e, 0x42, 0xff, 0x00, 0x00, | ||||||
|  |     0x00, 0x00, 0xfd, 0x7d, 0x86, 0xfe, 0x04, 0x00, 0x00, 0x00, 0x87, 0x7c, 0x1d, 0xfd, 0x12, 0x00, 0x00, 0x00, 0xb6, | ||||||
|  |     0x79, 0x7f, 0xfa, 0x3e, 0x00, 0x00, 0x00, 0x73, 0x74, 0xf9, 0xf5, 0xca, 0x00, 0x00, 0x00, 0x36, 0x6b, 0x33, 0xef, | ||||||
|  |     0x32, 0x02, 0x00, 0x00, 0x9b, 0x5c, 0x87, 0xe7, 0xce, 0x04, 0x00, 0x00, 0xf0, 0x48, 0xde, 0xe2, 0xa0, 0x07, 0x00, | ||||||
|  |     0x00, 0x6e, 0x33, 0x8a, 0xe4, 0xa4, 0x08, 0x00, 0x00, 0x9c, 0x20, 0x22, 0xeb, 0x4c, 0x07, 0x00, 0x00, 0x0a, 0x13, | ||||||
|  |     0x7d, 0xf2, 0x02, 0x05, 0x00, 0x00, 0x89, 0x0a, 0x17, 0xf8, 0x06, 0x03, 0x00, 0x00, 0xa6, 0x05, 0xa0, 0xfb, 0xb4, | ||||||
|  |     0x01, 0x00, 0x00, 0xfa, 0x02, 0xac, 0xfd, 0xe8, 0x00, 0x00, 0x00, 0x8e, 0x01, 0xc7, 0xfe, 0x7a, 0x00, 0x00, 0x00, | ||||||
|  |     0xcf, 0x00, 0x5c, 0xff, 0x40, 0x00, 0x00, 0x00, 0x6b, 0x00, 0xab, 0xff, 0x22, 0x00, 0x00, 0x00, 0x38, 0x00, 0xd3, | ||||||
|  |     0xff, 0x12, 0x00, 0x00, 0x00, 0x1d, 0x00, 0xea, 0xff, 0x08, 0x00, 0x00, 0x00, 0x0f, 0x00, 0xf3, 0xff, 0x06, 0x00, | ||||||
|  |     0x00, 0x00, 0x08, 0x00, 0xf8, 0xff, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0xfe, 0xff, 0x00, 0x00, 0x00, 0x00, 0x02, | ||||||
|  |     0x00, 0xfd, 0xff, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0xfd, 0xff, | ||||||
|  |     0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x52, 0xff, 0xff, 0xff, 0x04, 0x00, 0x00, | ||||||
|  |     0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x62, 0xff, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, | ||||||
|  |     0x00, 0x00, 0xf1, 0x00, 0x00, 0x00, 0x72, 0xff, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, | ||||||
|  |     0x00, 0x00, 0x00, 0x82, 0xff, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x4d, 0x01, 0x00, 0x00, | ||||||
|  |     0x92, 0xff, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | ||||||
|  |     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb2, 0xff, 0xff, 0xff, 0x04, 0x00, | ||||||
|  |     0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | ||||||
|  |     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | ||||||
|  |     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | ||||||
|  |     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x08, 0x00, | ||||||
|  |     0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0xc0, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, | ||||||
|  |     0x00, 0x02, 0x00, 0x04, 0x00, 0x05, 0x00, 0x07, 0x00, 0x0a, 0x00, 0x0d, 0x00, 0x10, 0x00, 0x13, 0x00, 0x17, 0x00, | ||||||
|  |     0x1b, 0x00, 0x20, 0x00, 0x25, 0x00, 0x2a, 0x00, 0x30, 0x00, 0x35, 0x00, 0x3c, 0x00, 0x42, 0x00, 0x49, 0x00, 0x51, | ||||||
|  |     0x00, 0x58, 0x00, 0x60, 0x00, 0x68, 0x00, 0x71, 0x00, 0x7a, 0x00, 0x83, 0x00, 0x8d, 0x00, 0x97, 0x00, 0xa1, 0x00, | ||||||
|  |     0xac, 0x00, 0xb7, 0x00, 0xc2, 0x00, 0xcd, 0x00, 0xd9, 0x00, 0xe5, 0x00, 0xf2, 0x00, 0xff, 0x00, 0x0c, 0x01, 0x19, | ||||||
|  |     0x01, 0x27, 0x01, 0x35, 0x01, 0x43, 0x01, 0x52, 0x01, 0x61, 0x01, 0x70, 0x01, 0x7f, 0x01, 0x8f, 0x01, 0x9f, 0x01, | ||||||
|  |     0xaf, 0x01, 0xc0, 0x01, 0xd1, 0x01, 0xe2, 0x01, 0xf3, 0x01, 0x05, 0x02, 0x17, 0x02, 0x29, 0x02, 0x3c, 0x02, 0x4e, | ||||||
|  |     0x02, 0x61, 0x02, 0x75, 0x02, 0x88, 0x02, 0x9c, 0x02, 0xb0, 0x02, 0xc4, 0x02, 0xd8, 0x02, 0xed, 0x02, 0x02, 0x03, | ||||||
|  |     0x17, 0x03, 0x2c, 0x03, 0x41, 0x03, 0x57, 0x03, 0x6d, 0x03, 0x83, 0x03, 0x99, 0x03, 0xb0, 0x03, 0xc7, 0x03, 0xdd, | ||||||
|  |     0x03, 0xf4, 0x03, 0x0c, 0x04, 0x23, 0x04, 0x3b, 0x04, 0x52, 0x04, 0x6a, 0x04, 0x82, 0x04, 0x9a, 0x04, 0xb3, 0x04, | ||||||
|  |     0xcb, 0x04, 0xe4, 0x04, 0xfd, 0x04, 0x16, 0x05, 0x2f, 0x05, 0x48, 0x05, 0x61, 0x05, 0x7a, 0x05, 0x94, 0x05, 0xad, | ||||||
|  |     0x05, 0xc7, 0x05, 0xe1, 0x05, 0xfb, 0x05, 0x15, 0x06, 0x2f, 0x06, 0x49, 0x06, 0x63, 0x06, 0x7e, 0x06, 0x98, 0x06, | ||||||
|  |     0xb2, 0x06, 0xcd, 0x06, 0xe7, 0x06, 0x02, 0x07, 0x1d, 0x07, 0x37, 0x07, 0x52, 0x07, 0x6d, 0x07, 0x87, 0x07, 0xa2, | ||||||
|  |     0x07, 0xbd, 0x07, 0xd8, 0x07, 0xf3, 0x07, 0x0d, 0x08, 0x28, 0x08, 0x43, 0x08, 0x5e, 0x08, 0x79, 0x08, 0x93, 0x08, | ||||||
|  |     0xae, 0x08, 0xc9, 0x08, 0xe3, 0x08, 0xfe, 0x08, 0x19, 0x09, 0x33, 0x09, 0x4e, 0x09, 0x68, 0x09, 0x82, 0x09, 0x9d, | ||||||
|  |     0x09, 0xb7, 0x09, 0xd1, 0x09, 0xeb, 0x09, 0x05, 0x0a, 0x1f, 0x0a, 0x39, 0x0a, 0x53, 0x0a, 0x6c, 0x0a, 0x86, 0x0a, | ||||||
|  |     0x9f, 0x0a, 0xb8, 0x0a, 0xd1, 0x0a, 0xea, 0x0a, 0x03, 0x0b, 0x1c, 0x0b, 0x35, 0x0b, 0x4d, 0x0b, 0x66, 0x0b, 0x7e, | ||||||
|  |     0x0b, 0x96, 0x0b, 0xae, 0x0b, 0xc5, 0x0b, 0xdd, 0x0b, 0xf4, 0x0b, 0x0c, 0x0c, 0x23, 0x0c, 0x39, 0x0c, 0x50, 0x0c, | ||||||
|  |     0x67, 0x0c, 0x7d, 0x0c, 0x93, 0x0c, 0xa9, 0x0c, 0xbf, 0x0c, 0xd4, 0x0c, 0xe9, 0x0c, 0xfe, 0x0c, 0x13, 0x0d, 0x28, | ||||||
|  |     0x0d, 0x3c, 0x0d, 0x50, 0x0d, 0x64, 0x0d, 0x78, 0x0d, 0x8b, 0x0d, 0x9f, 0x0d, 0xb2, 0x0d, 0xc4, 0x0d, 0xd7, 0x0d, | ||||||
|  |     0xe9, 0x0d, 0xfb, 0x0d, 0x0d, 0x0e, 0x1e, 0x0e, 0x2f, 0x0e, 0x40, 0x0e, 0x51, 0x0e, 0x61, 0x0e, 0x71, 0x0e, 0x81, | ||||||
|  |     0x0e, 0x90, 0x0e, 0x9f, 0x0e, 0xae, 0x0e, 0xbd, 0x0e, 0xcb, 0x0e, 0xd9, 0x0e, 0xe7, 0x0e, 0xf4, 0x0e, 0x01, 0x0f, | ||||||
|  |     0x0e, 0x0f, 0x1b, 0x0f, 0x27, 0x0f, 0x33, 0x0f, 0x3e, 0x0f, 0x49, 0x0f, 0x54, 0x0f, 0x5f, 0x0f, 0x69, 0x0f, 0x73, | ||||||
|  |     0x0f, 0x7d, 0x0f, 0x86, 0x0f, 0x8f, 0x0f, 0x98, 0x0f, 0xa0, 0x0f, 0xa8, 0x0f, 0xaf, 0x0f, 0xb7, 0x0f, 0xbe, 0x0f, | ||||||
|  |     0xc4, 0x0f, 0xcb, 0x0f, 0xd0, 0x0f, 0xd6, 0x0f, 0xdb, 0x0f, 0xe0, 0x0f, 0xe5, 0x0f, 0xe9, 0x0f, 0xed, 0x0f, 0xf0, | ||||||
|  |     0x0f, 0xf3, 0x0f, 0xf6, 0x0f, 0xf9, 0x0f, 0xfb, 0x0f, 0xfc, 0x0f, 0xfe, 0x0f, 0xff, 0x0f, 0x00, 0x10, 0x00, 0x10, | ||||||
|  |     0x00, 0x10, 0x00, 0x10, 0xff, 0x0f, 0xfe, 0x0f, 0xfc, 0x0f, 0xfb, 0x0f, 0xf9, 0x0f, 0xf6, 0x0f, 0xf3, 0x0f, 0xf0, | ||||||
|  |     0x0f, 0xed, 0x0f, 0xe9, 0x0f, 0xe5, 0x0f, 0xe0, 0x0f, 0xdb, 0x0f, 0xd6, 0x0f, 0xd0, 0x0f, 0xcb, 0x0f, 0xc4, 0x0f, | ||||||
|  |     0xbe, 0x0f, 0xb7, 0x0f, 0xaf, 0x0f, 0xa8, 0x0f, 0xa0, 0x0f, 0x98, 0x0f, 0x8f, 0x0f, 0x86, 0x0f, 0x7d, 0x0f, 0x73, | ||||||
|  |     0x0f, 0x69, 0x0f, 0x5f, 0x0f, 0x54, 0x0f, 0x49, 0x0f, 0x3e, 0x0f, 0x33, 0x0f, 0x27, 0x0f, 0x1b, 0x0f, 0x0e, 0x0f, | ||||||
|  |     0x01, 0x0f, 0xf4, 0x0e, 0xe7, 0x0e, 0xd9, 0x0e, 0xcb, 0x0e, 0xbd, 0x0e, 0xae, 0x0e, 0x9f, 0x0e, 0x90, 0x0e, 0x81, | ||||||
|  |     0x0e, 0x71, 0x0e, 0x61, 0x0e, 0x51, 0x0e, 0x40, 0x0e, 0x2f, 0x0e, 0x1e, 0x0e, 0x0d, 0x0e, 0xfb, 0x0d, 0xe9, 0x0d, | ||||||
|  |     0xd7, 0x0d, 0xc4, 0x0d, 0xb2, 0x0d, 0x9f, 0x0d, 0x8b, 0x0d, 0x78, 0x0d, 0x64, 0x0d, 0x50, 0x0d, 0x3c, 0x0d, 0x28, | ||||||
|  |     0x0d, 0x13, 0x0d, 0xfe, 0x0c, 0xe9, 0x0c, 0xd4, 0x0c, 0xbf, 0x0c, 0xa9, 0x0c, 0x93, 0x0c, 0x7d, 0x0c, 0x67, 0x0c, | ||||||
|  |     0x50, 0x0c, 0x39, 0x0c, 0x23, 0x0c, 0x0c, 0x0c, 0xf4, 0x0b, 0xdd, 0x0b, 0xc5, 0x0b, 0xae, 0x0b, 0x96, 0x0b, 0x7e, | ||||||
|  |     0x0b, 0x66, 0x0b, 0x4d, 0x0b, 0x35, 0x0b, 0x1c, 0x0b, 0x03, 0x0b, 0xea, 0x0a, 0xd1, 0x0a, 0xb8, 0x0a, 0x9f, 0x0a, | ||||||
|  |     0x86, 0x0a, 0x6c, 0x0a, 0x53, 0x0a, 0x39, 0x0a, 0x1f, 0x0a, 0x05, 0x0a, 0xeb, 0x09, 0xd1, 0x09, 0xb7, 0x09, 0x9d, | ||||||
|  |     0x09, 0x82, 0x09, 0x68, 0x09, 0x4e, 0x09, 0x33, 0x09, 0x19, 0x09, 0xfe, 0x08, 0xe3, 0x08, 0xc9, 0x08, 0xae, 0x08, | ||||||
|  |     0x93, 0x08, 0x79, 0x08, 0x5e, 0x08, 0x43, 0x08, 0x28, 0x08, 0x0d, 0x08, 0xf3, 0x07, 0xd8, 0x07, 0xbd, 0x07, 0xa2, | ||||||
|  |     0x07, 0x87, 0x07, 0x6d, 0x07, 0x52, 0x07, 0x37, 0x07, 0x1d, 0x07, 0x02, 0x07, 0xe7, 0x06, 0xcd, 0x06, 0xb2, 0x06, | ||||||
|  |     0x98, 0x06, 0x7e, 0x06, 0x63, 0x06, 0x49, 0x06, 0x2f, 0x06, 0x15, 0x06, 0xfb, 0x05, 0xe1, 0x05, 0xc7, 0x05, 0xad, | ||||||
|  |     0x05, 0x94, 0x05, 0x7a, 0x05, 0x61, 0x05, 0x48, 0x05, 0x2f, 0x05, 0x16, 0x05, 0xfd, 0x04, 0xe4, 0x04, 0xcb, 0x04, | ||||||
|  |     0xb3, 0x04, 0x9a, 0x04, 0x82, 0x04, 0x6a, 0x04, 0x52, 0x04, 0x3b, 0x04, 0x23, 0x04, 0x0c, 0x04, 0xf4, 0x03, 0xdd, | ||||||
|  |     0x03, 0xc7, 0x03, 0xb0, 0x03, 0x99, 0x03, 0x83, 0x03, 0x6d, 0x03, 0x57, 0x03, 0x41, 0x03, 0x2c, 0x03, 0x17, 0x03, | ||||||
|  |     0x02, 0x03, 0xed, 0x02, 0xd8, 0x02, 0xc4, 0x02, 0xb0, 0x02, 0x9c, 0x02, 0x88, 0x02, 0x75, 0x02, 0x61, 0x02, 0x4e, | ||||||
|  |     0x02, 0x3c, 0x02, 0x29, 0x02, 0x17, 0x02, 0x05, 0x02, 0xf3, 0x01, 0xe2, 0x01, 0xd1, 0x01, 0xc0, 0x01, 0xaf, 0x01, | ||||||
|  |     0x9f, 0x01, 0x8f, 0x01, 0x7f, 0x01, 0x70, 0x01, 0x61, 0x01, 0x52, 0x01, 0x43, 0x01, 0x35, 0x01, 0x27, 0x01, 0x19, | ||||||
|  |     0x01, 0x0c, 0x01, 0xff, 0x00, 0xf2, 0x00, 0xe5, 0x00, 0xd9, 0x00, 0xcd, 0x00, 0xc2, 0x00, 0xb7, 0x00, 0xac, 0x00, | ||||||
|  |     0xa1, 0x00, 0x97, 0x00, 0x8d, 0x00, 0x83, 0x00, 0x7a, 0x00, 0x71, 0x00, 0x68, 0x00, 0x60, 0x00, 0x58, 0x00, 0x51, | ||||||
|  |     0x00, 0x49, 0x00, 0x42, 0x00, 0x3c, 0x00, 0x35, 0x00, 0x30, 0x00, 0x2a, 0x00, 0x25, 0x00, 0x20, 0x00, 0x1b, 0x00, | ||||||
|  |     0x17, 0x00, 0x13, 0x00, 0x10, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x07, 0x00, 0x05, 0x00, 0x04, 0x00, 0x02, 0x00, 0x01, | ||||||
|  |     0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0xee, 0xff, 0xff, 0x38, 0xee, 0xff, 0xff, 0x0f, 0x00, 0x00, 0x00, 0x4d, 0x4c, | ||||||
|  |     0x49, 0x52, 0x20, 0x43, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x74, 0x65, 0x64, 0x2e, 0x00, 0x01, 0x00, 0x00, 0x00, 0x14, | ||||||
|  |     0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x18, 0x00, 0x14, 0x00, 0x10, 0x00, 0x0c, 0x00, 0x08, 0x00, 0x04, 0x00, | ||||||
|  |     0x0e, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0xf4, 0x05, 0x00, 0x00, 0xf8, 0x05, 0x00, | ||||||
|  |     0x00, 0xfc, 0x05, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x6d, 0x61, 0x69, 0x6e, 0x00, 0x00, 0x00, 0x00, 0x16, 0x00, | ||||||
|  |     0x00, 0x00, 0xa0, 0x05, 0x00, 0x00, 0x68, 0x05, 0x00, 0x00, 0x24, 0x05, 0x00, 0x00, 0xc8, 0x04, 0x00, 0x00, 0x70, | ||||||
|  |     0x04, 0x00, 0x00, 0x4c, 0x04, 0x00, 0x00, 0x10, 0x04, 0x00, 0x00, 0xc8, 0x03, 0x00, 0x00, 0xa4, 0x03, 0x00, 0x00, | ||||||
|  |     0x4c, 0x03, 0x00, 0x00, 0x14, 0x03, 0x00, 0x00, 0x10, 0x02, 0x00, 0x00, 0xc8, 0x01, 0x00, 0x00, 0x6c, 0x01, 0x00, | ||||||
|  |     0x00, 0x48, 0x01, 0x00, 0x00, 0x14, 0x01, 0x00, 0x00, 0xe0, 0x00, 0x00, 0x00, 0xac, 0x00, 0x00, 0x00, 0x78, 0x00, | ||||||
|  |     0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0xf6, 0xfa, 0xff, 0xff, 0x0c, | ||||||
|  |     0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x2a, 0x00, 0x00, 0x00, | ||||||
|  |     0x01, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x16, 0xfb, 0xff, 0xff, 0x0c, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, | ||||||
|  |     0x00, 0x11, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x28, 0x00, | ||||||
|  |     0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x3a, 0xfb, 0xff, 0xff, 0x0c, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x10, | ||||||
|  |     0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, | ||||||
|  |     0x0e, 0x00, 0x00, 0x00, 0xa6, 0xfc, 0xff, 0xff, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x10, 0x00, 0x00, | ||||||
|  |     0x00, 0x14, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x68, 0xef, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00, 0x27, 0x00, | ||||||
|  |     0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0xd6, 0xfc, 0xff, 0xff, 0x14, | ||||||
|  |     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1d, 0x10, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, | ||||||
|  |     0x98, 0xef, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, | ||||||
|  |     0x00, 0x12, 0x00, 0x00, 0x00, 0x06, 0xfd, 0xff, 0xff, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x10, 0x00, | ||||||
|  |     0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0xc8, 0xef, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00, 0x25, | ||||||
|  |     0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x36, 0xfd, 0xff, 0xff, | ||||||
|  |     0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x15, 0x10, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, | ||||||
|  |     0x00, 0xf8, 0xef, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x23, 0x00, | ||||||
|  |     0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x1e, 0xfc, 0xff, 0xff, 0x0c, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x05, | ||||||
|  |     0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, | ||||||
|  |     0x84, 0xfc, 0xff, 0xff, 0x10, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x44, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, | ||||||
|  |     0x00, 0x30, 0x00, 0x00, 0x00, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5f, 0x63, 0x6f, 0x72, 0x72, 0x65, 0x63, 0x74, 0x69, | ||||||
|  |     0x6f, 0x6e, 0x5f, 0x62, 0x69, 0x74, 0x73, 0x00, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x5f, 0x73, 0x63, 0x61, 0x6c, | ||||||
|  |     0x65, 0x00, 0x02, 0x24, 0x0f, 0x02, 0x01, 0x02, 0x03, 0x40, 0x04, 0x04, 0x04, 0x24, 0x01, 0x01, 0x00, 0x00, 0x00, | ||||||
|  |     0x22, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0xdc, 0xfc, 0xff, 0xff, 0x10, 0x00, 0x00, | ||||||
|  |     0x00, 0x24, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x73, 0x6e, | ||||||
|  |     0x72, 0x5f, 0x73, 0x68, 0x69, 0x66, 0x74, 0x00, 0x01, 0x0b, 0x01, 0x01, 0x01, 0x06, 0x04, 0x02, 0x24, 0x01, 0x01, | ||||||
|  |     0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, | ||||||
|  |     0x08, 0x00, 0x00, 0x00, 0x20, 0xfd, 0xff, 0xff, 0x10, 0x00, 0x00, 0x00, 0xe4, 0x00, 0x00, 0x00, 0xec, 0x00, 0x00, | ||||||
|  |     0x00, 0x0a, 0x00, 0x00, 0x00, 0xd2, 0x00, 0x00, 0x00, 0x61, 0x6c, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x74, 0x65, 0x5f, | ||||||
|  |     0x6f, 0x6e, 0x65, 0x5f, 0x6d, 0x69, 0x6e, 0x75, 0x73, 0x5f, 0x73, 0x6d, 0x6f, 0x6f, 0x74, 0x68, 0x69, 0x6e, 0x67, | ||||||
|  |     0x00, 0x61, 0x6c, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x74, 0x65, 0x5f, 0x73, 0x6d, 0x6f, 0x6f, 0x74, 0x68, 0x69, 0x6e, | ||||||
|  |     0x67, 0x00, 0x63, 0x6c, 0x61, 0x6d, 0x70, 0x69, 0x6e, 0x67, 0x00, 0x6d, 0x69, 0x6e, 0x5f, 0x73, 0x69, 0x67, 0x6e, | ||||||
|  |     0x61, 0x6c, 0x5f, 0x72, 0x65, 0x6d, 0x61, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x00, 0x6e, 0x75, 0x6d, 0x5f, 0x63, 0x68, | ||||||
|  |     0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x00, 0x6f, 0x6e, 0x65, 0x5f, 0x6d, 0x69, 0x6e, 0x75, 0x73, 0x5f, 0x73, 0x6d, | ||||||
|  |     0x6f, 0x6f, 0x74, 0x68, 0x69, 0x6e, 0x67, 0x00, 0x73, 0x6d, 0x6f, 0x6f, 0x74, 0x68, 0x69, 0x6e, 0x67, 0x00, 0x73, | ||||||
|  |     0x6d, 0x6f, 0x6f, 0x74, 0x68, 0x69, 0x6e, 0x67, 0x5f, 0x62, 0x69, 0x74, 0x73, 0x00, 0x73, 0x70, 0x65, 0x63, 0x74, | ||||||
|  |     0x72, 0x61, 0x6c, 0x5f, 0x73, 0x75, 0x62, 0x74, 0x72, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x62, 0x69, 0x74, | ||||||
|  |     0x73, 0x00, 0x09, 0xa5, 0x88, 0x75, 0x6d, 0x59, 0x4d, 0x3a, 0x31, 0x23, 0x09, 0x00, 0x01, 0x00, 0x09, 0x00, 0x29, | ||||||
|  |     0x3c, 0xd7, 0x03, 0x00, 0x00, 0x33, 0x03, 0x28, 0x00, 0x67, 0x3e, 0x99, 0x01, 0x0a, 0x00, 0x0e, 0x00, 0x05, 0x05, | ||||||
|  |     0x69, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x1b, 0x25, 0x01, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, | ||||||
|  |     0x00, 0x20, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x20, 0xfe, 0xff, 0xff, 0x10, 0x00, | ||||||
|  |     0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, | ||||||
|  |     0x00, 0x01, 0x00, 0x00, 0x24, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, | ||||||
|  |     0x1d, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x54, 0xfe, 0xff, 0xff, 0x10, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, | ||||||
|  |     0x00, 0x2c, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x6e, 0x75, 0x6d, 0x5f, 0x63, 0x68, | ||||||
|  |     0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x00, 0x01, 0x0e, 0x01, 0x01, 0x01, 0x28, 0x04, 0x02, 0x24, 0x01, 0x00, 0x01, | ||||||
|  |     0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, | ||||||
|  |     0x0c, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x62, 0xfe, 0xff, | ||||||
|  |     0xff, 0x0c, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x1c, 0x00, | ||||||
|  |     0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0xca, 0xff, 0xff, 0xff, 0x14, 0x00, 0x00, 0x00, 0x00, | ||||||
|  |     0x00, 0x00, 0x0a, 0x10, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x8c, 0xf2, 0xff, 0xff, | ||||||
|  |     0x01, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, | ||||||
|  |     0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x18, 0x00, 0x14, 0x00, 0x10, 0x00, 0x0c, 0x00, 0x0b, 0x00, | ||||||
|  |     0x04, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x10, 0x00, 0x00, 0x00, 0x14, | ||||||
|  |     0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0xd0, 0xf2, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, | ||||||
|  |     0x04, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, | ||||||
|  |     0x00, 0xfe, 0xfe, 0xff, 0xff, 0x0c, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x00, | ||||||
|  |     0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x64, 0xff, 0xff, 0xff, 0x10, | ||||||
|  |     0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, | ||||||
|  |     0x65, 0x6e, 0x64, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x00, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x69, 0x6e, 0x64, | ||||||
|  |     0x65, 0x78, 0x00, 0x02, 0x17, 0x0e, 0x00, 0x03, 0x00, 0x01, 0x00, 0x02, 0x00, 0xf1, 0x00, 0x05, 0x00, 0x05, 0x05, | ||||||
|  |     0x06, 0x25, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x17, | ||||||
|  |     0x00, 0x00, 0x00, 0xb8, 0xff, 0xff, 0xff, 0x10, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, | ||||||
|  |     0x03, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x54, 0x00, 0x66, 0x66, 0x74, 0x5f, 0x6c, 0x65, 0x6e, 0x67, 0x74, | ||||||
|  |     0x68, 0x00, 0x02, 0x0e, 0x0d, 0x02, 0x00, 0x01, 0x00, 0x02, 0x00, 0x07, 0x00, 0x00, 0x02, 0x05, 0x05, 0x06, 0x25, | ||||||
|  |     0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x10, | ||||||
|  |     0x00, 0x14, 0x00, 0x10, 0x00, 0x0c, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x10, 0x00, 0x00, 0x00, | ||||||
|  |     0x10, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, | ||||||
|  |     0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x24, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, | ||||||
|  |     0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x10, 0x00, 0x0c, 0x00, 0x08, | ||||||
|  |     0x00, 0x04, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, | ||||||
|  |     0x01, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, | ||||||
|  |     0x00, 0x10, 0x00, 0x10, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x10, 0x00, | ||||||
|  |     0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x73, | ||||||
|  |     0x68, 0x69, 0x66, 0x74, 0x00, 0x01, 0x07, 0x01, 0x01, 0x01, 0x0c, 0x04, 0x02, 0x24, 0x01, 0x01, 0x00, 0x00, 0x00, | ||||||
|  |     0x13, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, | ||||||
|  |     0x00, 0x2a, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x00, 0x00, 0xc4, 0x0a, | ||||||
|  |     0x00, 0x00, 0x74, 0x0a, 0x00, 0x00, 0x3c, 0x0a, 0x00, 0x00, 0x04, 0x0a, 0x00, 0x00, 0xd0, 0x09, 0x00, 0x00, 0x88, | ||||||
|  |     0x09, 0x00, 0x00, 0x40, 0x09, 0x00, 0x00, 0xfc, 0x08, 0x00, 0x00, 0xb8, 0x08, 0x00, 0x00, 0x6c, 0x08, 0x00, 0x00, | ||||||
|  |     0x20, 0x08, 0x00, 0x00, 0xd4, 0x07, 0x00, 0x00, 0x88, 0x07, 0x00, 0x00, 0x3c, 0x07, 0x00, 0x00, 0xf8, 0x06, 0x00, | ||||||
|  |     0x00, 0xb8, 0x06, 0x00, 0x00, 0x84, 0x06, 0x00, 0x00, 0x50, 0x06, 0x00, 0x00, 0x1c, 0x06, 0x00, 0x00, 0xd8, 0x05, | ||||||
|  |     0x00, 0x00, 0xa0, 0x05, 0x00, 0x00, 0x58, 0x05, 0x00, 0x00, 0x14, 0x05, 0x00, 0x00, 0xd8, 0x04, 0x00, 0x00, 0x98, | ||||||
|  |     0x04, 0x00, 0x00, 0x60, 0x04, 0x00, 0x00, 0x20, 0x04, 0x00, 0x00, 0xe8, 0x03, 0x00, 0x00, 0xb0, 0x03, 0x00, 0x00, | ||||||
|  |     0x6c, 0x03, 0x00, 0x00, 0x1c, 0x03, 0x00, 0x00, 0xc4, 0x02, 0x00, 0x00, 0x68, 0x02, 0x00, 0x00, 0x2c, 0x02, 0x00, | ||||||
|  |     0x00, 0xe4, 0x01, 0x00, 0x00, 0xac, 0x01, 0x00, 0x00, 0x78, 0x01, 0x00, 0x00, 0x44, 0x01, 0x00, 0x00, 0x08, 0x01, | ||||||
|  |     0x00, 0x00, 0xd0, 0x00, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0xfe, | ||||||
|  |     0xf5, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x00, 0x00, | ||||||
|  |     0x00, 0x00, 0x00, 0x09, 0x20, 0x00, 0x00, 0x00, 0x44, 0xf5, 0xff, 0xff, 0x11, 0x00, 0x00, 0x00, 0x50, 0x61, 0x72, | ||||||
|  |     0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x65, 0x64, 0x43, 0x61, 0x6c, 0x6c, 0x3a, 0x30, 0x00, 0x00, 0x00, 0x01, 0x00, | ||||||
|  |     0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x3e, 0xf6, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, | ||||||
|  |     0x00, 0x00, 0x00, 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x1c, 0x00, 0x00, 0x00, 0x84, 0xf5, 0xff, 0xff, | ||||||
|  |     0x0d, 0x00, 0x00, 0x00, 0x63, 0x6c, 0x69, 0x70, 0x5f, 0x62, 0x79, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x00, 0x00, | ||||||
|  |     0x00, 0x01, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x7a, 0xf6, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, | ||||||
|  |     0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x24, 0x00, 0x00, 0x00, 0xc0, | ||||||
|  |     0xf5, 0xff, 0xff, 0x15, 0x00, 0x00, 0x00, 0x63, 0x6c, 0x69, 0x70, 0x5f, 0x62, 0x79, 0x5f, 0x76, 0x61, 0x6c, 0x75, | ||||||
|  |     0x65, 0x2f, 0x4d, 0x69, 0x6e, 0x69, 0x6d, 0x75, 0x6d, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, | ||||||
|  |     0x00, 0xbe, 0xf6, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x28, 0x00, | ||||||
|  |     0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x14, 0x00, 0x00, 0x00, 0x04, 0xf6, 0xff, 0xff, 0x05, 0x00, 0x00, 0x00, 0x61, | ||||||
|  |     0x64, 0x64, 0x5f, 0x31, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0xf2, 0xf6, 0xff, 0xff, | ||||||
|  |     0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | ||||||
|  |     0x02, 0x18, 0x00, 0x00, 0x00, 0x38, 0xf6, 0xff, 0xff, 0x0b, 0x00, 0x00, 0x00, 0x54, 0x72, 0x75, 0x6e, 0x63, 0x61, | ||||||
|  |     0x74, 0x65, 0x44, 0x69, 0x76, 0x00, 0x01, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x2a, 0xf7, 0xff, 0xff, 0x00, | ||||||
|  |     0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, | ||||||
|  |     0x10, 0x00, 0x00, 0x00, 0x70, 0xf6, 0xff, 0xff, 0x03, 0x00, 0x00, 0x00, 0x61, 0x64, 0x64, 0x00, 0x01, 0x00, 0x00, | ||||||
|  |     0x00, 0x28, 0x00, 0x00, 0x00, 0x5a, 0xf7, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, | ||||||
|  |     0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x10, 0x00, 0x00, 0x00, 0xa0, 0xf6, 0xff, 0xff, 0x03, | ||||||
|  |     0x00, 0x00, 0x00, 0x6d, 0x75, 0x6c, 0x00, 0x01, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x8a, 0xf7, 0xff, 0xff, | ||||||
|  |     0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | ||||||
|  |     0x02, 0x14, 0x00, 0x00, 0x00, 0xd0, 0xf6, 0xff, 0xff, 0x06, 0x00, 0x00, 0x00, 0x43, 0x61, 0x73, 0x74, 0x5f, 0x32, | ||||||
|  |     0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0xbe, 0xf7, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, | ||||||
|  |     0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x24, 0x00, 0x00, 0x00, | ||||||
|  |     0x04, 0xf7, 0xff, 0xff, 0x16, 0x00, 0x00, 0x00, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x5f, 0x66, 0x69, 0x6c, 0x74, | ||||||
|  |     0x65, 0x72, 0x5f, 0x62, 0x61, 0x6e, 0x6b, 0x5f, 0x6c, 0x6f, 0x67, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x28, 0x00, | ||||||
|  |     0x00, 0x00, 0x02, 0xf8, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x22, | ||||||
|  |     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x18, 0x00, 0x00, 0x00, 0x48, 0xf7, 0xff, 0xff, 0x0b, 0x00, 0x00, 0x00, | ||||||
|  |     0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x5f, 0x70, 0x63, 0x61, 0x6e, 0x00, 0x01, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, | ||||||
|  |     0x00, 0x3a, 0xf8, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x21, 0x00, | ||||||
|  |     0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x38, 0x00, 0x00, 0x00, 0x80, 0xf7, 0xff, 0xff, 0x28, 0x00, 0x00, 0x00, 0x73, | ||||||
|  |     0x69, 0x67, 0x6e, 0x61, 0x6c, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x5f, 0x62, 0x61, 0x6e, 0x6b, 0x5f, 0x73, | ||||||
|  |     0x70, 0x65, 0x63, 0x74, 0x72, 0x61, 0x6c, 0x5f, 0x73, 0x75, 0x62, 0x74, 0x72, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, | ||||||
|  |     0x31, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x92, 0xf8, 0xff, 0xff, 0x00, 0x00, | ||||||
|  |     0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x34, | ||||||
|  |     0x00, 0x00, 0x00, 0xd8, 0xf7, 0xff, 0xff, 0x27, 0x00, 0x00, 0x00, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x5f, 0x66, | ||||||
|  |     0x69, 0x6c, 0x74, 0x65, 0x72, 0x5f, 0x62, 0x61, 0x6e, 0x6b, 0x5f, 0x73, 0x70, 0x65, 0x63, 0x74, 0x72, 0x61, 0x6c, | ||||||
|  |     0x5f, 0x73, 0x75, 0x62, 0x74, 0x72, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x01, 0x00, 0x00, 0x00, 0x28, 0x00, | ||||||
|  |     0x00, 0x00, 0xe6, 0xf8, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x1f, | ||||||
|  |     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x2c, 0x00, 0x00, 0x00, 0x2c, 0xf8, 0xff, 0xff, 0x1e, 0x00, 0x00, 0x00, | ||||||
|  |     0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x5f, 0x62, 0x61, 0x6e, 0x6b, 0x5f, | ||||||
|  |     0x73, 0x71, 0x75, 0x61, 0x72, 0x65, 0x5f, 0x72, 0x6f, 0x6f, 0x74, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x28, 0x00, | ||||||
|  |     0x00, 0x00, 0x32, 0xf9, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x1e, | ||||||
|  |     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x20, 0x00, 0x00, 0x00, 0x78, 0xf8, 0xff, 0xff, 0x12, 0x00, 0x00, 0x00, | ||||||
|  |     0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x5f, 0x62, 0x61, 0x6e, 0x6b, 0x00, | ||||||
|  |     0x00, 0x01, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x72, 0xf9, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, | ||||||
|  |     0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x14, 0x00, 0x00, 0x00, 0xb8, | ||||||
|  |     0xf8, 0xff, 0xff, 0x06, 0x00, 0x00, 0x00, 0x43, 0x61, 0x73, 0x74, 0x5f, 0x31, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, | ||||||
|  |     0x01, 0x01, 0x00, 0x00, 0xa6, 0xf9, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, | ||||||
|  |     0x00, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x14, 0x00, 0x00, 0x00, 0xec, 0xf8, 0xff, 0xff, 0x06, 0x00, | ||||||
|  |     0x00, 0x00, 0x63, 0x6f, 0x6e, 0x63, 0x61, 0x74, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0xda, | ||||||
|  |     0xf9, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, | ||||||
|  |     0x00, 0x00, 0x00, 0x02, 0x1c, 0x00, 0x00, 0x00, 0x20, 0xf9, 0xff, 0xff, 0x0d, 0x00, 0x00, 0x00, 0x73, 0x74, 0x72, | ||||||
|  |     0x69, 0x64, 0x65, 0x64, 0x5f, 0x73, 0x6c, 0x69, 0x63, 0x65, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xec, 0x00, | ||||||
|  |     0x00, 0x00, 0x16, 0xfa, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x1a, | ||||||
|  |     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x14, 0x00, 0x00, 0x00, 0x5c, 0xf9, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00, | ||||||
|  |     0x43, 0x61, 0x73, 0x74, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x4a, 0xfa, 0xff, | ||||||
|  |     0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, | ||||||
|  |     0x00, 0x0f, 0x1c, 0x00, 0x00, 0x00, 0x90, 0xf9, 0xff, 0xff, 0x0d, 0x00, 0x00, 0x00, 0x73, 0x69, 0x67, 0x6e, 0x61, | ||||||
|  |     0x6c, 0x5f, 0x65, 0x6e, 0x65, 0x72, 0x67, 0x79, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, | ||||||
|  |     0x86, 0xfa, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, | ||||||
|  |     0x00, 0x00, 0x00, 0x00, 0x07, 0x18, 0x00, 0x00, 0x00, 0xcc, 0xf9, 0xff, 0xff, 0x0b, 0x00, 0x00, 0x00, 0x73, 0x69, | ||||||
|  |     0x67, 0x6e, 0x61, 0x6c, 0x5f, 0x72, 0x66, 0x66, 0x74, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x02, 0x00, 0x00, 0xbe, | ||||||
|  |     0xfa, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, | ||||||
|  |     0x00, 0x00, 0x00, 0x02, 0x24, 0x00, 0x00, 0x00, 0x04, 0xfa, 0xff, 0xff, 0x16, 0x00, 0x00, 0x00, 0x73, 0x69, 0x67, | ||||||
|  |     0x6e, 0x61, 0x6c, 0x5f, 0x66, 0x66, 0x74, 0x5f, 0x61, 0x75, 0x74, 0x6f, 0x5f, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x31, | ||||||
|  |     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0xfa, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, | ||||||
|  |     0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x24, 0x00, 0x00, 0x00, 0x44, 0xfa, 0xff, 0xff, | ||||||
|  |     0x15, 0x00, 0x00, 0x00, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x5f, 0x66, 0x66, 0x74, 0x5f, 0x61, 0x75, 0x74, 0x6f, | ||||||
|  |     0x5f, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xe0, 0x01, 0x00, 0x00, 0x42, 0xfb, | ||||||
|  |     0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x00, | ||||||
|  |     0x00, 0x00, 0x07, 0x14, 0x00, 0x00, 0x00, 0x88, 0xfa, 0xff, 0xff, 0x07, 0x00, 0x00, 0x00, 0x52, 0x65, 0x73, 0x68, | ||||||
|  |     0x61, 0x70, 0x65, 0x00, 0x01, 0x00, 0x00, 0x00, 0xe0, 0x01, 0x00, 0x00, 0x76, 0xfb, 0xff, 0xff, 0x00, 0x00, 0x00, | ||||||
|  |     0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x1c, 0x00, | ||||||
|  |     0x00, 0x00, 0xbc, 0xfa, 0xff, 0xff, 0x0d, 0x00, 0x00, 0x00, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x5f, 0x77, 0x69, | ||||||
|  |     0x6e, 0x64, 0x6f, 0x77, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xe0, 0x01, 0x00, 0x00, | ||||||
|  |     0xb6, 0xfb, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, | ||||||
|  |     0x00, 0x00, 0x00, 0x00, 0x02, 0x14, 0x00, 0x00, 0x00, 0xfc, 0xfa, 0xff, 0xff, 0x07, 0x00, 0x00, 0x00, 0x43, 0x6f, | ||||||
|  |     0x6e, 0x73, 0x74, 0x5f, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe6, 0xfb, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, | ||||||
|  |     0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x14, 0x00, 0x00, 0x00, | ||||||
|  |     0x2c, 0xfb, 0xff, 0xff, 0x06, 0x00, 0x00, 0x00, 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, | ||||||
|  |     0x00, 0x16, 0xfc, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x11, 0x00, | ||||||
|  |     0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x14, 0x00, 0x00, 0x00, 0x5c, 0xfb, 0xff, 0xff, 0x07, 0x00, 0x00, 0x00, 0x43, | ||||||
|  |     0x6f, 0x6e, 0x73, 0x74, 0x5f, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0xfc, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, | ||||||
|  |     0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x1c, 0x00, 0x00, | ||||||
|  |     0x00, 0x8c, 0xfb, 0xff, 0xff, 0x0d, 0x00, 0x00, 0x00, 0x52, 0x65, 0x73, 0x68, 0x61, 0x70, 0x65, 0x2f, 0x73, 0x68, | ||||||
|  |     0x61, 0x70, 0x65, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x82, 0xfc, 0xff, 0xff, 0x00, | ||||||
|  |     0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, | ||||||
|  |     0x24, 0x00, 0x00, 0x00, 0xc8, 0xfb, 0xff, 0xff, 0x17, 0x00, 0x00, 0x00, 0x63, 0x6c, 0x69, 0x70, 0x5f, 0x62, 0x79, | ||||||
|  |     0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x2f, 0x4d, 0x69, 0x6e, 0x69, 0x6d, 0x75, 0x6d, 0x2f, 0x79, 0x00, 0x00, 0x00, | ||||||
|  |     0x00, 0x00, 0xc2, 0xfc, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x0e, | ||||||
|  |     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x28, 0x00, 0x00, 0x00, 0x08, 0xfc, 0xff, 0xff, 0x18, 0x00, 0x00, 0x00, | ||||||
|  |     0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x5f, 0x62, 0x61, 0x6e, 0x6b, 0x2f, | ||||||
|  |     0x43, 0x6f, 0x6e, 0x73, 0x74, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x3c, 0x01, 0x00, 0x00, 0x0a, 0xfd, | ||||||
|  |     0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x00, | ||||||
|  |     0x00, 0x00, 0x07, 0x28, 0x00, 0x00, 0x00, 0x50, 0xfc, 0xff, 0xff, 0x1a, 0x00, 0x00, 0x00, 0x73, 0x69, 0x67, 0x6e, | ||||||
|  |     0x61, 0x6c, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x5f, 0x62, 0x61, 0x6e, 0x6b, 0x2f, 0x43, 0x6f, 0x6e, 0x73, | ||||||
|  |     0x74, 0x5f, 0x31, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x3c, 0x01, 0x00, 0x00, 0x52, 0xfd, 0xff, 0xff, 0x00, 0x00, | ||||||
|  |     0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x28, | ||||||
|  |     0x00, 0x00, 0x00, 0x98, 0xfc, 0xff, 0xff, 0x1a, 0x00, 0x00, 0x00, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x5f, 0x66, | ||||||
|  |     0x69, 0x6c, 0x74, 0x65, 0x72, 0x5f, 0x62, 0x61, 0x6e, 0x6b, 0x2f, 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x5f, 0x32, 0x00, | ||||||
|  |     0x00, 0x01, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x9a, 0xfd, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, | ||||||
|  |     0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x28, 0x00, 0x00, 0x00, 0xe0, | ||||||
|  |     0xfc, 0xff, 0xff, 0x1a, 0x00, 0x00, 0x00, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, | ||||||
|  |     0x72, 0x5f, 0x62, 0x61, 0x6e, 0x6b, 0x2f, 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x5f, 0x33, 0x00, 0x00, 0x01, 0x00, 0x00, | ||||||
|  |     0x00, 0x29, 0x00, 0x00, 0x00, 0xe2, 0xfd, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, | ||||||
|  |     0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x28, 0x00, 0x00, 0x00, 0x28, 0xfd, 0xff, 0xff, 0x1a, | ||||||
|  |     0x00, 0x00, 0x00, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x5f, 0x62, 0x61, | ||||||
|  |     0x6e, 0x6b, 0x2f, 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x5f, 0x34, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, | ||||||
|  |     0x00, 0x2a, 0xfe, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x09, 0x00, | ||||||
|  |     0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x20, 0x00, 0x00, 0x00, 0x70, 0xfd, 0xff, 0xff, 0x11, 0x00, 0x00, 0x00, 0x73, | ||||||
|  |     0x69, 0x67, 0x6e, 0x61, 0x6c, 0x5f, 0x70, 0x63, 0x61, 0x6e, 0x2f, 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x00, 0x00, 0x00, | ||||||
|  |     0x01, 0x00, 0x00, 0x00, 0x7d, 0x00, 0x00, 0x00, 0x6a, 0xfe, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, | ||||||
|  |     0x00, 0x14, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x20, 0x00, 0x00, 0x00, 0xb0, 0xfd, | ||||||
|  |     0xff, 0xff, 0x13, 0x00, 0x00, 0x00, 0x73, 0x74, 0x72, 0x69, 0x64, 0x65, 0x64, 0x5f, 0x73, 0x6c, 0x69, 0x63, 0x65, | ||||||
|  |     0x2f, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xaa, 0xfe, 0xff, 0xff, | ||||||
|  |     0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | ||||||
|  |     0x02, 0x24, 0x00, 0x00, 0x00, 0xf0, 0xfd, 0xff, 0xff, 0x15, 0x00, 0x00, 0x00, 0x73, 0x74, 0x72, 0x69, 0x64, 0x65, | ||||||
|  |     0x64, 0x5f, 0x73, 0x6c, 0x69, 0x63, 0x65, 0x2f, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x5f, 0x31, 0x00, 0x00, 0x00, 0x01, | ||||||
|  |     0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xee, 0xfe, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, | ||||||
|  |     0x14, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x24, 0x00, 0x00, 0x00, 0x34, 0xfe, 0xff, | ||||||
|  |     0xff, 0x15, 0x00, 0x00, 0x00, 0x73, 0x74, 0x72, 0x69, 0x64, 0x65, 0x64, 0x5f, 0x73, 0x6c, 0x69, 0x63, 0x65, 0x2f, | ||||||
|  |     0x73, 0x74, 0x61, 0x63, 0x6b, 0x5f, 0x32, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x32, | ||||||
|  |     0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, | ||||||
|  |     0x00, 0x00, 0x00, 0x02, 0x14, 0x00, 0x00, 0x00, 0x78, 0xfe, 0xff, 0xff, 0x06, 0x00, 0x00, 0x00, 0x43, 0x61, 0x73, | ||||||
|  |     0x74, 0x5f, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x62, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, | ||||||
|  |     0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x14, 0x00, 0x00, 0x00, 0xa8, | ||||||
|  |     0xfe, 0xff, 0xff, 0x05, 0x00, 0x00, 0x00, 0x7a, 0x65, 0x72, 0x6f, 0x73, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, | ||||||
|  |     0x05, 0x00, 0x00, 0x00, 0x96, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, | ||||||
|  |     0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x14, 0x00, 0x00, 0x00, 0xdc, 0xfe, 0xff, 0xff, 0x07, 0x00, | ||||||
|  |     0x00, 0x00, 0x7a, 0x65, 0x72, 0x6f, 0x73, 0x5f, 0x31, 0x00, 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0xca, | ||||||
|  |     0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, | ||||||
|  |     0x00, 0x00, 0x00, 0x07, 0x14, 0x00, 0x00, 0x00, 0x10, 0xff, 0xff, 0xff, 0x05, 0x00, 0x00, 0x00, 0x43, 0x6f, 0x6e, | ||||||
|  |     0x73, 0x74, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xe0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x16, 0x00, 0x1c, 0x00, | ||||||
|  |     0x18, 0x00, 0x17, 0x00, 0x10, 0x00, 0x0c, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x16, | ||||||
|  |     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, | ||||||
|  |     0x00, 0x00, 0x00, 0x07, 0x2c, 0x00, 0x00, 0x00, 0x5c, 0xff, 0xff, 0xff, 0x1d, 0x00, 0x00, 0x00, 0x73, 0x65, 0x72, | ||||||
|  |     0x76, 0x69, 0x6e, 0x67, 0x5f, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x61, 0x75, 0x64, 0x69, 0x6f, 0x5f, | ||||||
|  |     0x66, 0x72, 0x61, 0x6d, 0x65, 0x3a, 0x30, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xe0, | ||||||
|  |     0x01, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x1c, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0xc8, 0x01, 0x00, 0x00, | ||||||
|  |     0xa4, 0x01, 0x00, 0x00, 0x7c, 0x01, 0x00, 0x00, 0x68, 0x01, 0x00, 0x00, 0x4c, 0x01, 0x00, 0x00, 0x3c, 0x01, 0x00, | ||||||
|  |     0x00, 0x10, 0x01, 0x00, 0x00, 0xdc, 0x00, 0x00, 0x00, 0xa0, 0x00, 0x00, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x50, 0x00, | ||||||
|  |     0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x04, | ||||||
|  |     0x00, 0x00, 0x00, 0x50, 0xfe, 0xff, 0xff, 0x37, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x37, 0x5c, 0xfe, 0xff, 0xff, | ||||||
|  |     0x39, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x39, 0x68, 0xfe, 0xff, 0xff, 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | ||||||
|  |     0x2a, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x00, 0x00, 0x7c, 0xfe, 0xff, 0xff, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, | ||||||
|  |     0x00, 0x12, 0x70, 0xfe, 0xff, 0xff, 0x20, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x13, | ||||||
|  |     0x00, 0x00, 0x00, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x42, 0x61, 0x6e, 0x6b, | ||||||
|  |     0x4c, 0x6f, 0x67, 0x00, 0x98, 0xfe, 0xff, 0xff, 0x20, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | ||||||
|  |     0x20, 0x0a, 0x00, 0x00, 0x00, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x50, 0x43, 0x41, 0x4e, 0x00, 0x00, 0xb8, 0xfe, | ||||||
|  |     0xff, 0xff, 0x20, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x23, 0x00, 0x00, 0x00, 0x53, | ||||||
|  |     0x69, 0x67, 0x6e, 0x61, 0x6c, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x42, 0x61, 0x6e, 0x6b, 0x53, 0x70, 0x65, 0x63, | ||||||
|  |     0x74, 0x72, 0x61, 0x6c, 0x53, 0x75, 0x62, 0x74, 0x72, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x00, 0xf0, 0xfe, 0xff, | ||||||
|  |     0xff, 0x20, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x1a, 0x00, 0x00, 0x00, 0x53, 0x69, | ||||||
|  |     0x67, 0x6e, 0x61, 0x6c, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x42, 0x61, 0x6e, 0x6b, 0x53, 0x71, 0x75, 0x61, 0x72, | ||||||
|  |     0x65, 0x52, 0x6f, 0x6f, 0x74, 0x00, 0x00, 0x20, 0xff, 0xff, 0xff, 0x20, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, | ||||||
|  |     0x00, 0x00, 0x00, 0x20, 0x10, 0x00, 0x00, 0x00, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x46, 0x69, 0x6c, 0x74, 0x65, | ||||||
|  |     0x72, 0x42, 0x61, 0x6e, 0x6b, 0x00, 0x00, 0x00, 0x00, 0x60, 0xff, 0xff, 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, | ||||||
|  |     0x00, 0x02, 0x6c, 0xff, 0xff, 0xff, 0x2d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2d, 0x0c, 0x00, 0x10, 0x00, 0x0f, | ||||||
|  |     0x00, 0x00, 0x00, 0x08, 0x00, 0x04, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, | ||||||
|  |     0x00, 0x00, 0x00, 0x35, 0x7c, 0xff, 0xff, 0xff, 0x20, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | ||||||
|  |     0x20, 0x0c, 0x00, 0x00, 0x00, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x45, 0x6e, 0x65, 0x72, 0x67, 0x79, 0x00, 0x00, | ||||||
|  |     0x00, 0x00, 0xa0, 0xff, 0xff, 0xff, 0x20, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x0a, | ||||||
|  |     0x00, 0x00, 0x00, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x52, 0x66, 0x66, 0x74, 0x00, 0x00, 0xc0, 0xff, 0xff, 0xff, | ||||||
|  |     0x20, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x12, 0x00, 0x00, 0x00, 0x53, 0x69, 0x67, | ||||||
|  |     0x6e, 0x61, 0x6c, 0x46, 0x66, 0x74, 0x41, 0x75, 0x74, 0x6f, 0x53, 0x63, 0x61, 0x6c, 0x65, 0x00, 0x00, 0x0c, 0x00, | ||||||
|  |     0x0c, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x00, | ||||||
|  |     0x00, 0x00, 0x16, 0x0c, 0x00, 0x10, 0x00, 0x0f, 0x00, 0x08, 0x00, 0x00, 0x00, 0x04, 0x00, 0x0c, 0x00, 0x00, 0x00, | ||||||
|  |     0x20, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x0c, 0x00, 0x00, 0x00, 0x53, 0x69, 0x67, | ||||||
|  |     0x6e, 0x61, 0x6c, 0x57, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x00, 0x00, 0x00, 0x00}; | ||||||
|  |  | ||||||
|  | }  // namespace micro_wake_word | ||||||
|  | }  // namespace esphome | ||||||
|  |  | ||||||
|  | #endif  // USE_ESP_IDF | ||||||
							
								
								
									
										521
									
								
								esphome/components/micro_wake_word/micro_wake_word.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										521
									
								
								esphome/components/micro_wake_word/micro_wake_word.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,521 @@ | |||||||
|  | #include "micro_wake_word.h" | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * This is a workaround until we can figure out a way to get | ||||||
|  |  * the tflite-micro idf component code available in CI | ||||||
|  |  * | ||||||
|  |  * */ | ||||||
|  | // | ||||||
|  | #ifndef CLANG_TIDY | ||||||
|  |  | ||||||
|  | #ifdef USE_ESP_IDF | ||||||
|  |  | ||||||
|  | #include "esphome/core/hal.h" | ||||||
|  | #include "esphome/core/helpers.h" | ||||||
|  | #include "esphome/core/log.h" | ||||||
|  |  | ||||||
|  | #include "audio_preprocessor_int8_model_data.h" | ||||||
|  |  | ||||||
|  | #include <tensorflow/lite/core/c/common.h> | ||||||
|  | #include <tensorflow/lite/micro/micro_interpreter.h> | ||||||
|  | #include <tensorflow/lite/micro/micro_mutable_op_resolver.h> | ||||||
|  |  | ||||||
|  | #include <cmath> | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace micro_wake_word { | ||||||
|  |  | ||||||
|  | static const char *const TAG = "micro_wake_word"; | ||||||
|  |  | ||||||
|  | static const size_t SAMPLE_RATE_HZ = 16000;  // 16 kHz | ||||||
|  | static const size_t BUFFER_LENGTH = 500;     // 0.5 seconds | ||||||
|  | static const size_t BUFFER_SIZE = SAMPLE_RATE_HZ / 1000 * BUFFER_LENGTH; | ||||||
|  | static const size_t INPUT_BUFFER_SIZE = 32 * SAMPLE_RATE_HZ / 1000;  // 32ms * 16kHz / 1000ms | ||||||
|  |  | ||||||
|  | float MicroWakeWord::get_setup_priority() const { return setup_priority::AFTER_CONNECTION; } | ||||||
|  |  | ||||||
|  | static const LogString *micro_wake_word_state_to_string(State state) { | ||||||
|  |   switch (state) { | ||||||
|  |     case State::IDLE: | ||||||
|  |       return LOG_STR("IDLE"); | ||||||
|  |     case State::START_MICROPHONE: | ||||||
|  |       return LOG_STR("START_MICROPHONE"); | ||||||
|  |     case State::STARTING_MICROPHONE: | ||||||
|  |       return LOG_STR("STARTING_MICROPHONE"); | ||||||
|  |     case State::DETECTING_WAKE_WORD: | ||||||
|  |       return LOG_STR("DETECTING_WAKE_WORD"); | ||||||
|  |     case State::STOP_MICROPHONE: | ||||||
|  |       return LOG_STR("STOP_MICROPHONE"); | ||||||
|  |     case State::STOPPING_MICROPHONE: | ||||||
|  |       return LOG_STR("STOPPING_MICROPHONE"); | ||||||
|  |     default: | ||||||
|  |       return LOG_STR("UNKNOWN"); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void MicroWakeWord::dump_config() { | ||||||
|  |   ESP_LOGCONFIG(TAG, "microWakeWord:"); | ||||||
|  |   ESP_LOGCONFIG(TAG, "  Wake Word: %s", this->get_wake_word().c_str()); | ||||||
|  |   ESP_LOGCONFIG(TAG, "  Probability cutoff: %.3f", this->probability_cutoff_); | ||||||
|  |   ESP_LOGCONFIG(TAG, "  Sliding window size: %d", this->sliding_window_average_size_); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void MicroWakeWord::setup() { | ||||||
|  |   ESP_LOGCONFIG(TAG, "Setting up microWakeWord..."); | ||||||
|  |  | ||||||
|  |   if (!this->initialize_models()) { | ||||||
|  |     ESP_LOGE(TAG, "Failed to initialize models"); | ||||||
|  |     this->mark_failed(); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   ExternalRAMAllocator<int16_t> allocator(ExternalRAMAllocator<int16_t>::ALLOW_FAILURE); | ||||||
|  |   this->input_buffer_ = allocator.allocate(INPUT_BUFFER_SIZE * sizeof(int16_t)); | ||||||
|  |   if (this->input_buffer_ == nullptr) { | ||||||
|  |     ESP_LOGW(TAG, "Could not allocate input buffer"); | ||||||
|  |     this->mark_failed(); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   this->ring_buffer_ = RingBuffer::create(BUFFER_SIZE * sizeof(int16_t)); | ||||||
|  |   if (this->ring_buffer_ == nullptr) { | ||||||
|  |     ESP_LOGW(TAG, "Could not allocate ring buffer"); | ||||||
|  |     this->mark_failed(); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   ESP_LOGCONFIG(TAG, "Micro Wake Word initialized"); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | int MicroWakeWord::read_microphone_() { | ||||||
|  |   size_t bytes_read = this->microphone_->read(this->input_buffer_, INPUT_BUFFER_SIZE * sizeof(int16_t)); | ||||||
|  |   if (bytes_read == 0) { | ||||||
|  |     return 0; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   size_t bytes_written = this->ring_buffer_->write((void *) this->input_buffer_, bytes_read); | ||||||
|  |   if (bytes_written != bytes_read) { | ||||||
|  |     ESP_LOGW(TAG, "Failed to write some data to ring buffer (written=%d, expected=%d)", bytes_written, bytes_read); | ||||||
|  |   } | ||||||
|  |   return bytes_written; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void MicroWakeWord::loop() { | ||||||
|  |   switch (this->state_) { | ||||||
|  |     case State::IDLE: | ||||||
|  |       break; | ||||||
|  |     case State::START_MICROPHONE: | ||||||
|  |       ESP_LOGD(TAG, "Starting Microphone"); | ||||||
|  |       this->microphone_->start(); | ||||||
|  |       this->set_state_(State::STARTING_MICROPHONE); | ||||||
|  |       this->high_freq_.start(); | ||||||
|  |       break; | ||||||
|  |     case State::STARTING_MICROPHONE: | ||||||
|  |       if (this->microphone_->is_running()) { | ||||||
|  |         this->set_state_(State::DETECTING_WAKE_WORD); | ||||||
|  |       } | ||||||
|  |       break; | ||||||
|  |     case State::DETECTING_WAKE_WORD: | ||||||
|  |       this->read_microphone_(); | ||||||
|  |       if (this->detect_wake_word_()) { | ||||||
|  |         ESP_LOGD(TAG, "Wake Word Detected"); | ||||||
|  |         this->detected_ = true; | ||||||
|  |         this->set_state_(State::STOP_MICROPHONE); | ||||||
|  |       } | ||||||
|  |       break; | ||||||
|  |     case State::STOP_MICROPHONE: | ||||||
|  |       ESP_LOGD(TAG, "Stopping Microphone"); | ||||||
|  |       this->microphone_->stop(); | ||||||
|  |       this->set_state_(State::STOPPING_MICROPHONE); | ||||||
|  |       this->high_freq_.stop(); | ||||||
|  |       break; | ||||||
|  |     case State::STOPPING_MICROPHONE: | ||||||
|  |       if (this->microphone_->is_stopped()) { | ||||||
|  |         this->set_state_(State::IDLE); | ||||||
|  |         if (this->detected_) { | ||||||
|  |           this->detected_ = false; | ||||||
|  |           this->wake_word_detected_trigger_->trigger(this->wake_word_); | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |       break; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void MicroWakeWord::start() { | ||||||
|  |   if (this->is_failed()) { | ||||||
|  |     ESP_LOGW(TAG, "Wake word component is marked as failed. Please check setup logs"); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   if (this->state_ != State::IDLE) { | ||||||
|  |     ESP_LOGW(TAG, "Wake word is already running"); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   this->set_state_(State::START_MICROPHONE); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void MicroWakeWord::stop() { | ||||||
|  |   if (this->state_ == State::IDLE) { | ||||||
|  |     ESP_LOGW(TAG, "Wake word is already stopped"); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   if (this->state_ == State::STOPPING_MICROPHONE) { | ||||||
|  |     ESP_LOGW(TAG, "Wake word is already stopping"); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   this->set_state_(State::STOP_MICROPHONE); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void MicroWakeWord::set_state_(State state) { | ||||||
|  |   ESP_LOGD(TAG, "State changed from %s to %s", LOG_STR_ARG(micro_wake_word_state_to_string(this->state_)), | ||||||
|  |            LOG_STR_ARG(micro_wake_word_state_to_string(state))); | ||||||
|  |   this->state_ = state; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool MicroWakeWord::initialize_models() { | ||||||
|  |   ExternalRAMAllocator<uint8_t> arena_allocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE); | ||||||
|  |   ExternalRAMAllocator<int8_t> features_allocator(ExternalRAMAllocator<int8_t>::ALLOW_FAILURE); | ||||||
|  |   ExternalRAMAllocator<int16_t> audio_samples_allocator(ExternalRAMAllocator<int16_t>::ALLOW_FAILURE); | ||||||
|  |  | ||||||
|  |   this->streaming_tensor_arena_ = arena_allocator.allocate(STREAMING_MODEL_ARENA_SIZE); | ||||||
|  |   if (this->streaming_tensor_arena_ == nullptr) { | ||||||
|  |     ESP_LOGE(TAG, "Could not allocate the streaming model's tensor arena."); | ||||||
|  |     return false; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   this->streaming_var_arena_ = arena_allocator.allocate(STREAMING_MODEL_VARIABLE_ARENA_SIZE); | ||||||
|  |   if (this->streaming_var_arena_ == nullptr) { | ||||||
|  |     ESP_LOGE(TAG, "Could not allocate the streaming model variable's tensor arena."); | ||||||
|  |     return false; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   this->preprocessor_tensor_arena_ = arena_allocator.allocate(PREPROCESSOR_ARENA_SIZE); | ||||||
|  |   if (this->preprocessor_tensor_arena_ == nullptr) { | ||||||
|  |     ESP_LOGE(TAG, "Could not allocate the audio preprocessor model's tensor arena."); | ||||||
|  |     return false; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   this->new_features_data_ = features_allocator.allocate(PREPROCESSOR_FEATURE_SIZE); | ||||||
|  |   if (this->new_features_data_ == nullptr) { | ||||||
|  |     ESP_LOGE(TAG, "Could not allocate the audio features buffer."); | ||||||
|  |     return false; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   this->preprocessor_audio_buffer_ = audio_samples_allocator.allocate(SAMPLE_DURATION_COUNT); | ||||||
|  |   if (this->preprocessor_audio_buffer_ == nullptr) { | ||||||
|  |     ESP_LOGE(TAG, "Could not allocate the audio preprocessor's buffer."); | ||||||
|  |     return false; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   this->preprocessor_stride_buffer_ = audio_samples_allocator.allocate(HISTORY_SAMPLES_TO_KEEP); | ||||||
|  |   if (this->preprocessor_stride_buffer_ == nullptr) { | ||||||
|  |     ESP_LOGE(TAG, "Could not allocate the audio preprocessor's stride buffer."); | ||||||
|  |     return false; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   this->preprocessor_model_ = tflite::GetModel(G_AUDIO_PREPROCESSOR_INT8_TFLITE); | ||||||
|  |   if (this->preprocessor_model_->version() != TFLITE_SCHEMA_VERSION) { | ||||||
|  |     ESP_LOGE(TAG, "Wake word's audio preprocessor model's schema is not supported"); | ||||||
|  |     return false; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   this->streaming_model_ = tflite::GetModel(this->model_start_); | ||||||
|  |   if (this->streaming_model_->version() != TFLITE_SCHEMA_VERSION) { | ||||||
|  |     ESP_LOGE(TAG, "Wake word's streaming model's schema is not supported"); | ||||||
|  |     return false; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   static tflite::MicroMutableOpResolver<18> preprocessor_op_resolver; | ||||||
|  |   static tflite::MicroMutableOpResolver<14> streaming_op_resolver; | ||||||
|  |  | ||||||
|  |   if (!this->register_preprocessor_ops_(preprocessor_op_resolver)) | ||||||
|  |     return false; | ||||||
|  |   if (!this->register_streaming_ops_(streaming_op_resolver)) | ||||||
|  |     return false; | ||||||
|  |  | ||||||
|  |   tflite::MicroAllocator *ma = | ||||||
|  |       tflite::MicroAllocator::Create(this->streaming_var_arena_, STREAMING_MODEL_VARIABLE_ARENA_SIZE); | ||||||
|  |   this->mrv_ = tflite::MicroResourceVariables::Create(ma, 15); | ||||||
|  |  | ||||||
|  |   static tflite::MicroInterpreter static_preprocessor_interpreter( | ||||||
|  |       this->preprocessor_model_, preprocessor_op_resolver, this->preprocessor_tensor_arena_, PREPROCESSOR_ARENA_SIZE); | ||||||
|  |  | ||||||
|  |   static tflite::MicroInterpreter static_streaming_interpreter(this->streaming_model_, streaming_op_resolver, | ||||||
|  |                                                                this->streaming_tensor_arena_, | ||||||
|  |                                                                STREAMING_MODEL_ARENA_SIZE, this->mrv_); | ||||||
|  |  | ||||||
|  |   this->preprocessor_interperter_ = &static_preprocessor_interpreter; | ||||||
|  |   this->streaming_interpreter_ = &static_streaming_interpreter; | ||||||
|  |  | ||||||
|  |   // Allocate tensors for each models. | ||||||
|  |   if (this->preprocessor_interperter_->AllocateTensors() != kTfLiteOk) { | ||||||
|  |     ESP_LOGE(TAG, "Failed to allocate tensors for the audio preprocessor"); | ||||||
|  |     return false; | ||||||
|  |   } | ||||||
|  |   if (this->streaming_interpreter_->AllocateTensors() != kTfLiteOk) { | ||||||
|  |     ESP_LOGE(TAG, "Failed to allocate tensors for the streaming model"); | ||||||
|  |     return false; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // Verify input tensor matches expected values | ||||||
|  |   TfLiteTensor *input = this->streaming_interpreter_->input(0); | ||||||
|  |   if ((input->dims->size != 3) || (input->dims->data[0] != 1) || (input->dims->data[0] != 1) || | ||||||
|  |       (input->dims->data[1] != 1) || (input->dims->data[2] != PREPROCESSOR_FEATURE_SIZE)) { | ||||||
|  |     ESP_LOGE(TAG, "Wake word detection model tensor input dimensions is not 1x1x%u", input->dims->data[2]); | ||||||
|  |     return false; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (input->type != kTfLiteInt8) { | ||||||
|  |     ESP_LOGE(TAG, "Wake word detection model tensor input is not int8."); | ||||||
|  |     return false; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // Verify output tensor matches expected values | ||||||
|  |   TfLiteTensor *output = this->streaming_interpreter_->output(0); | ||||||
|  |   if ((output->dims->size != 2) || (output->dims->data[0] != 1) || (output->dims->data[1] != 1)) { | ||||||
|  |     ESP_LOGE(TAG, "Wake word detection model tensor output dimensions is not 1x1."); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (output->type != kTfLiteUInt8) { | ||||||
|  |     ESP_LOGE(TAG, "Wake word detection model tensor input is not uint8."); | ||||||
|  |     return false; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   this->recent_streaming_probabilities_.resize(this->sliding_window_average_size_, 0.0); | ||||||
|  |  | ||||||
|  |   return true; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool MicroWakeWord::update_features_() { | ||||||
|  |   // Retrieve strided audio samples | ||||||
|  |   int16_t *audio_samples = nullptr; | ||||||
|  |   if (!this->stride_audio_samples_(&audio_samples)) { | ||||||
|  |     return false; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // Compute the features for the newest audio samples | ||||||
|  |   if (!this->generate_single_feature_(audio_samples, SAMPLE_DURATION_COUNT, this->new_features_data_)) { | ||||||
|  |     return false; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return true; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | float MicroWakeWord::perform_streaming_inference_() { | ||||||
|  |   TfLiteTensor *input = this->streaming_interpreter_->input(0); | ||||||
|  |  | ||||||
|  |   size_t bytes_to_copy = input->bytes; | ||||||
|  |  | ||||||
|  |   memcpy((void *) (tflite::GetTensorData<int8_t>(input)), (const void *) (this->new_features_data_), bytes_to_copy); | ||||||
|  |  | ||||||
|  |   uint32_t prior_invoke = millis(); | ||||||
|  |  | ||||||
|  |   TfLiteStatus invoke_status = this->streaming_interpreter_->Invoke(); | ||||||
|  |   if (invoke_status != kTfLiteOk) { | ||||||
|  |     ESP_LOGW(TAG, "Streaming Interpreter Invoke failed"); | ||||||
|  |     return false; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   ESP_LOGV(TAG, "Streaming Inference Latency=%u ms", (millis() - prior_invoke)); | ||||||
|  |  | ||||||
|  |   TfLiteTensor *output = this->streaming_interpreter_->output(0); | ||||||
|  |  | ||||||
|  |   return static_cast<float>(output->data.uint8[0]) / 255.0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool MicroWakeWord::detect_wake_word_() { | ||||||
|  |   // Preprocess the newest audio samples into features | ||||||
|  |   if (!this->update_features_()) { | ||||||
|  |     return false; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // Perform inference | ||||||
|  |   uint32_t streaming_size = micros(); | ||||||
|  |   float streaming_prob = this->perform_streaming_inference_(); | ||||||
|  |  | ||||||
|  |   // Add the most recent probability to the sliding window | ||||||
|  |   this->recent_streaming_probabilities_[this->last_n_index_] = streaming_prob; | ||||||
|  |   ++this->last_n_index_; | ||||||
|  |   if (this->last_n_index_ == this->sliding_window_average_size_) | ||||||
|  |     this->last_n_index_ = 0; | ||||||
|  |  | ||||||
|  |   float sum = 0.0; | ||||||
|  |   for (auto &prob : this->recent_streaming_probabilities_) { | ||||||
|  |     sum += prob; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   float sliding_window_average = sum / static_cast<float>(this->sliding_window_average_size_); | ||||||
|  |  | ||||||
|  |   // Ensure we have enough samples since the last positive detection | ||||||
|  |   this->ignore_windows_ = std::min(this->ignore_windows_ + 1, 0); | ||||||
|  |   if (this->ignore_windows_ < 0) { | ||||||
|  |     return false; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // Detect the wake word if the sliding window average is above the cutoff | ||||||
|  |   if (sliding_window_average > this->probability_cutoff_) { | ||||||
|  |     this->ignore_windows_ = -MIN_SLICES_BEFORE_DETECTION; | ||||||
|  |     for (auto &prob : this->recent_streaming_probabilities_) { | ||||||
|  |       prob = 0; | ||||||
|  |     } | ||||||
|  |     return true; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return false; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void MicroWakeWord::set_sliding_window_average_size(size_t size) { | ||||||
|  |   this->sliding_window_average_size_ = size; | ||||||
|  |   this->recent_streaming_probabilities_.resize(this->sliding_window_average_size_, 0.0); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool MicroWakeWord::slice_available_() { | ||||||
|  |   size_t available = this->ring_buffer_->available(); | ||||||
|  |  | ||||||
|  |   size_t free = this->ring_buffer_->free(); | ||||||
|  |  | ||||||
|  |   if (free < NEW_SAMPLES_TO_GET * sizeof(int16_t)) { | ||||||
|  |     // If the ring buffer is within one audio slice of being full, then wake word detection will have issues. | ||||||
|  |     // If this is constantly occuring, then some possibilities why are | ||||||
|  |     //  1) there are too many other slow components configured | ||||||
|  |     //  2) the ESP32 isn't fast enough; e.g., an ESP32 is much slower than an ESP32-S3 at inferences. | ||||||
|  |     //  3) the model is too large | ||||||
|  |     //  4) the model uses operations that are not optimized | ||||||
|  |     ESP_LOGW(TAG, | ||||||
|  |              "Audio buffer is nearly full. Wake word detection may be less accurate and have slower reponse times. " | ||||||
|  | #if !defined(USE_ESP32_VARIANT_ESP32S3) | ||||||
|  |              "microWakeWord is designed for the ESP32-S3. The current platform is too slow for this model." | ||||||
|  | #endif | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return available > (NEW_SAMPLES_TO_GET * sizeof(int16_t)); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool MicroWakeWord::stride_audio_samples_(int16_t **audio_samples) { | ||||||
|  |   if (!this->slice_available_()) { | ||||||
|  |     return false; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // Copy 320 bytes (160 samples over 10 ms) into preprocessor_audio_buffer_ from history in | ||||||
|  |   // preprocessor_stride_buffer_ | ||||||
|  |   memcpy((void *) (this->preprocessor_audio_buffer_), (void *) (this->preprocessor_stride_buffer_), | ||||||
|  |          HISTORY_SAMPLES_TO_KEEP * sizeof(int16_t)); | ||||||
|  |  | ||||||
|  |   // Copy 640 bytes (320 samples over 20 ms) from the ring buffer | ||||||
|  |   // The first 320 bytes (160 samples over 10 ms) will be from history | ||||||
|  |   size_t bytes_read = this->ring_buffer_->read((void *) (this->preprocessor_audio_buffer_ + HISTORY_SAMPLES_TO_KEEP), | ||||||
|  |                                                NEW_SAMPLES_TO_GET * sizeof(int16_t), pdMS_TO_TICKS(200)); | ||||||
|  |  | ||||||
|  |   if (bytes_read == 0) { | ||||||
|  |     ESP_LOGE(TAG, "Could not read data from Ring Buffer"); | ||||||
|  |   } else if (bytes_read < NEW_SAMPLES_TO_GET * sizeof(int16_t)) { | ||||||
|  |     ESP_LOGD(TAG, "Partial Read of Data by Model"); | ||||||
|  |     ESP_LOGD(TAG, "Could only read %d bytes when required %d bytes ", bytes_read, | ||||||
|  |              (int) (NEW_SAMPLES_TO_GET * sizeof(int16_t))); | ||||||
|  |     return false; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // Copy the last 320 bytes (160 samples over 10 ms) from the audio buffer into history stride buffer for the next | ||||||
|  |   // iteration | ||||||
|  |   memcpy((void *) (this->preprocessor_stride_buffer_), (void *) (this->preprocessor_audio_buffer_ + NEW_SAMPLES_TO_GET), | ||||||
|  |          HISTORY_SAMPLES_TO_KEEP * sizeof(int16_t)); | ||||||
|  |  | ||||||
|  |   *audio_samples = this->preprocessor_audio_buffer_; | ||||||
|  |   return true; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool MicroWakeWord::generate_single_feature_(const int16_t *audio_data, const int audio_data_size, | ||||||
|  |                                              int8_t feature_output[PREPROCESSOR_FEATURE_SIZE]) { | ||||||
|  |   TfLiteTensor *input = this->preprocessor_interperter_->input(0); | ||||||
|  |   TfLiteTensor *output = this->preprocessor_interperter_->output(0); | ||||||
|  |   std::copy_n(audio_data, audio_data_size, tflite::GetTensorData<int16_t>(input)); | ||||||
|  |  | ||||||
|  |   if (this->preprocessor_interperter_->Invoke() != kTfLiteOk) { | ||||||
|  |     ESP_LOGE(TAG, "Failed to preprocess audio for local wake word."); | ||||||
|  |     return false; | ||||||
|  |   } | ||||||
|  |   std::memcpy(feature_output, tflite::GetTensorData<int8_t>(output), PREPROCESSOR_FEATURE_SIZE * sizeof(int8_t)); | ||||||
|  |  | ||||||
|  |   return true; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool MicroWakeWord::register_preprocessor_ops_(tflite::MicroMutableOpResolver<18> &op_resolver) { | ||||||
|  |   if (op_resolver.AddReshape() != kTfLiteOk) | ||||||
|  |     return false; | ||||||
|  |   if (op_resolver.AddCast() != kTfLiteOk) | ||||||
|  |     return false; | ||||||
|  |   if (op_resolver.AddStridedSlice() != kTfLiteOk) | ||||||
|  |     return false; | ||||||
|  |   if (op_resolver.AddConcatenation() != kTfLiteOk) | ||||||
|  |     return false; | ||||||
|  |   if (op_resolver.AddMul() != kTfLiteOk) | ||||||
|  |     return false; | ||||||
|  |   if (op_resolver.AddAdd() != kTfLiteOk) | ||||||
|  |     return false; | ||||||
|  |   if (op_resolver.AddDiv() != kTfLiteOk) | ||||||
|  |     return false; | ||||||
|  |   if (op_resolver.AddMinimum() != kTfLiteOk) | ||||||
|  |     return false; | ||||||
|  |   if (op_resolver.AddMaximum() != kTfLiteOk) | ||||||
|  |     return false; | ||||||
|  |   if (op_resolver.AddWindow() != kTfLiteOk) | ||||||
|  |     return false; | ||||||
|  |   if (op_resolver.AddFftAutoScale() != kTfLiteOk) | ||||||
|  |     return false; | ||||||
|  |   if (op_resolver.AddRfft() != kTfLiteOk) | ||||||
|  |     return false; | ||||||
|  |   if (op_resolver.AddEnergy() != kTfLiteOk) | ||||||
|  |     return false; | ||||||
|  |   if (op_resolver.AddFilterBank() != kTfLiteOk) | ||||||
|  |     return false; | ||||||
|  |   if (op_resolver.AddFilterBankSquareRoot() != kTfLiteOk) | ||||||
|  |     return false; | ||||||
|  |   if (op_resolver.AddFilterBankSpectralSubtraction() != kTfLiteOk) | ||||||
|  |     return false; | ||||||
|  |   if (op_resolver.AddPCAN() != kTfLiteOk) | ||||||
|  |     return false; | ||||||
|  |   if (op_resolver.AddFilterBankLog() != kTfLiteOk) | ||||||
|  |     return false; | ||||||
|  |  | ||||||
|  |   return true; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool MicroWakeWord::register_streaming_ops_(tflite::MicroMutableOpResolver<14> &op_resolver) { | ||||||
|  |   if (op_resolver.AddCallOnce() != kTfLiteOk) | ||||||
|  |     return false; | ||||||
|  |   if (op_resolver.AddVarHandle() != kTfLiteOk) | ||||||
|  |     return false; | ||||||
|  |   if (op_resolver.AddReshape() != kTfLiteOk) | ||||||
|  |     return false; | ||||||
|  |   if (op_resolver.AddReadVariable() != kTfLiteOk) | ||||||
|  |     return false; | ||||||
|  |   if (op_resolver.AddStridedSlice() != kTfLiteOk) | ||||||
|  |     return false; | ||||||
|  |   if (op_resolver.AddConcatenation() != kTfLiteOk) | ||||||
|  |     return false; | ||||||
|  |   if (op_resolver.AddAssignVariable() != kTfLiteOk) | ||||||
|  |     return false; | ||||||
|  |   if (op_resolver.AddConv2D() != kTfLiteOk) | ||||||
|  |     return false; | ||||||
|  |   if (op_resolver.AddMul() != kTfLiteOk) | ||||||
|  |     return false; | ||||||
|  |   if (op_resolver.AddAdd() != kTfLiteOk) | ||||||
|  |     return false; | ||||||
|  |   if (op_resolver.AddMean() != kTfLiteOk) | ||||||
|  |     return false; | ||||||
|  |   if (op_resolver.AddFullyConnected() != kTfLiteOk) | ||||||
|  |     return false; | ||||||
|  |   if (op_resolver.AddLogistic() != kTfLiteOk) | ||||||
|  |     return false; | ||||||
|  |   if (op_resolver.AddQuantize() != kTfLiteOk) | ||||||
|  |     return false; | ||||||
|  |  | ||||||
|  |   return true; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | }  // namespace micro_wake_word | ||||||
|  | }  // namespace esphome | ||||||
|  |  | ||||||
|  | #endif  // USE_ESP_IDF | ||||||
|  |  | ||||||
|  | #endif  // CLANG_TIDY | ||||||
							
								
								
									
										207
									
								
								esphome/components/micro_wake_word/micro_wake_word.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										207
									
								
								esphome/components/micro_wake_word/micro_wake_word.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,207 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * This is a workaround until we can figure out a way to get | ||||||
|  |  * the tflite-micro idf component code available in CI | ||||||
|  |  * | ||||||
|  |  * */ | ||||||
|  | // | ||||||
|  | #ifndef CLANG_TIDY | ||||||
|  |  | ||||||
|  | #ifdef USE_ESP_IDF | ||||||
|  |  | ||||||
|  | #include "esphome/core/automation.h" | ||||||
|  | #include "esphome/core/component.h" | ||||||
|  | #include "esphome/core/ring_buffer.h" | ||||||
|  |  | ||||||
|  | #include "esphome/components/microphone/microphone.h" | ||||||
|  |  | ||||||
|  | #include <tensorflow/lite/core/c/common.h> | ||||||
|  | #include <tensorflow/lite/micro/micro_interpreter.h> | ||||||
|  | #include <tensorflow/lite/micro/micro_mutable_op_resolver.h> | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace micro_wake_word { | ||||||
|  |  | ||||||
|  | // The following are dictated by the preprocessor model | ||||||
|  | // | ||||||
|  | // The number of features the audio preprocessor generates per slice | ||||||
|  | static const uint8_t PREPROCESSOR_FEATURE_SIZE = 40; | ||||||
|  | // How frequently the preprocessor generates a new set of features | ||||||
|  | static const uint8_t FEATURE_STRIDE_MS = 20; | ||||||
|  | // Duration of each slice used as input into the preprocessor | ||||||
|  | static const uint8_t FEATURE_DURATION_MS = 30; | ||||||
|  | // Audio sample frequency in hertz | ||||||
|  | static const uint16_t AUDIO_SAMPLE_FREQUENCY = 16000; | ||||||
|  | // The number of old audio samples that are saved to be part of the next feature window | ||||||
|  | static const uint16_t HISTORY_SAMPLES_TO_KEEP = | ||||||
|  |     ((FEATURE_DURATION_MS - FEATURE_STRIDE_MS) * (AUDIO_SAMPLE_FREQUENCY / 1000)); | ||||||
|  | // The number of new audio samples to receive to be included with the next feature window | ||||||
|  | static const uint16_t NEW_SAMPLES_TO_GET = (FEATURE_STRIDE_MS * (AUDIO_SAMPLE_FREQUENCY / 1000)); | ||||||
|  | // The total number of audio samples included in the feature window | ||||||
|  | static const uint16_t SAMPLE_DURATION_COUNT = FEATURE_DURATION_MS * AUDIO_SAMPLE_FREQUENCY / 1000; | ||||||
|  | // Number of bytes in memory needed for the preprocessor arena | ||||||
|  | static const uint32_t PREPROCESSOR_ARENA_SIZE = 9528; | ||||||
|  |  | ||||||
|  | // The following configure the streaming wake word model | ||||||
|  | // | ||||||
|  | // The number of audio slices to process before accepting a positive detection | ||||||
|  | static const uint8_t MIN_SLICES_BEFORE_DETECTION = 74; | ||||||
|  |  | ||||||
|  | // Number of bytes in memory needed for the streaming wake word model | ||||||
|  | static const uint32_t STREAMING_MODEL_ARENA_SIZE = 64000; | ||||||
|  | static const uint32_t STREAMING_MODEL_VARIABLE_ARENA_SIZE = 1024; | ||||||
|  |  | ||||||
|  | enum State { | ||||||
|  |   IDLE, | ||||||
|  |   START_MICROPHONE, | ||||||
|  |   STARTING_MICROPHONE, | ||||||
|  |   DETECTING_WAKE_WORD, | ||||||
|  |   STOP_MICROPHONE, | ||||||
|  |   STOPPING_MICROPHONE, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | class MicroWakeWord : public Component { | ||||||
|  |  public: | ||||||
|  |   void setup() override; | ||||||
|  |   void loop() override; | ||||||
|  |   float get_setup_priority() const override; | ||||||
|  |   void dump_config() override; | ||||||
|  |  | ||||||
|  |   void start(); | ||||||
|  |   void stop(); | ||||||
|  |  | ||||||
|  |   bool is_running() const { return this->state_ != State::IDLE; } | ||||||
|  |  | ||||||
|  |   bool initialize_models(); | ||||||
|  |  | ||||||
|  |   std::string get_wake_word() { return this->wake_word_; } | ||||||
|  |  | ||||||
|  |   // Increasing either of these will reduce the rate of false acceptances while increasing the false rejection rate | ||||||
|  |   void set_probability_cutoff(float probability_cutoff) { this->probability_cutoff_ = probability_cutoff; } | ||||||
|  |   void set_sliding_window_average_size(size_t size); | ||||||
|  |  | ||||||
|  |   void set_microphone(microphone::Microphone *microphone) { this->microphone_ = microphone; } | ||||||
|  |  | ||||||
|  |   Trigger<std::string> *get_wake_word_detected_trigger() const { return this->wake_word_detected_trigger_; } | ||||||
|  |  | ||||||
|  |   void set_model_start(const uint8_t *model_start) { this->model_start_ = model_start; } | ||||||
|  |   void set_wake_word(const std::string &wake_word) { this->wake_word_ = wake_word; } | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   void set_state_(State state); | ||||||
|  |   int read_microphone_(); | ||||||
|  |  | ||||||
|  |   const uint8_t *model_start_; | ||||||
|  |   std::string wake_word_; | ||||||
|  |  | ||||||
|  |   microphone::Microphone *microphone_{nullptr}; | ||||||
|  |   Trigger<std::string> *wake_word_detected_trigger_ = new Trigger<std::string>(); | ||||||
|  |   State state_{State::IDLE}; | ||||||
|  |   HighFrequencyLoopRequester high_freq_; | ||||||
|  |  | ||||||
|  |   std::unique_ptr<RingBuffer> ring_buffer_; | ||||||
|  |  | ||||||
|  |   int16_t *input_buffer_; | ||||||
|  |  | ||||||
|  |   const tflite::Model *preprocessor_model_{nullptr}; | ||||||
|  |   const tflite::Model *streaming_model_{nullptr}; | ||||||
|  |   tflite::MicroInterpreter *streaming_interpreter_{nullptr}; | ||||||
|  |   tflite::MicroInterpreter *preprocessor_interperter_{nullptr}; | ||||||
|  |  | ||||||
|  |   std::vector<float> recent_streaming_probabilities_; | ||||||
|  |   size_t last_n_index_{0}; | ||||||
|  |  | ||||||
|  |   float probability_cutoff_{0.5}; | ||||||
|  |   size_t sliding_window_average_size_{10}; | ||||||
|  |  | ||||||
|  |   // When the wake word detection first starts or after the word has been detected once, we ignore this many audio | ||||||
|  |   // feature slices before accepting a positive detection again | ||||||
|  |   int16_t ignore_windows_{-MIN_SLICES_BEFORE_DETECTION}; | ||||||
|  |  | ||||||
|  |   uint8_t *streaming_var_arena_{nullptr}; | ||||||
|  |   uint8_t *streaming_tensor_arena_{nullptr}; | ||||||
|  |   uint8_t *preprocessor_tensor_arena_{nullptr}; | ||||||
|  |   int8_t *new_features_data_{nullptr}; | ||||||
|  |  | ||||||
|  |   tflite::MicroResourceVariables *mrv_{nullptr}; | ||||||
|  |  | ||||||
|  |   // Stores audio fed into feature generator preprocessor | ||||||
|  |   int16_t *preprocessor_audio_buffer_; | ||||||
|  |   int16_t *preprocessor_stride_buffer_; | ||||||
|  |  | ||||||
|  |   bool detected_{false}; | ||||||
|  |  | ||||||
|  |   /** Detects if wake word has been said | ||||||
|  |    * | ||||||
|  |    * If enough audio samples are available, it will generate one slice of new features. | ||||||
|  |    * If the streaming model predicts the wake word, then the nonstreaming model confirms it. | ||||||
|  |    * @param ring_Buffer Ring buffer containing raw audio samples | ||||||
|  |    * @return True if the wake word is detected, false otherwise | ||||||
|  |    */ | ||||||
|  |   bool detect_wake_word_(); | ||||||
|  |  | ||||||
|  |   /// @brief Returns true if there are enough audio samples in the buffer to generate another slice of features | ||||||
|  |   bool slice_available_(); | ||||||
|  |  | ||||||
|  |   /** Shifts previous feature slices over by one and generates a new slice of features | ||||||
|  |    * | ||||||
|  |    * @param ring_buffer ring buffer containing raw audio samples | ||||||
|  |    * @return True if a new slice of features was generated, false otherwise | ||||||
|  |    */ | ||||||
|  |   bool update_features_(); | ||||||
|  |  | ||||||
|  |   /** Generates features from audio samples | ||||||
|  |    * | ||||||
|  |    * Adapted from TFLite micro speech example | ||||||
|  |    * @param audio_data Pointer to array with the audio samples | ||||||
|  |    * @param audio_data_size The number of samples to use as input to the preprocessor model | ||||||
|  |    * @param feature_output Array that will store the features | ||||||
|  |    * @return True if successful, false otherwise. | ||||||
|  |    */ | ||||||
|  |   bool generate_single_feature_(const int16_t *audio_data, int audio_data_size, | ||||||
|  |                                 int8_t feature_output[PREPROCESSOR_FEATURE_SIZE]); | ||||||
|  |  | ||||||
|  |   /** Performs inference over the most recent feature slice with the streaming model | ||||||
|  |    * | ||||||
|  |    * @return Probability of the wake word between 0.0 and 1.0 | ||||||
|  |    */ | ||||||
|  |   float perform_streaming_inference_(); | ||||||
|  |  | ||||||
|  |   /** Strides the audio samples by keeping the last 10 ms of the previous slice | ||||||
|  |    * | ||||||
|  |    * Adapted from the TFLite micro speech example | ||||||
|  |    * @param ring_buffer Ring buffer containing raw audio samples | ||||||
|  |    * @param audio_samples Pointer to an array that will store the strided audio samples | ||||||
|  |    * @return True if successful, false otherwise | ||||||
|  |    */ | ||||||
|  |   bool stride_audio_samples_(int16_t **audio_samples); | ||||||
|  |  | ||||||
|  |   /// @brief Returns true if successfully registered the preprocessor's TensorFlow operations | ||||||
|  |   bool register_preprocessor_ops_(tflite::MicroMutableOpResolver<18> &op_resolver); | ||||||
|  |  | ||||||
|  |   /// @brief Returns true if successfully registered the streaming model's TensorFlow operations | ||||||
|  |   bool register_streaming_ops_(tflite::MicroMutableOpResolver<14> &op_resolver); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | template<typename... Ts> class StartAction : public Action<Ts...>, public Parented<MicroWakeWord> { | ||||||
|  |  public: | ||||||
|  |   void play(Ts... x) override { this->parent_->start(); } | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | template<typename... Ts> class StopAction : public Action<Ts...>, public Parented<MicroWakeWord> { | ||||||
|  |  public: | ||||||
|  |   void play(Ts... x) override { this->parent_->stop(); } | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | template<typename... Ts> class IsRunningCondition : public Condition<Ts...>, public Parented<MicroWakeWord> { | ||||||
|  |  public: | ||||||
|  |   bool check(Ts... x) override { return this->parent_->is_running(); } | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | }  // namespace micro_wake_word | ||||||
|  | }  // namespace esphome | ||||||
|  |  | ||||||
|  | #endif  // USE_ESP_IDF | ||||||
|  |  | ||||||
|  | #endif  // CLANG_TIDY | ||||||
| @@ -91,8 +91,13 @@ void MQTTClientComponent::send_device_info_() { | |||||||
|   this->publish_json( |   this->publish_json( | ||||||
|       topic, |       topic, | ||||||
|       [](JsonObject root) { |       [](JsonObject root) { | ||||||
|         auto ip = network::get_ip_address(); |         uint8_t index = 0; | ||||||
|         root["ip"] = ip.str(); |         for (auto &ip : network::get_ip_addresses()) { | ||||||
|  |           if (ip.is_set()) { | ||||||
|  |             root["ip" + (index == 0 ? "" : esphome::to_string(index))] = ip.str(); | ||||||
|  |             index++; | ||||||
|  |           } | ||||||
|  |         } | ||||||
|         root["name"] = App.get_name(); |         root["name"] = App.get_name(); | ||||||
| #ifdef USE_API | #ifdef USE_API | ||||||
|         root["port"] = api::global_api_server->get_port(); |         root["port"] = api::global_api_server->get_port(); | ||||||
| @@ -159,14 +164,13 @@ void MQTTClientComponent::start_dnslookup_() { | |||||||
|   this->dns_resolve_error_ = false; |   this->dns_resolve_error_ = false; | ||||||
|   this->dns_resolved_ = false; |   this->dns_resolved_ = false; | ||||||
|   ip_addr_t addr; |   ip_addr_t addr; | ||||||
| #if defined(USE_ESP32) || defined(USE_LIBRETINY) | #if USE_NETWORK_IPV6 | ||||||
|  |   err_t err = dns_gethostbyname_addrtype(this->credentials_.address.c_str(), &addr, | ||||||
|  |                                          MQTTClientComponent::dns_found_callback, this, LWIP_DNS_ADDRTYPE_IPV6_IPV4); | ||||||
|  | #else | ||||||
|   err_t err = dns_gethostbyname_addrtype(this->credentials_.address.c_str(), &addr, |   err_t err = dns_gethostbyname_addrtype(this->credentials_.address.c_str(), &addr, | ||||||
|                                          MQTTClientComponent::dns_found_callback, this, LWIP_DNS_ADDRTYPE_IPV4); |                                          MQTTClientComponent::dns_found_callback, this, LWIP_DNS_ADDRTYPE_IPV4); | ||||||
| #endif | #endif /* USE_NETWORK_IPV6 */ | ||||||
| #ifdef USE_ESP8266 |  | ||||||
|   err_t err = dns_gethostbyname(this->credentials_.address.c_str(), &addr, |  | ||||||
|                                 esphome::mqtt::MQTTClientComponent::dns_found_callback, this); |  | ||||||
| #endif |  | ||||||
|   switch (err) { |   switch (err) { | ||||||
|     case ERR_OK: { |     case ERR_OK: { | ||||||
|       // Got IP immediately |       // Got IP immediately | ||||||
|   | |||||||
| @@ -1,6 +1,8 @@ | |||||||
| #include "mqtt_text_sensor.h" | #include "mqtt_text_sensor.h" | ||||||
| #include "esphome/core/log.h" | #include "esphome/core/log.h" | ||||||
|  |  | ||||||
|  | #include "mqtt_const.h" | ||||||
|  |  | ||||||
| #ifdef USE_MQTT | #ifdef USE_MQTT | ||||||
| #ifdef USE_TEXT_SENSOR | #ifdef USE_TEXT_SENSOR | ||||||
|  |  | ||||||
| @@ -13,6 +15,8 @@ using namespace esphome::text_sensor; | |||||||
|  |  | ||||||
| MQTTTextSensor::MQTTTextSensor(TextSensor *sensor) : sensor_(sensor) {} | MQTTTextSensor::MQTTTextSensor(TextSensor *sensor) : sensor_(sensor) {} | ||||||
| void MQTTTextSensor::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { | void MQTTTextSensor::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { | ||||||
|  |   if (!this->sensor_->get_device_class().empty()) | ||||||
|  |     root[MQTT_DEVICE_CLASS] = this->sensor_->get_device_class(); | ||||||
|   config.command_topic = false; |   config.command_topic = false; | ||||||
| } | } | ||||||
| void MQTTTextSensor::setup() { | void MQTTTextSensor::setup() { | ||||||
|   | |||||||
							
								
								
									
										1
									
								
								esphome/components/ms8607/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								esphome/components/ms8607/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | |||||||
|  | CODEOWNERS = ["@e28eta"] | ||||||
							
								
								
									
										444
									
								
								esphome/components/ms8607/ms8607.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										444
									
								
								esphome/components/ms8607/ms8607.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,444 @@ | |||||||
|  | #include "ms8607.h" | ||||||
|  |  | ||||||
|  | #include "esphome/core/hal.h" | ||||||
|  | #include "esphome/core/helpers.h" | ||||||
|  | #include "esphome/core/log.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace ms8607 { | ||||||
|  |  | ||||||
|  | /// TAG used for logging calls | ||||||
|  | static const char *const TAG = "ms8607"; | ||||||
|  |  | ||||||
|  | /// Reset the Pressure/Temperature sensor | ||||||
|  | static const uint8_t MS8607_PT_CMD_RESET = 0x1E; | ||||||
|  |  | ||||||
|  | /// Beginning of PROM register addresses. Same for both i2c addresses. Each address has 16 bits of data, and | ||||||
|  | /// PROM addresses step by two, so the LSB is always 0 | ||||||
|  | static const uint8_t MS8607_PROM_START = 0xA0; | ||||||
|  | /// Last PROM register address. | ||||||
|  | static const uint8_t MS8607_PROM_END = 0xAE; | ||||||
|  | /// Number of PROM registers. | ||||||
|  | static const uint8_t MS8607_PROM_COUNT = (MS8607_PROM_END - MS8607_PROM_START) >> 1; | ||||||
|  |  | ||||||
|  | /// Reset the Humidity sensor | ||||||
|  | static const uint8_t MS8607_CMD_H_RESET = 0xFE; | ||||||
|  | /// Read relative humidity, without holding i2c master | ||||||
|  | static const uint8_t MS8607_CMD_H_MEASURE_NO_HOLD = 0xF5; | ||||||
|  | /// Temperature correction coefficient for Relative Humidity from datasheet | ||||||
|  | static const float MS8607_H_TEMP_COEFFICIENT = -0.18; | ||||||
|  |  | ||||||
|  | /// Read the converted analog value, either D1 (pressure) or D2 (temperature) | ||||||
|  | static const uint8_t MS8607_CMD_ADC_READ = 0x00; | ||||||
|  |  | ||||||
|  | // TODO: allow OSR to be turned down for speed and/or lower power consumption via configuration. | ||||||
|  | // ms8607 supports 6 different settings | ||||||
|  |  | ||||||
|  | /// Request conversion of analog D1 (pressure) with OSR=8192 (highest oversampling ratio). Takes maximum of 17.2ms | ||||||
|  | static const uint8_t MS8607_CMD_CONV_D1_OSR_8K = 0x4A; | ||||||
|  | /// Request conversion of analog D2 (temperature) with OSR=8192 (highest oversampling ratio). Takes maximum of 17.2ms | ||||||
|  | static const uint8_t MS8607_CMD_CONV_D2_OSR_8K = 0x5A; | ||||||
|  |  | ||||||
|  | enum class MS8607Component::ErrorCode { | ||||||
|  |   /// Component hasn't failed (yet?) | ||||||
|  |   NONE = 0, | ||||||
|  |   /// Both the Pressure/Temperature address and the Humidity address failed to reset | ||||||
|  |   PTH_RESET_FAILED = 1, | ||||||
|  |   /// Asking the Pressure/Temperature sensor to reset failed | ||||||
|  |   PT_RESET_FAILED = 2, | ||||||
|  |   /// Asking the Humidity sensor to reset failed | ||||||
|  |   H_RESET_FAILED = 3, | ||||||
|  |   /// Reading the PROM calibration values failed | ||||||
|  |   PROM_READ_FAILED = 4, | ||||||
|  |   /// The PROM calibration values failed the CRC check | ||||||
|  |   PROM_CRC_FAILED = 5, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | enum class MS8607Component::SetupStatus { | ||||||
|  |   /// This component has not successfully reset the PT & H devices | ||||||
|  |   NEEDS_RESET, | ||||||
|  |   /// Reset commands succeeded, need to wait >= 15ms to read PROM | ||||||
|  |   NEEDS_PROM_READ, | ||||||
|  |   /// Successfully read PROM and ready to update sensors | ||||||
|  |   SUCCESSFUL, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | static uint8_t crc4(uint16_t *buffer, size_t length); | ||||||
|  | static uint8_t hsensor_crc_check(uint16_t value); | ||||||
|  |  | ||||||
|  | void MS8607Component::setup() { | ||||||
|  |   ESP_LOGCONFIG(TAG, "Setting up MS8607..."); | ||||||
|  |   this->error_code_ = ErrorCode::NONE; | ||||||
|  |   this->setup_status_ = SetupStatus::NEEDS_RESET; | ||||||
|  |  | ||||||
|  |   // I do not know why the device sometimes NACKs the reset command, but | ||||||
|  |   // try 3 times in case it's a transitory issue on this boot | ||||||
|  |   this->set_retry( | ||||||
|  |       "reset", 5, 3, | ||||||
|  |       [this](const uint8_t remaining_setup_attempts) { | ||||||
|  |         ESP_LOGD(TAG, "Resetting both I2C addresses: 0x%02X, 0x%02X", this->address_, | ||||||
|  |                  this->humidity_device_->get_address()); | ||||||
|  |         // I believe sending the reset command to both addresses is preferable to | ||||||
|  |         // skipping humidity if PT fails for some reason. | ||||||
|  |         // However, only consider the reset successful if they both ACK | ||||||
|  |         bool const pt_successful = this->write_bytes(MS8607_PT_CMD_RESET, nullptr, 0); | ||||||
|  |         bool const h_successful = this->humidity_device_->write_bytes(MS8607_CMD_H_RESET, nullptr, 0); | ||||||
|  |  | ||||||
|  |         if (!(pt_successful && h_successful)) { | ||||||
|  |           ESP_LOGE(TAG, "Resetting I2C devices failed"); | ||||||
|  |           if (!pt_successful && !h_successful) { | ||||||
|  |             this->error_code_ = ErrorCode::PTH_RESET_FAILED; | ||||||
|  |           } else if (!pt_successful) { | ||||||
|  |             this->error_code_ = ErrorCode::PT_RESET_FAILED; | ||||||
|  |           } else { | ||||||
|  |             this->error_code_ = ErrorCode::H_RESET_FAILED; | ||||||
|  |           } | ||||||
|  |  | ||||||
|  |           if (remaining_setup_attempts > 0) { | ||||||
|  |             this->status_set_error(); | ||||||
|  |           } else { | ||||||
|  |             this->mark_failed(); | ||||||
|  |           } | ||||||
|  |           return RetryResult::RETRY; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         this->setup_status_ = SetupStatus::NEEDS_PROM_READ; | ||||||
|  |         this->error_code_ = ErrorCode::NONE; | ||||||
|  |         this->status_clear_error(); | ||||||
|  |  | ||||||
|  |         // 15ms delay matches datasheet, Adafruit_MS8607 & SparkFun_PHT_MS8607_Arduino_Library | ||||||
|  |         this->set_timeout("prom-read", 15, [this]() { | ||||||
|  |           if (this->read_calibration_values_from_prom_()) { | ||||||
|  |             this->setup_status_ = SetupStatus::SUCCESSFUL; | ||||||
|  |             this->status_clear_error(); | ||||||
|  |           } else { | ||||||
|  |             this->mark_failed(); | ||||||
|  |             return; | ||||||
|  |           } | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         return RetryResult::DONE; | ||||||
|  |       }, | ||||||
|  |       5.0f);  // executes at now, +5ms, +25ms | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void MS8607Component::update() { | ||||||
|  |   if (this->setup_status_ != SetupStatus::SUCCESSFUL) { | ||||||
|  |     // setup is still occurring, either because reset had to retry or due to the 15ms | ||||||
|  |     // delay needed between reset & reading the PROM values | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // Updating happens async and sequentially. | ||||||
|  |   // Temperature, then pressure, then humidity | ||||||
|  |   this->request_read_temperature_(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void MS8607Component::dump_config() { | ||||||
|  |   ESP_LOGCONFIG(TAG, "MS8607:"); | ||||||
|  |   LOG_I2C_DEVICE(this); | ||||||
|  |   // LOG_I2C_DEVICE doesn't work for humidity, the `address_` is protected. Log using get_address() | ||||||
|  |   ESP_LOGCONFIG(TAG, "  Address: 0x%02X", this->humidity_device_->get_address()); | ||||||
|  |   if (this->is_failed()) { | ||||||
|  |     ESP_LOGE(TAG, "Communication with MS8607 failed."); | ||||||
|  |     switch (this->error_code_) { | ||||||
|  |       case ErrorCode::PT_RESET_FAILED: | ||||||
|  |         ESP_LOGE(TAG, "Temperature/Pressure RESET failed"); | ||||||
|  |         break; | ||||||
|  |       case ErrorCode::H_RESET_FAILED: | ||||||
|  |         ESP_LOGE(TAG, "Humidity RESET failed"); | ||||||
|  |         break; | ||||||
|  |       case ErrorCode::PTH_RESET_FAILED: | ||||||
|  |         ESP_LOGE(TAG, "Temperature/Pressure && Humidity RESET failed"); | ||||||
|  |         break; | ||||||
|  |       case ErrorCode::PROM_READ_FAILED: | ||||||
|  |         ESP_LOGE(TAG, "Reading PROM failed"); | ||||||
|  |         break; | ||||||
|  |       case ErrorCode::PROM_CRC_FAILED: | ||||||
|  |         ESP_LOGE(TAG, "PROM values failed CRC"); | ||||||
|  |         break; | ||||||
|  |       case ErrorCode::NONE: | ||||||
|  |       default: | ||||||
|  |         ESP_LOGE(TAG, "Error reason unknown %u", static_cast<uint8_t>(this->error_code_)); | ||||||
|  |         break; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   LOG_UPDATE_INTERVAL(this); | ||||||
|  |   LOG_SENSOR("  ", "Temperature", this->temperature_sensor_); | ||||||
|  |   LOG_SENSOR("  ", "Pressure", this->pressure_sensor_); | ||||||
|  |   LOG_SENSOR("  ", "Humidity", this->humidity_sensor_); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool MS8607Component::read_calibration_values_from_prom_() { | ||||||
|  |   ESP_LOGD(TAG, "Reading calibration values from PROM"); | ||||||
|  |  | ||||||
|  |   uint16_t buffer[MS8607_PROM_COUNT]; | ||||||
|  |   bool successful = true; | ||||||
|  |  | ||||||
|  |   for (uint8_t idx = 0; idx < MS8607_PROM_COUNT; ++idx) { | ||||||
|  |     uint8_t const address_to_read = MS8607_PROM_START + (idx * 2); | ||||||
|  |     successful &= this->read_byte_16(address_to_read, &buffer[idx]); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (!successful) { | ||||||
|  |     ESP_LOGE(TAG, "Reading calibration values from PROM failed"); | ||||||
|  |     this->error_code_ = ErrorCode::PROM_READ_FAILED; | ||||||
|  |     return false; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   ESP_LOGD(TAG, "Checking CRC of calibration values from PROM"); | ||||||
|  |   uint8_t const expected_crc = (buffer[0] & 0xF000) >> 12;  // first 4 bits | ||||||
|  |   buffer[0] &= 0x0FFF;                                      // strip CRC from buffer, in order to run CRC | ||||||
|  |   uint8_t const actual_crc = crc4(buffer, MS8607_PROM_COUNT); | ||||||
|  |  | ||||||
|  |   if (expected_crc != actual_crc) { | ||||||
|  |     ESP_LOGE(TAG, "Incorrect CRC value. Provided value 0x%01X != calculated value 0x%01X", expected_crc, actual_crc); | ||||||
|  |     this->error_code_ = ErrorCode::PROM_CRC_FAILED; | ||||||
|  |     return false; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   this->calibration_values_.pressure_sensitivity = buffer[1]; | ||||||
|  |   this->calibration_values_.pressure_offset = buffer[2]; | ||||||
|  |   this->calibration_values_.pressure_sensitivity_temperature_coefficient = buffer[3]; | ||||||
|  |   this->calibration_values_.pressure_offset_temperature_coefficient = buffer[4]; | ||||||
|  |   this->calibration_values_.reference_temperature = buffer[5]; | ||||||
|  |   this->calibration_values_.temperature_coefficient_of_temperature = buffer[6]; | ||||||
|  |   ESP_LOGD(TAG, "Finished reading calibration values"); | ||||||
|  |  | ||||||
|  |   // Skipping reading Humidity PROM, since it doesn't have anything interesting for us | ||||||
|  |  | ||||||
|  |   return true; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  CRC-4 algorithm from datasheet. It operates on a buffer of 16-bit values, one byte at a time, using a 16-bit | ||||||
|  |  value to collect the CRC result into. | ||||||
|  |  | ||||||
|  |  The provided/expected CRC value must already be zeroed out from the buffer. | ||||||
|  |  */ | ||||||
|  | static uint8_t crc4(uint16_t *buffer, size_t length) { | ||||||
|  |   uint16_t crc_remainder = 0; | ||||||
|  |  | ||||||
|  |   // algorithm to add a byte into the crc | ||||||
|  |   auto apply_crc = [&crc_remainder](uint8_t next) { | ||||||
|  |     crc_remainder ^= next; | ||||||
|  |     for (uint8_t bit = 8; bit > 0; --bit) { | ||||||
|  |       if (crc_remainder & 0x8000) { | ||||||
|  |         crc_remainder = (crc_remainder << 1) ^ 0x3000; | ||||||
|  |       } else { | ||||||
|  |         crc_remainder = (crc_remainder << 1); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   // add all the bytes | ||||||
|  |   for (size_t idx = 0; idx < length; ++idx) { | ||||||
|  |     for (auto byte : decode_value(buffer[idx])) { | ||||||
|  |       apply_crc(byte); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   // For the MS8607 CRC, add a pair of zeros to shift the last byte from `buffer` through | ||||||
|  |   apply_crc(0); | ||||||
|  |   apply_crc(0); | ||||||
|  |  | ||||||
|  |   return (crc_remainder >> 12) & 0xF;  // only the most significant 4 bits | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @brief Calculates CRC value for the provided humidity (+ status bits) value | ||||||
|  |  * | ||||||
|  |  * CRC-8 check comes from other MS8607 libraries on github. I did not find it in the datasheet, | ||||||
|  |  * and it differs from the crc8 implementation that's already part of esphome. | ||||||
|  |  * | ||||||
|  |  * @param value two byte humidity sensor value read from i2c | ||||||
|  |  * @return uint8_t computed crc value | ||||||
|  |  */ | ||||||
|  | static uint8_t hsensor_crc_check(uint16_t value) { | ||||||
|  |   uint32_t polynom = 0x988000;  // x^8 + x^5 + x^4 + 1 | ||||||
|  |   uint32_t msb = 0x800000; | ||||||
|  |   uint32_t mask = 0xFF8000; | ||||||
|  |   uint32_t result = (uint32_t) value << 8;  // Pad with zeros as specified in spec | ||||||
|  |  | ||||||
|  |   while (msb != 0x80) { | ||||||
|  |     // Check if msb of current value is 1 and apply XOR mask | ||||||
|  |     if (result & msb) { | ||||||
|  |       result = ((result ^ polynom) & mask) | (result & ~mask); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // Shift by one | ||||||
|  |     msb >>= 1; | ||||||
|  |     mask >>= 1; | ||||||
|  |     polynom >>= 1; | ||||||
|  |   } | ||||||
|  |   return result & 0xFF; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void MS8607Component::request_read_temperature_() { | ||||||
|  |   // Tell MS8607 to start ADC conversion of temperature sensor | ||||||
|  |   if (!this->write_bytes(MS8607_CMD_CONV_D2_OSR_8K, nullptr, 0)) { | ||||||
|  |     this->status_set_warning(); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   auto f = std::bind(&MS8607Component::read_temperature_, this); | ||||||
|  |   // datasheet says 17.2ms max conversion time at OSR 8192 | ||||||
|  |   this->set_timeout("temperature", 20, f); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void MS8607Component::read_temperature_() { | ||||||
|  |   uint8_t bytes[3];  // 24 bits | ||||||
|  |   if (!this->read_bytes(MS8607_CMD_ADC_READ, bytes, 3)) { | ||||||
|  |     this->status_set_warning(); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   const uint32_t d2_raw_temperature = encode_uint32(0, bytes[0], bytes[1], bytes[2]); | ||||||
|  |   this->request_read_pressure_(d2_raw_temperature); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void MS8607Component::request_read_pressure_(uint32_t d2_raw_temperature) { | ||||||
|  |   if (!this->write_bytes(MS8607_CMD_CONV_D1_OSR_8K, nullptr, 0)) { | ||||||
|  |     this->status_set_warning(); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   auto f = std::bind(&MS8607Component::read_pressure_, this, d2_raw_temperature); | ||||||
|  |   // datasheet says 17.2ms max conversion time at OSR 8192 | ||||||
|  |   this->set_timeout("pressure", 20, f); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void MS8607Component::read_pressure_(uint32_t d2_raw_temperature) { | ||||||
|  |   uint8_t bytes[3];  // 24 bits | ||||||
|  |   if (!this->read_bytes(MS8607_CMD_ADC_READ, bytes, 3)) { | ||||||
|  |     this->status_set_warning(); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   const uint32_t d1_raw_pressure = encode_uint32(0, bytes[0], bytes[1], bytes[2]); | ||||||
|  |   this->calculate_values_(d2_raw_temperature, d1_raw_pressure); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void MS8607Component::request_read_humidity_(float temperature_float) { | ||||||
|  |   if (!this->humidity_device_->write_bytes(MS8607_CMD_H_MEASURE_NO_HOLD, nullptr, 0)) { | ||||||
|  |     ESP_LOGW(TAG, "Request to measure humidity failed"); | ||||||
|  |     this->status_set_warning(); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   auto f = std::bind(&MS8607Component::read_humidity_, this, temperature_float); | ||||||
|  |   // datasheet says 15.89ms max conversion time at OSR 8192 | ||||||
|  |   this->set_timeout("humidity", 20, f); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void MS8607Component::read_humidity_(float temperature_float) { | ||||||
|  |   uint8_t bytes[3]; | ||||||
|  |   if (!this->humidity_device_->read_bytes_raw(bytes, 3)) { | ||||||
|  |     ESP_LOGW(TAG, "Failed to read the measured humidity value"); | ||||||
|  |     this->status_set_warning(); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // "the measurement is stored into 14 bits. The two remaining LSBs are used for transmitting status information. | ||||||
|  |   // Bit1 of the two LSBS must be set to '1'. Bit0 is currently not assigned" | ||||||
|  |   uint16_t humidity = encode_uint16(bytes[0], bytes[1]); | ||||||
|  |   uint8_t const expected_crc = bytes[2]; | ||||||
|  |   uint8_t const actual_crc = hsensor_crc_check(humidity); | ||||||
|  |   if (expected_crc != actual_crc) { | ||||||
|  |     ESP_LOGE(TAG, "Incorrect Humidity CRC value. Provided value 0x%01X != calculated value 0x%01X", expected_crc, | ||||||
|  |              actual_crc); | ||||||
|  |     this->status_set_warning(); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   if (!(humidity & 0x2)) { | ||||||
|  |     // data sheet says Bit1 should always set, but nothing about what happens if it isn't | ||||||
|  |     ESP_LOGE(TAG, "Humidity status bit was not set to 1?"); | ||||||
|  |   } | ||||||
|  |   humidity &= ~(0b11);  // strip status & unassigned bits from data | ||||||
|  |  | ||||||
|  |   // map 16 bit humidity value into range [-6%, 118%] | ||||||
|  |   float const humidity_partial = double(humidity) / (1 << 16); | ||||||
|  |   float const humidity_percentage = lerp(humidity_partial, -6.0, 118.0); | ||||||
|  |   float const compensated_humidity_percentage = | ||||||
|  |       humidity_percentage + (20 - temperature_float) * MS8607_H_TEMP_COEFFICIENT; | ||||||
|  |   ESP_LOGD(TAG, "Compensated for temperature, humidity=%.2f%%", compensated_humidity_percentage); | ||||||
|  |  | ||||||
|  |   if (this->humidity_sensor_ != nullptr) { | ||||||
|  |     this->humidity_sensor_->publish_state(compensated_humidity_percentage); | ||||||
|  |   } | ||||||
|  |   this->status_clear_warning(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void MS8607Component::calculate_values_(uint32_t d2_raw_temperature, uint32_t d1_raw_pressure) { | ||||||
|  |   // Perform the first order pressure/temperature calculation | ||||||
|  |  | ||||||
|  |   // d_t: "difference between actual and reference temperature" = D2 - [C5] * 2**8 | ||||||
|  |   const int32_t d_t = int32_t(d2_raw_temperature) - (int32_t(this->calibration_values_.reference_temperature) << 8); | ||||||
|  |   // actual temperature as hundredths of degree celsius in range [-4000, 8500] | ||||||
|  |   // 2000 + d_t * [C6] / (2**23) | ||||||
|  |   int32_t temperature = | ||||||
|  |       2000 + ((int64_t(d_t) * this->calibration_values_.temperature_coefficient_of_temperature) >> 23); | ||||||
|  |  | ||||||
|  |   // offset at actual temperature. [C2] * (2**17) + (d_t * [C4] / (2**6)) | ||||||
|  |   int64_t pressure_offset = (int64_t(this->calibration_values_.pressure_offset) << 17) + | ||||||
|  |                             ((int64_t(d_t) * this->calibration_values_.pressure_offset_temperature_coefficient) >> 6); | ||||||
|  |   // sensitivity at actual temperature. [C1] * (2**16) + ([C3] * d_t) / (2**7) | ||||||
|  |   int64_t pressure_sensitivity = | ||||||
|  |       (int64_t(this->calibration_values_.pressure_sensitivity) << 16) + | ||||||
|  |       ((int64_t(d_t) * this->calibration_values_.pressure_sensitivity_temperature_coefficient) >> 7); | ||||||
|  |  | ||||||
|  |   // Perform the second order compensation, for non-linearity over temperature range | ||||||
|  |   const int64_t d_t_squared = int64_t(d_t) * d_t; | ||||||
|  |   int64_t temperature_2 = 0; | ||||||
|  |   int32_t pressure_offset_2 = 0; | ||||||
|  |   int32_t pressure_sensitivity_2 = 0; | ||||||
|  |   if (temperature < 2000) { | ||||||
|  |     // (TEMP - 2000)**2 / 2**4 | ||||||
|  |     const int32_t low_temperature_adjustment = (temperature - 2000) * (temperature - 2000) >> 4; | ||||||
|  |  | ||||||
|  |     // T2 = 3 * (d_t**2) / 2**33 | ||||||
|  |     temperature_2 = (3 * d_t_squared) >> 33; | ||||||
|  |     // OFF2 = 61 * (TEMP-2000)**2 / 2**4 | ||||||
|  |     pressure_offset_2 = 61 * low_temperature_adjustment; | ||||||
|  |     // SENS2 = 29 * (TEMP-2000)**2 / 2**4 | ||||||
|  |     pressure_sensitivity_2 = 29 * low_temperature_adjustment; | ||||||
|  |  | ||||||
|  |     if (temperature < -1500) { | ||||||
|  |       // (TEMP+1500)**2 | ||||||
|  |       const int32_t very_low_temperature_adjustment = (temperature + 1500) * (temperature + 1500); | ||||||
|  |  | ||||||
|  |       // OFF2 = OFF2 + 17 * (TEMP+1500)**2 | ||||||
|  |       pressure_offset_2 += 17 * very_low_temperature_adjustment; | ||||||
|  |       // SENS2 = SENS2 + 9 * (TEMP+1500)**2 | ||||||
|  |       pressure_sensitivity_2 += 9 * very_low_temperature_adjustment; | ||||||
|  |     } | ||||||
|  |   } else { | ||||||
|  |     // T2 = 5 * (d_t**2) / 2**38 | ||||||
|  |     temperature_2 = (5 * d_t_squared) >> 38; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   temperature -= temperature_2; | ||||||
|  |   pressure_offset -= pressure_offset_2; | ||||||
|  |   pressure_sensitivity -= pressure_sensitivity_2; | ||||||
|  |  | ||||||
|  |   // Temperature compensated pressure. [1000, 120000] => [10.00 mbar, 1200.00 mbar] | ||||||
|  |   const int32_t pressure = (((d1_raw_pressure * pressure_sensitivity) >> 21) - pressure_offset) >> 15; | ||||||
|  |  | ||||||
|  |   const float temperature_float = temperature / 100.0f; | ||||||
|  |   const float pressure_float = pressure / 100.0f; | ||||||
|  |   ESP_LOGD(TAG, "Temperature=%0.2f°C, Pressure=%0.2fhPa", temperature_float, pressure_float); | ||||||
|  |  | ||||||
|  |   if (this->temperature_sensor_ != nullptr) { | ||||||
|  |     this->temperature_sensor_->publish_state(temperature_float); | ||||||
|  |   } | ||||||
|  |   if (this->pressure_sensor_ != nullptr) { | ||||||
|  |     this->pressure_sensor_->publish_state(pressure_float);  // hPa aka mbar | ||||||
|  |   } | ||||||
|  |   this->status_clear_warning(); | ||||||
|  |  | ||||||
|  |   if (this->humidity_sensor_ != nullptr) { | ||||||
|  |     // now that we have temperature (to compensate the humidity with), kick off that read | ||||||
|  |     this->request_read_humidity_(temperature_float); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | }  // namespace ms8607 | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										109
									
								
								esphome/components/ms8607/ms8607.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										109
									
								
								esphome/components/ms8607/ms8607.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,109 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include "esphome/core/component.h" | ||||||
|  | #include "esphome/components/sensor/sensor.h" | ||||||
|  | #include "esphome/components/i2c/i2c.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace ms8607 { | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  Class for I2CDevice used to communicate with the Humidity sensor | ||||||
|  |  on the chip. See MS8607Component instead | ||||||
|  |  */ | ||||||
|  | class MS8607HumidityDevice : public i2c::I2CDevice { | ||||||
|  |  public: | ||||||
|  |   uint8_t get_address() { return address_; } | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  Temperature, pressure, and humidity sensor. | ||||||
|  |  | ||||||
|  |  By default, the MS8607 measures sensors at the highest resolution. | ||||||
|  |  A potential enhancement would be to expose the resolution as a configurable | ||||||
|  |  setting.  A lower resolution speeds up ADC conversion time & uses less power. | ||||||
|  |  | ||||||
|  |  Datasheet: | ||||||
|  |  https://www.te.com/commerce/DocumentDelivery/DDEController?Action=showdoc&DocId=Data+Sheet%7FMS8607-02BA01%7FB3%7Fpdf%7FEnglish%7FENG_DS_MS8607-02BA01_B3.pdf%7FCAT-BLPS0018 | ||||||
|  |  | ||||||
|  |  Other implementations: | ||||||
|  |  - https://github.com/TEConnectivity/MS8607_Generic_C_Driver | ||||||
|  |  - https://github.com/adafruit/Adafruit_MS8607 | ||||||
|  |  - https://github.com/sparkfun/SparkFun_PHT_MS8607_Arduino_Library | ||||||
|  |  */ | ||||||
|  | class MS8607Component : public PollingComponent, public i2c::I2CDevice { | ||||||
|  |  public: | ||||||
|  |   virtual ~MS8607Component() = default; | ||||||
|  |   void setup() override; | ||||||
|  |   void update() override; | ||||||
|  |   void dump_config() override; | ||||||
|  |   float get_setup_priority() const override { return setup_priority::DATA; }; | ||||||
|  |  | ||||||
|  |   void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; } | ||||||
|  |   void set_pressure_sensor(sensor::Sensor *pressure_sensor) { pressure_sensor_ = pressure_sensor; } | ||||||
|  |   void set_humidity_sensor(sensor::Sensor *humidity_sensor) { humidity_sensor_ = humidity_sensor; } | ||||||
|  |   void set_humidity_device(MS8607HumidityDevice *humidity_device) { humidity_device_ = humidity_device; } | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   /** | ||||||
|  |    Read and store the Pressure & Temperature calibration settings from the PROM. | ||||||
|  |    Intended to be called during setup(), this will set the `failure_reason_` | ||||||
|  |    */ | ||||||
|  |   bool read_calibration_values_from_prom_(); | ||||||
|  |  | ||||||
|  |   /// Start async temperature read | ||||||
|  |   void request_read_temperature_(); | ||||||
|  |   /// Process async temperature read | ||||||
|  |   void read_temperature_(); | ||||||
|  |   /// start async pressure read | ||||||
|  |   void request_read_pressure_(uint32_t raw_temperature); | ||||||
|  |   /// process async pressure read | ||||||
|  |   void read_pressure_(uint32_t raw_temperature); | ||||||
|  |   /// start async humidity read | ||||||
|  |   void request_read_humidity_(float temperature_float); | ||||||
|  |   /// process async humidity read | ||||||
|  |   void read_humidity_(float temperature_float); | ||||||
|  |   /// use raw temperature & pressure to calculate & publish values | ||||||
|  |   void calculate_values_(uint32_t raw_temperature, uint32_t raw_pressure); | ||||||
|  |  | ||||||
|  |   sensor::Sensor *temperature_sensor_; | ||||||
|  |   sensor::Sensor *pressure_sensor_; | ||||||
|  |   sensor::Sensor *humidity_sensor_; | ||||||
|  |  | ||||||
|  |   /** I2CDevice object to communicate with secondary I2C address for the humidity sensor | ||||||
|  |    * | ||||||
|  |    * The MS8607 only has one set of I2C pins, despite using two different addresses. | ||||||
|  |    * | ||||||
|  |    * Default address for humidity is 0x40 | ||||||
|  |    */ | ||||||
|  |   MS8607HumidityDevice *humidity_device_; | ||||||
|  |  | ||||||
|  |   /// This device's pressure & temperature calibration values, read from PROM | ||||||
|  |   struct CalibrationValues { | ||||||
|  |     /// Pressure sensitivity | SENS-T1. [C1] | ||||||
|  |     uint16_t pressure_sensitivity; | ||||||
|  |     /// Temperature coefficient of pressure sensitivity | TCS. [C3] | ||||||
|  |     uint16_t pressure_sensitivity_temperature_coefficient; | ||||||
|  |     /// Pressure offset | OFF-T1. [C2] | ||||||
|  |     uint16_t pressure_offset; | ||||||
|  |     /// Temperature coefficient of pressure offset | TCO. [C4] | ||||||
|  |     uint16_t pressure_offset_temperature_coefficient; | ||||||
|  |     /// Reference temperature | T-REF. [C5] | ||||||
|  |     uint16_t reference_temperature; | ||||||
|  |     /// Temperature coefficient of the temperature | TEMPSENS. [C6] | ||||||
|  |     uint16_t temperature_coefficient_of_temperature; | ||||||
|  |   } calibration_values_; | ||||||
|  |  | ||||||
|  |   /// Possible failure reasons of this component | ||||||
|  |   enum class ErrorCode; | ||||||
|  |   /// Keep track of the reason why this component failed, to augment the dumped config | ||||||
|  |   ErrorCode error_code_; | ||||||
|  |  | ||||||
|  |   /// Current progress through required component setup | ||||||
|  |   enum class SetupStatus; | ||||||
|  |   /// Current step in the multi-step & possibly delayed setup() process | ||||||
|  |   SetupStatus setup_status_; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | }  // namespace ms8607 | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										83
									
								
								esphome/components/ms8607/sensor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										83
									
								
								esphome/components/ms8607/sensor.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,83 @@ | |||||||
|  | import esphome.codegen as cg | ||||||
|  | import esphome.config_validation as cv | ||||||
|  | from esphome.components import i2c, sensor | ||||||
|  | from esphome.const import ( | ||||||
|  |     CONF_HUMIDITY, | ||||||
|  |     CONF_ID, | ||||||
|  |     CONF_PRESSURE, | ||||||
|  |     CONF_TEMPERATURE, | ||||||
|  |     DEVICE_CLASS_HUMIDITY, | ||||||
|  |     DEVICE_CLASS_PRESSURE, | ||||||
|  |     DEVICE_CLASS_TEMPERATURE, | ||||||
|  |     STATE_CLASS_MEASUREMENT, | ||||||
|  |     UNIT_CELSIUS, | ||||||
|  |     UNIT_HECTOPASCAL, | ||||||
|  |     UNIT_PERCENT, | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | DEPENDENCIES = ["i2c"] | ||||||
|  |  | ||||||
|  | ms8607_ns = cg.esphome_ns.namespace("ms8607") | ||||||
|  | MS8607Component = ms8607_ns.class_( | ||||||
|  |     "MS8607Component", cg.PollingComponent, i2c.I2CDevice | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | CONF_HUMIDITY_I2C_ID = "humidity_i2c_id" | ||||||
|  | MS8607HumidityDevice = ms8607_ns.class_("MS8607HumidityDevice", i2c.I2CDevice) | ||||||
|  |  | ||||||
|  | CONFIG_SCHEMA = ( | ||||||
|  |     cv.Schema( | ||||||
|  |         { | ||||||
|  |             cv.GenerateID(): cv.declare_id(MS8607Component), | ||||||
|  |             cv.Required(CONF_TEMPERATURE): sensor.sensor_schema( | ||||||
|  |                 unit_of_measurement=UNIT_CELSIUS, | ||||||
|  |                 accuracy_decimals=2,  # Resolution: 0.01 | ||||||
|  |                 device_class=DEVICE_CLASS_TEMPERATURE, | ||||||
|  |                 state_class=STATE_CLASS_MEASUREMENT, | ||||||
|  |             ), | ||||||
|  |             cv.Required(CONF_PRESSURE): sensor.sensor_schema( | ||||||
|  |                 unit_of_measurement=UNIT_HECTOPASCAL, | ||||||
|  |                 accuracy_decimals=2,  # Resolution: 0.016 | ||||||
|  |                 device_class=DEVICE_CLASS_PRESSURE, | ||||||
|  |                 state_class=STATE_CLASS_MEASUREMENT, | ||||||
|  |             ), | ||||||
|  |             cv.Required(CONF_HUMIDITY): sensor.sensor_schema( | ||||||
|  |                 unit_of_measurement=UNIT_PERCENT, | ||||||
|  |                 accuracy_decimals=2,  # Resolution: 0.04 | ||||||
|  |                 device_class=DEVICE_CLASS_HUMIDITY, | ||||||
|  |                 state_class=STATE_CLASS_MEASUREMENT, | ||||||
|  |             ) | ||||||
|  |             .extend( | ||||||
|  |                 { | ||||||
|  |                     cv.GenerateID(CONF_HUMIDITY_I2C_ID): cv.declare_id( | ||||||
|  |                         MS8607HumidityDevice | ||||||
|  |                     ), | ||||||
|  |                 } | ||||||
|  |             ) | ||||||
|  |             .extend(i2c.i2c_device_schema(0x40)),  # default address for humidity | ||||||
|  |         } | ||||||
|  |     ) | ||||||
|  |     .extend(cv.polling_component_schema("60s")) | ||||||
|  |     .extend(i2c.i2c_device_schema(0x76))  # default address for temp/pressure | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | async def to_code(config): | ||||||
|  |     var = cg.new_Pvariable(config[CONF_ID]) | ||||||
|  |     await cg.register_component(var, config) | ||||||
|  |     await i2c.register_i2c_device(var, config) | ||||||
|  |  | ||||||
|  |     if temperature_config := config.get(CONF_TEMPERATURE): | ||||||
|  |         sens = await sensor.new_sensor(temperature_config) | ||||||
|  |         cg.add(var.set_temperature_sensor(sens)) | ||||||
|  |  | ||||||
|  |     if pressure_config := config.get(CONF_PRESSURE): | ||||||
|  |         sens = await sensor.new_sensor(pressure_config) | ||||||
|  |         cg.add(var.set_pressure_sensor(sens)) | ||||||
|  |  | ||||||
|  |     if humidity_config := config.get(CONF_HUMIDITY): | ||||||
|  |         sens = await sensor.new_sensor(humidity_config) | ||||||
|  |         cg.add(var.set_humidity_sensor(sens)) | ||||||
|  |         humidity_device = cg.new_Pvariable(humidity_config[CONF_HUMIDITY_I2C_ID]) | ||||||
|  |         await i2c.register_i2c_device(humidity_device, humidity_config) | ||||||
|  |         cg.add(var.set_humidity_device(humidity_device)) | ||||||
| @@ -5,6 +5,7 @@ from esphome.components.esp32 import add_idf_sdkconfig_option | |||||||
|  |  | ||||||
| from esphome.const import ( | from esphome.const import ( | ||||||
|     CONF_ENABLE_IPV6, |     CONF_ENABLE_IPV6, | ||||||
|  |     CONF_MIN_IPV6_ADDR_COUNT, | ||||||
| ) | ) | ||||||
|  |  | ||||||
| CODEOWNERS = ["@esphome/core"] | CODEOWNERS = ["@esphome/core"] | ||||||
| @@ -16,12 +17,14 @@ IPAddress = network_ns.class_("IPAddress") | |||||||
| CONFIG_SCHEMA = cv.Schema( | CONFIG_SCHEMA = cv.Schema( | ||||||
|     { |     { | ||||||
|         cv.Optional(CONF_ENABLE_IPV6, default=False): cv.boolean, |         cv.Optional(CONF_ENABLE_IPV6, default=False): cv.boolean, | ||||||
|  |         cv.Optional(CONF_MIN_IPV6_ADDR_COUNT, default=0): cv.positive_int, | ||||||
|     } |     } | ||||||
| ) | ) | ||||||
|  |  | ||||||
|  |  | ||||||
| async def to_code(config): | async def to_code(config): | ||||||
|     cg.add_define("ENABLE_IPV6", config[CONF_ENABLE_IPV6]) |     cg.add_define("USE_NETWORK_IPV6", config[CONF_ENABLE_IPV6]) | ||||||
|  |     cg.add_define("USE_NETWORK_MIN_IPV6_ADDR_COUNT", config[CONF_MIN_IPV6_ADDR_COUNT]) | ||||||
|     if CORE.using_esp_idf: |     if CORE.using_esp_idf: | ||||||
|         add_idf_sdkconfig_option("CONFIG_LWIP_IPV6", config[CONF_ENABLE_IPV6]) |         add_idf_sdkconfig_option("CONFIG_LWIP_IPV6", config[CONF_ENABLE_IPV6]) | ||||||
|         add_idf_sdkconfig_option( |         add_idf_sdkconfig_option( | ||||||
|   | |||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user