mirror of
https://github.com/esphome/esphome.git
synced 2024-11-28 12:46:22 +01:00
Merge branch 'dev' into add-graphical-layout-system
This commit is contained in:
commit
9f5df0aa34
@ -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.0
|
||||||
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
|
||||||
@ -156,6 +157,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
|
||||||
@ -200,6 +202,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
|
||||||
@ -363,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 \
|
||||||
|
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))
|
@ -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) {
|
||||||
|
if (this->supply_voltage_ == SUPPLY_VOLTAGE_3V3) {
|
||||||
const uint8_t config[] = {
|
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[] = {
|
||||||
|
#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[] = {
|
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};
|
||||||
|
@ -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);
|
||||||
|
@ -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)
|
||||||
|
@ -160,7 +160,7 @@ 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;
|
||||||
uint8_t white = this->is_wrgb_ ? 0 : 3;
|
uint8_t white = this->is_wrgb_ ? 0 : 3;
|
||||||
|
|
||||||
return {this->buf_ + (index * multiplier) + r + this->is_wrgb_,
|
return {this->buf_ + (index * multiplier) + r + this->is_wrgb_,
|
||||||
|
@ -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]);
|
||||||
|
@ -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"]
|
||||||
|
@ -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:
|
||||||
|
@ -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
|
||||||
|
@ -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("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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
|
@ -14,7 +14,7 @@ static const uint8_t NBITS_ADDRESS = 16;
|
|||||||
static const uint8_t NBITS_CHANNEL = 5;
|
static const uint8_t NBITS_CHANNEL = 5;
|
||||||
static const uint8_t NBITS_COMMAND = 7;
|
static const uint8_t NBITS_COMMAND = 7;
|
||||||
static const uint8_t NDATABITS = NBITS_ADDRESS + NBITS_CHANNEL + NBITS_COMMAND;
|
static const uint8_t NDATABITS = NBITS_ADDRESS + NBITS_CHANNEL + NBITS_COMMAND;
|
||||||
static const uint8_t MIN_RX_SRC = (NDATABITS * 2 + NBITS_SYNC / 2);
|
static const uint8_t MIN_RX_SRC = (NDATABITS + NBITS_SYNC / 2);
|
||||||
|
|
||||||
static const uint8_t CMD_ON = 0x41;
|
static const uint8_t CMD_ON = 0x41;
|
||||||
static const uint8_t CMD_OFF = 0x02;
|
static const uint8_t CMD_OFF = 0x02;
|
||||||
@ -135,7 +135,7 @@ optional<DraytonData> DraytonProtocol::decode(RemoteReceiveData src) {
|
|||||||
.command = 0,
|
.command = 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
while (src.size() - src.get_index() > MIN_RX_SRC) {
|
while (src.size() - src.get_index() >= MIN_RX_SRC) {
|
||||||
ESP_LOGVV(TAG,
|
ESP_LOGVV(TAG,
|
||||||
"Decode Drayton: %" PRId32 ", %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32
|
"Decode Drayton: %" PRId32 ", %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32
|
||||||
" %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32
|
" %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32
|
||||||
@ -150,7 +150,7 @@ optional<DraytonData> DraytonProtocol::decode(RemoteReceiveData src) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Look for sync pulse, after. If sucessful index points to space of sync symbol
|
// Look for sync pulse, after. If sucessful index points to space of sync symbol
|
||||||
while (src.size() - src.get_index() >= NDATABITS) {
|
while (src.size() - src.get_index() >= MIN_RX_SRC) {
|
||||||
ESP_LOGVV(TAG, "Decode Drayton: sync search %d, %" PRId32 " %" PRId32, src.size() - src.get_index(), src.peek(),
|
ESP_LOGVV(TAG, "Decode Drayton: sync search %d, %" PRId32 " %" PRId32, src.size() - src.get_index(), src.peek(),
|
||||||
src.peek(1));
|
src.peek(1));
|
||||||
if (src.peek_mark(2 * BIT_TIME_US) &&
|
if (src.peek_mark(2 * BIT_TIME_US) &&
|
||||||
|
@ -29,7 +29,8 @@ from esphome.const import (
|
|||||||
from esphome.core import HexInt, CORE
|
from esphome.core import HexInt, CORE
|
||||||
|
|
||||||
DOMAIN = "shelly_dimmer"
|
DOMAIN = "shelly_dimmer"
|
||||||
DEPENDENCIES = ["sensor", "uart", "esp8266"]
|
AUTO_LOAD = ["sensor"]
|
||||||
|
DEPENDENCIES = ["uart", "esp8266"]
|
||||||
|
|
||||||
shelly_dimmer_ns = cg.esphome_ns.namespace("shelly_dimmer")
|
shelly_dimmer_ns = cg.esphome_ns.namespace("shelly_dimmer")
|
||||||
ShellyDimmer = shelly_dimmer_ns.class_(
|
ShellyDimmer = shelly_dimmer_ns.class_(
|
||||||
|
@ -74,7 +74,8 @@ CONF_FORCE_SW = "force_sw"
|
|||||||
CONF_INTERFACE = "interface"
|
CONF_INTERFACE = "interface"
|
||||||
CONF_INTERFACE_INDEX = "interface_index"
|
CONF_INTERFACE_INDEX = "interface_index"
|
||||||
|
|
||||||
# RP2040 SPI pin assignments are complicated. Refer to https://datasheets.raspberrypi.com/rp2040/rp2040-datasheet.pdf
|
# RP2040 SPI pin assignments are complicated;
|
||||||
|
# refer to GPIO function select table in https://datasheets.raspberrypi.com/rp2040/rp2040-datasheet.pdf
|
||||||
|
|
||||||
RP_SPI_PINSETS = [
|
RP_SPI_PINSETS = [
|
||||||
{
|
{
|
||||||
@ -85,7 +86,7 @@ RP_SPI_PINSETS = [
|
|||||||
{
|
{
|
||||||
CONF_MISO_PIN: [8, 12, 24, 28, -1],
|
CONF_MISO_PIN: [8, 12, 24, 28, -1],
|
||||||
CONF_CLK_PIN: [10, 14, 26],
|
CONF_CLK_PIN: [10, 14, 26],
|
||||||
CONF_MOSI_PIN: [11, 23, 27, -1],
|
CONF_MOSI_PIN: [11, 15, 27, -1],
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -13,6 +13,7 @@ from esphome.const import (
|
|||||||
CODEOWNERS = ["@freekode"]
|
CODEOWNERS = ["@freekode"]
|
||||||
|
|
||||||
tm1651_ns = cg.esphome_ns.namespace("tm1651")
|
tm1651_ns = cg.esphome_ns.namespace("tm1651")
|
||||||
|
TM1651Brightness = tm1651_ns.enum("TM1651Brightness")
|
||||||
TM1651Display = tm1651_ns.class_("TM1651Display", cg.Component)
|
TM1651Display = tm1651_ns.class_("TM1651Display", cg.Component)
|
||||||
|
|
||||||
SetLevelPercentAction = tm1651_ns.class_("SetLevelPercentAction", automation.Action)
|
SetLevelPercentAction = tm1651_ns.class_("SetLevelPercentAction", automation.Action)
|
||||||
@ -24,9 +25,9 @@ TurnOffAction = tm1651_ns.class_("SetLevelPercentAction", automation.Action)
|
|||||||
CONF_LEVEL_PERCENT = "level_percent"
|
CONF_LEVEL_PERCENT = "level_percent"
|
||||||
|
|
||||||
TM1651_BRIGHTNESS_OPTIONS = {
|
TM1651_BRIGHTNESS_OPTIONS = {
|
||||||
1: TM1651Display.TM1651_BRIGHTNESS_LOW,
|
1: TM1651Brightness.TM1651_BRIGHTNESS_LOW,
|
||||||
2: TM1651Display.TM1651_BRIGHTNESS_MEDIUM,
|
2: TM1651Brightness.TM1651_BRIGHTNESS_MEDIUM,
|
||||||
3: TM1651Display.TM1651_BRIGHTNESS_HIGH,
|
3: TM1651Brightness.TM1651_BRIGHTNESS_HIGH,
|
||||||
}
|
}
|
||||||
|
|
||||||
CONFIG_SCHEMA = cv.All(
|
CONFIG_SCHEMA = cv.All(
|
||||||
|
@ -12,9 +12,9 @@ static const char *const TAG = "tm1651.display";
|
|||||||
static const uint8_t MAX_INPUT_LEVEL_PERCENT = 100;
|
static const uint8_t MAX_INPUT_LEVEL_PERCENT = 100;
|
||||||
static const uint8_t TM1651_MAX_LEVEL = 7;
|
static const uint8_t TM1651_MAX_LEVEL = 7;
|
||||||
|
|
||||||
static const uint8_t TM1651_BRIGHTNESS_LOW = 0;
|
static const uint8_t TM1651_BRIGHTNESS_LOW_HW = 0;
|
||||||
static const uint8_t TM1651_BRIGHTNESS_MEDIUM = 2;
|
static const uint8_t TM1651_BRIGHTNESS_MEDIUM_HW = 2;
|
||||||
static const uint8_t TM1651_BRIGHTNESS_HIGH = 7;
|
static const uint8_t TM1651_BRIGHTNESS_HIGH_HW = 7;
|
||||||
|
|
||||||
void TM1651Display::setup() {
|
void TM1651Display::setup() {
|
||||||
ESP_LOGCONFIG(TAG, "Setting up TM1651...");
|
ESP_LOGCONFIG(TAG, "Setting up TM1651...");
|
||||||
@ -78,14 +78,14 @@ uint8_t TM1651Display::calculate_level_(uint8_t new_level) {
|
|||||||
|
|
||||||
uint8_t TM1651Display::calculate_brightness_(uint8_t new_brightness) {
|
uint8_t TM1651Display::calculate_brightness_(uint8_t new_brightness) {
|
||||||
if (new_brightness <= 1) {
|
if (new_brightness <= 1) {
|
||||||
return TM1651_BRIGHTNESS_LOW;
|
return TM1651_BRIGHTNESS_LOW_HW;
|
||||||
} else if (new_brightness == 2) {
|
} else if (new_brightness == 2) {
|
||||||
return TM1651_BRIGHTNESS_MEDIUM;
|
return TM1651_BRIGHTNESS_MEDIUM_HW;
|
||||||
} else if (new_brightness >= 3) {
|
} else if (new_brightness >= 3) {
|
||||||
return TM1651_BRIGHTNESS_HIGH;
|
return TM1651_BRIGHTNESS_HIGH_HW;
|
||||||
}
|
}
|
||||||
|
|
||||||
return TM1651_BRIGHTNESS_LOW;
|
return TM1651_BRIGHTNESS_LOW_HW;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace tm1651
|
} // namespace tm1651
|
||||||
|
@ -13,6 +13,12 @@
|
|||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace tm1651 {
|
namespace tm1651 {
|
||||||
|
|
||||||
|
enum TM1651Brightness : uint8_t {
|
||||||
|
TM1651_BRIGHTNESS_LOW = 1,
|
||||||
|
TM1651_BRIGHTNESS_MEDIUM = 2,
|
||||||
|
TM1651_BRIGHTNESS_HIGH = 3,
|
||||||
|
};
|
||||||
|
|
||||||
class TM1651Display : public Component {
|
class TM1651Display : public Component {
|
||||||
public:
|
public:
|
||||||
void set_clk_pin(InternalGPIOPin *pin) { clk_pin_ = pin; }
|
void set_clk_pin(InternalGPIOPin *pin) { clk_pin_ = pin; }
|
||||||
@ -24,6 +30,7 @@ class TM1651Display : public Component {
|
|||||||
void set_level_percent(uint8_t new_level);
|
void set_level_percent(uint8_t new_level);
|
||||||
void set_level(uint8_t new_level);
|
void set_level(uint8_t new_level);
|
||||||
void set_brightness(uint8_t new_brightness);
|
void set_brightness(uint8_t new_brightness);
|
||||||
|
void set_brightness(TM1651Brightness new_brightness) { this->set_brightness(static_cast<uint8_t>(new_brightness)); }
|
||||||
|
|
||||||
void turn_on();
|
void turn_on();
|
||||||
void turn_off();
|
void turn_off();
|
||||||
|
@ -7,6 +7,7 @@ reading temperatures to a resolution of 0.0625°C.
|
|||||||
https://www.sparkfun.com/datasheets/Sensors/Temperature/tmp102.pdf
|
https://www.sparkfun.com/datasheets/Sensors/Temperature/tmp102.pdf
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.components import i2c, sensor
|
from esphome.components import i2c, sensor
|
||||||
|
@ -34,9 +34,13 @@ void TuyaFan::setup() {
|
|||||||
}
|
}
|
||||||
if (this->oscillation_id_.has_value()) {
|
if (this->oscillation_id_.has_value()) {
|
||||||
this->parent_->register_listener(*this->oscillation_id_, [this](const TuyaDatapoint &datapoint) {
|
this->parent_->register_listener(*this->oscillation_id_, [this](const TuyaDatapoint &datapoint) {
|
||||||
|
// Whether data type is BOOL or ENUM, it will still be a 1 or a 0, so the functions below are valid in both
|
||||||
|
// scenarios
|
||||||
ESP_LOGV(TAG, "MCU reported oscillation is: %s", ONOFF(datapoint.value_bool));
|
ESP_LOGV(TAG, "MCU reported oscillation is: %s", ONOFF(datapoint.value_bool));
|
||||||
this->oscillating = datapoint.value_bool;
|
this->oscillating = datapoint.value_bool;
|
||||||
this->publish_state();
|
this->publish_state();
|
||||||
|
|
||||||
|
this->oscillation_type_ = datapoint.type;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (this->direction_id_.has_value()) {
|
if (this->direction_id_.has_value()) {
|
||||||
@ -80,8 +84,12 @@ void TuyaFan::control(const fan::FanCall &call) {
|
|||||||
this->parent_->set_boolean_datapoint_value(*this->switch_id_, *call.get_state());
|
this->parent_->set_boolean_datapoint_value(*this->switch_id_, *call.get_state());
|
||||||
}
|
}
|
||||||
if (this->oscillation_id_.has_value() && call.get_oscillating().has_value()) {
|
if (this->oscillation_id_.has_value() && call.get_oscillating().has_value()) {
|
||||||
|
if (this->oscillation_type_ == TuyaDatapointType::ENUM) {
|
||||||
|
this->parent_->set_enum_datapoint_value(*this->oscillation_id_, *call.get_oscillating());
|
||||||
|
} else if (this->speed_type_ == TuyaDatapointType::BOOLEAN) {
|
||||||
this->parent_->set_boolean_datapoint_value(*this->oscillation_id_, *call.get_oscillating());
|
this->parent_->set_boolean_datapoint_value(*this->oscillation_id_, *call.get_oscillating());
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (this->direction_id_.has_value() && call.get_direction().has_value()) {
|
if (this->direction_id_.has_value() && call.get_direction().has_value()) {
|
||||||
bool enable = *call.get_direction() == fan::FanDirection::REVERSE;
|
bool enable = *call.get_direction() == fan::FanDirection::REVERSE;
|
||||||
this->parent_->set_enum_datapoint_value(*this->direction_id_, enable);
|
this->parent_->set_enum_datapoint_value(*this->direction_id_, enable);
|
||||||
|
@ -29,6 +29,7 @@ class TuyaFan : public Component, public fan::Fan {
|
|||||||
optional<uint8_t> direction_id_{};
|
optional<uint8_t> direction_id_{};
|
||||||
int speed_count_{};
|
int speed_count_{};
|
||||||
TuyaDatapointType speed_type_{};
|
TuyaDatapointType speed_type_{};
|
||||||
|
TuyaDatapointType oscillation_type_{};
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace tuya
|
} // namespace tuya
|
||||||
|
78
esphome/components/uponor_smatrix/__init__.py
Normal file
78
esphome/components/uponor_smatrix/__init__.py
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
import esphome.codegen as cg
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
from esphome.components import uart, time
|
||||||
|
from esphome.const import (
|
||||||
|
CONF_ADDRESS,
|
||||||
|
CONF_ID,
|
||||||
|
CONF_TIME_ID,
|
||||||
|
)
|
||||||
|
|
||||||
|
CODEOWNERS = ["@kroimon"]
|
||||||
|
|
||||||
|
DEPENDENCIES = ["uart"]
|
||||||
|
|
||||||
|
MULTI_CONF = True
|
||||||
|
|
||||||
|
uponor_smatrix_ns = cg.esphome_ns.namespace("uponor_smatrix")
|
||||||
|
UponorSmatrixComponent = uponor_smatrix_ns.class_(
|
||||||
|
"UponorSmatrixComponent", cg.Component, uart.UARTDevice
|
||||||
|
)
|
||||||
|
UponorSmatrixDevice = uponor_smatrix_ns.class_(
|
||||||
|
"UponorSmatrixDevice", cg.Parented.template(UponorSmatrixComponent)
|
||||||
|
)
|
||||||
|
|
||||||
|
CONF_UPONOR_SMATRIX_ID = "uponor_smatrix_id"
|
||||||
|
CONF_TIME_DEVICE_ADDRESS = "time_device_address"
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = (
|
||||||
|
cv.Schema(
|
||||||
|
{
|
||||||
|
cv.GenerateID(): cv.declare_id(UponorSmatrixComponent),
|
||||||
|
cv.Optional(CONF_ADDRESS): cv.hex_uint16_t,
|
||||||
|
cv.Optional(CONF_TIME_ID): cv.use_id(time.RealTimeClock),
|
||||||
|
cv.Optional(CONF_TIME_DEVICE_ADDRESS): cv.hex_uint16_t,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.extend(cv.COMPONENT_SCHEMA)
|
||||||
|
.extend(uart.UART_DEVICE_SCHEMA)
|
||||||
|
)
|
||||||
|
|
||||||
|
FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema(
|
||||||
|
"uponor_smatrix",
|
||||||
|
baud_rate=19200,
|
||||||
|
require_tx=True,
|
||||||
|
require_rx=True,
|
||||||
|
data_bits=8,
|
||||||
|
parity=None,
|
||||||
|
stop_bits=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
# A schema to use for all Uponor Smatrix devices
|
||||||
|
UPONOR_SMATRIX_DEVICE_SCHEMA = cv.Schema(
|
||||||
|
{
|
||||||
|
cv.GenerateID(CONF_UPONOR_SMATRIX_ID): cv.use_id(UponorSmatrixComponent),
|
||||||
|
cv.Required(CONF_ADDRESS): cv.hex_uint16_t,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def to_code(config):
|
||||||
|
cg.add_global(uponor_smatrix_ns.using)
|
||||||
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
|
await cg.register_component(var, config)
|
||||||
|
await uart.register_uart_device(var, config)
|
||||||
|
|
||||||
|
if address := config.get(CONF_ADDRESS):
|
||||||
|
cg.add(var.set_system_address(address))
|
||||||
|
if time_id := config.get(CONF_TIME_ID):
|
||||||
|
time_ = await cg.get_variable(time_id)
|
||||||
|
cg.add(var.set_time_id(time_))
|
||||||
|
if time_device_address := config.get(CONF_TIME_DEVICE_ADDRESS):
|
||||||
|
cg.add(var.set_time_device_address(time_device_address))
|
||||||
|
|
||||||
|
|
||||||
|
async def register_uponor_smatrix_device(var, config):
|
||||||
|
parent = await cg.get_variable(config[CONF_UPONOR_SMATRIX_ID])
|
||||||
|
cg.add(var.set_parent(parent))
|
||||||
|
cg.add(var.set_device_address(config[CONF_ADDRESS]))
|
||||||
|
cg.add(parent.register_device(var))
|
33
esphome/components/uponor_smatrix/climate/__init__.py
Normal file
33
esphome/components/uponor_smatrix/climate/__init__.py
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import esphome.codegen as cg
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
from esphome.components import climate
|
||||||
|
from esphome.const import CONF_ID
|
||||||
|
|
||||||
|
from .. import (
|
||||||
|
uponor_smatrix_ns,
|
||||||
|
UponorSmatrixDevice,
|
||||||
|
UPONOR_SMATRIX_DEVICE_SCHEMA,
|
||||||
|
register_uponor_smatrix_device,
|
||||||
|
)
|
||||||
|
|
||||||
|
DEPENDENCIES = ["uponor_smatrix"]
|
||||||
|
|
||||||
|
UponorSmatrixClimate = uponor_smatrix_ns.class_(
|
||||||
|
"UponorSmatrixClimate",
|
||||||
|
climate.Climate,
|
||||||
|
cg.Component,
|
||||||
|
UponorSmatrixDevice,
|
||||||
|
)
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = climate.CLIMATE_SCHEMA.extend(
|
||||||
|
{
|
||||||
|
cv.GenerateID(): cv.declare_id(UponorSmatrixClimate),
|
||||||
|
}
|
||||||
|
).extend(UPONOR_SMATRIX_DEVICE_SCHEMA)
|
||||||
|
|
||||||
|
|
||||||
|
async def to_code(config):
|
||||||
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
|
await cg.register_component(var, config)
|
||||||
|
await climate.register_climate(var, config)
|
||||||
|
await register_uponor_smatrix_device(var, config)
|
@ -0,0 +1,101 @@
|
|||||||
|
#include "uponor_smatrix_climate.h"
|
||||||
|
#include "esphome/core/helpers.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace uponor_smatrix {
|
||||||
|
|
||||||
|
static const char *const TAG = "uponor_smatrix.climate";
|
||||||
|
|
||||||
|
void UponorSmatrixClimate::dump_config() {
|
||||||
|
LOG_CLIMATE("", "Uponor Smatrix Climate", this);
|
||||||
|
ESP_LOGCONFIG(TAG, " Device address: 0x%04X", this->address_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UponorSmatrixClimate::loop() {
|
||||||
|
const uint32_t now = millis();
|
||||||
|
|
||||||
|
// Publish state after all update packets are processed
|
||||||
|
if (this->last_data_ != 0 && (now - this->last_data_ > 100) && this->target_temperature_raw_ != 0) {
|
||||||
|
float temp = raw_to_celsius((this->preset == climate::CLIMATE_PRESET_ECO)
|
||||||
|
? (this->target_temperature_raw_ - this->eco_setback_value_raw_)
|
||||||
|
: this->target_temperature_raw_);
|
||||||
|
float step = this->get_traits().get_visual_target_temperature_step();
|
||||||
|
this->target_temperature = roundf(temp / step) * step;
|
||||||
|
this->publish_state();
|
||||||
|
this->last_data_ = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
climate::ClimateTraits UponorSmatrixClimate::traits() {
|
||||||
|
auto traits = climate::ClimateTraits();
|
||||||
|
traits.set_supports_current_temperature(true);
|
||||||
|
traits.set_supports_current_humidity(true);
|
||||||
|
traits.set_supported_modes({climate::CLIMATE_MODE_HEAT});
|
||||||
|
traits.set_supports_action(true);
|
||||||
|
traits.set_supported_presets({climate::CLIMATE_PRESET_ECO});
|
||||||
|
traits.set_visual_min_temperature(this->min_temperature_);
|
||||||
|
traits.set_visual_max_temperature(this->max_temperature_);
|
||||||
|
traits.set_visual_current_temperature_step(0.1f);
|
||||||
|
traits.set_visual_target_temperature_step(0.5f);
|
||||||
|
return traits;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UponorSmatrixClimate::control(const climate::ClimateCall &call) {
|
||||||
|
if (call.get_target_temperature().has_value()) {
|
||||||
|
uint16_t temp = celsius_to_raw(*call.get_target_temperature());
|
||||||
|
if (this->preset == climate::CLIMATE_PRESET_ECO) {
|
||||||
|
// During ECO mode, the thermostat automatically substracts the setback value from the setpoint,
|
||||||
|
// so we need to add it here first
|
||||||
|
temp += this->eco_setback_value_raw_;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For unknown reasons, we need to send a null setpoint first for the thermostat to react
|
||||||
|
UponorSmatrixData data[] = {{UPONOR_ID_TARGET_TEMP, 0}, {UPONOR_ID_TARGET_TEMP, temp}};
|
||||||
|
this->send(data, sizeof(data) / sizeof(data[0]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UponorSmatrixClimate::on_device_data(const UponorSmatrixData *data, size_t data_len) {
|
||||||
|
for (int i = 0; i < data_len; i++) {
|
||||||
|
switch (data[i].id) {
|
||||||
|
case UPONOR_ID_TARGET_TEMP_MIN:
|
||||||
|
this->min_temperature_ = raw_to_celsius(data[i].value);
|
||||||
|
break;
|
||||||
|
case UPONOR_ID_TARGET_TEMP_MAX:
|
||||||
|
this->max_temperature_ = raw_to_celsius(data[i].value);
|
||||||
|
break;
|
||||||
|
case UPONOR_ID_TARGET_TEMP:
|
||||||
|
// Ignore invalid values here as they are used by the controller to explicitely request the setpoint from a
|
||||||
|
// thermostat
|
||||||
|
if (data[i].value != UPONOR_INVALID_VALUE)
|
||||||
|
this->target_temperature_raw_ = data[i].value;
|
||||||
|
break;
|
||||||
|
case UPONOR_ID_ECO_SETBACK:
|
||||||
|
this->eco_setback_value_raw_ = data[i].value;
|
||||||
|
break;
|
||||||
|
case UPONOR_ID_DEMAND:
|
||||||
|
if (data[i].value & 0x1000) {
|
||||||
|
this->mode = climate::CLIMATE_MODE_COOL;
|
||||||
|
this->action = (data[i].value & 0x0040) ? climate::CLIMATE_ACTION_COOLING : climate::CLIMATE_ACTION_IDLE;
|
||||||
|
} else {
|
||||||
|
this->mode = climate::CLIMATE_MODE_HEAT;
|
||||||
|
this->action = (data[i].value & 0x0040) ? climate::CLIMATE_ACTION_HEATING : climate::CLIMATE_ACTION_IDLE;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case UPONOR_ID_MODE1:
|
||||||
|
this->set_preset_((data[i].value & 0x0008) ? climate::CLIMATE_PRESET_ECO : climate::CLIMATE_PRESET_NONE);
|
||||||
|
break;
|
||||||
|
case UPONOR_ID_ROOM_TEMP:
|
||||||
|
this->current_temperature = raw_to_celsius(data[i].value);
|
||||||
|
break;
|
||||||
|
case UPONOR_ID_HUMIDITY:
|
||||||
|
this->current_humidity = data[i].value & 0x00FF;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this->last_data_ = millis();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace uponor_smatrix
|
||||||
|
} // namespace esphome
|
@ -0,0 +1,28 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/components/climate/climate.h"
|
||||||
|
#include "esphome/components/uponor_smatrix/uponor_smatrix.h"
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace uponor_smatrix {
|
||||||
|
|
||||||
|
class UponorSmatrixClimate : public climate::Climate, public Component, public UponorSmatrixDevice {
|
||||||
|
public:
|
||||||
|
void dump_config() override;
|
||||||
|
void loop() override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
climate::ClimateTraits traits() override;
|
||||||
|
void control(const climate::ClimateCall &call) override;
|
||||||
|
void on_device_data(const UponorSmatrixData *data, size_t data_len) override;
|
||||||
|
|
||||||
|
uint32_t last_data_;
|
||||||
|
float min_temperature_{5.0f};
|
||||||
|
float max_temperature_{35.0f};
|
||||||
|
uint16_t eco_setback_value_raw_{0x0048};
|
||||||
|
uint16_t target_temperature_raw_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace uponor_smatrix
|
||||||
|
} // namespace esphome
|
70
esphome/components/uponor_smatrix/sensor/__init__.py
Normal file
70
esphome/components/uponor_smatrix/sensor/__init__.py
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
import esphome.codegen as cg
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
from esphome.components import sensor
|
||||||
|
from esphome.const import (
|
||||||
|
CONF_EXTERNAL_TEMPERATURE,
|
||||||
|
CONF_HUMIDITY,
|
||||||
|
CONF_TEMPERATURE,
|
||||||
|
CONF_ID,
|
||||||
|
DEVICE_CLASS_HUMIDITY,
|
||||||
|
DEVICE_CLASS_TEMPERATURE,
|
||||||
|
STATE_CLASS_MEASUREMENT,
|
||||||
|
UNIT_CELSIUS,
|
||||||
|
UNIT_PERCENT,
|
||||||
|
)
|
||||||
|
|
||||||
|
from .. import (
|
||||||
|
uponor_smatrix_ns,
|
||||||
|
UponorSmatrixDevice,
|
||||||
|
UPONOR_SMATRIX_DEVICE_SCHEMA,
|
||||||
|
register_uponor_smatrix_device,
|
||||||
|
)
|
||||||
|
|
||||||
|
DEPENDENCIES = ["uponor_smatrix"]
|
||||||
|
|
||||||
|
UponorSmatrixSensor = uponor_smatrix_ns.class_(
|
||||||
|
"UponorSmatrixSensor",
|
||||||
|
sensor.Sensor,
|
||||||
|
cg.Component,
|
||||||
|
UponorSmatrixDevice,
|
||||||
|
)
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = cv.COMPONENT_SCHEMA.extend(
|
||||||
|
{
|
||||||
|
cv.GenerateID(): cv.declare_id(UponorSmatrixSensor),
|
||||||
|
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
|
||||||
|
unit_of_measurement=UNIT_CELSIUS,
|
||||||
|
accuracy_decimals=1,
|
||||||
|
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||||
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_EXTERNAL_TEMPERATURE): sensor.sensor_schema(
|
||||||
|
unit_of_measurement=UNIT_CELSIUS,
|
||||||
|
accuracy_decimals=1,
|
||||||
|
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||||
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
|
||||||
|
unit_of_measurement=UNIT_PERCENT,
|
||||||
|
accuracy_decimals=0,
|
||||||
|
device_class=DEVICE_CLASS_HUMIDITY,
|
||||||
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
).extend(UPONOR_SMATRIX_DEVICE_SCHEMA)
|
||||||
|
|
||||||
|
|
||||||
|
async def to_code(config):
|
||||||
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
|
await cg.register_component(var, config)
|
||||||
|
await register_uponor_smatrix_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 external_temperature_config := config.get(CONF_EXTERNAL_TEMPERATURE):
|
||||||
|
sens = await sensor.new_sensor(external_temperature_config)
|
||||||
|
cg.add(var.set_external_temperature_sensor(sens))
|
||||||
|
if humidity_config := config.get(CONF_HUMIDITY):
|
||||||
|
sens = await sensor.new_sensor(humidity_config)
|
||||||
|
cg.add(var.set_humidity_sensor(sens))
|
@ -0,0 +1,37 @@
|
|||||||
|
#include "uponor_smatrix_sensor.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace uponor_smatrix {
|
||||||
|
|
||||||
|
static const char *const TAG = "uponor_smatrix.sensor";
|
||||||
|
|
||||||
|
void UponorSmatrixSensor::dump_config() {
|
||||||
|
ESP_LOGCONFIG(TAG, "Uponor Smatrix Sensor");
|
||||||
|
ESP_LOGCONFIG(TAG, " Device address: 0x%04X", this->address_);
|
||||||
|
LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
|
||||||
|
LOG_SENSOR(" ", "External Temperature", this->external_temperature_sensor_);
|
||||||
|
LOG_SENSOR(" ", "Humidity", this->humidity_sensor_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UponorSmatrixSensor::on_device_data(const UponorSmatrixData *data, size_t data_len) {
|
||||||
|
for (int i = 0; i < data_len; i++) {
|
||||||
|
switch (data[i].id) {
|
||||||
|
case UPONOR_ID_ROOM_TEMP:
|
||||||
|
if (this->temperature_sensor_ != nullptr)
|
||||||
|
this->temperature_sensor_->publish_state(raw_to_celsius(data[i].value));
|
||||||
|
break;
|
||||||
|
case UPONOR_ID_EXTERNAL_TEMP:
|
||||||
|
if (this->external_temperature_sensor_ != nullptr)
|
||||||
|
this->external_temperature_sensor_->publish_state(raw_to_celsius(data[i].value));
|
||||||
|
break;
|
||||||
|
case UPONOR_ID_HUMIDITY:
|
||||||
|
if (this->humidity_sensor_ != nullptr)
|
||||||
|
this->humidity_sensor_->publish_state(data[i].value & 0x00FF);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace uponor_smatrix
|
||||||
|
} // namespace esphome
|
@ -0,0 +1,23 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/components/sensor/sensor.h"
|
||||||
|
#include "esphome/components/uponor_smatrix/uponor_smatrix.h"
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace uponor_smatrix {
|
||||||
|
|
||||||
|
class UponorSmatrixSensor : public sensor::Sensor, public Component, public UponorSmatrixDevice {
|
||||||
|
SUB_SENSOR(temperature)
|
||||||
|
SUB_SENSOR(external_temperature)
|
||||||
|
SUB_SENSOR(humidity)
|
||||||
|
|
||||||
|
public:
|
||||||
|
void dump_config() override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void on_device_data(const UponorSmatrixData *data, size_t data_len) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace uponor_smatrix
|
||||||
|
} // namespace esphome
|
225
esphome/components/uponor_smatrix/uponor_smatrix.cpp
Normal file
225
esphome/components/uponor_smatrix/uponor_smatrix.cpp
Normal file
@ -0,0 +1,225 @@
|
|||||||
|
#include "uponor_smatrix.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace uponor_smatrix {
|
||||||
|
|
||||||
|
static const char *const TAG = "uponor_smatrix";
|
||||||
|
|
||||||
|
void UponorSmatrixComponent::setup() {
|
||||||
|
#ifdef USE_TIME
|
||||||
|
if (this->time_id_ != nullptr) {
|
||||||
|
this->time_id_->add_on_time_sync_callback([this] { this->send_time(); });
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void UponorSmatrixComponent::dump_config() {
|
||||||
|
ESP_LOGCONFIG(TAG, "Uponor Smatrix");
|
||||||
|
ESP_LOGCONFIG(TAG, " System address: 0x%04X", this->address_);
|
||||||
|
#ifdef USE_TIME
|
||||||
|
if (this->time_id_ != nullptr) {
|
||||||
|
ESP_LOGCONFIG(TAG, " Time synchronization: YES");
|
||||||
|
ESP_LOGCONFIG(TAG, " Time master device address: 0x%04X", this->time_device_address_);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
this->check_uart_settings(19200);
|
||||||
|
|
||||||
|
if (!this->unknown_devices_.empty()) {
|
||||||
|
ESP_LOGCONFIG(TAG, " Detected unknown device addresses:");
|
||||||
|
for (auto device_address : this->unknown_devices_) {
|
||||||
|
ESP_LOGCONFIG(TAG, " 0x%04X", device_address);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UponorSmatrixComponent::loop() {
|
||||||
|
const uint32_t now = millis();
|
||||||
|
|
||||||
|
// Discard stale data
|
||||||
|
if (!this->rx_buffer_.empty() && (now - this->last_rx_ > 50)) {
|
||||||
|
ESP_LOGD(TAG, "Discarding %d bytes of unparsed data", this->rx_buffer_.size());
|
||||||
|
this->rx_buffer_.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read incoming data
|
||||||
|
while (this->available()) {
|
||||||
|
// The controller polls devices every 10 seconds, with around 200 ms between devices.
|
||||||
|
// Remember timestamps so we can send our own packets when the bus is expected to be silent.
|
||||||
|
if (now - this->last_rx_ > 500) {
|
||||||
|
this->last_poll_start_ = now;
|
||||||
|
}
|
||||||
|
this->last_rx_ = now;
|
||||||
|
|
||||||
|
uint8_t byte;
|
||||||
|
this->read_byte(&byte);
|
||||||
|
if (this->parse_byte_(byte)) {
|
||||||
|
this->rx_buffer_.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send packets during bus silence
|
||||||
|
if ((now - this->last_rx_ > 300) && (now - this->last_poll_start_ < 9500) && (now - this->last_tx_ > 200)) {
|
||||||
|
// Only build time packet when bus is silent and queue is empty to make sure we can send it right away
|
||||||
|
if (this->send_time_requested_ && this->tx_queue_.empty() && this->do_send_time_())
|
||||||
|
this->send_time_requested_ = false;
|
||||||
|
// Send the next packet in the queue
|
||||||
|
if (!this->tx_queue_.empty()) {
|
||||||
|
auto packet = std::move(this->tx_queue_.front());
|
||||||
|
this->tx_queue_.pop();
|
||||||
|
|
||||||
|
this->write_array(packet);
|
||||||
|
this->flush();
|
||||||
|
|
||||||
|
this->last_tx_ = now;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UponorSmatrixComponent::parse_byte_(uint8_t byte) {
|
||||||
|
this->rx_buffer_.push_back(byte);
|
||||||
|
const uint8_t *packet = this->rx_buffer_.data();
|
||||||
|
size_t packet_len = this->rx_buffer_.size();
|
||||||
|
|
||||||
|
if (packet_len < 7) {
|
||||||
|
// Minimum packet size is 7 bytes, wait for more
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t system_address = encode_uint16(packet[0], packet[1]);
|
||||||
|
uint16_t device_address = encode_uint16(packet[2], packet[3]);
|
||||||
|
uint16_t crc = encode_uint16(packet[packet_len - 1], packet[packet_len - 2]);
|
||||||
|
|
||||||
|
uint16_t computed_crc = crc16(packet, packet_len - 2);
|
||||||
|
if (crc != computed_crc) {
|
||||||
|
// CRC did not match, more data might be coming
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ESP_LOGV(TAG, "Received packet: sys=%04X, dev=%04X, data=%s, crc=%04X", system_address, device_address,
|
||||||
|
format_hex(&packet[4], packet_len - 6).c_str(), crc);
|
||||||
|
|
||||||
|
// Detect or check system address
|
||||||
|
if (this->address_ == 0) {
|
||||||
|
ESP_LOGI(TAG, "Using detected system address 0x%04X", system_address);
|
||||||
|
this->address_ = system_address;
|
||||||
|
} else if (this->address_ != system_address) {
|
||||||
|
// This should never happen except if the system address was set or detected incorrectly, so warn the user.
|
||||||
|
ESP_LOGW(TAG, "Received packet from unknown system address 0x%04X", system_address);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle packet
|
||||||
|
size_t data_len = (packet_len - 6) / 3;
|
||||||
|
if (data_len == 0) {
|
||||||
|
if (packet[4] == UPONOR_ID_REQUEST)
|
||||||
|
ESP_LOGVV(TAG, "Ignoring request packet for device 0x%04X", device_address);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode packet payload data for easy access
|
||||||
|
UponorSmatrixData data[data_len];
|
||||||
|
for (int i = 0; i < data_len; i++) {
|
||||||
|
data[i].id = packet[(i * 3) + 4];
|
||||||
|
data[i].value = encode_uint16(packet[(i * 3) + 5], packet[(i * 3) + 6]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef USE_TIME
|
||||||
|
// Detect device that acts as time master if not set explicitely
|
||||||
|
if (this->time_device_address_ == 0 && data_len >= 2) {
|
||||||
|
// The first thermostat paired to the controller will act as the time master. Time can only be manually adjusted at
|
||||||
|
// this first thermostat. To synchronize time, we need to know its address, so we search for packets coming from a
|
||||||
|
// thermostat sending both room temperature and time information.
|
||||||
|
bool found_temperature = false;
|
||||||
|
bool found_time = false;
|
||||||
|
for (int i = 0; i < data_len; i++) {
|
||||||
|
if (data[i].id == UPONOR_ID_ROOM_TEMP)
|
||||||
|
found_temperature = true;
|
||||||
|
if (data[i].id == UPONOR_ID_DATETIME1)
|
||||||
|
found_time = true;
|
||||||
|
if (found_temperature && found_time) {
|
||||||
|
ESP_LOGI(TAG, "Using detected time device address 0x%04X", device_address);
|
||||||
|
this->time_device_address_ = device_address;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Forward data to device components
|
||||||
|
bool found = false;
|
||||||
|
for (auto *device : this->devices_) {
|
||||||
|
if (device->address_ == device_address) {
|
||||||
|
found = true;
|
||||||
|
device->on_device_data(data, data_len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log unknown device addresses
|
||||||
|
if (!found && !this->unknown_devices_.count(device_address)) {
|
||||||
|
ESP_LOGI(TAG, "Received packet for unknown device address 0x%04X ", device_address);
|
||||||
|
this->unknown_devices_.insert(device_address);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return true to reset buffer
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UponorSmatrixComponent::send(uint16_t device_address, const UponorSmatrixData *data, size_t data_len) {
|
||||||
|
if (this->address_ == 0 || device_address == 0 || data == nullptr || data_len == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Assemble packet for send queue. All fields are big-endian except for the little-endian checksum.
|
||||||
|
std::vector<uint8_t> packet(6 + 3 * data_len);
|
||||||
|
packet.push_back(this->address_ >> 8);
|
||||||
|
packet.push_back(this->address_ >> 0);
|
||||||
|
packet.push_back(device_address >> 8);
|
||||||
|
packet.push_back(device_address >> 0);
|
||||||
|
|
||||||
|
for (int i = 0; i < data_len; i++) {
|
||||||
|
packet.push_back(data[i].id);
|
||||||
|
packet.push_back(data[i].value >> 8);
|
||||||
|
packet.push_back(data[i].value >> 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto crc = crc16(packet.data(), packet.size());
|
||||||
|
packet.push_back(crc >> 0);
|
||||||
|
packet.push_back(crc >> 8);
|
||||||
|
|
||||||
|
this->tx_queue_.push(packet);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef USE_TIME
|
||||||
|
bool UponorSmatrixComponent::do_send_time_() {
|
||||||
|
if (this->time_device_address_ == 0 || this->time_id_ == nullptr)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
ESPTime now = this->time_id_->now();
|
||||||
|
if (!now.is_valid())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
uint8_t year = now.year - 2000;
|
||||||
|
uint8_t month = now.month;
|
||||||
|
// ESPHome days are [1-7] starting with Sunday, Uponor days are [0-6] starting with Monday
|
||||||
|
uint8_t day_of_week = (now.day_of_week == 1) ? 6 : (now.day_of_week - 2);
|
||||||
|
uint8_t day_of_month = now.day_of_month;
|
||||||
|
uint8_t hour = now.hour;
|
||||||
|
uint8_t minute = now.minute;
|
||||||
|
uint8_t second = now.second;
|
||||||
|
|
||||||
|
uint16_t time1 = (year & 0x7F) << 7 | (month & 0x0F) << 3 | (day_of_week & 0x07);
|
||||||
|
uint16_t time2 = (day_of_month & 0x1F) << 11 | (hour & 0x1F) << 6 | (minute & 0x3F);
|
||||||
|
uint16_t time3 = second;
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "Sending local time: %04d-%02d-%02d %02d:%02d:%02d", now.year, now.month, now.day_of_month, now.hour,
|
||||||
|
now.minute, now.second);
|
||||||
|
|
||||||
|
UponorSmatrixData data[] = {{UPONOR_ID_DATETIME1, time1}, {UPONOR_ID_DATETIME2, time2}, {UPONOR_ID_DATETIME3, time3}};
|
||||||
|
return this->send(this->time_device_address_, data, sizeof(data) / sizeof(data[0]));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
} // namespace uponor_smatrix
|
||||||
|
} // namespace esphome
|
128
esphome/components/uponor_smatrix/uponor_smatrix.h
Normal file
128
esphome/components/uponor_smatrix/uponor_smatrix.h
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/components/uart/uart.h"
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
#include "esphome/core/helpers.h"
|
||||||
|
|
||||||
|
#ifdef USE_TIME
|
||||||
|
#include "esphome/components/time/real_time_clock.h"
|
||||||
|
#include "esphome/core/time.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <queue>
|
||||||
|
#include <set>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace uponor_smatrix {
|
||||||
|
|
||||||
|
/// Date/Time Part 1 (year, month, day of week)
|
||||||
|
static const uint8_t UPONOR_ID_DATETIME1 = 0x08;
|
||||||
|
/// Date/Time Part 2 (day of month, hour, minute)
|
||||||
|
static const uint8_t UPONOR_ID_DATETIME2 = 0x09;
|
||||||
|
/// Date/Time Part 3 (seconds)
|
||||||
|
static const uint8_t UPONOR_ID_DATETIME3 = 0x0A;
|
||||||
|
/// Unknown (observed values: 0x0342, 0x0024)
|
||||||
|
static const uint8_t UPONOR_ID_UNKNOWN1 = 0x0C;
|
||||||
|
/// Outdoor Temperature? (sent by controller)
|
||||||
|
static const uint8_t UPONOR_ID_OUTDOOR_TEMP = 0x2D;
|
||||||
|
/// Unknown (observed values: 0x8000)
|
||||||
|
static const uint8_t UPONOR_ID_UNKNOWN2 = 0x35;
|
||||||
|
/// Room Temperature Setpoint Minimum
|
||||||
|
static const uint8_t UPONOR_ID_TARGET_TEMP_MIN = 0x37;
|
||||||
|
/// Room Temperature Setpoint Maximum
|
||||||
|
static const uint8_t UPONOR_ID_TARGET_TEMP_MAX = 0x38;
|
||||||
|
/// Room Temperature Setpoint
|
||||||
|
static const uint8_t UPONOR_ID_TARGET_TEMP = 0x3B;
|
||||||
|
/// Room Temperature Setpoint Setback for ECO Mode
|
||||||
|
static const uint8_t UPONOR_ID_ECO_SETBACK = 0x3C;
|
||||||
|
/// Heating/Cooling Demand
|
||||||
|
static const uint8_t UPONOR_ID_DEMAND = 0x3D;
|
||||||
|
/// Thermostat Operating Mode 1 (ECO state, program schedule state)
|
||||||
|
static const uint8_t UPONOR_ID_MODE1 = 0x3E;
|
||||||
|
/// Thermostat Operating Mode 2 (sensor configuration, heating/cooling allowed)
|
||||||
|
static const uint8_t UPONOR_ID_MODE2 = 0x3F;
|
||||||
|
/// Current Room Temperature
|
||||||
|
static const uint8_t UPONOR_ID_ROOM_TEMP = 0x40;
|
||||||
|
/// Current External (Floor/Outdoor) Sensor Temperature
|
||||||
|
static const uint8_t UPONOR_ID_EXTERNAL_TEMP = 0x41;
|
||||||
|
/// Current Room Humidity
|
||||||
|
static const uint8_t UPONOR_ID_HUMIDITY = 0x42;
|
||||||
|
/// Data Request (sent by controller)
|
||||||
|
static const uint8_t UPONOR_ID_REQUEST = 0xFF;
|
||||||
|
|
||||||
|
/// Indicating an invalid/missing value
|
||||||
|
static const uint16_t UPONOR_INVALID_VALUE = 0x7FFF;
|
||||||
|
|
||||||
|
struct UponorSmatrixData {
|
||||||
|
uint8_t id;
|
||||||
|
uint16_t value;
|
||||||
|
};
|
||||||
|
|
||||||
|
class UponorSmatrixDevice;
|
||||||
|
|
||||||
|
class UponorSmatrixComponent : public uart::UARTDevice, public Component {
|
||||||
|
public:
|
||||||
|
UponorSmatrixComponent() = default;
|
||||||
|
|
||||||
|
void setup() override;
|
||||||
|
void dump_config() override;
|
||||||
|
void loop() override;
|
||||||
|
|
||||||
|
void set_system_address(uint16_t address) { this->address_ = address; }
|
||||||
|
void register_device(UponorSmatrixDevice *device) { this->devices_.push_back(device); }
|
||||||
|
|
||||||
|
bool send(uint16_t device_address, const UponorSmatrixData *data, size_t data_len);
|
||||||
|
|
||||||
|
#ifdef USE_TIME
|
||||||
|
void set_time_id(time::RealTimeClock *time_id) { this->time_id_ = time_id; }
|
||||||
|
void set_time_device_address(uint16_t address) { this->time_device_address_ = address; }
|
||||||
|
void send_time() { this->send_time_requested_ = true; }
|
||||||
|
#endif
|
||||||
|
|
||||||
|
protected:
|
||||||
|
bool parse_byte_(uint8_t byte);
|
||||||
|
|
||||||
|
uint16_t address_;
|
||||||
|
std::vector<UponorSmatrixDevice *> devices_;
|
||||||
|
std::set<uint16_t> unknown_devices_;
|
||||||
|
|
||||||
|
std::vector<uint8_t> rx_buffer_;
|
||||||
|
std::queue<std::vector<uint8_t>> tx_queue_;
|
||||||
|
uint32_t last_rx_;
|
||||||
|
uint32_t last_tx_;
|
||||||
|
uint32_t last_poll_start_;
|
||||||
|
|
||||||
|
#ifdef USE_TIME
|
||||||
|
time::RealTimeClock *time_id_{nullptr};
|
||||||
|
uint16_t time_device_address_;
|
||||||
|
bool send_time_requested_;
|
||||||
|
bool do_send_time_();
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
class UponorSmatrixDevice : public Parented<UponorSmatrixComponent> {
|
||||||
|
public:
|
||||||
|
void set_device_address(uint16_t address) { this->address_ = address; }
|
||||||
|
|
||||||
|
virtual void on_device_data(const UponorSmatrixData *data, size_t data_len) = 0;
|
||||||
|
bool send(const UponorSmatrixData *data, size_t data_len) {
|
||||||
|
return this->parent_->send(this->address_, data, data_len);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
friend UponorSmatrixComponent;
|
||||||
|
uint16_t address_;
|
||||||
|
};
|
||||||
|
|
||||||
|
inline float raw_to_celsius(uint16_t raw) {
|
||||||
|
return (raw == UPONOR_INVALID_VALUE) ? NAN : fahrenheit_to_celsius(raw / 10.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline uint16_t celsius_to_raw(float celsius) {
|
||||||
|
return std::isnan(celsius) ? UPONOR_INVALID_VALUE
|
||||||
|
: static_cast<uint16_t>(lroundf(celsius_to_fahrenheit(celsius) * 10.0f));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace uponor_smatrix
|
||||||
|
} // namespace esphome
|
@ -32,6 +32,7 @@ CONF_ON_TTS_START = "on_tts_start"
|
|||||||
CONF_ON_TTS_STREAM_START = "on_tts_stream_start"
|
CONF_ON_TTS_STREAM_START = "on_tts_stream_start"
|
||||||
CONF_ON_TTS_STREAM_END = "on_tts_stream_end"
|
CONF_ON_TTS_STREAM_END = "on_tts_stream_end"
|
||||||
CONF_ON_WAKE_WORD_DETECTED = "on_wake_word_detected"
|
CONF_ON_WAKE_WORD_DETECTED = "on_wake_word_detected"
|
||||||
|
CONF_ON_IDLE = "on_idle"
|
||||||
|
|
||||||
CONF_SILENCE_DETECTION = "silence_detection"
|
CONF_SILENCE_DETECTION = "silence_detection"
|
||||||
CONF_USE_WAKE_WORD = "use_wake_word"
|
CONF_USE_WAKE_WORD = "use_wake_word"
|
||||||
@ -127,6 +128,7 @@ CONFIG_SCHEMA = cv.All(
|
|||||||
cv.Optional(CONF_ON_TTS_STREAM_END): automation.validate_automation(
|
cv.Optional(CONF_ON_TTS_STREAM_END): automation.validate_automation(
|
||||||
single=True
|
single=True
|
||||||
),
|
),
|
||||||
|
cv.Optional(CONF_ON_IDLE): automation.validate_automation(single=True),
|
||||||
}
|
}
|
||||||
).extend(cv.COMPONENT_SCHEMA),
|
).extend(cv.COMPONENT_SCHEMA),
|
||||||
tts_stream_validate,
|
tts_stream_validate,
|
||||||
@ -259,6 +261,13 @@ async def to_code(config):
|
|||||||
config[CONF_ON_TTS_STREAM_END],
|
config[CONF_ON_TTS_STREAM_END],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if CONF_ON_IDLE in config:
|
||||||
|
await automation.build_automation(
|
||||||
|
var.get_idle_trigger(),
|
||||||
|
[],
|
||||||
|
config[CONF_ON_IDLE],
|
||||||
|
)
|
||||||
|
|
||||||
cg.add_define("USE_VOICE_ASSISTANT")
|
cg.add_define("USE_VOICE_ASSISTANT")
|
||||||
|
|
||||||
|
|
||||||
|
@ -135,6 +135,8 @@ void VoiceAssistant::loop() {
|
|||||||
switch (this->state_) {
|
switch (this->state_) {
|
||||||
case State::IDLE: {
|
case State::IDLE: {
|
||||||
if (this->continuous_ && this->desired_state_ == State::IDLE) {
|
if (this->continuous_ && this->desired_state_ == State::IDLE) {
|
||||||
|
this->idle_trigger_->trigger();
|
||||||
|
|
||||||
this->ring_buffer_->reset();
|
this->ring_buffer_->reset();
|
||||||
#ifdef USE_ESP_ADF
|
#ifdef USE_ESP_ADF
|
||||||
if (this->use_wake_word_) {
|
if (this->use_wake_word_) {
|
||||||
@ -618,6 +620,9 @@ void VoiceAssistant::on_event(const api::VoiceAssistantEventResponse &msg) {
|
|||||||
{
|
{
|
||||||
this->set_state_(State::IDLE, State::IDLE);
|
this->set_state_(State::IDLE, State::IDLE);
|
||||||
}
|
}
|
||||||
|
} else if (this->state_ == State::AWAITING_RESPONSE) {
|
||||||
|
// No TTS start event ("nevermind")
|
||||||
|
this->set_state_(State::IDLE, State::IDLE);
|
||||||
}
|
}
|
||||||
this->defer([this]() { this->end_trigger_->trigger(); });
|
this->defer([this]() { this->end_trigger_->trigger(); });
|
||||||
break;
|
break;
|
||||||
|
@ -116,6 +116,7 @@ class VoiceAssistant : public Component {
|
|||||||
Trigger<std::string> *get_tts_end_trigger() const { return this->tts_end_trigger_; }
|
Trigger<std::string> *get_tts_end_trigger() const { return this->tts_end_trigger_; }
|
||||||
Trigger<std::string> *get_tts_start_trigger() const { return this->tts_start_trigger_; }
|
Trigger<std::string> *get_tts_start_trigger() const { return this->tts_start_trigger_; }
|
||||||
Trigger<std::string, std::string> *get_error_trigger() const { return this->error_trigger_; }
|
Trigger<std::string, std::string> *get_error_trigger() const { return this->error_trigger_; }
|
||||||
|
Trigger<> *get_idle_trigger() const { return this->idle_trigger_; }
|
||||||
|
|
||||||
Trigger<> *get_client_connected_trigger() const { return this->client_connected_trigger_; }
|
Trigger<> *get_client_connected_trigger() const { return this->client_connected_trigger_; }
|
||||||
Trigger<> *get_client_disconnected_trigger() const { return this->client_disconnected_trigger_; }
|
Trigger<> *get_client_disconnected_trigger() const { return this->client_disconnected_trigger_; }
|
||||||
@ -148,6 +149,7 @@ class VoiceAssistant : public Component {
|
|||||||
Trigger<std::string> *tts_end_trigger_ = new Trigger<std::string>();
|
Trigger<std::string> *tts_end_trigger_ = new Trigger<std::string>();
|
||||||
Trigger<std::string> *tts_start_trigger_ = new Trigger<std::string>();
|
Trigger<std::string> *tts_start_trigger_ = new Trigger<std::string>();
|
||||||
Trigger<std::string, std::string> *error_trigger_ = new Trigger<std::string, std::string>();
|
Trigger<std::string, std::string> *error_trigger_ = new Trigger<std::string, std::string>();
|
||||||
|
Trigger<> *idle_trigger_ = new Trigger<>();
|
||||||
|
|
||||||
Trigger<> *client_connected_trigger_ = new Trigger<>();
|
Trigger<> *client_connected_trigger_ = new Trigger<>();
|
||||||
Trigger<> *client_disconnected_trigger_ = new Trigger<>();
|
Trigger<> *client_disconnected_trigger_ = new Trigger<>();
|
||||||
|
@ -785,6 +785,8 @@ std::string WebServer::cover_json(cover::Cover *obj, JsonDetail start_config) {
|
|||||||
obj->position, start_config);
|
obj->position, start_config);
|
||||||
root["current_operation"] = cover::cover_operation_to_str(obj->current_operation);
|
root["current_operation"] = cover::cover_operation_to_str(obj->current_operation);
|
||||||
|
|
||||||
|
if (obj->get_traits().get_supports_position())
|
||||||
|
root["position"] = obj->position;
|
||||||
if (obj->get_traits().get_supports_tilt())
|
if (obj->get_traits().get_supports_tilt())
|
||||||
root["tilt"] = obj->tilt;
|
root["tilt"] = obj->tilt;
|
||||||
});
|
});
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
The cryptography package is loaded lazily in the functions
|
The cryptography package is loaded lazily in the functions
|
||||||
so that it doesn't crash if it's not installed.
|
so that it doesn't crash if it's not installed.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
@ -164,7 +164,7 @@ bool WLEDLightEffect::parse_notifier_frame_(light::AddressableLight &it, const u
|
|||||||
|
|
||||||
uint8_t payload_sync_group_mask = payload[34];
|
uint8_t payload_sync_group_mask = payload[34];
|
||||||
|
|
||||||
if ((payload_sync_group_mask & this->sync_group_mask_) != this->sync_group_mask_) {
|
if (this->sync_group_mask_ && !(payload_sync_group_mask & this->sync_group_mask_)) {
|
||||||
ESP_LOGD(TAG, "sync group mask does not match");
|
ESP_LOGD(TAG, "sync group mask does not match");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -36,14 +36,14 @@ bool XL9535Component::digital_read(uint8_t pin) {
|
|||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
state = (port & (pin - 10)) != 0;
|
state = (port & (1 << (pin - 10))) != 0;
|
||||||
} else {
|
} else {
|
||||||
if (this->read_register(XL9535_INPUT_PORT_0_REGISTER, &port, 1) != i2c::ERROR_OK) {
|
if (this->read_register(XL9535_INPUT_PORT_0_REGISTER, &port, 1) != i2c::ERROR_OK) {
|
||||||
this->status_set_warning();
|
this->status_set_warning();
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
state = (port & pin) != 0;
|
state = (port & (1 << pin)) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
this->status_clear_warning();
|
this->status_clear_warning();
|
||||||
|
@ -292,8 +292,7 @@ class ConfigValidationStep(abc.ABC):
|
|||||||
priority: float = 0.0
|
priority: float = 0.0
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def run(self, result: Config) -> None:
|
def run(self, result: Config) -> None: ... # noqa: E704
|
||||||
...
|
|
||||||
|
|
||||||
|
|
||||||
class LoadValidationStep(ConfigValidationStep):
|
class LoadValidationStep(ConfigValidationStep):
|
||||||
|
@ -57,6 +57,7 @@ from esphome.const import (
|
|||||||
TYPE_GIT,
|
TYPE_GIT,
|
||||||
TYPE_LOCAL,
|
TYPE_LOCAL,
|
||||||
VALID_SUBSTITUTIONS_CHARACTERS,
|
VALID_SUBSTITUTIONS_CHARACTERS,
|
||||||
|
__version__ as ESPHOME_VERSION,
|
||||||
)
|
)
|
||||||
from esphome.core import (
|
from esphome.core import (
|
||||||
CORE,
|
CORE,
|
||||||
@ -1895,6 +1896,16 @@ def version_number(value):
|
|||||||
raise Invalid("Not a valid version number") from e
|
raise Invalid("Not a valid version number") from e
|
||||||
|
|
||||||
|
|
||||||
|
def validate_esphome_version(value: str):
|
||||||
|
min_version = Version.parse(value)
|
||||||
|
current_version = Version.parse(ESPHOME_VERSION)
|
||||||
|
if current_version < min_version:
|
||||||
|
raise Invalid(
|
||||||
|
f"Your ESPHome version is too old. Please update to at least {min_version}"
|
||||||
|
)
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
def platformio_version_constraint(value):
|
def platformio_version_constraint(value):
|
||||||
# for documentation on valid version constraints:
|
# for documentation on valid version constraints:
|
||||||
# https://docs.platformio.org/en/latest/core/userguide/platforms/cmd_install.html#cmd-platform-install
|
# https://docs.platformio.org/en/latest/core/userguide/platforms/cmd_install.html#cmd-platform-install
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
"""Constants used by esphome."""
|
"""Constants used by esphome."""
|
||||||
|
|
||||||
__version__ = "2024.1.0-dev"
|
__version__ = "2024.3.0-dev"
|
||||||
|
|
||||||
ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
|
ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
|
||||||
VALID_SUBSTITUTIONS_CHARACTERS = (
|
VALID_SUBSTITUTIONS_CHARACTERS = (
|
||||||
@ -252,6 +252,7 @@ CONF_EXPORT_ACTIVE_ENERGY = "export_active_energy"
|
|||||||
CONF_EXPORT_REACTIVE_ENERGY = "export_reactive_energy"
|
CONF_EXPORT_REACTIVE_ENERGY = "export_reactive_energy"
|
||||||
CONF_EXTERNAL_CLOCK_INPUT = "external_clock_input"
|
CONF_EXTERNAL_CLOCK_INPUT = "external_clock_input"
|
||||||
CONF_EXTERNAL_COMPONENTS = "external_components"
|
CONF_EXTERNAL_COMPONENTS = "external_components"
|
||||||
|
CONF_EXTERNAL_TEMPERATURE = "external_temperature"
|
||||||
CONF_EXTERNAL_VCC = "external_vcc"
|
CONF_EXTERNAL_VCC = "external_vcc"
|
||||||
CONF_FALLING_EDGE = "falling_edge"
|
CONF_FALLING_EDGE = "falling_edge"
|
||||||
CONF_FAMILY = "family"
|
CONF_FAMILY = "family"
|
||||||
|
@ -102,16 +102,6 @@ def valid_project_name(value: str):
|
|||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
def validate_version(value: str):
|
|
||||||
min_version = cv.Version.parse(value)
|
|
||||||
current_version = cv.Version.parse(ESPHOME_VERSION)
|
|
||||||
if current_version < min_version:
|
|
||||||
raise cv.Invalid(
|
|
||||||
f"Your ESPHome version is too old. Please update to at least {min_version}"
|
|
||||||
)
|
|
||||||
return value
|
|
||||||
|
|
||||||
|
|
||||||
if "ESPHOME_DEFAULT_COMPILE_PROCESS_LIMIT" in os.environ:
|
if "ESPHOME_DEFAULT_COMPILE_PROCESS_LIMIT" in os.environ:
|
||||||
_compile_process_limit_default = min(
|
_compile_process_limit_default = min(
|
||||||
int(os.environ["ESPHOME_DEFAULT_COMPILE_PROCESS_LIMIT"]),
|
int(os.environ["ESPHOME_DEFAULT_COMPILE_PROCESS_LIMIT"]),
|
||||||
@ -164,7 +154,7 @@ CONFIG_SCHEMA = cv.All(
|
|||||||
}
|
}
|
||||||
),
|
),
|
||||||
cv.Optional(CONF_MIN_VERSION, default=ESPHOME_VERSION): cv.All(
|
cv.Optional(CONF_MIN_VERSION, default=ESPHOME_VERSION): cv.All(
|
||||||
cv.version_number, validate_version
|
cv.version_number, cv.validate_esphome_version
|
||||||
),
|
),
|
||||||
cv.Optional(
|
cv.Optional(
|
||||||
CONF_COMPILE_PROCESS_LIMIT, default=_compile_process_limit_default
|
CONF_COMPILE_PROCESS_LIMIT, default=_compile_process_limit_default
|
||||||
|
@ -478,7 +478,7 @@ def variable(
|
|||||||
:param type_: Manually define a type for the variable, only use this when it's not possible
|
:param type_: Manually define a type for the variable, only use this when it's not possible
|
||||||
to do so during config validation phase (for example because of template arguments).
|
to do so during config validation phase (for example because of template arguments).
|
||||||
|
|
||||||
:returns The new variable as a MockObj.
|
:return: The new variable as a MockObj.
|
||||||
"""
|
"""
|
||||||
assert isinstance(id_, ID)
|
assert isinstance(id_, ID)
|
||||||
rhs = safe_exp(rhs)
|
rhs = safe_exp(rhs)
|
||||||
@ -526,7 +526,7 @@ def new_variable(id_: ID, rhs: SafeExpType, type_: "MockObj" = None) -> "MockObj
|
|||||||
:param type_: Manually define a type for the variable, only use this when it's not possible
|
:param type_: Manually define a type for the variable, only use this when it's not possible
|
||||||
to do so during config validation phase (for example because of template arguments).
|
to do so during config validation phase (for example because of template arguments).
|
||||||
|
|
||||||
:returns The new variable as a MockObj.
|
:return: The new variable as a MockObj.
|
||||||
"""
|
"""
|
||||||
assert isinstance(id_, ID)
|
assert isinstance(id_, ID)
|
||||||
rhs = safe_exp(rhs)
|
rhs = safe_exp(rhs)
|
||||||
@ -549,7 +549,7 @@ def Pvariable(id_: ID, rhs: SafeExpType, type_: "MockObj" = None) -> "MockObj":
|
|||||||
:param type_: Manually define a type for the variable, only use this when it's not possible
|
:param type_: Manually define a type for the variable, only use this when it's not possible
|
||||||
to do so during config validation phase (for example because of template arguments).
|
to do so during config validation phase (for example because of template arguments).
|
||||||
|
|
||||||
:returns The new variable as a MockObj.
|
:return: The new variable as a MockObj.
|
||||||
"""
|
"""
|
||||||
rhs = safe_exp(rhs)
|
rhs = safe_exp(rhs)
|
||||||
obj = MockObj(id_, "->")
|
obj = MockObj(id_, "->")
|
||||||
@ -570,7 +570,7 @@ def new_Pvariable(id_: ID, *args: SafeExpType) -> Pvariable:
|
|||||||
:param id_: The ID used to declare the variable (also specifies the type).
|
:param id_: The ID used to declare the variable (also specifies the type).
|
||||||
:param args: The values to pass to the constructor.
|
:param args: The values to pass to the constructor.
|
||||||
|
|
||||||
:returns The new variable as a MockObj.
|
:return: The new variable as a MockObj.
|
||||||
"""
|
"""
|
||||||
if args and isinstance(args[0], TemplateArguments):
|
if args and isinstance(args[0], TemplateArguments):
|
||||||
id_ = id_.copy()
|
id_ = id_.copy()
|
||||||
|
@ -8,3 +8,5 @@ MAX_EXECUTOR_WORKERS = 48
|
|||||||
|
|
||||||
|
|
||||||
SENTINEL = object()
|
SENTINEL = object()
|
||||||
|
|
||||||
|
DASHBOARD_COMMAND = ["esphome", "--dashboard"]
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
|
import contextlib
|
||||||
import logging
|
import logging
|
||||||
import threading
|
import threading
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from typing import TYPE_CHECKING, Any, Callable
|
from typing import TYPE_CHECKING, Any, Callable
|
||||||
|
from collections.abc import Coroutine
|
||||||
|
|
||||||
from ..zeroconf import DiscoveredImport
|
from ..zeroconf import DiscoveredImport
|
||||||
from .dns import DNSCache
|
from .dns import DNSCache
|
||||||
@ -71,6 +73,7 @@ class ESPHomeDashboard:
|
|||||||
"mdns_status",
|
"mdns_status",
|
||||||
"settings",
|
"settings",
|
||||||
"dns_cache",
|
"dns_cache",
|
||||||
|
"_background_tasks",
|
||||||
)
|
)
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
@ -85,6 +88,7 @@ class ESPHomeDashboard:
|
|||||||
self.mdns_status: MDNSStatus | None = None
|
self.mdns_status: MDNSStatus | None = None
|
||||||
self.settings = DashboardSettings()
|
self.settings = DashboardSettings()
|
||||||
self.dns_cache = DNSCache()
|
self.dns_cache = DNSCache()
|
||||||
|
self._background_tasks: set[asyncio.Task] = set()
|
||||||
|
|
||||||
async def async_setup(self) -> None:
|
async def async_setup(self) -> None:
|
||||||
"""Setup the dashboard."""
|
"""Setup the dashboard."""
|
||||||
@ -132,7 +136,19 @@ class ESPHomeDashboard:
|
|||||||
if settings.status_use_mqtt:
|
if settings.status_use_mqtt:
|
||||||
status_thread_mqtt.join()
|
status_thread_mqtt.join()
|
||||||
self.mqtt_ping_request.set()
|
self.mqtt_ping_request.set()
|
||||||
|
for task in self._background_tasks:
|
||||||
|
task.cancel()
|
||||||
|
with contextlib.suppress(asyncio.CancelledError):
|
||||||
|
await task
|
||||||
await asyncio.sleep(0)
|
await asyncio.sleep(0)
|
||||||
|
|
||||||
|
def async_create_background_task(
|
||||||
|
self, coro: Coroutine[Any, Any, Any]
|
||||||
|
) -> asyncio.Task:
|
||||||
|
"""Create a background task."""
|
||||||
|
task = self.loop.create_task(coro)
|
||||||
|
task.add_done_callback(self._background_tasks.discard)
|
||||||
|
return task
|
||||||
|
|
||||||
|
|
||||||
DASHBOARD = ESPHomeDashboard()
|
DASHBOARD = ESPHomeDashboard()
|
||||||
|
@ -10,12 +10,14 @@ from esphome import const, util
|
|||||||
from esphome.storage_json import StorageJSON, ext_storage_path
|
from esphome.storage_json import StorageJSON, ext_storage_path
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
|
DASHBOARD_COMMAND,
|
||||||
EVENT_ENTRY_ADDED,
|
EVENT_ENTRY_ADDED,
|
||||||
EVENT_ENTRY_REMOVED,
|
EVENT_ENTRY_REMOVED,
|
||||||
EVENT_ENTRY_STATE_CHANGED,
|
EVENT_ENTRY_STATE_CHANGED,
|
||||||
EVENT_ENTRY_UPDATED,
|
EVENT_ENTRY_UPDATED,
|
||||||
)
|
)
|
||||||
from .enum import StrEnum
|
from .enum import StrEnum
|
||||||
|
from .util.subprocess import async_run_system_command
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from .core import ESPHomeDashboard
|
from .core import ESPHomeDashboard
|
||||||
@ -235,6 +237,14 @@ class DashboardEntries:
|
|||||||
)
|
)
|
||||||
return path_to_cache_key
|
return path_to_cache_key
|
||||||
|
|
||||||
|
def async_schedule_storage_json_update(self, filename: str) -> None:
|
||||||
|
"""Schedule a task to update the storage JSON file."""
|
||||||
|
self._dashboard.async_create_background_task(
|
||||||
|
async_run_system_command(
|
||||||
|
[*DASHBOARD_COMMAND, "compile", "--only-generate", filename]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class DashboardEntry:
|
class DashboardEntry:
|
||||||
"""Represents a single dashboard entry.
|
"""Represents a single dashboard entry.
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
"""Enum backports from standard lib."""
|
"""Enum backports from standard lib."""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
@ -9,11 +9,11 @@ import hashlib
|
|||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import time
|
|
||||||
import secrets
|
import secrets
|
||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
import threading
|
import threading
|
||||||
|
import time
|
||||||
from collections.abc import Iterable
|
from collections.abc import Iterable
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import TYPE_CHECKING, Any, Callable, TypeVar
|
from typing import TYPE_CHECKING, Any, Callable, TypeVar
|
||||||
@ -40,6 +40,7 @@ from esphome.storage_json import StorageJSON, ext_storage_path, trash_storage_pa
|
|||||||
from esphome.util import get_serial_ports, shlex_quote
|
from esphome.util import get_serial_ports, shlex_quote
|
||||||
from esphome.yaml_util import FastestAvailableSafeLoader
|
from esphome.yaml_util import FastestAvailableSafeLoader
|
||||||
|
|
||||||
|
from .const import DASHBOARD_COMMAND
|
||||||
from .core import DASHBOARD
|
from .core import DASHBOARD
|
||||||
from .entries import EntryState, entry_state_to_bool
|
from .entries import EntryState, entry_state_to_bool
|
||||||
from .util.file import write_file
|
from .util.file import write_file
|
||||||
@ -286,9 +287,6 @@ class EsphomeCommandWebSocket(tornado.websocket.WebSocketHandler):
|
|||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
DASHBOARD_COMMAND = ["esphome", "--dashboard"]
|
|
||||||
|
|
||||||
|
|
||||||
class EsphomePortCommandWebSocket(EsphomeCommandWebSocket):
|
class EsphomePortCommandWebSocket(EsphomeCommandWebSocket):
|
||||||
"""Base class for commands that require a port."""
|
"""Base class for commands that require a port."""
|
||||||
|
|
||||||
@ -808,8 +806,16 @@ class EditRequestHandler(BaseHandler):
|
|||||||
@bind_config
|
@bind_config
|
||||||
async def get(self, configuration: str | None = None) -> None:
|
async def get(self, configuration: str | None = None) -> None:
|
||||||
"""Get the content of a file."""
|
"""Get the content of a file."""
|
||||||
loop = asyncio.get_running_loop()
|
if not configuration.endswith((".yaml", ".yml")):
|
||||||
|
self.send_error(404)
|
||||||
|
return
|
||||||
|
|
||||||
filename = settings.rel_path(configuration)
|
filename = settings.rel_path(configuration)
|
||||||
|
if Path(filename).resolve().parent != settings.absolute_config_dir:
|
||||||
|
self.send_error(404)
|
||||||
|
return
|
||||||
|
|
||||||
|
loop = asyncio.get_running_loop()
|
||||||
content = await loop.run_in_executor(
|
content = await loop.run_in_executor(
|
||||||
None, self._read_file, filename, configuration
|
None, self._read_file, filename, configuration
|
||||||
)
|
)
|
||||||
@ -835,15 +841,19 @@ class EditRequestHandler(BaseHandler):
|
|||||||
@bind_config
|
@bind_config
|
||||||
async def post(self, configuration: str | None = None) -> None:
|
async def post(self, configuration: str | None = None) -> None:
|
||||||
"""Write the content of a file."""
|
"""Write the content of a file."""
|
||||||
|
if not configuration.endswith((".yaml", ".yml")):
|
||||||
|
self.send_error(404)
|
||||||
|
return
|
||||||
|
|
||||||
|
filename = settings.rel_path(configuration)
|
||||||
|
if Path(filename).resolve().parent != settings.absolute_config_dir:
|
||||||
|
self.send_error(404)
|
||||||
|
return
|
||||||
|
|
||||||
loop = asyncio.get_running_loop()
|
loop = asyncio.get_running_loop()
|
||||||
config_file = settings.rel_path(configuration)
|
await loop.run_in_executor(None, self._write_file, filename, self.request.body)
|
||||||
await loop.run_in_executor(
|
|
||||||
None, self._write_file, config_file, self.request.body
|
|
||||||
)
|
|
||||||
# Ensure the StorageJSON is updated as well
|
# Ensure the StorageJSON is updated as well
|
||||||
await async_run_system_command(
|
DASHBOARD.entries.async_schedule_storage_json_update(filename)
|
||||||
[*DASHBOARD_COMMAND, "compile", "--only-generate", config_file]
|
|
||||||
)
|
|
||||||
self.set_status(200)
|
self.set_status(200)
|
||||||
|
|
||||||
|
|
||||||
|
@ -206,8 +206,11 @@ def perform_ota(
|
|||||||
|
|
||||||
_, version = receive_exactly(sock, 2, "version", RESPONSE_OK)
|
_, version = receive_exactly(sock, 2, "version", RESPONSE_OK)
|
||||||
_LOGGER.debug("Device support OTA version: %s", version)
|
_LOGGER.debug("Device support OTA version: %s", version)
|
||||||
if version not in (OTA_VERSION_1_0, OTA_VERSION_2_0):
|
supported_versions = (OTA_VERSION_1_0, OTA_VERSION_2_0)
|
||||||
raise OTAError(f"Unsupported OTA version {version}")
|
if version not in supported_versions:
|
||||||
|
raise OTAError(
|
||||||
|
f"Device uses unsupported OTA version {version}, this ESPHome supports {supported_versions}"
|
||||||
|
)
|
||||||
|
|
||||||
# Features
|
# Features
|
||||||
send_check(sock, FEATURE_SUPPORTS_COMPRESSION, "features")
|
send_check(sock, FEATURE_SUPPORTS_COMPRESSION, "features")
|
||||||
|
@ -8,7 +8,6 @@ originally do.
|
|||||||
However there is a property to further disable decorator
|
However there is a property to further disable decorator
|
||||||
impact."""
|
impact."""
|
||||||
|
|
||||||
|
|
||||||
# This is set to true by script/build_language_schema.py
|
# This is set to true by script/build_language_schema.py
|
||||||
# only, so data is collected (again functionality is not modified)
|
# only, so data is collected (again functionality is not modified)
|
||||||
EnableSchemaExtraction = False
|
EnableSchemaExtraction = False
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
"""This helper module tracks commonly used types in the esphome python codebase."""
|
"""This helper module tracks commonly used types in the esphome python codebase."""
|
||||||
|
|
||||||
from typing import Union
|
from typing import Union
|
||||||
|
|
||||||
from esphome.core import ID, Lambda, EsphomeCore
|
from esphome.core import ID, Lambda, EsphomeCore
|
||||||
|
@ -64,7 +64,7 @@ class _Schema(vol.Schema):
|
|||||||
|
|
||||||
# Recursively compile schema
|
# Recursively compile schema
|
||||||
_compiled_schema = {}
|
_compiled_schema = {}
|
||||||
for skey, svalue in vol.iteritems(schema):
|
for skey, svalue in schema.items():
|
||||||
new_key = self._compile(skey)
|
new_key = self._compile(skey)
|
||||||
new_value = self._compile(svalue)
|
new_value = self._compile(svalue)
|
||||||
_compiled_schema[skey] = (new_key, new_value)
|
_compiled_schema[skey] = (new_key, new_value)
|
||||||
|
@ -51,10 +51,12 @@ BASE_CONFIG_FRIENDLY = """esphome:
|
|||||||
friendly_name: {friendly_name}
|
friendly_name: {friendly_name}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
LOGGER_API_CONFIG = """
|
LOGGER_CONFIG = """
|
||||||
# Enable logging
|
# Enable logging
|
||||||
logger:
|
logger:
|
||||||
|
"""
|
||||||
|
|
||||||
|
API_CONFIG = """
|
||||||
# Enable Home Assistant API
|
# Enable Home Assistant API
|
||||||
api:
|
api:
|
||||||
"""
|
"""
|
||||||
@ -136,7 +138,12 @@ def wizard_file(**kwargs):
|
|||||||
|
|
||||||
config += HARDWARE_BASE_CONFIGS[kwargs["platform"]].format(**kwargs)
|
config += HARDWARE_BASE_CONFIGS[kwargs["platform"]].format(**kwargs)
|
||||||
|
|
||||||
config += LOGGER_API_CONFIG
|
config += LOGGER_CONFIG
|
||||||
|
|
||||||
|
if kwargs["board"] == "rpipico":
|
||||||
|
return config
|
||||||
|
|
||||||
|
config += API_CONFIG
|
||||||
|
|
||||||
# Configure API
|
# Configure API
|
||||||
if "password" in kwargs:
|
if "password" in kwargs:
|
||||||
|
@ -110,7 +110,7 @@ class DashboardImportDiscovery:
|
|||||||
self, zeroconf: Zeroconf, info: AsyncServiceInfo, service_type: str, name: str
|
self, zeroconf: Zeroconf, info: AsyncServiceInfo, service_type: str, name: str
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Process a service info."""
|
"""Process a service info."""
|
||||||
if await info.async_request(zeroconf):
|
if await info.async_request(zeroconf, timeout=3000):
|
||||||
self._process_service_info(name, info)
|
self._process_service_info(name, info)
|
||||||
|
|
||||||
def _process_service_info(self, name: str, info: ServiceInfo) -> None:
|
def _process_service_info(self, name: str, info: ServiceInfo) -> None:
|
||||||
|
@ -93,7 +93,7 @@ lib_deps =
|
|||||||
ESP8266HTTPClient ; http_request (Arduino built-in)
|
ESP8266HTTPClient ; http_request (Arduino built-in)
|
||||||
ESP8266mDNS ; mdns (Arduino built-in)
|
ESP8266mDNS ; mdns (Arduino built-in)
|
||||||
DNSServer ; captive_portal (Arduino built-in)
|
DNSServer ; captive_portal (Arduino built-in)
|
||||||
crankyoldgit/IRremoteESP8266@2.7.12 ; heatpumpir
|
crankyoldgit/IRremoteESP8266@~2.8.4 ; heatpumpir
|
||||||
build_flags =
|
build_flags =
|
||||||
${common:arduino.build_flags}
|
${common:arduino.build_flags}
|
||||||
-Wno-nonnull-compare
|
-Wno-nonnull-compare
|
||||||
@ -122,7 +122,7 @@ lib_deps =
|
|||||||
ESPmDNS ; mdns (Arduino built-in)
|
ESPmDNS ; mdns (Arduino built-in)
|
||||||
DNSServer ; captive_portal (Arduino built-in)
|
DNSServer ; captive_portal (Arduino built-in)
|
||||||
esphome/ESP32-audioI2S@2.0.7 ; i2s_audio
|
esphome/ESP32-audioI2S@2.0.7 ; i2s_audio
|
||||||
crankyoldgit/IRremoteESP8266@2.7.12 ; heatpumpir
|
crankyoldgit/IRremoteESP8266@~2.8.4 ; heatpumpir
|
||||||
droscy/esp_wireguard@0.3.2 ; wireguard
|
droscy/esp_wireguard@0.3.2 ; wireguard
|
||||||
build_flags =
|
build_flags =
|
||||||
${common:arduino.build_flags}
|
${common:arduino.build_flags}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
async_timeout==4.0.3; python_version <= "3.10"
|
async_timeout==4.0.3; python_version <= "3.10"
|
||||||
cryptography==42.0.2
|
cryptography==42.0.2
|
||||||
voluptuous==0.14.1
|
voluptuous==0.14.2
|
||||||
PyYAML==6.0.1
|
PyYAML==6.0.1
|
||||||
paho-mqtt==1.6.1
|
paho-mqtt==1.6.1
|
||||||
colorama==0.4.6
|
colorama==0.4.6
|
||||||
@ -13,7 +13,7 @@ platformio==6.1.13 # When updating platformio, also update Dockerfile
|
|||||||
esptool==4.7.0
|
esptool==4.7.0
|
||||||
click==8.1.7
|
click==8.1.7
|
||||||
esphome-dashboard==20231107.0
|
esphome-dashboard==20231107.0
|
||||||
aioesphomeapi==21.0.2
|
aioesphomeapi==22.0.0
|
||||||
zeroconf==0.131.0
|
zeroconf==0.131.0
|
||||||
python-magic==0.4.27
|
python-magic==0.4.27
|
||||||
|
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
pylint==3.0.3
|
pylint==3.0.3
|
||||||
flake8==7.0.0 # also change in .pre-commit-config.yaml when updating
|
flake8==7.0.0 # also change in .pre-commit-config.yaml when updating
|
||||||
black==23.12.1 # also change in .pre-commit-config.yaml when updating
|
black==24.2.0 # also change in .pre-commit-config.yaml when updating
|
||||||
pyupgrade==3.15.0 # also change in .pre-commit-config.yaml when updating
|
pyupgrade==3.15.1 # also change in .pre-commit-config.yaml when updating
|
||||||
pre-commit
|
pre-commit
|
||||||
|
|
||||||
# Unit tests
|
# Unit tests
|
||||||
pytest==7.4.4
|
pytest==8.0.1
|
||||||
pytest-cov==4.1.0
|
pytest-cov==4.1.0
|
||||||
pytest-mock==3.12.0
|
pytest-mock==3.12.0
|
||||||
pytest-asyncio==0.23.3
|
pytest-asyncio==0.23.5
|
||||||
asyncmock==0.4.2
|
asyncmock==0.4.2
|
||||||
hypothesis==6.92.1
|
hypothesis==6.92.1
|
||||||
|
@ -849,9 +849,11 @@ def convert(schema, config_var, path):
|
|||||||
|
|
||||||
config_var["id_type"] = {
|
config_var["id_type"] = {
|
||||||
"class": str(data.base),
|
"class": str(data.base),
|
||||||
"parents": [str(x.base) for x in parents]
|
"parents": (
|
||||||
|
[str(x.base) for x in parents]
|
||||||
if isinstance(parents, list)
|
if isinstance(parents, list)
|
||||||
else None,
|
else None
|
||||||
|
),
|
||||||
}
|
}
|
||||||
elif schema_type == "use_id":
|
elif schema_type == "use_id":
|
||||||
if inspect.ismodule(data):
|
if inspect.ismodule(data):
|
||||||
|
@ -3,6 +3,9 @@
|
|||||||
set -e
|
set -e
|
||||||
# set -x
|
# set -x
|
||||||
|
|
||||||
|
apt update
|
||||||
|
apt-get install avahi-utils -y
|
||||||
|
|
||||||
mkdir -p config
|
mkdir -p config
|
||||||
script/setup
|
script/setup
|
||||||
|
|
||||||
|
56
tests/components/ade7880/common.yaml
Normal file
56
tests/components/ade7880/common.yaml
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
i2c:
|
||||||
|
- id: i2c_ade7880
|
||||||
|
scl: ${scl_pin}
|
||||||
|
sda: ${sda_pin}
|
||||||
|
|
||||||
|
sensor:
|
||||||
|
- platform: ade7880
|
||||||
|
i2c_id: i2c_ade7880
|
||||||
|
irq0_pin: ${irq0_pin}
|
||||||
|
irq1_pin: ${irq1_pin}
|
||||||
|
reset_pin: ${reset_pin}
|
||||||
|
frequency: 60Hz
|
||||||
|
phase_a:
|
||||||
|
name: Channel A
|
||||||
|
voltage: Voltage
|
||||||
|
current: Current
|
||||||
|
active_power: Active Power
|
||||||
|
power_factor: Power Factor
|
||||||
|
forward_active_energy: Forward Active Energy
|
||||||
|
reverse_active_energy: Reverse Active Energy
|
||||||
|
calibration:
|
||||||
|
current_gain: 3116628
|
||||||
|
voltage_gain: -757178
|
||||||
|
power_gain: -1344457
|
||||||
|
phase_angle: 188
|
||||||
|
phase_b:
|
||||||
|
name: Channel B
|
||||||
|
voltage: Voltage
|
||||||
|
current: Current
|
||||||
|
active_power: Active Power
|
||||||
|
power_factor: Power Factor
|
||||||
|
forward_active_energy: Forward Active Energy
|
||||||
|
reverse_active_energy: Reverse Active Energy
|
||||||
|
calibration:
|
||||||
|
current_gain: 3133655
|
||||||
|
voltage_gain: -755235
|
||||||
|
power_gain: -1345638
|
||||||
|
phase_angle: 188
|
||||||
|
phase_c:
|
||||||
|
name: Channel C
|
||||||
|
voltage: Voltage
|
||||||
|
current: Current
|
||||||
|
active_power: Active Power
|
||||||
|
power_factor: Power Factor
|
||||||
|
forward_active_energy: Forward Active Energy
|
||||||
|
reverse_active_energy: Reverse Active Energy
|
||||||
|
calibration:
|
||||||
|
current_gain: 3111158
|
||||||
|
voltage_gain: -743813
|
||||||
|
power_gain: -1351437
|
||||||
|
phase_angle: 180
|
||||||
|
neutral:
|
||||||
|
name: Neutral
|
||||||
|
current: Current
|
||||||
|
calibration:
|
||||||
|
current_gain: 3189
|
8
tests/components/ade7880/test.esp32-c3-idf.yaml
Normal file
8
tests/components/ade7880/test.esp32-c3-idf.yaml
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
substitutions:
|
||||||
|
scl_pin: GPIO5
|
||||||
|
sda_pin: GPIO4
|
||||||
|
irq0_pin: GPIO6
|
||||||
|
irq1_pin: GPIO7
|
||||||
|
reset_pin: GPIO10
|
||||||
|
|
||||||
|
<<: !include common.yaml
|
8
tests/components/ade7880/test.esp32-c3.yaml
Normal file
8
tests/components/ade7880/test.esp32-c3.yaml
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
substitutions:
|
||||||
|
scl_pin: GPIO5
|
||||||
|
sda_pin: GPIO4
|
||||||
|
irq0_pin: GPIO6
|
||||||
|
irq1_pin: GPIO7
|
||||||
|
reset_pin: GPIO10
|
||||||
|
|
||||||
|
<<: !include common.yaml
|
8
tests/components/ade7880/test.esp32-idf.yaml
Normal file
8
tests/components/ade7880/test.esp32-idf.yaml
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
substitutions:
|
||||||
|
scl_pin: GPIO5
|
||||||
|
sda_pin: GPIO4
|
||||||
|
irq0_pin: GPIO13
|
||||||
|
irq1_pin: GPIO15
|
||||||
|
reset_pin: GPIO16
|
||||||
|
|
||||||
|
<<: !include common.yaml
|
8
tests/components/ade7880/test.esp32.yaml
Normal file
8
tests/components/ade7880/test.esp32.yaml
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
substitutions:
|
||||||
|
scl_pin: GPIO5
|
||||||
|
sda_pin: GPIO4
|
||||||
|
irq0_pin: GPIO13
|
||||||
|
irq1_pin: GPIO15
|
||||||
|
reset_pin: GPIO16
|
||||||
|
|
||||||
|
<<: !include common.yaml
|
8
tests/components/ade7880/test.esp8266.yaml
Normal file
8
tests/components/ade7880/test.esp8266.yaml
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
substitutions:
|
||||||
|
scl_pin: GPIO5
|
||||||
|
sda_pin: GPIO4
|
||||||
|
irq0_pin: GPIO13
|
||||||
|
irq1_pin: GPIO15
|
||||||
|
reset_pin: GPIO16
|
||||||
|
|
||||||
|
<<: !include common.yaml
|
8
tests/components/ade7880/test.rp2040.yaml
Normal file
8
tests/components/ade7880/test.rp2040.yaml
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
substitutions:
|
||||||
|
scl_pin: GPIO5
|
||||||
|
sda_pin: GPIO4
|
||||||
|
irq0_pin: GPIO13
|
||||||
|
irq1_pin: GPIO15
|
||||||
|
reset_pin: GPIO16
|
||||||
|
|
||||||
|
<<: !include common.yaml
|
@ -20,4 +20,4 @@ animation:
|
|||||||
- id: rgb565_animation
|
- id: rgb565_animation
|
||||||
file: ../../pnglogo.png
|
file: ../../pnglogo.png
|
||||||
type: RGB565
|
type: RGB565
|
||||||
use_transparency: no
|
use_transparency: false
|
||||||
|
@ -20,4 +20,4 @@ animation:
|
|||||||
- id: rgb565_animation
|
- id: rgb565_animation
|
||||||
file: ../../pnglogo.png
|
file: ../../pnglogo.png
|
||||||
type: RGB565
|
type: RGB565
|
||||||
use_transparency: no
|
use_transparency: false
|
||||||
|
@ -20,4 +20,4 @@ animation:
|
|||||||
- id: rgb565_animation
|
- id: rgb565_animation
|
||||||
file: ../../pnglogo.png
|
file: ../../pnglogo.png
|
||||||
type: RGB565
|
type: RGB565
|
||||||
use_transparency: no
|
use_transparency: false
|
||||||
|
@ -20,4 +20,4 @@ animation:
|
|||||||
- id: rgb565_animation
|
- id: rgb565_animation
|
||||||
file: ../../pnglogo.png
|
file: ../../pnglogo.png
|
||||||
type: RGB565
|
type: RGB565
|
||||||
use_transparency: no
|
use_transparency: false
|
||||||
|
@ -20,4 +20,4 @@ animation:
|
|||||||
- id: rgb565_animation
|
- id: rgb565_animation
|
||||||
file: ../../pnglogo.png
|
file: ../../pnglogo.png
|
||||||
type: RGB565
|
type: RGB565
|
||||||
use_transparency: no
|
use_transparency: false
|
||||||
|
@ -20,4 +20,4 @@ animation:
|
|||||||
- id: rgb565_animation
|
- id: rgb565_animation
|
||||||
file: ../../pnglogo.png
|
file: ../../pnglogo.png
|
||||||
type: RGB565
|
type: RGB565
|
||||||
use_transparency: no
|
use_transparency: false
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user