mirror of
https://github.com/esphome/esphome.git
synced 2025-04-11 05:10:34 +01:00
Merge remote-tracking branch 'origin/dev' into nrf52
This commit is contained in:
commit
ac967c5d2e
@ -7,8 +7,21 @@
|
||||
"PIP_BREAK_SYSTEM_PACKAGES": "1",
|
||||
"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,
|
||||
// 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": {
|
||||
"vscode": {
|
||||
"extensions": [
|
||||
|
3
.github/workflows/ci.yml
vendored
3
.github/workflows/ci.yml
vendored
@ -11,6 +11,7 @@ on:
|
||||
- "**"
|
||||
- "!.github/workflows/*.yml"
|
||||
- ".github/workflows/ci.yml"
|
||||
- "!.yamllint"
|
||||
merge_group:
|
||||
|
||||
permissions:
|
||||
@ -218,7 +219,7 @@ jobs:
|
||||
. venv/bin/activate
|
||||
pytest -vv --cov-report=xml --tb=native tests
|
||||
- name: Upload coverage to Codecov
|
||||
uses: codecov/codecov-action@v3
|
||||
uses: codecov/codecov-action@v4
|
||||
with:
|
||||
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
|
||||
|
||||
# yamllint disable-line rule:truthy
|
||||
on:
|
||||
pull_request:
|
||||
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
|
||||
|
||||
# yamllint disable-line rule:truthy
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
@ -36,7 +37,7 @@ jobs:
|
||||
python ./script/sync-device_class.py
|
||||
|
||||
- name: Commit changes
|
||||
uses: peter-evans/create-pull-request@v5.0.2
|
||||
uses: peter-evans/create-pull-request@v6.0.1
|
||||
with:
|
||||
commit-message: "Synchronise Device Classes from Home Assistant"
|
||||
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
|
||||
|
||||
# yamllint disable-line rule:truthy
|
||||
on:
|
||||
push:
|
||||
branches: [dev, beta, release]
|
||||
@ -19,4 +21,6 @@ jobs:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.1.1
|
||||
- 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
|
||||
repos:
|
||||
- repo: https://github.com/psf/black-pre-commit-mirror
|
||||
rev: 23.12.1
|
||||
rev: 24.2.0
|
||||
hooks:
|
||||
- id: black
|
||||
args:
|
||||
@ -27,7 +27,7 @@ repos:
|
||||
- --branch=release
|
||||
- --branch=beta
|
||||
- repo: https://github.com/asottile/pyupgrade
|
||||
rev: v3.15.0
|
||||
rev: v3.15.1
|
||||
hooks:
|
||||
- id: pyupgrade
|
||||
args: [--py39-plus]
|
||||
|
19
.yamllint
19
.yamllint
@ -1,3 +1,18 @@
|
||||
---
|
||||
ignore: |
|
||||
venv/
|
||||
extends: default
|
||||
|
||||
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/adc128s102/* @DeerMaximum
|
||||
esphome/components/addressable_light/* @justfalter
|
||||
esphome/components/ade7880/* @kpfleming
|
||||
esphome/components/ade7953/* @angelnu
|
||||
esphome/components/ade7953_i2c/* @angelnu
|
||||
esphome/components/ade7953_spi/* @angelnu
|
||||
@ -155,6 +156,7 @@ esphome/components/iaqcore/* @yozik04
|
||||
esphome/components/ili9xxx/* @clydebarrow @nielsnl68
|
||||
esphome/components/improv_base/* @esphome/core
|
||||
esphome/components/improv_serial/* @esphome/core
|
||||
esphome/components/ina226/* @Sergio303 @latonita
|
||||
esphome/components/ina260/* @mreditor97
|
||||
esphome/components/inkbird_ibsth1_mini/* @fkirill
|
||||
esphome/components/inkplate6/* @jesserockz
|
||||
@ -199,6 +201,7 @@ esphome/components/mcp9808/* @k7hpn
|
||||
esphome/components/md5/* @esphome/core
|
||||
esphome/components/mdns/* @esphome/core
|
||||
esphome/components/media_player/* @jesserockz
|
||||
esphome/components/micro_wake_word/* @jesserockz @kahrendt
|
||||
esphome/components/micronova/* @jorre05
|
||||
esphome/components/microphone/* @jesserockz
|
||||
esphome/components/mics_4514/* @jesserockz
|
||||
@ -222,6 +225,7 @@ esphome/components/mopeka_pro_check/* @spbrogan
|
||||
esphome/components/mopeka_std_check/* @Fabian-Schmidt
|
||||
esphome/components/mpl3115a2/* @kbickar
|
||||
esphome/components/mpu6886/* @fabaff
|
||||
esphome/components/ms8607/* @e28eta
|
||||
esphome/components/network/* @esphome/core
|
||||
esphome/components/nextion/* @senexcrenshaw
|
||||
esphome/components/nextion/binary_sensor/* @senexcrenshaw
|
||||
@ -362,6 +366,7 @@ esphome/components/uart/button/* @ssieb
|
||||
esphome/components/ufire_ec/* @pvizeli
|
||||
esphome/components/ufire_ise/* @pvizeli
|
||||
esphome/components/ultrasonic/* @OttoWinter
|
||||
esphome/components/uponor_smatrix/* @kroimon
|
||||
esphome/components/vbus/* @ssieb
|
||||
esphome/components/veml3235/* @kbx81
|
||||
esphome/components/version/* @esphome/core
|
||||
|
@ -35,7 +35,7 @@ RUN \
|
||||
iputils-ping=3:20221126-1 \
|
||||
git=1:2.39.2-1.1 \
|
||||
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 \
|
||||
libcairo2=1.16.0-7 \
|
||||
libmagic1=1:5.44-3 \
|
||||
|
@ -21,4 +21,10 @@ export PLATFORMIO_PLATFORMS_DIR="${pio_cache_base}/platforms"
|
||||
export PLATFORMIO_PACKAGES_DIR="${pio_cache_base}/packages"
|
||||
export PLATFORMIO_CACHE_DIR="${pio_cache_base}/cache"
|
||||
|
||||
# If /build is mounted, use that as the build path
|
||||
# otherwise use path in /config (so that builds aren't lost on container restart)
|
||||
if [[ -d /build ]]; then
|
||||
export ESPHOME_BUILD_PATH=/build
|
||||
fi
|
||||
|
||||
exec esphome "$@"
|
||||
|
1
esphome/components/ade7880/__init__.py
Normal file
1
esphome/components/ade7880/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
CODEOWNERS = ["@kpfleming"]
|
302
esphome/components/ade7880/ade7880.cpp
Normal file
302
esphome/components/ade7880/ade7880.cpp
Normal file
@ -0,0 +1,302 @@
|
||||
// This component was developed using knowledge gathered by a number
|
||||
// of people who reverse-engineered the Shelly 3EM:
|
||||
//
|
||||
// @AndreKR on GitHub
|
||||
// Axel (@Axel830 on GitHub)
|
||||
// Marko (@goodkiller on GitHub)
|
||||
// Michaël Piron (@michaelpiron on GitHub)
|
||||
// Theo Arends (@arendst on GitHub)
|
||||
|
||||
#include "ade7880.h"
|
||||
#include "ade7880_registers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ade7880 {
|
||||
|
||||
static const char *const TAG = "ade7880";
|
||||
|
||||
void IRAM_ATTR ADE7880Store::gpio_intr(ADE7880Store *arg) { arg->reset_done = true; }
|
||||
|
||||
void ADE7880::setup() {
|
||||
if (this->irq0_pin_ != nullptr) {
|
||||
this->irq0_pin_->setup();
|
||||
}
|
||||
this->irq1_pin_->setup();
|
||||
if (this->reset_pin_ != nullptr) {
|
||||
this->reset_pin_->setup();
|
||||
}
|
||||
this->store_.irq1_pin = this->irq1_pin_->to_isr();
|
||||
this->irq1_pin_->attach_interrupt(ADE7880Store::gpio_intr, &this->store_, gpio::INTERRUPT_FALLING_EDGE);
|
||||
|
||||
// if IRQ1 is already asserted, the cause must be determined
|
||||
if (this->irq1_pin_->digital_read() == 0) {
|
||||
ESP_LOGD(TAG, "IRQ1 found asserted during setup()");
|
||||
auto status1 = read_u32_register16_(STATUS1);
|
||||
if ((status1 & ~STATUS1_RSTDONE) != 0) {
|
||||
// not safe to proceed, must initiate reset
|
||||
ESP_LOGD(TAG, "IRQ1 asserted for !RSTDONE, resetting device");
|
||||
this->reset_device_();
|
||||
return;
|
||||
}
|
||||
if ((status1 & STATUS1_RSTDONE) == STATUS1_RSTDONE) {
|
||||
// safe to proceed, device has just completed reset cycle
|
||||
ESP_LOGD(TAG, "Acknowledging RSTDONE");
|
||||
this->write_u32_register16_(STATUS0, 0xFFFF);
|
||||
this->write_u32_register16_(STATUS1, 0xFFFF);
|
||||
this->init_device_();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this->reset_device_();
|
||||
}
|
||||
|
||||
void ADE7880::loop() {
|
||||
// check for completion of a reset cycle
|
||||
if (!this->store_.reset_done) {
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, "Acknowledging RSTDONE");
|
||||
this->write_u32_register16_(STATUS0, 0xFFFF);
|
||||
this->write_u32_register16_(STATUS1, 0xFFFF);
|
||||
this->init_device_();
|
||||
this->store_.reset_done = false;
|
||||
this->store_.reset_pending = false;
|
||||
}
|
||||
|
||||
template<typename F>
|
||||
void ADE7880::update_sensor_from_s24zp_register16_(sensor::Sensor *sensor, uint16_t a_register, F &&f) {
|
||||
if (sensor == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
float val = this->read_s24zp_register16_(a_register);
|
||||
sensor->publish_state(f(val));
|
||||
}
|
||||
|
||||
template<typename F>
|
||||
void ADE7880::update_sensor_from_s16_register16_(sensor::Sensor *sensor, uint16_t a_register, F &&f) {
|
||||
if (sensor == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
float val = this->read_s16_register16_(a_register);
|
||||
sensor->publish_state(f(val));
|
||||
}
|
||||
|
||||
template<typename F>
|
||||
void ADE7880::update_sensor_from_s32_register16_(sensor::Sensor *sensor, uint16_t a_register, F &&f) {
|
||||
if (sensor == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
float val = this->read_s32_register16_(a_register);
|
||||
sensor->publish_state(f(val));
|
||||
}
|
||||
|
||||
void ADE7880::update() {
|
||||
if (this->store_.reset_pending) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto start = millis();
|
||||
|
||||
if (this->channel_n_ != nullptr) {
|
||||
auto *chan = this->channel_n_;
|
||||
this->update_sensor_from_s24zp_register16_(chan->current, NIRMS, [](float val) { return val / 100000.0f; });
|
||||
}
|
||||
|
||||
if (this->channel_a_ != nullptr) {
|
||||
auto *chan = this->channel_a_;
|
||||
this->update_sensor_from_s24zp_register16_(chan->current, AIRMS, [](float val) { return val / 100000.0f; });
|
||||
this->update_sensor_from_s24zp_register16_(chan->voltage, BVRMS, [](float val) { return val / 10000.0f; });
|
||||
this->update_sensor_from_s24zp_register16_(chan->active_power, AWATT, [](float val) { return val / 100.0f; });
|
||||
this->update_sensor_from_s24zp_register16_(chan->apparent_power, AVA, [](float val) { return val / 100.0f; });
|
||||
this->update_sensor_from_s16_register16_(chan->power_factor, APF,
|
||||
[](float val) { return std::abs(val / -327.68f); });
|
||||
this->update_sensor_from_s32_register16_(chan->forward_active_energy, AFWATTHR, [&chan](float val) {
|
||||
return chan->forward_active_energy_total += val / 14400.0f;
|
||||
});
|
||||
this->update_sensor_from_s32_register16_(chan->reverse_active_energy, AFWATTHR, [&chan](float val) {
|
||||
return chan->reverse_active_energy_total += val / 14400.0f;
|
||||
});
|
||||
}
|
||||
|
||||
if (this->channel_b_ != nullptr) {
|
||||
auto *chan = this->channel_b_;
|
||||
this->update_sensor_from_s24zp_register16_(chan->current, BIRMS, [](float val) { return val / 100000.0f; });
|
||||
this->update_sensor_from_s24zp_register16_(chan->voltage, BVRMS, [](float val) { return val / 10000.0f; });
|
||||
this->update_sensor_from_s24zp_register16_(chan->active_power, BWATT, [](float val) { return val / 100.0f; });
|
||||
this->update_sensor_from_s24zp_register16_(chan->apparent_power, BVA, [](float val) { return val / 100.0f; });
|
||||
this->update_sensor_from_s16_register16_(chan->power_factor, BPF,
|
||||
[](float val) { return std::abs(val / -327.68f); });
|
||||
this->update_sensor_from_s32_register16_(chan->forward_active_energy, BFWATTHR, [&chan](float val) {
|
||||
return chan->forward_active_energy_total += val / 14400.0f;
|
||||
});
|
||||
this->update_sensor_from_s32_register16_(chan->reverse_active_energy, BFWATTHR, [&chan](float val) {
|
||||
return chan->reverse_active_energy_total += val / 14400.0f;
|
||||
});
|
||||
}
|
||||
|
||||
if (this->channel_c_ != nullptr) {
|
||||
auto *chan = this->channel_c_;
|
||||
this->update_sensor_from_s24zp_register16_(chan->current, CIRMS, [](float val) { return val / 100000.0f; });
|
||||
this->update_sensor_from_s24zp_register16_(chan->voltage, CVRMS, [](float val) { return val / 10000.0f; });
|
||||
this->update_sensor_from_s24zp_register16_(chan->active_power, CWATT, [](float val) { return val / 100.0f; });
|
||||
this->update_sensor_from_s24zp_register16_(chan->apparent_power, CVA, [](float val) { return val / 100.0f; });
|
||||
this->update_sensor_from_s16_register16_(chan->power_factor, CPF,
|
||||
[](float val) { return std::abs(val / -327.68f); });
|
||||
this->update_sensor_from_s32_register16_(chan->forward_active_energy, CFWATTHR, [&chan](float val) {
|
||||
return chan->forward_active_energy_total += val / 14400.0f;
|
||||
});
|
||||
this->update_sensor_from_s32_register16_(chan->reverse_active_energy, CFWATTHR, [&chan](float val) {
|
||||
return chan->reverse_active_energy_total += val / 14400.0f;
|
||||
});
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, "update took %u ms", millis() - start);
|
||||
}
|
||||
|
||||
void ADE7880::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "ADE7880:");
|
||||
LOG_PIN(" IRQ0 Pin: ", this->irq0_pin_);
|
||||
LOG_PIN(" IRQ1 Pin: ", this->irq1_pin_);
|
||||
LOG_PIN(" RESET Pin: ", this->reset_pin_);
|
||||
ESP_LOGCONFIG(TAG, " Frequency: %.0f Hz", this->frequency_);
|
||||
|
||||
if (this->channel_a_ != nullptr) {
|
||||
ESP_LOGCONFIG(TAG, " Phase A:");
|
||||
LOG_SENSOR(" ", "Current", this->channel_a_->current);
|
||||
LOG_SENSOR(" ", "Voltage", this->channel_a_->voltage);
|
||||
LOG_SENSOR(" ", "Active Power", this->channel_a_->active_power);
|
||||
LOG_SENSOR(" ", "Apparent Power", this->channel_a_->apparent_power);
|
||||
LOG_SENSOR(" ", "Power Factor", this->channel_a_->power_factor);
|
||||
LOG_SENSOR(" ", "Forward Active Energy", this->channel_a_->forward_active_energy);
|
||||
LOG_SENSOR(" ", "Reverse Active Energy", this->channel_a_->reverse_active_energy);
|
||||
ESP_LOGCONFIG(TAG, " Calibration:");
|
||||
ESP_LOGCONFIG(TAG, " Current: %u", this->channel_a_->current_gain_calibration);
|
||||
ESP_LOGCONFIG(TAG, " Voltage: %d", this->channel_a_->voltage_gain_calibration);
|
||||
ESP_LOGCONFIG(TAG, " Power: %d", this->channel_a_->power_gain_calibration);
|
||||
ESP_LOGCONFIG(TAG, " Phase Angle: %u", this->channel_a_->phase_angle_calibration);
|
||||
}
|
||||
|
||||
if (this->channel_b_ != nullptr) {
|
||||
ESP_LOGCONFIG(TAG, " Phase B:");
|
||||
LOG_SENSOR(" ", "Current", this->channel_b_->current);
|
||||
LOG_SENSOR(" ", "Voltage", this->channel_b_->voltage);
|
||||
LOG_SENSOR(" ", "Active Power", this->channel_b_->active_power);
|
||||
LOG_SENSOR(" ", "Apparent Power", this->channel_b_->apparent_power);
|
||||
LOG_SENSOR(" ", "Power Factor", this->channel_b_->power_factor);
|
||||
LOG_SENSOR(" ", "Forward Active Energy", this->channel_b_->forward_active_energy);
|
||||
LOG_SENSOR(" ", "Reverse Active Energy", this->channel_b_->reverse_active_energy);
|
||||
ESP_LOGCONFIG(TAG, " Calibration:");
|
||||
ESP_LOGCONFIG(TAG, " Current: %u", this->channel_b_->current_gain_calibration);
|
||||
ESP_LOGCONFIG(TAG, " Voltage: %d", this->channel_b_->voltage_gain_calibration);
|
||||
ESP_LOGCONFIG(TAG, " Power: %d", this->channel_b_->power_gain_calibration);
|
||||
ESP_LOGCONFIG(TAG, " Phase Angle: %u", this->channel_b_->phase_angle_calibration);
|
||||
}
|
||||
|
||||
if (this->channel_c_ != nullptr) {
|
||||
ESP_LOGCONFIG(TAG, " Phase C:");
|
||||
LOG_SENSOR(" ", "Current", this->channel_c_->current);
|
||||
LOG_SENSOR(" ", "Voltage", this->channel_c_->voltage);
|
||||
LOG_SENSOR(" ", "Active Power", this->channel_c_->active_power);
|
||||
LOG_SENSOR(" ", "Apparent Power", this->channel_c_->apparent_power);
|
||||
LOG_SENSOR(" ", "Power Factor", this->channel_c_->power_factor);
|
||||
LOG_SENSOR(" ", "Forward Active Energy", this->channel_c_->forward_active_energy);
|
||||
LOG_SENSOR(" ", "Reverse Active Energy", this->channel_c_->reverse_active_energy);
|
||||
ESP_LOGCONFIG(TAG, " Calibration:");
|
||||
ESP_LOGCONFIG(TAG, " Current: %u", this->channel_c_->current_gain_calibration);
|
||||
ESP_LOGCONFIG(TAG, " Voltage: %d", this->channel_c_->voltage_gain_calibration);
|
||||
ESP_LOGCONFIG(TAG, " Power: %d", this->channel_c_->power_gain_calibration);
|
||||
ESP_LOGCONFIG(TAG, " Phase Angle: %u", this->channel_c_->phase_angle_calibration);
|
||||
}
|
||||
|
||||
if (this->channel_n_ != nullptr) {
|
||||
ESP_LOGCONFIG(TAG, " Neutral:");
|
||||
LOG_SENSOR(" ", "Current", this->channel_n_->current);
|
||||
ESP_LOGCONFIG(TAG, " Calibration:");
|
||||
ESP_LOGCONFIG(TAG, " Current: %u", this->channel_n_->current_gain_calibration);
|
||||
}
|
||||
|
||||
LOG_I2C_DEVICE(this);
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
}
|
||||
|
||||
void ADE7880::calibrate_s10zp_reading_(uint16_t a_register, int16_t calibration) {
|
||||
if (calibration == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
this->write_s10zp_register16_(a_register, calibration);
|
||||
}
|
||||
|
||||
void ADE7880::calibrate_s24zpse_reading_(uint16_t a_register, int32_t calibration) {
|
||||
if (calibration == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
this->write_s24zpse_register16_(a_register, calibration);
|
||||
}
|
||||
|
||||
void ADE7880::init_device_() {
|
||||
this->write_u8_register16_(CONFIG2, CONFIG2_I2C_LOCK);
|
||||
|
||||
this->write_u16_register16_(GAIN, 0);
|
||||
|
||||
if (this->frequency_ > 55) {
|
||||
this->write_u16_register16_(COMPMODE, COMPMODE_DEFAULT | COMPMODE_SELFREQ);
|
||||
}
|
||||
|
||||
if (this->channel_n_ != nullptr) {
|
||||
this->calibrate_s24zpse_reading_(NIGAIN, this->channel_n_->current_gain_calibration);
|
||||
}
|
||||
|
||||
if (this->channel_a_ != nullptr) {
|
||||
this->calibrate_s24zpse_reading_(AIGAIN, this->channel_a_->current_gain_calibration);
|
||||
this->calibrate_s24zpse_reading_(AVGAIN, this->channel_a_->voltage_gain_calibration);
|
||||
this->calibrate_s24zpse_reading_(APGAIN, this->channel_a_->power_gain_calibration);
|
||||
this->calibrate_s10zp_reading_(APHCAL, this->channel_a_->phase_angle_calibration);
|
||||
}
|
||||
|
||||
if (this->channel_b_ != nullptr) {
|
||||
this->calibrate_s24zpse_reading_(BIGAIN, this->channel_b_->current_gain_calibration);
|
||||
this->calibrate_s24zpse_reading_(BVGAIN, this->channel_b_->voltage_gain_calibration);
|
||||
this->calibrate_s24zpse_reading_(BPGAIN, this->channel_b_->power_gain_calibration);
|
||||
this->calibrate_s10zp_reading_(BPHCAL, this->channel_b_->phase_angle_calibration);
|
||||
}
|
||||
|
||||
if (this->channel_c_ != nullptr) {
|
||||
this->calibrate_s24zpse_reading_(CIGAIN, this->channel_c_->current_gain_calibration);
|
||||
this->calibrate_s24zpse_reading_(CVGAIN, this->channel_c_->voltage_gain_calibration);
|
||||
this->calibrate_s24zpse_reading_(CPGAIN, this->channel_c_->power_gain_calibration);
|
||||
this->calibrate_s10zp_reading_(CPHCAL, this->channel_c_->phase_angle_calibration);
|
||||
}
|
||||
|
||||
// write three default values to data memory RAM to flush the I2C write queue
|
||||
this->write_s32_register16_(VLEVEL, 0);
|
||||
this->write_s32_register16_(VLEVEL, 0);
|
||||
this->write_s32_register16_(VLEVEL, 0);
|
||||
|
||||
this->write_u8_register16_(DSPWP_SEL, DSPWP_SEL_SET);
|
||||
this->write_u8_register16_(DSPWP_SET, DSPWP_SET_RO);
|
||||
this->write_u16_register16_(RUN, RUN_ENABLE);
|
||||
}
|
||||
|
||||
void ADE7880::reset_device_() {
|
||||
if (this->reset_pin_ != nullptr) {
|
||||
ESP_LOGD(TAG, "Reset device using RESET pin");
|
||||
this->reset_pin_->digital_write(false);
|
||||
delay(1);
|
||||
this->reset_pin_->digital_write(true);
|
||||
} else {
|
||||
ESP_LOGD(TAG, "Reset device using SWRST command");
|
||||
this->write_u16_register16_(CONFIG, CONFIG_SWRST);
|
||||
}
|
||||
this->store_.reset_pending = true;
|
||||
}
|
||||
|
||||
} // namespace ade7880
|
||||
} // namespace esphome
|
131
esphome/components/ade7880/ade7880.h
Normal file
131
esphome/components/ade7880/ade7880.h
Normal file
@ -0,0 +1,131 @@
|
||||
#pragma once
|
||||
|
||||
// This component was developed using knowledge gathered by a number
|
||||
// of people who reverse-engineered the Shelly 3EM:
|
||||
//
|
||||
// @AndreKR on GitHub
|
||||
// Axel (@Axel830 on GitHub)
|
||||
// Marko (@goodkiller on GitHub)
|
||||
// Michaël Piron (@michaelpiron on GitHub)
|
||||
// Theo Arends (@arendst on GitHub)
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
|
||||
#include "ade7880_registers.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ade7880 {
|
||||
|
||||
struct NeutralChannel {
|
||||
void set_current(sensor::Sensor *sens) { this->current = sens; }
|
||||
|
||||
void set_current_gain_calibration(int32_t val) { this->current_gain_calibration = val; }
|
||||
|
||||
sensor::Sensor *current{nullptr};
|
||||
int32_t current_gain_calibration{0};
|
||||
};
|
||||
|
||||
struct PowerChannel {
|
||||
void set_current(sensor::Sensor *sens) { this->current = sens; }
|
||||
void set_voltage(sensor::Sensor *sens) { this->voltage = sens; }
|
||||
void set_active_power(sensor::Sensor *sens) { this->active_power = sens; }
|
||||
void set_apparent_power(sensor::Sensor *sens) { this->apparent_power = sens; }
|
||||
void set_power_factor(sensor::Sensor *sens) { this->power_factor = sens; }
|
||||
void set_forward_active_energy(sensor::Sensor *sens) { this->forward_active_energy = sens; }
|
||||
void set_reverse_active_energy(sensor::Sensor *sens) { this->reverse_active_energy = sens; }
|
||||
|
||||
void set_current_gain_calibration(int32_t val) { this->current_gain_calibration = val; }
|
||||
void set_voltage_gain_calibration(int32_t val) { this->voltage_gain_calibration = val; }
|
||||
void set_power_gain_calibration(int32_t val) { this->power_gain_calibration = val; }
|
||||
void set_phase_angle_calibration(int32_t val) { this->phase_angle_calibration = val; }
|
||||
|
||||
sensor::Sensor *current{nullptr};
|
||||
sensor::Sensor *voltage{nullptr};
|
||||
sensor::Sensor *active_power{nullptr};
|
||||
sensor::Sensor *apparent_power{nullptr};
|
||||
sensor::Sensor *power_factor{nullptr};
|
||||
sensor::Sensor *forward_active_energy{nullptr};
|
||||
sensor::Sensor *reverse_active_energy{nullptr};
|
||||
int32_t current_gain_calibration{0};
|
||||
int32_t voltage_gain_calibration{0};
|
||||
int32_t power_gain_calibration{0};
|
||||
uint16_t phase_angle_calibration{0};
|
||||
float forward_active_energy_total{0};
|
||||
float reverse_active_energy_total{0};
|
||||
};
|
||||
|
||||
// Store data in a class that doesn't use multiple-inheritance (no vtables in flash!)
|
||||
struct ADE7880Store {
|
||||
volatile bool reset_done{false};
|
||||
bool reset_pending{false};
|
||||
ISRInternalGPIOPin irq1_pin;
|
||||
|
||||
static void gpio_intr(ADE7880Store *arg);
|
||||
};
|
||||
|
||||
class ADE7880 : public i2c::I2CDevice, public PollingComponent {
|
||||
public:
|
||||
void set_irq0_pin(InternalGPIOPin *pin) { this->irq0_pin_ = pin; }
|
||||
void set_irq1_pin(InternalGPIOPin *pin) { this->irq1_pin_ = pin; }
|
||||
void set_reset_pin(InternalGPIOPin *pin) { this->reset_pin_ = pin; }
|
||||
void set_frequency(float frequency) { this->frequency_ = frequency; }
|
||||
void set_channel_n(NeutralChannel *channel) { this->channel_n_ = channel; }
|
||||
void set_channel_a(PowerChannel *channel) { this->channel_a_ = channel; }
|
||||
void set_channel_b(PowerChannel *channel) { this->channel_b_ = channel; }
|
||||
void set_channel_c(PowerChannel *channel) { this->channel_c_ = channel; }
|
||||
|
||||
void setup() override;
|
||||
|
||||
void loop() override;
|
||||
|
||||
void update() override;
|
||||
|
||||
void dump_config() override;
|
||||
|
||||
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||
|
||||
protected:
|
||||
ADE7880Store store_{};
|
||||
InternalGPIOPin *irq0_pin_{nullptr};
|
||||
InternalGPIOPin *irq1_pin_{nullptr};
|
||||
InternalGPIOPin *reset_pin_{nullptr};
|
||||
float frequency_;
|
||||
NeutralChannel *channel_n_{nullptr};
|
||||
PowerChannel *channel_a_{nullptr};
|
||||
PowerChannel *channel_b_{nullptr};
|
||||
PowerChannel *channel_c_{nullptr};
|
||||
|
||||
void calibrate_s10zp_reading_(uint16_t a_register, int16_t calibration);
|
||||
void calibrate_s24zpse_reading_(uint16_t a_register, int32_t calibration);
|
||||
|
||||
void init_device_();
|
||||
|
||||
// each of these functions allow the caller to pass in a lambda (or any other callable)
|
||||
// which modifies the value read from the register before it is passed to the sensor
|
||||
// the callable will be passed a 'float' value and is expected to return a 'float'
|
||||
template<typename F> void update_sensor_from_s24zp_register16_(sensor::Sensor *sensor, uint16_t a_register, F &&f);
|
||||
template<typename F> void update_sensor_from_s16_register16_(sensor::Sensor *sensor, uint16_t a_register, F &&f);
|
||||
template<typename F> void update_sensor_from_s32_register16_(sensor::Sensor *sensor, uint16_t a_register, F &&f);
|
||||
|
||||
void reset_device_();
|
||||
|
||||
uint8_t read_u8_register16_(uint16_t a_register);
|
||||
int16_t read_s16_register16_(uint16_t a_register);
|
||||
uint16_t read_u16_register16_(uint16_t a_register);
|
||||
int32_t read_s24zp_register16_(uint16_t a_register);
|
||||
int32_t read_s32_register16_(uint16_t a_register);
|
||||
uint32_t read_u32_register16_(uint16_t a_register);
|
||||
|
||||
void write_u8_register16_(uint16_t a_register, uint8_t value);
|
||||
void write_s10zp_register16_(uint16_t a_register, int16_t value);
|
||||
void write_u16_register16_(uint16_t a_register, uint16_t value);
|
||||
void write_s24zpse_register16_(uint16_t a_register, int32_t value);
|
||||
void write_s32_register16_(uint16_t a_register, int32_t value);
|
||||
void write_u32_register16_(uint16_t a_register, uint32_t value);
|
||||
};
|
||||
|
||||
} // namespace ade7880
|
||||
} // namespace esphome
|
101
esphome/components/ade7880/ade7880_i2c.cpp
Normal file
101
esphome/components/ade7880/ade7880_i2c.cpp
Normal file
@ -0,0 +1,101 @@
|
||||
// This component was developed using knowledge gathered by a number
|
||||
// of people who reverse-engineered the Shelly 3EM:
|
||||
//
|
||||
// @AndreKR on GitHub
|
||||
// Axel (@Axel830 on GitHub)
|
||||
// Marko (@goodkiller on GitHub)
|
||||
// Michaël Piron (@michaelpiron on GitHub)
|
||||
// Theo Arends (@arendst on GitHub)
|
||||
|
||||
#include "ade7880.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ade7880 {
|
||||
|
||||
// adapted from https://stackoverflow.com/a/55912127/1886371
|
||||
template<size_t Bits, typename T> inline T sign_extend(const T &v) noexcept {
|
||||
using S = struct { signed Val : Bits; };
|
||||
return reinterpret_cast<const S *>(&v)->Val;
|
||||
}
|
||||
|
||||
// Register types
|
||||
// unsigned 8-bit (uint8_t)
|
||||
// signed 10-bit - 16-bit ZP on wire (int16_t, needs sign extension)
|
||||
// unsigned 16-bit (uint16_t)
|
||||
// unsigned 20-bit - 32-bit ZP on wire (uint32_t)
|
||||
// signed 24-bit - 32-bit ZPSE on wire (int32_t, needs sign extension)
|
||||
// signed 24-bit - 32-bit ZP on wire (int32_t, needs sign extension)
|
||||
// signed 24-bit - 32-bit SE on wire (int32_t)
|
||||
// signed 28-bit - 32-bit ZP on wire (int32_t, needs sign extension)
|
||||
// unsigned 32-bit (uint32_t)
|
||||
// signed 32-bit (int32_t)
|
||||
|
||||
uint8_t ADE7880::read_u8_register16_(uint16_t a_register) {
|
||||
uint8_t in;
|
||||
this->read_register16(a_register, &in, sizeof(in));
|
||||
return in;
|
||||
}
|
||||
|
||||
int16_t ADE7880::read_s16_register16_(uint16_t a_register) {
|
||||
int16_t in;
|
||||
this->read_register16(a_register, reinterpret_cast<uint8_t *>(&in), sizeof(in));
|
||||
return convert_big_endian(in);
|
||||
}
|
||||
|
||||
uint16_t ADE7880::read_u16_register16_(uint16_t a_register) {
|
||||
uint16_t in;
|
||||
this->read_register16(a_register, reinterpret_cast<uint8_t *>(&in), sizeof(in));
|
||||
return convert_big_endian(in);
|
||||
}
|
||||
|
||||
int32_t ADE7880::read_s24zp_register16_(uint16_t a_register) {
|
||||
// s24zp means 24 bit signed value in the lower 24 bits of a 32-bit register
|
||||
int32_t in;
|
||||
this->read_register16(a_register, reinterpret_cast<uint8_t *>(&in), sizeof(in));
|
||||
return sign_extend<24>(convert_big_endian(in));
|
||||
}
|
||||
|
||||
int32_t ADE7880::read_s32_register16_(uint16_t a_register) {
|
||||
int32_t in;
|
||||
this->read_register16(a_register, reinterpret_cast<uint8_t *>(&in), sizeof(in));
|
||||
return convert_big_endian(in);
|
||||
}
|
||||
|
||||
uint32_t ADE7880::read_u32_register16_(uint16_t a_register) {
|
||||
uint32_t in;
|
||||
this->read_register16(a_register, reinterpret_cast<uint8_t *>(&in), sizeof(in));
|
||||
return convert_big_endian(in);
|
||||
}
|
||||
|
||||
void ADE7880::write_u8_register16_(uint16_t a_register, uint8_t value) {
|
||||
this->write_register16(a_register, &value, sizeof(value));
|
||||
}
|
||||
|
||||
void ADE7880::write_s10zp_register16_(uint16_t a_register, int16_t value) {
|
||||
int16_t out = convert_big_endian(value & 0x03FF);
|
||||
this->write_register16(a_register, reinterpret_cast<uint8_t *>(&out), sizeof(out));
|
||||
}
|
||||
|
||||
void ADE7880::write_u16_register16_(uint16_t a_register, uint16_t value) {
|
||||
uint16_t out = convert_big_endian(value);
|
||||
this->write_register16(a_register, reinterpret_cast<uint8_t *>(&out), sizeof(out));
|
||||
}
|
||||
|
||||
void ADE7880::write_s24zpse_register16_(uint16_t a_register, int32_t value) {
|
||||
// s24zpse means a 24-bit signed value, sign-extended to 28 bits, in the lower 28 bits of a 32-bit register
|
||||
int32_t out = convert_big_endian(value & 0x0FFFFFFF);
|
||||
this->write_register16(a_register, reinterpret_cast<uint8_t *>(&out), sizeof(out));
|
||||
}
|
||||
|
||||
void ADE7880::write_s32_register16_(uint16_t a_register, int32_t value) {
|
||||
int32_t out = convert_big_endian(value);
|
||||
this->write_register16(a_register, reinterpret_cast<uint8_t *>(&out), sizeof(out));
|
||||
}
|
||||
|
||||
void ADE7880::write_u32_register16_(uint16_t a_register, uint32_t value) {
|
||||
uint32_t out = convert_big_endian(value);
|
||||
this->write_register16(a_register, reinterpret_cast<uint8_t *>(&out), sizeof(out));
|
||||
}
|
||||
|
||||
} // namespace ade7880
|
||||
} // namespace esphome
|
243
esphome/components/ade7880/ade7880_registers.h
Normal file
243
esphome/components/ade7880/ade7880_registers.h
Normal file
@ -0,0 +1,243 @@
|
||||
#pragma once
|
||||
|
||||
// This file is a modified version of the one created by Michaël Piron (@michaelpiron on GitHub)
|
||||
|
||||
// Source: https://www.analog.com/media/en/technical-documentation/application-notes/AN-1127.pdf
|
||||
|
||||
namespace esphome {
|
||||
namespace ade7880 {
|
||||
|
||||
// DSP Data Memory RAM registers
|
||||
constexpr uint16_t AIGAIN = 0x4380;
|
||||
constexpr uint16_t AVGAIN = 0x4381;
|
||||
constexpr uint16_t BIGAIN = 0x4382;
|
||||
constexpr uint16_t BVGAIN = 0x4383;
|
||||
constexpr uint16_t CIGAIN = 0x4384;
|
||||
constexpr uint16_t CVGAIN = 0x4385;
|
||||
constexpr uint16_t NIGAIN = 0x4386;
|
||||
|
||||
constexpr uint16_t DICOEFF = 0x4388;
|
||||
|
||||
constexpr uint16_t APGAIN = 0x4389;
|
||||
constexpr uint16_t AWATTOS = 0x438A;
|
||||
constexpr uint16_t BPGAIN = 0x438B;
|
||||
constexpr uint16_t BWATTOS = 0x438C;
|
||||
constexpr uint16_t CPGAIN = 0x438D;
|
||||
constexpr uint16_t CWATTOS = 0x438E;
|
||||
constexpr uint16_t AIRMSOS = 0x438F;
|
||||
constexpr uint16_t AVRMSOS = 0x4390;
|
||||
constexpr uint16_t BIRMSOS = 0x4391;
|
||||
constexpr uint16_t BVRMSOS = 0x4392;
|
||||
constexpr uint16_t CIRMSOS = 0x4393;
|
||||
constexpr uint16_t CVRMSOS = 0x4394;
|
||||
constexpr uint16_t NIRMSOS = 0x4395;
|
||||
constexpr uint16_t HPGAIN = 0x4398;
|
||||
constexpr uint16_t ISUMLVL = 0x4399;
|
||||
|
||||
constexpr uint16_t VLEVEL = 0x439F;
|
||||
|
||||
constexpr uint16_t AFWATTOS = 0x43A2;
|
||||
constexpr uint16_t BFWATTOS = 0x43A3;
|
||||
constexpr uint16_t CFWATTOS = 0x43A4;
|
||||
|
||||
constexpr uint16_t AFVAROS = 0x43A5;
|
||||
constexpr uint16_t BFVAROS = 0x43A6;
|
||||
constexpr uint16_t CFVAROS = 0x43A7;
|
||||
|
||||
constexpr uint16_t AFIRMSOS = 0x43A8;
|
||||
constexpr uint16_t BFIRMSOS = 0x43A9;
|
||||
constexpr uint16_t CFIRMSOS = 0x43AA;
|
||||
|
||||
constexpr uint16_t AFVRMSOS = 0x43AB;
|
||||
constexpr uint16_t BFVRMSOS = 0x43AC;
|
||||
constexpr uint16_t CFVRMSOS = 0x43AD;
|
||||
|
||||
constexpr uint16_t HXWATTOS = 0x43AE;
|
||||
constexpr uint16_t HYWATTOS = 0x43AF;
|
||||
constexpr uint16_t HZWATTOS = 0x43B0;
|
||||
constexpr uint16_t HXVAROS = 0x43B1;
|
||||
constexpr uint16_t HYVAROS = 0x43B2;
|
||||
constexpr uint16_t HZVAROS = 0x43B3;
|
||||
|
||||
constexpr uint16_t HXIRMSOS = 0x43B4;
|
||||
constexpr uint16_t HYIRMSOS = 0x43B5;
|
||||
constexpr uint16_t HZIRMSOS = 0x43B6;
|
||||
constexpr uint16_t HXVRMSOS = 0x43B7;
|
||||
constexpr uint16_t HYVRMSOS = 0x43B8;
|
||||
constexpr uint16_t HZVRMSOS = 0x43B9;
|
||||
|
||||
constexpr uint16_t AIRMS = 0x43C0;
|
||||
constexpr uint16_t AVRMS = 0x43C1;
|
||||
constexpr uint16_t BIRMS = 0x43C2;
|
||||
constexpr uint16_t BVRMS = 0x43C3;
|
||||
constexpr uint16_t CIRMS = 0x43C4;
|
||||
constexpr uint16_t CVRMS = 0x43C5;
|
||||
constexpr uint16_t NIRMS = 0x43C6;
|
||||
|
||||
constexpr uint16_t ISUM = 0x43C7;
|
||||
|
||||
// Internal DSP Memory RAM registers
|
||||
constexpr uint16_t RUN = 0xE228;
|
||||
|
||||
constexpr uint16_t AWATTHR = 0xE400;
|
||||
constexpr uint16_t BWATTHR = 0xE401;
|
||||
constexpr uint16_t CWATTHR = 0xE402;
|
||||
constexpr uint16_t AFWATTHR = 0xE403;
|
||||
constexpr uint16_t BFWATTHR = 0xE404;
|
||||
constexpr uint16_t CFWATTHR = 0xE405;
|
||||
constexpr uint16_t AFVARHR = 0xE409;
|
||||
constexpr uint16_t BFVARHR = 0xE40A;
|
||||
constexpr uint16_t CFVARHR = 0xE40B;
|
||||
|
||||
constexpr uint16_t AVAHR = 0xE40C;
|
||||
constexpr uint16_t BVAHR = 0xE40D;
|
||||
constexpr uint16_t CVAHR = 0xE40E;
|
||||
|
||||
constexpr uint16_t IPEAK = 0xE500;
|
||||
constexpr uint16_t VPEAK = 0xE501;
|
||||
|
||||
constexpr uint16_t STATUS0 = 0xE502;
|
||||
constexpr uint16_t STATUS1 = 0xE503;
|
||||
|
||||
constexpr uint16_t AIMAV = 0xE504;
|
||||
constexpr uint16_t BIMAV = 0xE505;
|
||||
constexpr uint16_t CIMAV = 0xE506;
|
||||
|
||||
constexpr uint16_t OILVL = 0xE507;
|
||||
constexpr uint16_t OVLVL = 0xE508;
|
||||
constexpr uint16_t SAGLVL = 0xE509;
|
||||
constexpr uint16_t MASK0 = 0xE50A;
|
||||
constexpr uint16_t MASK1 = 0xE50B;
|
||||
|
||||
constexpr uint16_t IAWV = 0xE50C;
|
||||
constexpr uint16_t IBWV = 0xE50D;
|
||||
constexpr uint16_t ICWV = 0xE50E;
|
||||
constexpr uint16_t INWV = 0xE50F;
|
||||
constexpr uint16_t VAWV = 0xE510;
|
||||
constexpr uint16_t VBWV = 0xE511;
|
||||
constexpr uint16_t VCWV = 0xE512;
|
||||
|
||||
constexpr uint16_t AWATT = 0xE513;
|
||||
constexpr uint16_t BWATT = 0xE514;
|
||||
constexpr uint16_t CWATT = 0xE515;
|
||||
|
||||
constexpr uint16_t AFVAR = 0xE516;
|
||||
constexpr uint16_t BFVAR = 0xE517;
|
||||
constexpr uint16_t CFVAR = 0xE518;
|
||||
|
||||
constexpr uint16_t AVA = 0xE519;
|
||||
constexpr uint16_t BVA = 0xE51A;
|
||||
constexpr uint16_t CVA = 0xE51B;
|
||||
|
||||
constexpr uint16_t CHECKSUM = 0xE51F;
|
||||
constexpr uint16_t VNOM = 0xE520;
|
||||
constexpr uint16_t LAST_RWDATA_24BIT = 0xE5FF;
|
||||
constexpr uint16_t PHSTATUS = 0xE600;
|
||||
constexpr uint16_t ANGLE0 = 0xE601;
|
||||
constexpr uint16_t ANGLE1 = 0xE602;
|
||||
constexpr uint16_t ANGLE2 = 0xE603;
|
||||
constexpr uint16_t PHNOLOAD = 0xE608;
|
||||
constexpr uint16_t LINECYC = 0xE60C;
|
||||
constexpr uint16_t ZXTOUT = 0xE60D;
|
||||
constexpr uint16_t COMPMODE = 0xE60E;
|
||||
constexpr uint16_t GAIN = 0xE60F;
|
||||
constexpr uint16_t CFMODE = 0xE610;
|
||||
constexpr uint16_t CF1DEN = 0xE611;
|
||||
constexpr uint16_t CF2DEN = 0xE612;
|
||||
constexpr uint16_t CF3DEN = 0xE613;
|
||||
constexpr uint16_t APHCAL = 0xE614;
|
||||
constexpr uint16_t BPHCAL = 0xE615;
|
||||
constexpr uint16_t CPHCAL = 0xE616;
|
||||
constexpr uint16_t PHSIGN = 0xE617;
|
||||
constexpr uint16_t CONFIG = 0xE618;
|
||||
constexpr uint16_t MMODE = 0xE700;
|
||||
constexpr uint16_t ACCMODE = 0xE701;
|
||||
constexpr uint16_t LCYCMODE = 0xE702;
|
||||
constexpr uint16_t PEAKCYC = 0xE703;
|
||||
constexpr uint16_t SAGCYC = 0xE704;
|
||||
constexpr uint16_t CFCYC = 0xE705;
|
||||
constexpr uint16_t HSDC_CFG = 0xE706;
|
||||
constexpr uint16_t VERSION = 0xE707;
|
||||
constexpr uint16_t DSPWP_SET = 0xE7E3;
|
||||
constexpr uint16_t LAST_RWDATA_8BIT = 0xE7FD;
|
||||
constexpr uint16_t DSPWP_SEL = 0xE7FE;
|
||||
constexpr uint16_t FVRMS = 0xE880;
|
||||
constexpr uint16_t FIRMS = 0xE881;
|
||||
constexpr uint16_t FWATT = 0xE882;
|
||||
constexpr uint16_t FVAR = 0xE883;
|
||||
constexpr uint16_t FVA = 0xE884;
|
||||
constexpr uint16_t FPF = 0xE885;
|
||||
constexpr uint16_t VTHDN = 0xE886;
|
||||
constexpr uint16_t ITHDN = 0xE887;
|
||||
constexpr uint16_t HXVRMS = 0xE888;
|
||||
constexpr uint16_t HXIRMS = 0xE889;
|
||||
constexpr uint16_t HXWATT = 0xE88A;
|
||||
constexpr uint16_t HXVAR = 0xE88B;
|
||||
constexpr uint16_t HXVA = 0xE88C;
|
||||
constexpr uint16_t HXPF = 0xE88D;
|
||||
constexpr uint16_t HXVHD = 0xE88E;
|
||||
constexpr uint16_t HXIHD = 0xE88F;
|
||||
constexpr uint16_t HYVRMS = 0xE890;
|
||||
constexpr uint16_t HYIRMS = 0xE891;
|
||||
constexpr uint16_t HYWATT = 0xE892;
|
||||
constexpr uint16_t HYVAR = 0xE893;
|
||||
constexpr uint16_t HYVA = 0xE894;
|
||||
constexpr uint16_t HYPF = 0xE895;
|
||||
constexpr uint16_t HYVHD = 0xE896;
|
||||
constexpr uint16_t HYIHD = 0xE897;
|
||||
constexpr uint16_t HZVRMS = 0xE898;
|
||||
constexpr uint16_t HZIRMS = 0xE899;
|
||||
constexpr uint16_t HZWATT = 0xE89A;
|
||||
constexpr uint16_t HZVAR = 0xE89B;
|
||||
constexpr uint16_t HZVA = 0xE89C;
|
||||
constexpr uint16_t HZPF = 0xE89D;
|
||||
constexpr uint16_t HZVHD = 0xE89E;
|
||||
constexpr uint16_t HZIHD = 0xE89F;
|
||||
constexpr uint16_t HCONFIG = 0xE900;
|
||||
constexpr uint16_t APF = 0xE902;
|
||||
constexpr uint16_t BPF = 0xE903;
|
||||
constexpr uint16_t CPF = 0xE904;
|
||||
constexpr uint16_t APERIOD = 0xE905;
|
||||
constexpr uint16_t BPERIOD = 0xE906;
|
||||
constexpr uint16_t CPERIOD = 0xE907;
|
||||
constexpr uint16_t APNOLOAD = 0xE908;
|
||||
constexpr uint16_t VARNOLOAD = 0xE909;
|
||||
constexpr uint16_t VANOLOAD = 0xE90A;
|
||||
constexpr uint16_t LAST_ADD = 0xE9FE;
|
||||
constexpr uint16_t LAST_RWDATA_16BIT = 0xE9FF;
|
||||
constexpr uint16_t CONFIG3 = 0xEA00;
|
||||
constexpr uint16_t LAST_OP = 0xEA01;
|
||||
constexpr uint16_t WTHR = 0xEA02;
|
||||
constexpr uint16_t VARTHR = 0xEA03;
|
||||
constexpr uint16_t VATHR = 0xEA04;
|
||||
|
||||
constexpr uint16_t HX_REG = 0xEA08;
|
||||
constexpr uint16_t HY_REG = 0xEA09;
|
||||
constexpr uint16_t HZ_REG = 0xEA0A;
|
||||
constexpr uint16_t LPOILVL = 0xEC00;
|
||||
constexpr uint16_t CONFIG2 = 0xEC01;
|
||||
|
||||
// STATUS1 Register Bits
|
||||
constexpr uint32_t STATUS1_RSTDONE = (1 << 15);
|
||||
|
||||
// CONFIG Register Bits
|
||||
constexpr uint16_t CONFIG_SWRST = (1 << 7);
|
||||
|
||||
// CONFIG2 Register Bits
|
||||
constexpr uint8_t CONFIG2_I2C_LOCK = (1 << 1);
|
||||
|
||||
// COMPMODE Register Bits
|
||||
constexpr uint16_t COMPMODE_DEFAULT = 0x01FF;
|
||||
constexpr uint16_t COMPMODE_SELFREQ = (1 << 14);
|
||||
|
||||
// RUN Register Bits
|
||||
constexpr uint16_t RUN_ENABLE = (1 << 0);
|
||||
|
||||
// DSPWP_SET Register Bits
|
||||
constexpr uint8_t DSPWP_SET_RO = (1 << 7);
|
||||
|
||||
// DSPWP_SEL Register Bits
|
||||
constexpr uint8_t DSPWP_SEL_SET = 0xAD;
|
||||
|
||||
} // namespace ade7880
|
||||
} // namespace esphome
|
290
esphome/components/ade7880/sensor.py
Normal file
290
esphome/components/ade7880/sensor.py
Normal file
@ -0,0 +1,290 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import sensor, i2c
|
||||
from esphome import pins
|
||||
from esphome.const import (
|
||||
CONF_ACTIVE_POWER,
|
||||
CONF_APPARENT_POWER,
|
||||
CONF_CALIBRATION,
|
||||
CONF_CURRENT,
|
||||
CONF_FORWARD_ACTIVE_ENERGY,
|
||||
CONF_FREQUENCY,
|
||||
CONF_ID,
|
||||
CONF_NAME,
|
||||
CONF_PHASE_A,
|
||||
CONF_PHASE_ANGLE,
|
||||
CONF_PHASE_B,
|
||||
CONF_PHASE_C,
|
||||
CONF_POWER_FACTOR,
|
||||
CONF_RESET_PIN,
|
||||
CONF_REVERSE_ACTIVE_ENERGY,
|
||||
CONF_VOLTAGE,
|
||||
DEVICE_CLASS_APPARENT_POWER,
|
||||
DEVICE_CLASS_CURRENT,
|
||||
DEVICE_CLASS_ENERGY,
|
||||
DEVICE_CLASS_POWER,
|
||||
DEVICE_CLASS_POWER_FACTOR,
|
||||
DEVICE_CLASS_VOLTAGE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
STATE_CLASS_TOTAL_INCREASING,
|
||||
UNIT_AMPERE,
|
||||
UNIT_PERCENT,
|
||||
UNIT_VOLT,
|
||||
UNIT_VOLT_AMPS,
|
||||
UNIT_VOLT_AMPS_REACTIVE_HOURS,
|
||||
UNIT_WATT,
|
||||
UNIT_WATT_HOURS,
|
||||
)
|
||||
|
||||
DEPENDENCIES = ["i2c"]
|
||||
|
||||
ade7880_ns = cg.esphome_ns.namespace("ade7880")
|
||||
ADE7880 = ade7880_ns.class_("ADE7880", cg.PollingComponent, i2c.I2CDevice)
|
||||
NeutralChannel = ade7880_ns.struct("NeutralChannel")
|
||||
PowerChannel = ade7880_ns.struct("PowerChannel")
|
||||
|
||||
CONF_CURRENT_GAIN = "current_gain"
|
||||
CONF_IRQ0_PIN = "irq0_pin"
|
||||
CONF_IRQ1_PIN = "irq1_pin"
|
||||
CONF_POWER_GAIN = "power_gain"
|
||||
CONF_VOLTAGE_GAIN = "voltage_gain"
|
||||
|
||||
CONF_NEUTRAL = "neutral"
|
||||
|
||||
NEUTRAL_CHANNEL_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(NeutralChannel),
|
||||
cv.Optional(CONF_NAME): cv.string_strict,
|
||||
cv.Required(CONF_CURRENT): cv.maybe_simple_value(
|
||||
sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_AMPERE,
|
||||
accuracy_decimals=2,
|
||||
device_class=DEVICE_CLASS_CURRENT,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
key=CONF_NAME,
|
||||
),
|
||||
cv.Required(CONF_CALIBRATION): cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_CURRENT_GAIN): cv.int_,
|
||||
},
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
POWER_CHANNEL_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(PowerChannel),
|
||||
cv.Optional(CONF_NAME): cv.string_strict,
|
||||
cv.Optional(CONF_VOLTAGE): cv.maybe_simple_value(
|
||||
sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_VOLT,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_VOLTAGE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
key=CONF_NAME,
|
||||
),
|
||||
cv.Optional(CONF_CURRENT): cv.maybe_simple_value(
|
||||
sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_AMPERE,
|
||||
accuracy_decimals=2,
|
||||
device_class=DEVICE_CLASS_CURRENT,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
key=CONF_NAME,
|
||||
),
|
||||
cv.Optional(CONF_ACTIVE_POWER): cv.maybe_simple_value(
|
||||
sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_WATT,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_POWER,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
key=CONF_NAME,
|
||||
),
|
||||
cv.Optional(CONF_APPARENT_POWER): cv.maybe_simple_value(
|
||||
sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_VOLT_AMPS,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_APPARENT_POWER,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
key=CONF_NAME,
|
||||
),
|
||||
cv.Optional(CONF_POWER_FACTOR): cv.maybe_simple_value(
|
||||
sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_PERCENT,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_POWER_FACTOR,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
key=CONF_NAME,
|
||||
),
|
||||
cv.Optional(CONF_FORWARD_ACTIVE_ENERGY): cv.maybe_simple_value(
|
||||
sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_WATT_HOURS,
|
||||
accuracy_decimals=2,
|
||||
device_class=DEVICE_CLASS_ENERGY,
|
||||
state_class=STATE_CLASS_TOTAL_INCREASING,
|
||||
),
|
||||
key=CONF_NAME,
|
||||
),
|
||||
cv.Optional(CONF_REVERSE_ACTIVE_ENERGY): cv.maybe_simple_value(
|
||||
sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_VOLT_AMPS_REACTIVE_HOURS,
|
||||
accuracy_decimals=2,
|
||||
device_class=DEVICE_CLASS_ENERGY,
|
||||
state_class=STATE_CLASS_TOTAL_INCREASING,
|
||||
),
|
||||
key=CONF_NAME,
|
||||
),
|
||||
cv.Required(CONF_CALIBRATION): cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_CURRENT_GAIN): cv.int_,
|
||||
cv.Required(CONF_VOLTAGE_GAIN): cv.int_,
|
||||
cv.Required(CONF_POWER_GAIN): cv.int_,
|
||||
cv.Required(CONF_PHASE_ANGLE): cv.int_,
|
||||
},
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(ADE7880),
|
||||
cv.Optional(CONF_FREQUENCY, default="50Hz"): cv.All(
|
||||
cv.frequency, cv.Range(min=45.0, max=66.0)
|
||||
),
|
||||
cv.Optional(CONF_IRQ0_PIN): pins.internal_gpio_input_pin_schema,
|
||||
cv.Required(CONF_IRQ1_PIN): pins.internal_gpio_input_pin_schema,
|
||||
cv.Optional(CONF_RESET_PIN): pins.internal_gpio_output_pin_schema,
|
||||
cv.Optional(CONF_PHASE_A): POWER_CHANNEL_SCHEMA,
|
||||
cv.Optional(CONF_PHASE_B): POWER_CHANNEL_SCHEMA,
|
||||
cv.Optional(CONF_PHASE_C): POWER_CHANNEL_SCHEMA,
|
||||
cv.Optional(CONF_NEUTRAL): NEUTRAL_CHANNEL_SCHEMA,
|
||||
}
|
||||
)
|
||||
.extend(cv.polling_component_schema("60s"))
|
||||
.extend(i2c.i2c_device_schema(0x38))
|
||||
)
|
||||
|
||||
|
||||
async def neutral_channel(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
|
||||
current = config[CONF_CURRENT]
|
||||
sens = await sensor.new_sensor(current)
|
||||
cg.add(var.set_current(sens))
|
||||
|
||||
cg.add(
|
||||
var.set_current_gain_calibration(config[CONF_CALIBRATION][CONF_CURRENT_GAIN])
|
||||
)
|
||||
|
||||
return var
|
||||
|
||||
|
||||
async def power_channel(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
|
||||
for sensor_type in [
|
||||
CONF_CURRENT,
|
||||
CONF_VOLTAGE,
|
||||
CONF_ACTIVE_POWER,
|
||||
CONF_APPARENT_POWER,
|
||||
CONF_POWER_FACTOR,
|
||||
CONF_FORWARD_ACTIVE_ENERGY,
|
||||
CONF_REVERSE_ACTIVE_ENERGY,
|
||||
]:
|
||||
if conf := config.get(sensor_type):
|
||||
sens = await sensor.new_sensor(conf)
|
||||
cg.add(getattr(var, f"set_{sensor_type}")(sens))
|
||||
|
||||
for calib_type in [
|
||||
CONF_CURRENT_GAIN,
|
||||
CONF_VOLTAGE_GAIN,
|
||||
CONF_POWER_GAIN,
|
||||
CONF_PHASE_ANGLE,
|
||||
]:
|
||||
cg.add(
|
||||
getattr(var, f"set_{calib_type}_calibration")(
|
||||
config[CONF_CALIBRATION][calib_type]
|
||||
)
|
||||
)
|
||||
|
||||
return var
|
||||
|
||||
|
||||
def final_validate(config):
|
||||
for channel in [CONF_PHASE_A, CONF_PHASE_B, CONF_PHASE_C]:
|
||||
if channel := config.get(channel):
|
||||
channel_name = channel.get(CONF_NAME)
|
||||
|
||||
for sensor_type in [
|
||||
CONF_CURRENT,
|
||||
CONF_VOLTAGE,
|
||||
CONF_ACTIVE_POWER,
|
||||
CONF_APPARENT_POWER,
|
||||
CONF_POWER_FACTOR,
|
||||
CONF_FORWARD_ACTIVE_ENERGY,
|
||||
CONF_REVERSE_ACTIVE_ENERGY,
|
||||
]:
|
||||
if conf := channel.get(sensor_type):
|
||||
sensor_name = conf.get(CONF_NAME)
|
||||
if (
|
||||
sensor_name
|
||||
and channel_name
|
||||
and not sensor_name.startswith(channel_name)
|
||||
):
|
||||
conf[CONF_NAME] = f"{channel_name} {sensor_name}"
|
||||
|
||||
if channel := config.get(CONF_NEUTRAL):
|
||||
channel_name = channel.get(CONF_NAME)
|
||||
if conf := channel.get(CONF_CURRENT):
|
||||
sensor_name = conf.get(CONF_NAME)
|
||||
if (
|
||||
sensor_name
|
||||
and channel_name
|
||||
and not sensor_name.startswith(channel_name)
|
||||
):
|
||||
conf[CONF_NAME] = f"{channel_name} {sensor_name}"
|
||||
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = final_validate
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
|
||||
if irq0_pin := config.get(CONF_IRQ0_PIN):
|
||||
pin = await cg.gpio_pin_expression(irq0_pin)
|
||||
cg.add(var.set_irq0_pin(pin))
|
||||
|
||||
pin = await cg.gpio_pin_expression(config[CONF_IRQ1_PIN])
|
||||
cg.add(var.set_irq1_pin(pin))
|
||||
|
||||
if reset_pin := config.get(CONF_RESET_PIN):
|
||||
pin = await cg.gpio_pin_expression(reset_pin)
|
||||
cg.add(var.set_reset_pin(pin))
|
||||
|
||||
if frequency := config.get(CONF_FREQUENCY):
|
||||
cg.add(var.set_frequency(frequency))
|
||||
|
||||
if channel := config.get(CONF_PHASE_A):
|
||||
chan = await power_channel(channel)
|
||||
cg.add(var.set_channel_a(chan))
|
||||
|
||||
if channel := config.get(CONF_PHASE_B):
|
||||
chan = await power_channel(channel)
|
||||
cg.add(var.set_channel_b(chan))
|
||||
|
||||
if channel := config.get(CONF_PHASE_C):
|
||||
chan = await power_channel(channel)
|
||||
cg.add(var.set_channel_c(chan))
|
||||
|
||||
if channel := config.get(CONF_NEUTRAL):
|
||||
chan = await neutral_channel(channel)
|
||||
cg.add(var.set_channel_n(chan))
|
@ -21,30 +21,39 @@ namespace esphome {
|
||||
namespace aht10 {
|
||||
|
||||
static const char *const TAG = "aht10";
|
||||
static const size_t SIZE_CALIBRATE_CMD = 3;
|
||||
static const uint8_t AHT10_CALIBRATE_CMD[] = {0xE1, 0x08, 0x00};
|
||||
static const uint8_t AHT20_CALIBRATE_CMD[] = {0xBE, 0x08, 0x00};
|
||||
static const uint8_t AHT10_INITIALIZE_CMD[] = {0xE1, 0x08, 0x00};
|
||||
static const uint8_t AHT20_INITIALIZE_CMD[] = {0xBE, 0x08, 0x00};
|
||||
static const uint8_t AHT10_MEASURE_CMD[] = {0xAC, 0x33, 0x00};
|
||||
static const uint8_t AHT10_DEFAULT_DELAY = 5; // ms, for calibration and temperature measurement
|
||||
static const uint8_t AHT10_HUMIDITY_DELAY = 30; // ms
|
||||
static const uint8_t AHT10_ATTEMPTS = 3; // safety margin, normally 3 attempts are enough: 3*30=90ms
|
||||
static const uint8_t AHT10_CAL_ATTEMPTS = 10;
|
||||
static const uint8_t AHT10_SOFTRESET_CMD[] = {0xBA};
|
||||
|
||||
static const uint8_t AHT10_DEFAULT_DELAY = 5; // ms, for initialization and temperature measurement
|
||||
static const uint8_t AHT10_HUMIDITY_DELAY = 30; // ms
|
||||
static const uint8_t AHT10_SOFTRESET_DELAY = 30; // ms
|
||||
|
||||
static const uint8_t AHT10_ATTEMPTS = 3; // safety margin, normally 3 attempts are enough: 3*30=90ms
|
||||
static const uint8_t AHT10_INIT_ATTEMPTS = 10;
|
||||
|
||||
static const uint8_t AHT10_STATUS_BUSY = 0x80;
|
||||
|
||||
void AHT10Component::setup() {
|
||||
const uint8_t *calibrate_cmd;
|
||||
if (this->write(AHT10_SOFTRESET_CMD, sizeof(AHT10_SOFTRESET_CMD)) != i2c::ERROR_OK) {
|
||||
ESP_LOGE(TAG, "Reset AHT10 failed!");
|
||||
}
|
||||
delay(AHT10_SOFTRESET_DELAY);
|
||||
|
||||
const uint8_t *init_cmd;
|
||||
switch (this->variant_) {
|
||||
case AHT10Variant::AHT20:
|
||||
calibrate_cmd = AHT20_CALIBRATE_CMD;
|
||||
init_cmd = AHT20_INITIALIZE_CMD;
|
||||
ESP_LOGCONFIG(TAG, "Setting up AHT20");
|
||||
break;
|
||||
case AHT10Variant::AHT10:
|
||||
default:
|
||||
calibrate_cmd = AHT10_CALIBRATE_CMD;
|
||||
init_cmd = AHT10_INITIALIZE_CMD;
|
||||
ESP_LOGCONFIG(TAG, "Setting up AHT10");
|
||||
}
|
||||
|
||||
if (this->write(calibrate_cmd, SIZE_CALIBRATE_CMD) != i2c::ERROR_OK) {
|
||||
if (this->write(init_cmd, sizeof(init_cmd)) != i2c::ERROR_OK) {
|
||||
ESP_LOGE(TAG, "Communication with AHT10 failed!");
|
||||
this->mark_failed();
|
||||
return;
|
||||
@ -59,19 +68,19 @@ void AHT10Component::setup() {
|
||||
return;
|
||||
}
|
||||
++cal_attempts;
|
||||
if (cal_attempts > AHT10_CAL_ATTEMPTS) {
|
||||
ESP_LOGE(TAG, "AHT10 calibration timed out!");
|
||||
if (cal_attempts > AHT10_INIT_ATTEMPTS) {
|
||||
ESP_LOGE(TAG, "AHT10 initialization timed out!");
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
}
|
||||
if ((data & 0x68) != 0x08) { // Bit[6:5] = 0b00, NORMAL mode and Bit[3] = 0b1, CALIBRATED
|
||||
ESP_LOGE(TAG, "AHT10 calibration failed!");
|
||||
ESP_LOGE(TAG, "AHT10 initialization failed!");
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGV(TAG, "AHT10 calibrated");
|
||||
ESP_LOGV(TAG, "AHT10 initialization");
|
||||
}
|
||||
|
||||
void AHT10Component::update() {
|
||||
|
@ -600,6 +600,7 @@ message ListEntitiesTextSensorResponse {
|
||||
string icon = 5;
|
||||
bool disabled_by_default = 6;
|
||||
EntityCategory entity_category = 7;
|
||||
string device_class = 8;
|
||||
}
|
||||
message TextSensorStateResponse {
|
||||
option (id) = 27;
|
||||
@ -1449,6 +1450,7 @@ message VoiceAssistantRequest {
|
||||
string conversation_id = 2;
|
||||
uint32 flags = 3;
|
||||
VoiceAssistantAudioSettings audio_settings = 4;
|
||||
string wake_word_phrase = 5;
|
||||
}
|
||||
|
||||
message VoiceAssistantResponse {
|
||||
|
@ -543,6 +543,7 @@ bool APIConnection::send_text_sensor_info(text_sensor::TextSensor *text_sensor)
|
||||
msg.icon = text_sensor->get_icon();
|
||||
msg.disabled_by_default = text_sensor->is_disabled_by_default();
|
||||
msg.entity_category = static_cast<enums::EntityCategory>(text_sensor->get_entity_category());
|
||||
msg.device_class = text_sensor->get_device_class();
|
||||
return this->send_list_entities_text_sensor_response(msg);
|
||||
}
|
||||
#endif
|
||||
|
@ -2721,6 +2721,10 @@ bool ListEntitiesTextSensorResponse::decode_length(uint32_t field_id, ProtoLengt
|
||||
this->icon = value.as_string();
|
||||
return true;
|
||||
}
|
||||
case 8: {
|
||||
this->device_class = value.as_string();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@ -2743,6 +2747,7 @@ void ListEntitiesTextSensorResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_string(5, this->icon);
|
||||
buffer.encode_bool(6, this->disabled_by_default);
|
||||
buffer.encode_enum<enums::EntityCategory>(7, this->entity_category);
|
||||
buffer.encode_string(8, this->device_class);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void ListEntitiesTextSensorResponse::dump_to(std::string &out) const {
|
||||
@ -2776,6 +2781,10 @@ void ListEntitiesTextSensorResponse::dump_to(std::string &out) const {
|
||||
out.append(" entity_category: ");
|
||||
out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" device_class: ");
|
||||
out.append("'").append(this->device_class).append("'");
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
@ -6594,6 +6603,10 @@ bool VoiceAssistantRequest::decode_length(uint32_t field_id, ProtoLengthDelimite
|
||||
this->audio_settings = value.as_message<VoiceAssistantAudioSettings>();
|
||||
return true;
|
||||
}
|
||||
case 5: {
|
||||
this->wake_word_phrase = value.as_string();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@ -6603,6 +6616,7 @@ void VoiceAssistantRequest::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_string(2, this->conversation_id);
|
||||
buffer.encode_uint32(3, this->flags);
|
||||
buffer.encode_message<VoiceAssistantAudioSettings>(4, this->audio_settings);
|
||||
buffer.encode_string(5, this->wake_word_phrase);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void VoiceAssistantRequest::dump_to(std::string &out) const {
|
||||
@ -6624,6 +6638,10 @@ void VoiceAssistantRequest::dump_to(std::string &out) const {
|
||||
out.append(" audio_settings: ");
|
||||
this->audio_settings.dump_to(out);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" wake_word_phrase: ");
|
||||
out.append("'").append(this->wake_word_phrase).append("'");
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
|
@ -713,6 +713,7 @@ class ListEntitiesTextSensorResponse : public ProtoMessage {
|
||||
std::string icon{};
|
||||
bool disabled_by_default{false};
|
||||
enums::EntityCategory entity_category{};
|
||||
std::string device_class{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
@ -1701,6 +1702,7 @@ class VoiceAssistantRequest : public ProtoMessage {
|
||||
std::string conversation_id{};
|
||||
uint32_t flags{0};
|
||||
VoiceAssistantAudioSettings audio_settings{};
|
||||
std::string wake_word_phrase{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
|
@ -22,7 +22,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
async def to_code(config):
|
||||
if CORE.is_esp32 or CORE.is_libretiny:
|
||||
# https://github.com/esphome/AsyncTCP/blob/master/library.json
|
||||
cg.add_library("esphome/AsyncTCP-esphome", "2.0.1")
|
||||
cg.add_library("esphome/AsyncTCP-esphome", "2.1.3")
|
||||
elif CORE.is_esp8266:
|
||||
# https://github.com/esphome/ESPAsyncTCP
|
||||
cg.add_library("esphome/ESPAsyncTCP-esphome", "2.0.0")
|
||||
|
@ -4,6 +4,7 @@ from esphome.components import sensor, uart
|
||||
from esphome.const import (
|
||||
CONF_CURRENT,
|
||||
CONF_ENERGY,
|
||||
CONF_EXTERNAL_TEMPERATURE,
|
||||
CONF_ID,
|
||||
CONF_POWER,
|
||||
CONF_VOLTAGE,
|
||||
@ -24,7 +25,6 @@ from esphome.const import (
|
||||
DEPENDENCIES = ["uart"]
|
||||
|
||||
CONF_INTERNAL_TEMPERATURE = "internal_temperature"
|
||||
CONF_EXTERNAL_TEMPERATURE = "external_temperature"
|
||||
|
||||
bl0940_ns = cg.esphome_ns.namespace("bl0940")
|
||||
BL0940 = bl0940_ns.class_("BL0940", cg.PollingComponent, uart.UARTDevice)
|
||||
|
@ -11,6 +11,7 @@ MULTI_CONF = True
|
||||
CONF_BME680_BSEC_ID = "bme680_bsec_id"
|
||||
CONF_TEMPERATURE_OFFSET = "temperature_offset"
|
||||
CONF_IAQ_MODE = "iaq_mode"
|
||||
CONF_SUPPLY_VOLTAGE = "supply_voltage"
|
||||
CONF_SAMPLE_RATE = "sample_rate"
|
||||
CONF_STATE_SAVE_INTERVAL = "state_save_interval"
|
||||
|
||||
@ -22,6 +23,12 @@ IAQ_MODE_OPTIONS = {
|
||||
"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")
|
||||
SAMPLE_RATE_OPTIONS = {
|
||||
"LP": SampleRate.SAMPLE_RATE_LP,
|
||||
@ -40,6 +47,9 @@ CONFIG_SCHEMA = cv.All(
|
||||
cv.Optional(CONF_IAQ_MODE, default="STATIC"): cv.enum(
|
||||
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(
|
||||
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_temperature_offset(config[CONF_TEMPERATURE_OFFSET]))
|
||||
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_state_save_interval(config[CONF_STATE_SAVE_INTERVAL].total_milliseconds)
|
||||
|
@ -52,17 +52,33 @@ void BME680BSECComponent::setup() {
|
||||
|
||||
void BME680BSECComponent::set_config_() {
|
||||
if (this->sample_rate_ == SAMPLE_RATE_ULP) {
|
||||
const uint8_t config[] = {
|
||||
if (this->supply_voltage_ == SUPPLY_VOLTAGE_3V3) {
|
||||
const uint8_t config[] = {
|
||||
#include "config/generic_33v_300s_28d/bsec_iaq.txt"
|
||||
};
|
||||
this->bsec_status_ =
|
||||
bsec_set_configuration(config, BSEC_MAX_PROPERTY_BLOB_SIZE, this->work_buffer_, sizeof(this->work_buffer_));
|
||||
} else {
|
||||
const uint8_t config[] = {
|
||||
};
|
||||
this->bsec_status_ =
|
||||
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_300s_28d/bsec_iaq.txt"
|
||||
};
|
||||
this->bsec_status_ =
|
||||
bsec_set_configuration(config, BSEC_MAX_PROPERTY_BLOB_SIZE, this->work_buffer_, sizeof(this->work_buffer_));
|
||||
}
|
||||
} else { // SAMPLE_RATE_LP
|
||||
if (this->supply_voltage_ == SUPPLY_VOLTAGE_3V3) {
|
||||
const uint8_t config[] = {
|
||||
#include "config/generic_33v_3s_28d/bsec_iaq.txt"
|
||||
};
|
||||
this->bsec_status_ =
|
||||
bsec_set_configuration(config, BSEC_MAX_PROPERTY_BLOB_SIZE, this->work_buffer_, sizeof(this->work_buffer_));
|
||||
};
|
||||
this->bsec_status_ =
|
||||
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, " 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, " State Save Interval: %ims", this->state_save_interval_ms_);
|
||||
|
||||
|
@ -21,6 +21,11 @@ enum IAQMode {
|
||||
IAQ_MODE_MOBILE = 1,
|
||||
};
|
||||
|
||||
enum SupplyVoltage {
|
||||
SUPPLY_VOLTAGE_3V3 = 0,
|
||||
SUPPLY_VOLTAGE_1V8 = 1,
|
||||
};
|
||||
|
||||
enum SampleRate {
|
||||
SAMPLE_RATE_LP = 0,
|
||||
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_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_supply_voltage(SupplyVoltage supply_voltage) { this->supply_voltage_ = supply_voltage; }
|
||||
|
||||
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; }
|
||||
@ -109,6 +115,7 @@ class BME680BSECComponent : public Component, public i2c::I2CDevice {
|
||||
std::string device_id_;
|
||||
float temperature_offset_{0};
|
||||
IAQMode iaq_mode_{IAQ_MODE_STATIC};
|
||||
SupplyVoltage supply_voltage_;
|
||||
|
||||
SampleRate sample_rate_{SAMPLE_RATE_LP}; // Core/gas sample rate
|
||||
SampleRate temperature_sample_rate_{SAMPLE_RATE_DEFAULT};
|
||||
|
@ -1,6 +1,8 @@
|
||||
#include "cse7766.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include <cinttypes>
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
|
||||
namespace esphome {
|
||||
namespace cse7766 {
|
||||
@ -68,20 +70,26 @@ bool CSE7766Component::check_byte_() {
|
||||
return true;
|
||||
}
|
||||
void CSE7766Component::parse_data_() {
|
||||
ESP_LOGVV(TAG, "CSE7766 Data: ");
|
||||
for (uint8_t i = 0; i < 23; i++) {
|
||||
ESP_LOGVV(TAG, " %u: 0b" BYTE_TO_BINARY_PATTERN " (0x%02X)", i + 1, BYTE_TO_BINARY(this->raw_data_[i]),
|
||||
this->raw_data_[i]);
|
||||
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "Raw data:" << std::hex << std::uppercase << std::setfill('0');
|
||||
for (uint8_t i = 0; i < 23; i++) {
|
||||
ss << ' ' << std::setw(2) << static_cast<unsigned>(this->raw_data_[i]);
|
||||
}
|
||||
ESP_LOGVV(TAG, "%s", ss.str().c_str());
|
||||
}
|
||||
#endif
|
||||
|
||||
// Parse header
|
||||
uint8_t header1 = this->raw_data_[0];
|
||||
|
||||
if (header1 == 0xAA) {
|
||||
ESP_LOGE(TAG, "CSE7766 not calibrated!");
|
||||
return;
|
||||
}
|
||||
|
||||
bool power_cycle_exceeds_range = false;
|
||||
|
||||
if ((header1 & 0xF0) == 0xF0) {
|
||||
if (header1 & 0xD) {
|
||||
ESP_LOGE(TAG, "CSE7766 reports abnormal external circuit or chip damage: (0x%02X)", header1);
|
||||
@ -94,74 +102,122 @@ void CSE7766Component::parse_data_() {
|
||||
if (header1 & (1 << 0)) {
|
||||
ESP_LOGE(TAG, " Coefficient storage area is abnormal.");
|
||||
}
|
||||
|
||||
// Datasheet: voltage or current cycle exceeding range means invalid values
|
||||
return;
|
||||
}
|
||||
|
||||
power_cycle_exceeds_range = header1 & (1 << 1);
|
||||
}
|
||||
|
||||
uint32_t voltage_calib = this->get_24_bit_uint_(2);
|
||||
// Parse data frame
|
||||
uint32_t voltage_coeff = this->get_24_bit_uint_(2);
|
||||
uint32_t voltage_cycle = this->get_24_bit_uint_(5);
|
||||
uint32_t current_calib = this->get_24_bit_uint_(8);
|
||||
uint32_t current_coeff = this->get_24_bit_uint_(8);
|
||||
uint32_t current_cycle = this->get_24_bit_uint_(11);
|
||||
uint32_t power_calib = this->get_24_bit_uint_(14);
|
||||
uint32_t power_coeff = this->get_24_bit_uint_(14);
|
||||
uint32_t power_cycle = this->get_24_bit_uint_(17);
|
||||
|
||||
uint8_t adj = this->raw_data_[20];
|
||||
uint32_t cf_pulses = (this->raw_data_[21] << 8) + this->raw_data_[22];
|
||||
|
||||
bool have_voltage = adj & 0x40;
|
||||
if (have_voltage) {
|
||||
// voltage cycle of serial port outputted is a complete cycle;
|
||||
float voltage = voltage_calib / float(voltage_cycle);
|
||||
if (this->voltage_sensor_ != nullptr)
|
||||
this->voltage_sensor_->publish_state(voltage);
|
||||
}
|
||||
uint16_t cf_pulses = (this->raw_data_[21] << 8) + this->raw_data_[22];
|
||||
|
||||
bool have_power = adj & 0x10;
|
||||
float power = 0.0f;
|
||||
bool have_current = adj & 0x20;
|
||||
bool have_voltage = adj & 0x40;
|
||||
|
||||
if (have_power) {
|
||||
// power cycle of serial port outputted is a complete cycle;
|
||||
// According to the user manual, power cycle exceeding range means the measured power is 0
|
||||
if (!power_cycle_exceeds_range) {
|
||||
power = power_calib / float(power_cycle);
|
||||
float voltage = 0.0f;
|
||||
if (have_voltage) {
|
||||
voltage = voltage_coeff / float(voltage_cycle);
|
||||
if (this->voltage_sensor_ != nullptr) {
|
||||
this->voltage_sensor_->publish_state(voltage);
|
||||
}
|
||||
if (this->power_sensor_ != nullptr)
|
||||
this->power_sensor_->publish_state(power);
|
||||
}
|
||||
|
||||
uint32_t difference;
|
||||
if (this->cf_pulses_last_ == 0) {
|
||||
float energy = 0.0;
|
||||
if (this->energy_sensor_ != nullptr) {
|
||||
if (this->cf_pulses_last_ == 0 && !this->energy_sensor_->has_state()) {
|
||||
this->cf_pulses_last_ = cf_pulses;
|
||||
}
|
||||
|
||||
if (cf_pulses < this->cf_pulses_last_) {
|
||||
difference = cf_pulses + (0x10000 - this->cf_pulses_last_);
|
||||
} else {
|
||||
difference = cf_pulses - this->cf_pulses_last_;
|
||||
}
|
||||
uint16_t cf_diff = cf_pulses - this->cf_pulses_last_;
|
||||
this->cf_pulses_total_ += cf_diff;
|
||||
this->cf_pulses_last_ = cf_pulses;
|
||||
this->energy_total_ += difference * float(power_calib) / 1000000.0f / 3600.0f;
|
||||
if (this->energy_sensor_ != nullptr)
|
||||
this->energy_sensor_->publish_state(this->energy_total_);
|
||||
} else if ((this->energy_sensor_ != nullptr) && !this->energy_sensor_->has_state()) {
|
||||
this->energy_sensor_->publish_state(0);
|
||||
energy = this->cf_pulses_total_ * float(power_coeff) / 1000000.0f / 3600.0f;
|
||||
this->energy_sensor_->publish_state(energy);
|
||||
}
|
||||
|
||||
if (adj & 0x20) {
|
||||
// indicates current cycle of serial port outputted is a complete cycle;
|
||||
float current = 0.0f;
|
||||
if (have_voltage && !have_power) {
|
||||
// Testing has shown that when we have voltage and current but not power, that means the power is 0.
|
||||
// We report a power of 0, which in turn means we should report a current of 0.
|
||||
if (this->power_sensor_ != nullptr)
|
||||
this->power_sensor_->publish_state(0);
|
||||
} else if (power != 0.0f) {
|
||||
current = current_calib / float(current_cycle);
|
||||
float power = 0.0f;
|
||||
if (power_cycle_exceeds_range) {
|
||||
// Datasheet: power cycle exceeding range means active power is 0
|
||||
if (this->power_sensor_ != nullptr) {
|
||||
this->power_sensor_->publish_state(0.0f);
|
||||
}
|
||||
} else if (have_power) {
|
||||
power = power_coeff / float(power_cycle);
|
||||
if (this->power_sensor_ != nullptr) {
|
||||
this->power_sensor_->publish_state(power);
|
||||
}
|
||||
if (this->current_sensor_ != nullptr)
|
||||
this->current_sensor_->publish_state(current);
|
||||
}
|
||||
|
||||
float current = 0.0f;
|
||||
float calculated_current = 0.0f;
|
||||
if (have_current) {
|
||||
// Assumption: if we don't have power measurement, then current is likely below 50mA
|
||||
if (have_power && voltage > 1.0f) {
|
||||
calculated_current = power / voltage;
|
||||
}
|
||||
// Datasheet: minimum measured current is 50mA
|
||||
if (calculated_current > 0.05f) {
|
||||
current = current_coeff / float(current_cycle);
|
||||
}
|
||||
if (this->current_sensor_ != nullptr) {
|
||||
this->current_sensor_->publish_state(current);
|
||||
}
|
||||
}
|
||||
|
||||
if (have_voltage && have_current) {
|
||||
const float apparent_power = voltage * current;
|
||||
if (this->apparent_power_sensor_ != nullptr) {
|
||||
this->apparent_power_sensor_->publish_state(apparent_power);
|
||||
}
|
||||
if (this->power_factor_sensor_ != nullptr && (have_power || power_cycle_exceeds_range)) {
|
||||
float pf = NAN;
|
||||
if (apparent_power > 0) {
|
||||
pf = power / apparent_power;
|
||||
if (pf < 0 || pf > 1) {
|
||||
ESP_LOGD(TAG, "Impossible power factor: %.4f not in interval [0, 1]", pf);
|
||||
pf = NAN;
|
||||
}
|
||||
} else if (apparent_power == 0 && power == 0) {
|
||||
// No load, report ideal power factor
|
||||
pf = 1.0f;
|
||||
} else if (current == 0 && calculated_current <= 0.05f) {
|
||||
// Datasheet: minimum measured current is 50mA
|
||||
ESP_LOGV(TAG, "Can't calculate power factor (current below minimum for CSE7766)");
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Can't calculate power factor from P = %.4f W, S = %.4f VA", power, apparent_power);
|
||||
}
|
||||
this->power_factor_sensor_->publish_state(pf);
|
||||
}
|
||||
}
|
||||
|
||||
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "Parsed:";
|
||||
if (have_voltage) {
|
||||
ss << " V=" << voltage << "V";
|
||||
}
|
||||
if (have_current) {
|
||||
ss << " I=" << current * 1000.0f << "mA (~" << calculated_current * 1000.0f << "mA)";
|
||||
}
|
||||
if (have_power) {
|
||||
ss << " P=" << power << "W";
|
||||
}
|
||||
if (energy != 0.0f) {
|
||||
ss << " E=" << energy << "kWh (" << cf_pulses << ")";
|
||||
}
|
||||
ESP_LOGVV(TAG, "%s", ss.str().c_str());
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
uint32_t CSE7766Component::get_24_bit_uint_(uint8_t start_index) {
|
||||
@ -175,6 +231,8 @@ void CSE7766Component::dump_config() {
|
||||
LOG_SENSOR(" ", "Current", this->current_sensor_);
|
||||
LOG_SENSOR(" ", "Power", this->power_sensor_);
|
||||
LOG_SENSOR(" ", "Energy", this->energy_sensor_);
|
||||
LOG_SENSOR(" ", "Apparent Power", this->apparent_power_sensor_);
|
||||
LOG_SENSOR(" ", "Power Factor", this->power_factor_sensor_);
|
||||
this->check_uart_settings(4800);
|
||||
}
|
||||
|
||||
|
@ -13,6 +13,10 @@ class CSE7766Component : public Component, public uart::UARTDevice {
|
||||
void set_current_sensor(sensor::Sensor *current_sensor) { current_sensor_ = current_sensor; }
|
||||
void set_power_sensor(sensor::Sensor *power_sensor) { power_sensor_ = power_sensor; }
|
||||
void set_energy_sensor(sensor::Sensor *energy_sensor) { energy_sensor_ = energy_sensor; }
|
||||
void set_apparent_power_sensor(sensor::Sensor *apparent_power_sensor) {
|
||||
apparent_power_sensor_ = apparent_power_sensor;
|
||||
}
|
||||
void set_power_factor_sensor(sensor::Sensor *power_factor_sensor) { power_factor_sensor_ = power_factor_sensor; }
|
||||
|
||||
void loop() override;
|
||||
float get_setup_priority() const override;
|
||||
@ -30,8 +34,10 @@ class CSE7766Component : public Component, public uart::UARTDevice {
|
||||
sensor::Sensor *current_sensor_{nullptr};
|
||||
sensor::Sensor *power_sensor_{nullptr};
|
||||
sensor::Sensor *energy_sensor_{nullptr};
|
||||
float energy_total_{0.0f};
|
||||
uint32_t cf_pulses_last_{0};
|
||||
sensor::Sensor *apparent_power_sensor_{nullptr};
|
||||
sensor::Sensor *power_factor_sensor_{nullptr};
|
||||
uint32_t cf_pulses_total_{0};
|
||||
uint16_t cf_pulses_last_{0};
|
||||
};
|
||||
|
||||
} // namespace cse7766
|
||||
|
@ -2,19 +2,24 @@ import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import sensor, uart
|
||||
from esphome.const import (
|
||||
CONF_APPARENT_POWER,
|
||||
CONF_CURRENT,
|
||||
CONF_ENERGY,
|
||||
CONF_ID,
|
||||
CONF_POWER,
|
||||
CONF_POWER_FACTOR,
|
||||
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_VOLT,
|
||||
UNIT_AMPERE,
|
||||
UNIT_VOLT,
|
||||
UNIT_VOLT_AMPS,
|
||||
UNIT_WATT,
|
||||
UNIT_WATT_HOURS,
|
||||
)
|
||||
@ -51,6 +56,17 @@ CONFIG_SCHEMA = cv.Schema(
|
||||
device_class=DEVICE_CLASS_ENERGY,
|
||||
state_class=STATE_CLASS_TOTAL_INCREASING,
|
||||
),
|
||||
cv.Optional(CONF_APPARENT_POWER): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_VOLT_AMPS,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_APPARENT_POWER,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_POWER_FACTOR): sensor.sensor_schema(
|
||||
accuracy_decimals=2,
|
||||
device_class=DEVICE_CLASS_POWER_FACTOR,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
}
|
||||
).extend(uart.UART_DEVICE_SCHEMA)
|
||||
FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema(
|
||||
@ -75,3 +91,9 @@ async def to_code(config):
|
||||
if energy_config := config.get(CONF_ENERGY):
|
||||
sens = await sensor.new_sensor(energy_config)
|
||||
cg.add(var.set_energy_sensor(sens))
|
||||
if apparent_power_config := config.get(CONF_APPARENT_POWER):
|
||||
sens = await sensor.new_sensor(apparent_power_config)
|
||||
cg.add(var.set_apparent_power_sensor(sens))
|
||||
if power_factor_config := config.get(CONF_POWER_FACTOR):
|
||||
sens = await sensor.new_sensor(power_factor_config)
|
||||
cg.add(var.set_power_factor_sensor(sens))
|
||||
|
@ -168,10 +168,6 @@ bool IRAM_ATTR DallasTemperatureSensor::read_scratch_pad() {
|
||||
if (!wire->reset()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
InterruptLock lock;
|
||||
|
||||
wire->select(this->address_);
|
||||
wire->write8(DALLAS_COMMAND_READ_SCRATCH_PAD);
|
||||
|
@ -1,7 +1,6 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import automation
|
||||
from esphome import core
|
||||
from esphome.automation import maybe_simple_id
|
||||
from esphome.const import CONF_ID
|
||||
from esphome.components import uart
|
||||
@ -101,7 +100,7 @@ def range_segment_list(input):
|
||||
|
||||
largest_distance = -1
|
||||
for distance in input:
|
||||
if isinstance(distance, core.Lambda):
|
||||
if isinstance(distance, cv.Lambda):
|
||||
continue
|
||||
m = cv.distance(distance)
|
||||
if m > 9:
|
||||
@ -128,14 +127,14 @@ MMWAVE_SETTINGS_SCHEMA = cv.Schema(
|
||||
cv.Optional(CONF_OUTPUT_LATENCY): {
|
||||
cv.Required(CONF_DELAY_AFTER_DETECT): cv.templatable(
|
||||
cv.All(
|
||||
cv.positive_time_period,
|
||||
cv.Range(max=core.TimePeriod(seconds=1638.375)),
|
||||
cv.positive_time_period_milliseconds,
|
||||
cv.Range(max=cv.TimePeriod(seconds=1638.375)),
|
||||
)
|
||||
),
|
||||
cv.Required(CONF_DELAY_AFTER_DISAPPEAR): cv.templatable(
|
||||
cv.All(
|
||||
cv.positive_time_period,
|
||||
cv.Range(max=core.TimePeriod(seconds=1638.375)),
|
||||
cv.positive_time_period_milliseconds,
|
||||
cv.Range(max=cv.TimePeriod(seconds=1638.375)),
|
||||
)
|
||||
),
|
||||
},
|
||||
|
@ -50,7 +50,7 @@ class DfrobotSen0395SettingsAction : public Action<Ts...>, public Parented<Dfrob
|
||||
float detect = this->delay_after_detect_.value(x...);
|
||||
float disappear = this->delay_after_disappear_.value(x...);
|
||||
if (detect >= 0 && disappear >= 0) {
|
||||
this->parent_->enqueue(make_unique<OutputLatencyCommand>(detect, disappear));
|
||||
this->parent_->enqueue(make_unique<SetLatencyCommand>(detect, disappear));
|
||||
}
|
||||
}
|
||||
if (this->start_after_power_on_.has_value()) {
|
||||
|
@ -1,5 +1,7 @@
|
||||
#include "commands.h"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#include "dfrobot_sen0395.h"
|
||||
@ -194,32 +196,22 @@ uint8_t DetRangeCfgCommand::on_message(std::string &message) {
|
||||
return 0; // Command not done yet.
|
||||
}
|
||||
|
||||
OutputLatencyCommand::OutputLatencyCommand(float delay_after_detection, float delay_after_disappear) {
|
||||
delay_after_detection = round(delay_after_detection / 0.025) * 0.025;
|
||||
delay_after_disappear = round(delay_after_disappear / 0.025) * 0.025;
|
||||
if (delay_after_detection < 0)
|
||||
delay_after_detection = 0;
|
||||
if (delay_after_detection > 1638.375)
|
||||
delay_after_detection = 1638.375;
|
||||
if (delay_after_disappear < 0)
|
||||
delay_after_disappear = 0;
|
||||
if (delay_after_disappear > 1638.375)
|
||||
delay_after_disappear = 1638.375;
|
||||
|
||||
this->delay_after_detection_ = delay_after_detection;
|
||||
this->delay_after_disappear_ = delay_after_disappear;
|
||||
|
||||
this->cmd_ = str_sprintf("outputLatency -1 %.0f %.0f", delay_after_detection / 0.025, delay_after_disappear / 0.025);
|
||||
SetLatencyCommand::SetLatencyCommand(float delay_after_detection, float delay_after_disappear) {
|
||||
delay_after_detection = std::round(delay_after_detection / 0.025f) * 0.025f;
|
||||
delay_after_disappear = std::round(delay_after_disappear / 0.025f) * 0.025f;
|
||||
this->delay_after_detection_ = clamp(delay_after_detection, 0.0f, 1638.375f);
|
||||
this->delay_after_disappear_ = clamp(delay_after_disappear, 0.0f, 1638.375f);
|
||||
this->cmd_ = str_sprintf("setLatency %.03f %.03f", this->delay_after_detection_, this->delay_after_disappear_);
|
||||
};
|
||||
|
||||
uint8_t OutputLatencyCommand::on_message(std::string &message) {
|
||||
uint8_t SetLatencyCommand::on_message(std::string &message) {
|
||||
if (message == "sensor is not stopped") {
|
||||
ESP_LOGE(TAG, "Cannot configure output latency. Sensor is not stopped!");
|
||||
return 1; // Command done
|
||||
} else if (message == "Done") {
|
||||
ESP_LOGI(TAG, "Updated output latency config:");
|
||||
ESP_LOGI(TAG, "Signal that someone was detected is delayed by %.02fs.", this->delay_after_detection_);
|
||||
ESP_LOGI(TAG, "Signal that nobody is detected anymore is delayed by %.02fs.", this->delay_after_disappear_);
|
||||
ESP_LOGI(TAG, "Signal that someone was detected is delayed by %.03f s.", this->delay_after_detection_);
|
||||
ESP_LOGI(TAG, "Signal that nobody is detected anymore is delayed by %.03f s.", this->delay_after_disappear_);
|
||||
ESP_LOGD(TAG, "Used command: %s", this->cmd_.c_str());
|
||||
return 1; // Command done
|
||||
}
|
||||
|
@ -62,9 +62,9 @@ class DetRangeCfgCommand : public Command {
|
||||
// TODO: Set min max values in component, so they can be published as sensor.
|
||||
};
|
||||
|
||||
class OutputLatencyCommand : public Command {
|
||||
class SetLatencyCommand : public Command {
|
||||
public:
|
||||
OutputLatencyCommand(float delay_after_detection, float delay_after_disappear);
|
||||
SetLatencyCommand(float delay_after_detection, float delay_after_disappear);
|
||||
uint8_t on_message(std::string &message) override;
|
||||
|
||||
protected:
|
||||
|
@ -257,6 +257,67 @@ void Display::filled_triangle(int x1, int y1, int x2, int y2, int x3, int y3, Co
|
||||
this->filled_flat_side_triangle_(x3, y3, x2, y2, x_temp, y_temp, color);
|
||||
}
|
||||
}
|
||||
void HOT Display::get_regular_polygon_vertex(int vertex_id, int *vertex_x, int *vertex_y, int center_x, int center_y,
|
||||
int radius, int edges, RegularPolygonVariation variation,
|
||||
float rotation_degrees) {
|
||||
if (edges >= 2) {
|
||||
// Given the orientation of the display component, an angle is measured clockwise from the x axis.
|
||||
// For a regular polygon, the human reference would be the top of the polygon,
|
||||
// hence we rotate the shape by 270° to orient the polygon up.
|
||||
rotation_degrees += ROTATION_270_DEGREES;
|
||||
// Convert the rotation to radians, easier to use in trigonometrical calculations
|
||||
float rotation_radians = rotation_degrees * PI / 180;
|
||||
// A pointy top variation means the first vertex of the polygon is at the top center of the shape, this requires no
|
||||
// additional rotation of the shape.
|
||||
// A flat top variation means the first point of the polygon has to be rotated so that the first edge is horizontal,
|
||||
// this requires to rotate the shape by π/edges radians counter-clockwise so that the first point is located on the
|
||||
// left side of the first horizontal edge.
|
||||
rotation_radians -= (variation == VARIATION_FLAT_TOP) ? PI / edges : 0.0;
|
||||
|
||||
float vertex_angle = ((float) vertex_id) / edges * 2 * PI + rotation_radians;
|
||||
*vertex_x = (int) round(cos(vertex_angle) * radius) + center_x;
|
||||
*vertex_y = (int) round(sin(vertex_angle) * radius) + center_y;
|
||||
}
|
||||
}
|
||||
|
||||
void HOT Display::regular_polygon(int x, int y, int radius, int edges, RegularPolygonVariation variation,
|
||||
float rotation_degrees, Color color, RegularPolygonDrawing drawing) {
|
||||
if (edges >= 2) {
|
||||
int previous_vertex_x, previous_vertex_y;
|
||||
for (int current_vertex_id = 0; current_vertex_id <= edges; current_vertex_id++) {
|
||||
int current_vertex_x, current_vertex_y;
|
||||
get_regular_polygon_vertex(current_vertex_id, ¤t_vertex_x, ¤t_vertex_y, x, y, radius, edges,
|
||||
variation, rotation_degrees);
|
||||
if (current_vertex_id > 0) { // Start drawing after the 2nd vertex coordinates has been calculated
|
||||
if (drawing == DRAWING_FILLED) {
|
||||
this->filled_triangle(x, y, previous_vertex_x, previous_vertex_y, current_vertex_x, current_vertex_y, color);
|
||||
} else if (drawing == DRAWING_OUTLINE) {
|
||||
this->line(previous_vertex_x, previous_vertex_y, current_vertex_x, current_vertex_y, color);
|
||||
}
|
||||
}
|
||||
previous_vertex_x = current_vertex_x;
|
||||
previous_vertex_y = current_vertex_y;
|
||||
}
|
||||
}
|
||||
}
|
||||
void HOT Display::regular_polygon(int x, int y, int radius, int edges, RegularPolygonVariation variation, Color color,
|
||||
RegularPolygonDrawing drawing) {
|
||||
regular_polygon(x, y, radius, edges, variation, ROTATION_0_DEGREES, color, drawing);
|
||||
}
|
||||
void HOT Display::regular_polygon(int x, int y, int radius, int edges, Color color, RegularPolygonDrawing drawing) {
|
||||
regular_polygon(x, y, radius, edges, VARIATION_POINTY_TOP, ROTATION_0_DEGREES, color, drawing);
|
||||
}
|
||||
void Display::filled_regular_polygon(int x, int y, int radius, int edges, RegularPolygonVariation variation,
|
||||
float rotation_degrees, Color color) {
|
||||
regular_polygon(x, y, radius, edges, variation, rotation_degrees, color, DRAWING_FILLED);
|
||||
}
|
||||
void Display::filled_regular_polygon(int x, int y, int radius, int edges, RegularPolygonVariation variation,
|
||||
Color color) {
|
||||
regular_polygon(x, y, radius, edges, variation, ROTATION_0_DEGREES, color, DRAWING_FILLED);
|
||||
}
|
||||
void Display::filled_regular_polygon(int x, int y, int radius, int edges, Color color) {
|
||||
regular_polygon(x, y, radius, edges, VARIATION_POINTY_TOP, ROTATION_0_DEGREES, color, DRAWING_FILLED);
|
||||
}
|
||||
|
||||
void Display::print(int x, int y, BaseFont *font, Color color, TextAlign align, const char *text) {
|
||||
int x_start, y_start;
|
||||
|
@ -137,6 +137,42 @@ enum DisplayRotation {
|
||||
DISPLAY_ROTATION_270_DEGREES = 270,
|
||||
};
|
||||
|
||||
#define PI 3.1415926535897932384626433832795
|
||||
|
||||
const int EDGES_TRIGON = 3;
|
||||
const int EDGES_TRIANGLE = 3;
|
||||
const int EDGES_TETRAGON = 4;
|
||||
const int EDGES_QUADRILATERAL = 4;
|
||||
const int EDGES_PENTAGON = 5;
|
||||
const int EDGES_HEXAGON = 6;
|
||||
const int EDGES_HEPTAGON = 7;
|
||||
const int EDGES_OCTAGON = 8;
|
||||
const int EDGES_NONAGON = 9;
|
||||
const int EDGES_ENNEAGON = 9;
|
||||
const int EDGES_DECAGON = 10;
|
||||
const int EDGES_HENDECAGON = 11;
|
||||
const int EDGES_DODECAGON = 12;
|
||||
const int EDGES_TRIDECAGON = 13;
|
||||
const int EDGES_TETRADECAGON = 14;
|
||||
const int EDGES_PENTADECAGON = 15;
|
||||
const int EDGES_HEXADECAGON = 16;
|
||||
|
||||
const float ROTATION_0_DEGREES = 0.0;
|
||||
const float ROTATION_45_DEGREES = 45.0;
|
||||
const float ROTATION_90_DEGREES = 90.0;
|
||||
const float ROTATION_180_DEGREES = 180.0;
|
||||
const float ROTATION_270_DEGREES = 270.0;
|
||||
|
||||
enum RegularPolygonVariation {
|
||||
VARIATION_POINTY_TOP = 0,
|
||||
VARIATION_FLAT_TOP = 1,
|
||||
};
|
||||
|
||||
enum RegularPolygonDrawing {
|
||||
DRAWING_OUTLINE = 0,
|
||||
DRAWING_FILLED = 1,
|
||||
};
|
||||
|
||||
class Display;
|
||||
class DisplayPage;
|
||||
class DisplayOnPageChangeTrigger;
|
||||
@ -175,10 +211,15 @@ class Display : public PollingComponent {
|
||||
/// Clear the entire screen by filling it with OFF pixels.
|
||||
void clear();
|
||||
|
||||
/// Get the width of the image in pixels with rotation applied.
|
||||
virtual int get_width() = 0;
|
||||
/// Get the height of the image in pixels with rotation applied.
|
||||
virtual int get_height() = 0;
|
||||
/// Get the calculated width of the display in pixels with rotation applied.
|
||||
virtual int get_width() { return this->get_width_internal(); }
|
||||
/// Get the calculated height of the display in pixels with rotation applied.
|
||||
virtual int get_height() { return this->get_height_internal(); }
|
||||
|
||||
/// Get the native (original) width of the display in pixels.
|
||||
int get_native_width() { return this->get_width_internal(); }
|
||||
/// Get the native (original) height of the display in pixels.
|
||||
int get_native_height() { return this->get_height_internal(); }
|
||||
|
||||
/// Set a single pixel at the specified coordinates to default color.
|
||||
inline void draw_pixel_at(int x, int y) { this->draw_pixel_at(x, y, COLOR_ON); }
|
||||
@ -242,6 +283,42 @@ class Display : public PollingComponent {
|
||||
/// Fill a triangle contained between the points [x1,y1], [x2,y2] and [x3,y3] with the given color.
|
||||
void filled_triangle(int x1, int y1, int x2, int y2, int x3, int y3, Color color = COLOR_ON);
|
||||
|
||||
/// Get the specified vertex (x,y) coordinates for the regular polygon inscribed in the circle centered on
|
||||
/// [center_x,center_y] with the given radius. Vertex id are 0-indexed and rotate clockwise. In a pointy-topped
|
||||
/// variation of a polygon with a 0° rotation, the vertex #0 is located at the top of the polygon. In a flat-topped
|
||||
/// variation of a polygon with a 0° rotation, the vertex #0 is located on the left-side of the horizontal top
|
||||
/// edge, and the vertex #1 is located on the right-side of the horizontal top edge.
|
||||
/// Use the edges constants (e.g.: EDGES_HEXAGON) or any integer to specify the number of edges of the polygon.
|
||||
/// Use the variation to switch between the flat-topped or the pointy-topped variation of the polygon.
|
||||
/// Use the rotation in degrees to rotate the shape clockwise.
|
||||
void get_regular_polygon_vertex(int vertex_id, int *vertex_x, int *vertex_y, int center_x, int center_y, int radius,
|
||||
int edges, RegularPolygonVariation variation = VARIATION_POINTY_TOP,
|
||||
float rotation_degrees = ROTATION_0_DEGREES);
|
||||
|
||||
/// Draw the outline of a regular polygon inscribed in the circle centered on [x,y] with the given
|
||||
/// radius and color.
|
||||
/// Use the edges constants (e.g.: EDGES_HEXAGON) or any integer to specify the number of edges of the polygon.
|
||||
/// Use the variation to switch between the flat-topped or the pointy-topped variation of the polygon.
|
||||
/// Use the rotation in degrees to rotate the shape clockwise.
|
||||
/// Use the drawing to switch between outlining or filling the polygon.
|
||||
void regular_polygon(int x, int y, int radius, int edges, RegularPolygonVariation variation = VARIATION_POINTY_TOP,
|
||||
float rotation_degrees = ROTATION_0_DEGREES, Color color = COLOR_ON,
|
||||
RegularPolygonDrawing drawing = DRAWING_OUTLINE);
|
||||
void regular_polygon(int x, int y, int radius, int edges, RegularPolygonVariation variation, Color color,
|
||||
RegularPolygonDrawing drawing = DRAWING_OUTLINE);
|
||||
void regular_polygon(int x, int y, int radius, int edges, Color color,
|
||||
RegularPolygonDrawing drawing = DRAWING_OUTLINE);
|
||||
|
||||
/// Fill a regular polygon inscribed in the circle centered on [x,y] with the given radius and color.
|
||||
/// Use the edges constants (e.g.: EDGES_HEXAGON) or any integer to specify the number of edges of the polygon.
|
||||
/// Use the variation to switch between the flat-topped or the pointy-topped variation of the polygon.
|
||||
/// Use the rotation in degrees to rotate the shape clockwise.
|
||||
void filled_regular_polygon(int x, int y, int radius, int edges,
|
||||
RegularPolygonVariation variation = VARIATION_POINTY_TOP,
|
||||
float rotation_degrees = ROTATION_0_DEGREES, Color color = COLOR_ON);
|
||||
void filled_regular_polygon(int x, int y, int radius, int edges, RegularPolygonVariation variation, Color color);
|
||||
void filled_regular_polygon(int x, int y, int radius, int edges, Color color);
|
||||
|
||||
/** Print `text` with the anchor point at [x,y] with `font`.
|
||||
*
|
||||
* @param x The x coordinate of the text alignment anchor point.
|
||||
@ -538,6 +615,9 @@ class Display : public PollingComponent {
|
||||
void do_update_();
|
||||
void clear_clipping_();
|
||||
|
||||
virtual int get_height_internal() = 0;
|
||||
virtual int get_width_internal() = 0;
|
||||
|
||||
/**
|
||||
* This method fills a triangle using only integer variables by using a
|
||||
* modified bresenham algorithm.
|
||||
|
@ -22,9 +22,6 @@ class DisplayBuffer : public Display {
|
||||
/// Set a single pixel at the specified coordinates to the given color.
|
||||
void draw_pixel_at(int x, int y, Color color) override;
|
||||
|
||||
virtual int get_height_internal() = 0;
|
||||
virtual int get_width_internal() = 0;
|
||||
|
||||
protected:
|
||||
virtual void draw_absolute_pixel_internal(int x, int y, Color color) = 0;
|
||||
|
||||
|
@ -34,24 +34,27 @@ void EKTF2232Touchscreen::setup() {
|
||||
|
||||
// Get touch resolution
|
||||
uint8_t received[4];
|
||||
this->write(GET_X_RES, 4);
|
||||
if (this->read(received, 4)) {
|
||||
ESP_LOGE(TAG, "Failed to read X resolution!");
|
||||
this->interrupt_pin_->detach_interrupt();
|
||||
this->mark_failed();
|
||||
return;
|
||||
if (this->x_raw_max_ == this->x_raw_min_) {
|
||||
this->write(GET_X_RES, 4);
|
||||
if (this->read(received, 4)) {
|
||||
ESP_LOGE(TAG, "Failed to read X resolution!");
|
||||
this->interrupt_pin_->detach_interrupt();
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
this->x_raw_max_ = ((received[2])) | ((received[3] & 0xf0) << 4);
|
||||
}
|
||||
this->x_raw_max_ = ((received[2])) | ((received[3] & 0xf0) << 4);
|
||||
|
||||
this->write(GET_Y_RES, 4);
|
||||
if (this->read(received, 4)) {
|
||||
ESP_LOGE(TAG, "Failed to read Y resolution!");
|
||||
this->interrupt_pin_->detach_interrupt();
|
||||
this->mark_failed();
|
||||
return;
|
||||
if (this->y_raw_max_ == this->y_raw_min_) {
|
||||
this->write(GET_Y_RES, 4);
|
||||
if (this->read(received, 4)) {
|
||||
ESP_LOGE(TAG, "Failed to read Y resolution!");
|
||||
this->interrupt_pin_->detach_interrupt();
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
this->y_raw_max_ = ((received[2])) | ((received[3] & 0xf0) << 4);
|
||||
}
|
||||
this->y_raw_max_ = ((received[2])) | ((received[3] & 0xf0) << 4);
|
||||
|
||||
this->set_power_state(true);
|
||||
}
|
||||
|
||||
|
@ -7,6 +7,8 @@ CODEOWNERS = ["@ellull"]
|
||||
|
||||
DEPENDENCIES = ["i2c"]
|
||||
|
||||
MULTI_CONF = True
|
||||
|
||||
CONF_PWM = "pwm"
|
||||
CONF_DIVIDER = "divider"
|
||||
CONF_DAC = "dac"
|
||||
|
@ -2,6 +2,7 @@ import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import sensor
|
||||
from esphome.const import (
|
||||
CONF_EXTERNAL_TEMPERATURE,
|
||||
CONF_ID,
|
||||
CONF_SPEED,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
@ -16,7 +17,6 @@ from .. import EMC2101_COMPONENT_SCHEMA, CONF_EMC2101_ID, emc2101_ns
|
||||
DEPENDENCIES = ["emc2101"]
|
||||
|
||||
CONF_INTERNAL_TEMPERATURE = "internal_temperature"
|
||||
CONF_EXTERNAL_TEMPERATURE = "external_temperature"
|
||||
CONF_DUTY_CYCLE = "duty_cycle"
|
||||
|
||||
EMC2101Sensor = emc2101_ns.class_("EMC2101Sensor", cg.PollingComponent)
|
||||
|
@ -141,9 +141,13 @@ void ESP32ImprovComponent::loop() {
|
||||
|
||||
std::vector<std::string> urls = {ESPHOME_MY_LINK};
|
||||
#ifdef USE_WEBSERVER
|
||||
auto ip = wifi::global_wifi_component->wifi_sta_ip();
|
||||
std::string webserver_url = "http://" + ip.str() + ":" + to_string(USE_WEBSERVER_PORT);
|
||||
urls.push_back(webserver_url);
|
||||
for (auto &ip : wifi::global_wifi_component->wifi_sta_ip_addresses()) {
|
||||
if (ip.is_ip4()) {
|
||||
std::string webserver_url = "http://" + ip.str() + ":" + to_string(USE_WEBSERVER_PORT);
|
||||
urls.push_back(webserver_url);
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
std::vector<uint8_t> data = improv::build_rpc_response(improv::WIFI_SETTINGS, urls);
|
||||
this->send_response_(data);
|
||||
@ -289,7 +293,7 @@ void ESP32ImprovComponent::process_incoming_data_() {
|
||||
this->connecting_sta_ = sta;
|
||||
|
||||
wifi::global_wifi_component->set_sta(sta);
|
||||
wifi::global_wifi_component->start_scanning();
|
||||
wifi::global_wifi_component->start_connecting(sta, false);
|
||||
this->set_state_(improv::STATE_PROVISIONING);
|
||||
ESP_LOGD(TAG, "Received Improv wifi settings ssid=%s, password=" LOG_SECRET("%s"), command.ssid.c_str(),
|
||||
command.password.c_str());
|
||||
|
@ -160,11 +160,13 @@ light::ESPColorView ESP32RMTLEDStripLightOutput::get_view_internal(int32_t index
|
||||
b = 0;
|
||||
break;
|
||||
}
|
||||
uint8_t multiplier = this->is_rgbw_ ? 4 : 3;
|
||||
return {this->buf_ + (index * multiplier) + r,
|
||||
this->buf_ + (index * multiplier) + g,
|
||||
this->buf_ + (index * multiplier) + b,
|
||||
this->is_rgbw_ ? this->buf_ + (index * multiplier) + 3 : nullptr,
|
||||
uint8_t multiplier = this->is_rgbw_ || this->is_wrgb_ ? 4 : 3;
|
||||
uint8_t white = this->is_wrgb_ ? 0 : 3;
|
||||
|
||||
return {this->buf_ + (index * multiplier) + r + this->is_wrgb_,
|
||||
this->buf_ + (index * multiplier) + g + this->is_wrgb_,
|
||||
this->buf_ + (index * multiplier) + b + this->is_wrgb_,
|
||||
this->is_rgbw_ || this->is_wrgb_ ? this->buf_ + (index * multiplier) + white : nullptr,
|
||||
&this->effect_data_[index],
|
||||
&this->correction_};
|
||||
}
|
||||
|
@ -33,7 +33,7 @@ class ESP32RMTLEDStripLightOutput : public light::AddressableLight {
|
||||
int32_t size() const override { return this->num_leds_; }
|
||||
light::LightTraits get_traits() override {
|
||||
auto traits = light::LightTraits();
|
||||
if (this->is_rgbw_) {
|
||||
if (this->is_rgbw_ || this->is_wrgb_) {
|
||||
traits.set_supported_color_modes({light::ColorMode::RGB_WHITE, light::ColorMode::WHITE});
|
||||
} else {
|
||||
traits.set_supported_color_modes({light::ColorMode::RGB});
|
||||
@ -44,6 +44,7 @@ class ESP32RMTLEDStripLightOutput : public light::AddressableLight {
|
||||
void set_pin(uint8_t pin) { this->pin_ = pin; }
|
||||
void set_num_leds(uint16_t num_leds) { this->num_leds_ = num_leds; }
|
||||
void set_is_rgbw(bool is_rgbw) { this->is_rgbw_ = is_rgbw; }
|
||||
void set_is_wrgb(bool is_wrgb) { this->is_wrgb_ = is_wrgb; }
|
||||
|
||||
/// Set a maximum refresh rate in µs as some lights do not like being updated too often.
|
||||
void set_max_refresh_rate(uint32_t interval_us) { this->max_refresh_rate_ = interval_us; }
|
||||
@ -72,6 +73,7 @@ class ESP32RMTLEDStripLightOutput : public light::AddressableLight {
|
||||
uint8_t pin_;
|
||||
uint16_t num_leds_;
|
||||
bool is_rgbw_;
|
||||
bool is_wrgb_;
|
||||
|
||||
rmt_item32_t bit0_, bit1_;
|
||||
RGBOrder rgb_order_;
|
||||
|
@ -52,6 +52,7 @@ CHIPSETS = {
|
||||
|
||||
|
||||
CONF_IS_RGBW = "is_rgbw"
|
||||
CONF_IS_WRGB = "is_wrgb"
|
||||
CONF_BIT0_HIGH = "bit0_high"
|
||||
CONF_BIT0_LOW = "bit0_low"
|
||||
CONF_BIT1_HIGH = "bit1_high"
|
||||
@ -90,6 +91,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
cv.Optional(CONF_MAX_REFRESH_RATE): cv.positive_time_period_microseconds,
|
||||
cv.Optional(CONF_CHIPSET): cv.one_of(*CHIPSETS, upper=True),
|
||||
cv.Optional(CONF_IS_RGBW, default=False): cv.boolean,
|
||||
cv.Optional(CONF_IS_WRGB, default=False): cv.boolean,
|
||||
cv.Inclusive(
|
||||
CONF_BIT0_HIGH,
|
||||
"custom",
|
||||
@ -145,6 +147,7 @@ async def to_code(config):
|
||||
|
||||
cg.add(var.set_rgb_order(config[CONF_RGB_ORDER]))
|
||||
cg.add(var.set_is_rgbw(config[CONF_IS_RGBW]))
|
||||
cg.add(var.set_is_wrgb(config[CONF_IS_WRGB]))
|
||||
|
||||
cg.add(
|
||||
var.set_rmt_channel(
|
||||
|
@ -1,6 +1,13 @@
|
||||
from esphome import pins
|
||||
import esphome.config_validation as cv
|
||||
import esphome.final_validate as fv
|
||||
import esphome.codegen as cg
|
||||
from esphome.components.esp32 import add_idf_sdkconfig_option, get_esp32_variant
|
||||
from esphome.components.esp32.const import (
|
||||
VARIANT_ESP32C3,
|
||||
VARIANT_ESP32S2,
|
||||
VARIANT_ESP32S3,
|
||||
)
|
||||
from esphome.const import (
|
||||
CONF_DOMAIN,
|
||||
CONF_ID,
|
||||
@ -12,9 +19,17 @@ from esphome.const import (
|
||||
CONF_SUBNET,
|
||||
CONF_DNS1,
|
||||
CONF_DNS2,
|
||||
CONF_CLK_PIN,
|
||||
CONF_MISO_PIN,
|
||||
CONF_MOSI_PIN,
|
||||
CONF_CS_PIN,
|
||||
CONF_INTERRUPT_PIN,
|
||||
CONF_RESET_PIN,
|
||||
CONF_SPI,
|
||||
)
|
||||
from esphome.core import CORE, coroutine_with_priority
|
||||
from esphome.components.network import IPAddress
|
||||
from esphome.components.spi import get_spi_interface, CONF_INTERFACE_INDEX
|
||||
|
||||
CONFLICTS_WITH = ["wifi"]
|
||||
DEPENDENCIES = ["esp32"]
|
||||
@ -27,6 +42,8 @@ CONF_MDIO_PIN = "mdio_pin"
|
||||
CONF_CLK_MODE = "clk_mode"
|
||||
CONF_POWER_PIN = "power_pin"
|
||||
|
||||
CONF_CLOCK_SPEED = "clock_speed"
|
||||
|
||||
EthernetType = ethernet_ns.enum("EthernetType")
|
||||
ETHERNET_TYPES = {
|
||||
"LAN8720": EthernetType.ETHERNET_TYPE_LAN8720,
|
||||
@ -36,8 +53,11 @@ ETHERNET_TYPES = {
|
||||
"JL1101": EthernetType.ETHERNET_TYPE_JL1101,
|
||||
"KSZ8081": EthernetType.ETHERNET_TYPE_KSZ8081,
|
||||
"KSZ8081RNA": EthernetType.ETHERNET_TYPE_KSZ8081RNA,
|
||||
"W5500": EthernetType.ETHERNET_TYPE_W5500,
|
||||
}
|
||||
|
||||
SPI_ETHERNET_TYPES = ["W5500"]
|
||||
|
||||
emac_rmii_clock_mode_t = cg.global_ns.enum("emac_rmii_clock_mode_t")
|
||||
emac_rmii_clock_gpio_t = cg.global_ns.enum("emac_rmii_clock_gpio_t")
|
||||
CLK_MODES = {
|
||||
@ -84,11 +104,22 @@ def _validate(config):
|
||||
return config
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
BASE_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(EthernetComponent),
|
||||
cv.Optional(CONF_MANUAL_IP): MANUAL_IP_SCHEMA,
|
||||
cv.Optional(CONF_DOMAIN, default=".local"): cv.domain_name,
|
||||
cv.Optional(CONF_USE_ADDRESS): cv.string_strict,
|
||||
cv.Optional("enable_mdns"): cv.invalid(
|
||||
"This option has been removed. Please use the [disabled] option under the "
|
||||
"new mdns component instead."
|
||||
),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
RMII_SCHEMA = BASE_SCHEMA.extend(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(EthernetComponent),
|
||||
cv.Required(CONF_TYPE): cv.enum(ETHERNET_TYPES, upper=True),
|
||||
cv.Required(CONF_MDC_PIN): pins.internal_gpio_output_pin_number,
|
||||
cv.Required(CONF_MDIO_PIN): pins.internal_gpio_output_pin_number,
|
||||
cv.Optional(CONF_CLK_MODE, default="GPIO0_IN"): cv.enum(
|
||||
@ -96,19 +127,64 @@ CONFIG_SCHEMA = cv.All(
|
||||
),
|
||||
cv.Optional(CONF_PHY_ADDR, default=0): cv.int_range(min=0, max=31),
|
||||
cv.Optional(CONF_POWER_PIN): pins.internal_gpio_output_pin_number,
|
||||
cv.Optional(CONF_MANUAL_IP): MANUAL_IP_SCHEMA,
|
||||
cv.Optional(CONF_DOMAIN, default=".local"): cv.domain_name,
|
||||
cv.Optional(CONF_USE_ADDRESS): cv.string_strict,
|
||||
cv.Optional("enable_mdns"): cv.invalid(
|
||||
"This option has been removed. Please use the [disabled] option under the "
|
||||
"new mdns component instead."
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
SPI_SCHEMA = BASE_SCHEMA.extend(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_CLK_PIN): pins.internal_gpio_output_pin_number,
|
||||
cv.Required(CONF_MISO_PIN): pins.internal_gpio_input_pin_number,
|
||||
cv.Required(CONF_MOSI_PIN): pins.internal_gpio_output_pin_number,
|
||||
cv.Required(CONF_CS_PIN): pins.internal_gpio_output_pin_number,
|
||||
cv.Optional(CONF_INTERRUPT_PIN): pins.internal_gpio_input_pin_number,
|
||||
cv.Optional(CONF_RESET_PIN): pins.internal_gpio_output_pin_number,
|
||||
cv.Optional(CONF_CLOCK_SPEED, default="26.67MHz"): cv.All(
|
||||
cv.frequency, cv.int_range(int(8e6), int(80e6))
|
||||
),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA),
|
||||
),
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.typed_schema(
|
||||
{
|
||||
"LAN8720": RMII_SCHEMA,
|
||||
"RTL8201": RMII_SCHEMA,
|
||||
"DP83848": RMII_SCHEMA,
|
||||
"IP101": RMII_SCHEMA,
|
||||
"JL1101": RMII_SCHEMA,
|
||||
"W5500": SPI_SCHEMA,
|
||||
},
|
||||
upper=True,
|
||||
),
|
||||
_validate,
|
||||
)
|
||||
|
||||
|
||||
def _final_validate(config):
|
||||
if config[CONF_TYPE] not in SPI_ETHERNET_TYPES:
|
||||
return
|
||||
if spi_configs := fv.full_config.get().get(CONF_SPI):
|
||||
variant = get_esp32_variant()
|
||||
if variant in (VARIANT_ESP32C3, VARIANT_ESP32S2, VARIANT_ESP32S3):
|
||||
spi_host = "SPI2_HOST"
|
||||
else:
|
||||
spi_host = "SPI3_HOST"
|
||||
for spi_conf in spi_configs:
|
||||
if (index := spi_conf.get(CONF_INTERFACE_INDEX)) is not None:
|
||||
interface = get_spi_interface(index)
|
||||
if interface == spi_host:
|
||||
raise cv.Invalid(
|
||||
f"`spi` component is using interface '{interface}'. "
|
||||
f"To use {config[CONF_TYPE]}, you must change the `interface` on the `spi` component.",
|
||||
)
|
||||
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = _final_validate
|
||||
|
||||
|
||||
def manual_ip(config):
|
||||
return cg.StructInitializer(
|
||||
ManualIP,
|
||||
@ -125,15 +201,31 @@ async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
|
||||
cg.add(var.set_phy_addr(config[CONF_PHY_ADDR]))
|
||||
cg.add(var.set_mdc_pin(config[CONF_MDC_PIN]))
|
||||
cg.add(var.set_mdio_pin(config[CONF_MDIO_PIN]))
|
||||
cg.add(var.set_type(config[CONF_TYPE]))
|
||||
cg.add(var.set_clk_mode(*CLK_MODES[config[CONF_CLK_MODE]]))
|
||||
cg.add(var.set_use_address(config[CONF_USE_ADDRESS]))
|
||||
if config[CONF_TYPE] == "W5500":
|
||||
cg.add(var.set_clk_pin(config[CONF_CLK_PIN]))
|
||||
cg.add(var.set_miso_pin(config[CONF_MISO_PIN]))
|
||||
cg.add(var.set_mosi_pin(config[CONF_MOSI_PIN]))
|
||||
cg.add(var.set_cs_pin(config[CONF_CS_PIN]))
|
||||
if CONF_INTERRUPT_PIN in config:
|
||||
cg.add(var.set_interrupt_pin(config[CONF_INTERRUPT_PIN]))
|
||||
if CONF_RESET_PIN in config:
|
||||
cg.add(var.set_reset_pin(config[CONF_RESET_PIN]))
|
||||
cg.add(var.set_clock_speed(config[CONF_CLOCK_SPEED]))
|
||||
|
||||
if CONF_POWER_PIN in config:
|
||||
cg.add(var.set_power_pin(config[CONF_POWER_PIN]))
|
||||
cg.add_define("USE_ETHERNET_SPI")
|
||||
if CORE.using_esp_idf:
|
||||
add_idf_sdkconfig_option("CONFIG_ETH_USE_SPI_ETHERNET", True)
|
||||
add_idf_sdkconfig_option("CONFIG_ETH_SPI_ETHERNET_W5500", True)
|
||||
else:
|
||||
cg.add(var.set_phy_addr(config[CONF_PHY_ADDR]))
|
||||
cg.add(var.set_mdc_pin(config[CONF_MDC_PIN]))
|
||||
cg.add(var.set_mdio_pin(config[CONF_MDIO_PIN]))
|
||||
cg.add(var.set_clk_mode(*CLK_MODES[config[CONF_CLK_MODE]]))
|
||||
if CONF_POWER_PIN in config:
|
||||
cg.add(var.set_power_pin(config[CONF_POWER_PIN]))
|
||||
|
||||
cg.add(var.set_type(ETHERNET_TYPES[config[CONF_TYPE]]))
|
||||
cg.add(var.set_use_address(config[CONF_USE_ADDRESS]))
|
||||
|
||||
if CONF_MANUAL_IP in config:
|
||||
cg.add(var.set_manual_ip(manual_ip(config[CONF_MANUAL_IP])))
|
||||
|
@ -9,6 +9,11 @@
|
||||
#include <lwip/dns.h>
|
||||
#include "esp_event.h"
|
||||
|
||||
#ifdef USE_ETHERNET_SPI
|
||||
#include <driver/gpio.h>
|
||||
#include <driver/spi_master.h>
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
namespace ethernet {
|
||||
|
||||
@ -33,6 +38,36 @@ void EthernetComponent::setup() {
|
||||
}
|
||||
|
||||
esp_err_t err;
|
||||
|
||||
#ifdef USE_ETHERNET_SPI
|
||||
// Install GPIO ISR handler to be able to service SPI Eth modules interrupts
|
||||
gpio_install_isr_service(0);
|
||||
|
||||
spi_bus_config_t buscfg = {
|
||||
.mosi_io_num = this->mosi_pin_,
|
||||
.miso_io_num = this->miso_pin_,
|
||||
.sclk_io_num = this->clk_pin_,
|
||||
.quadwp_io_num = -1,
|
||||
.quadhd_io_num = -1,
|
||||
.data4_io_num = -1,
|
||||
.data5_io_num = -1,
|
||||
.data6_io_num = -1,
|
||||
.data7_io_num = -1,
|
||||
.max_transfer_sz = 0,
|
||||
.flags = 0,
|
||||
.intr_flags = 0,
|
||||
};
|
||||
|
||||
#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
|
||||
auto host = SPI2_HOST;
|
||||
#else
|
||||
auto host = SPI3_HOST;
|
||||
#endif
|
||||
|
||||
err = spi_bus_initialize(host, &buscfg, SPI_DMA_CH_AUTO);
|
||||
ESPHL_ERROR_CHECK(err, "SPI bus initialize error");
|
||||
#endif
|
||||
|
||||
err = esp_netif_init();
|
||||
ESPHL_ERROR_CHECK(err, "ETH netif init error");
|
||||
err = esp_event_loop_create_default();
|
||||
@ -43,10 +78,40 @@ void EthernetComponent::setup() {
|
||||
|
||||
// Init MAC and PHY configs to default
|
||||
eth_phy_config_t phy_config = ETH_PHY_DEFAULT_CONFIG();
|
||||
eth_mac_config_t mac_config = ETH_MAC_DEFAULT_CONFIG();
|
||||
|
||||
#ifdef USE_ETHERNET_SPI // Configure SPI interface and Ethernet driver for specific SPI module
|
||||
spi_device_interface_config_t devcfg = {
|
||||
.command_bits = 16, // Actually it's the address phase in W5500 SPI frame
|
||||
.address_bits = 8, // Actually it's the control phase in W5500 SPI frame
|
||||
.dummy_bits = 0,
|
||||
.mode = 0,
|
||||
.duty_cycle_pos = 0,
|
||||
.cs_ena_pretrans = 0,
|
||||
.cs_ena_posttrans = 0,
|
||||
.clock_speed_hz = this->clock_speed_,
|
||||
.input_delay_ns = 0,
|
||||
.spics_io_num = this->cs_pin_,
|
||||
.flags = 0,
|
||||
.queue_size = 20,
|
||||
.pre_cb = nullptr,
|
||||
.post_cb = nullptr,
|
||||
};
|
||||
|
||||
spi_device_handle_t spi_handle = nullptr;
|
||||
err = spi_bus_add_device(host, &devcfg, &spi_handle);
|
||||
ESPHL_ERROR_CHECK(err, "SPI bus add device error");
|
||||
|
||||
eth_w5500_config_t w5500_config = ETH_W5500_DEFAULT_CONFIG(spi_handle);
|
||||
w5500_config.int_gpio_num = this->interrupt_pin_;
|
||||
phy_config.phy_addr = this->phy_addr_spi_;
|
||||
phy_config.reset_gpio_num = this->reset_pin_;
|
||||
|
||||
esp_eth_mac_t *mac = esp_eth_mac_new_w5500(&w5500_config, &mac_config);
|
||||
#else
|
||||
phy_config.phy_addr = this->phy_addr_;
|
||||
phy_config.reset_gpio_num = this->power_pin_;
|
||||
|
||||
eth_mac_config_t mac_config = ETH_MAC_DEFAULT_CONFIG();
|
||||
#if ESP_IDF_VERSION_MAJOR >= 5
|
||||
eth_esp32_emac_config_t esp32_emac_config = ETH_ESP32_EMAC_DEFAULT_CONFIG();
|
||||
esp32_emac_config.smi_mdc_gpio_num = this->mdc_pin_;
|
||||
@ -62,9 +127,11 @@ void EthernetComponent::setup() {
|
||||
mac_config.clock_config.rmii.clock_gpio = this->clk_gpio_;
|
||||
|
||||
esp_eth_mac_t *mac = esp_eth_mac_new_esp32(&mac_config);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
switch (this->type_) {
|
||||
#if CONFIG_ETH_USE_ESP32_EMAC
|
||||
case ETHERNET_TYPE_LAN8720: {
|
||||
this->phy_ = esp_eth_phy_new_lan87xx(&phy_config);
|
||||
break;
|
||||
@ -94,6 +161,13 @@ void EthernetComponent::setup() {
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_ETHERNET_SPI
|
||||
case ETHERNET_TYPE_W5500: {
|
||||
this->phy_ = esp_eth_phy_new_w5500(&phy_config);
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
default: {
|
||||
this->mark_failed();
|
||||
return;
|
||||
@ -105,10 +179,18 @@ void EthernetComponent::setup() {
|
||||
err = esp_eth_driver_install(ð_config, &this->eth_handle_);
|
||||
ESPHL_ERROR_CHECK(err, "ETH driver install error");
|
||||
|
||||
#ifndef USE_ETHERNET_SPI
|
||||
if (this->type_ == ETHERNET_TYPE_KSZ8081RNA && this->clk_mode_ == EMAC_CLK_OUT) {
|
||||
// KSZ8081RNA default is incorrect. It expects a 25MHz clock instead of the 50MHz we provide.
|
||||
this->ksz8081_set_clock_reference_(mac);
|
||||
}
|
||||
#endif
|
||||
|
||||
// use ESP internal eth mac
|
||||
uint8_t mac_addr[6];
|
||||
esp_read_mac(mac_addr, ESP_MAC_ETH);
|
||||
err = esp_eth_ioctl(this->eth_handle_, ETH_CMD_S_MAC_ADDR, mac_addr);
|
||||
ESPHL_ERROR_CHECK(err, "set mac address error");
|
||||
|
||||
/* attach Ethernet driver to TCP/IP stack */
|
||||
err = esp_netif_attach(this->eth_netif_, esp_eth_new_netif_glue(this->eth_handle_));
|
||||
@ -119,10 +201,10 @@ void EthernetComponent::setup() {
|
||||
ESPHL_ERROR_CHECK(err, "ETH event handler register error");
|
||||
err = esp_event_handler_register(IP_EVENT, IP_EVENT_ETH_GOT_IP, &EthernetComponent::got_ip_event_handler, nullptr);
|
||||
ESPHL_ERROR_CHECK(err, "GOT IP event handler register error");
|
||||
#if ENABLE_IPV6
|
||||
#if USE_NETWORK_IPV6
|
||||
err = esp_event_handler_register(IP_EVENT, IP_EVENT_GOT_IP6, &EthernetComponent::got_ip6_event_handler, nullptr);
|
||||
ESPHL_ERROR_CHECK(err, "GOT IP6 event handler register error");
|
||||
#endif /* ENABLE_IPV6 */
|
||||
ESPHL_ERROR_CHECK(err, "GOT IPv6 event handler register error");
|
||||
#endif /* USE_NETWORK_IPV6 */
|
||||
|
||||
/* start Ethernet driver state machine */
|
||||
err = esp_eth_start(this->eth_handle_);
|
||||
@ -165,20 +247,6 @@ void EthernetComponent::loop() {
|
||||
this->state_ = EthernetComponentState::CONNECTING;
|
||||
this->start_connect_();
|
||||
}
|
||||
#if ENABLE_IPV6
|
||||
else if (this->got_ipv6_) {
|
||||
esp_ip6_addr_t ip6_addr;
|
||||
if (esp_netif_get_ip6_global(this->eth_netif_, &ip6_addr) == 0 &&
|
||||
esp_netif_ip6_get_addr_type(&ip6_addr) == ESP_IP6_ADDR_IS_GLOBAL) {
|
||||
ESP_LOGCONFIG(TAG, "IPv6 Addr (Global): " IPV6STR, IPV62STR(ip6_addr));
|
||||
} else {
|
||||
esp_netif_get_ip6_linklocal(this->eth_netif_, &ip6_addr);
|
||||
ESP_LOGCONFIG(TAG, " IPv6: " IPV6STR, IPV62STR(ip6_addr));
|
||||
}
|
||||
|
||||
this->got_ipv6_ = false;
|
||||
}
|
||||
#endif /* ENABLE_IPV6 */
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -214,6 +282,10 @@ void EthernetComponent::dump_config() {
|
||||
eth_type = "KSZ8081RNA";
|
||||
break;
|
||||
|
||||
case ETHERNET_TYPE_W5500:
|
||||
eth_type = "W5500";
|
||||
break;
|
||||
|
||||
default:
|
||||
eth_type = "Unknown";
|
||||
break;
|
||||
@ -221,23 +293,51 @@ void EthernetComponent::dump_config() {
|
||||
|
||||
ESP_LOGCONFIG(TAG, "Ethernet:");
|
||||
this->dump_connect_params_();
|
||||
#ifdef USE_ETHERNET_SPI
|
||||
ESP_LOGCONFIG(TAG, " CLK Pin: %u", this->clk_pin_);
|
||||
ESP_LOGCONFIG(TAG, " MISO Pin: %u", this->miso_pin_);
|
||||
ESP_LOGCONFIG(TAG, " MOSI Pin: %u", this->mosi_pin_);
|
||||
ESP_LOGCONFIG(TAG, " CS Pin: %u", this->cs_pin_);
|
||||
ESP_LOGCONFIG(TAG, " IRQ Pin: %u", this->interrupt_pin_);
|
||||
ESP_LOGCONFIG(TAG, " Reset Pin: %d", this->reset_pin_);
|
||||
ESP_LOGCONFIG(TAG, " Clock Speed: %d MHz", this->clock_speed_ / 1000000);
|
||||
#else
|
||||
if (this->power_pin_ != -1) {
|
||||
ESP_LOGCONFIG(TAG, " Power Pin: %u", this->power_pin_);
|
||||
}
|
||||
ESP_LOGCONFIG(TAG, " MDC Pin: %u", this->mdc_pin_);
|
||||
ESP_LOGCONFIG(TAG, " MDIO Pin: %u", this->mdio_pin_);
|
||||
ESP_LOGCONFIG(TAG, " Type: %s", eth_type);
|
||||
ESP_LOGCONFIG(TAG, " PHY addr: %u", this->phy_addr_);
|
||||
#endif
|
||||
ESP_LOGCONFIG(TAG, " Type: %s", eth_type);
|
||||
}
|
||||
|
||||
float EthernetComponent::get_setup_priority() const { return setup_priority::WIFI; }
|
||||
|
||||
bool EthernetComponent::can_proceed() { return this->is_connected(); }
|
||||
|
||||
network::IPAddress EthernetComponent::get_ip_address() {
|
||||
network::IPAddresses EthernetComponent::get_ip_addresses() {
|
||||
network::IPAddresses addresses;
|
||||
esp_netif_ip_info_t ip;
|
||||
esp_netif_get_ip_info(this->eth_netif_, &ip);
|
||||
return network::IPAddress(&ip.ip);
|
||||
esp_err_t err = esp_netif_get_ip_info(this->eth_netif_, &ip);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGV(TAG, "esp_netif_get_ip_info failed: %s", esp_err_to_name(err));
|
||||
// TODO: do something smarter
|
||||
// return false;
|
||||
} else {
|
||||
addresses[0] = network::IPAddress(&ip.ip);
|
||||
}
|
||||
#if USE_NETWORK_IPV6
|
||||
struct esp_ip6_addr if_ip6s[CONFIG_LWIP_IPV6_NUM_ADDRESSES];
|
||||
uint8_t count = 0;
|
||||
count = esp_netif_get_all_ip6(this->eth_netif_, if_ip6s);
|
||||
assert(count <= CONFIG_LWIP_IPV6_NUM_ADDRESSES);
|
||||
for (int i = 0; i < count; i++) {
|
||||
addresses[i + 1] = network::IPAddress(&if_ip6s[i]);
|
||||
}
|
||||
#endif /* USE_NETWORK_IPV6 */
|
||||
|
||||
return addresses;
|
||||
}
|
||||
|
||||
void EthernetComponent::eth_event_handler(void *arg, esp_event_base_t event_base, int32_t event, void *event_data) {
|
||||
@ -269,20 +369,33 @@ void EthernetComponent::eth_event_handler(void *arg, esp_event_base_t event_base
|
||||
|
||||
void EthernetComponent::got_ip_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id,
|
||||
void *event_data) {
|
||||
ip_event_got_ip_t *event = (ip_event_got_ip_t *) event_data;
|
||||
const esp_netif_ip_info_t *ip_info = &event->ip_info;
|
||||
ESP_LOGV(TAG, "[Ethernet event] ETH Got IP " IPSTR, IP2STR(&ip_info->ip));
|
||||
global_eth_component->got_ipv4_address_ = true;
|
||||
#if USE_NETWORK_IPV6
|
||||
global_eth_component->connected_ = global_eth_component->ipv6_count_ >= USE_NETWORK_MIN_IPV6_ADDR_COUNT;
|
||||
#else
|
||||
global_eth_component->connected_ = true;
|
||||
ESP_LOGV(TAG, "[Ethernet event] ETH Got IP (num=%" PRId32 ")", event_id);
|
||||
#endif /* USE_NETWORK_IPV6 */
|
||||
}
|
||||
|
||||
#if ENABLE_IPV6
|
||||
#if USE_NETWORK_IPV6
|
||||
void EthernetComponent::got_ip6_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id,
|
||||
void *event_data) {
|
||||
ESP_LOGV(TAG, "[Ethernet event] ETH Got IP6 (num=%" PRId32 ")", event_id);
|
||||
global_eth_component->got_ipv6_ = true;
|
||||
ip_event_got_ip6_t *event = (ip_event_got_ip6_t *) event_data;
|
||||
ESP_LOGV(TAG, "[Ethernet event] ETH Got IPv6: " IPV6STR, IPV62STR(event->ip6_info.ip));
|
||||
global_eth_component->ipv6_count_ += 1;
|
||||
global_eth_component->connected_ =
|
||||
global_eth_component->got_ipv4_address_ && (global_eth_component->ipv6_count_ >= USE_NETWORK_MIN_IPV6_ADDR_COUNT);
|
||||
}
|
||||
#endif /* ENABLE_IPV6 */
|
||||
#endif /* USE_NETWORK_IPV6 */
|
||||
|
||||
void EthernetComponent::start_connect_() {
|
||||
global_eth_component->got_ipv4_address_ = false;
|
||||
#if USE_NETWORK_IPV6
|
||||
global_eth_component->ipv6_count_ = 0;
|
||||
#endif /* USE_NETWORK_IPV6 */
|
||||
this->connect_begin_ = millis();
|
||||
this->status_set_warning();
|
||||
|
||||
@ -334,12 +447,12 @@ void EthernetComponent::start_connect_() {
|
||||
if (err != ESP_ERR_ESP_NETIF_DHCP_ALREADY_STARTED) {
|
||||
ESPHL_ERROR_CHECK(err, "DHCPC start error");
|
||||
}
|
||||
#if ENABLE_IPV6
|
||||
#if USE_NETWORK_IPV6
|
||||
err = esp_netif_create_ip6_linklocal(this->eth_netif_);
|
||||
if (err != ESP_OK) {
|
||||
ESPHL_ERROR_CHECK(err, "IPv6 local failed");
|
||||
ESPHL_ERROR_CHECK(err, "Enable IPv6 link local failed");
|
||||
}
|
||||
#endif /* ENABLE_IPV6 */
|
||||
#endif /* USE_NETWORK_IPV6 */
|
||||
}
|
||||
|
||||
this->connect_begin_ = millis();
|
||||
@ -362,18 +475,15 @@ void EthernetComponent::dump_connect_params_() {
|
||||
ESP_LOGCONFIG(TAG, " DNS1: %s", network::IPAddress(dns_ip1).str().c_str());
|
||||
ESP_LOGCONFIG(TAG, " DNS2: %s", network::IPAddress(dns_ip2).str().c_str());
|
||||
|
||||
#if ENABLE_IPV6
|
||||
if (this->ipv6_count_ > 0) {
|
||||
esp_ip6_addr_t ip6_addr;
|
||||
esp_netif_get_ip6_linklocal(this->eth_netif_, &ip6_addr);
|
||||
ESP_LOGCONFIG(TAG, " IPv6: " IPV6STR, IPV62STR(ip6_addr));
|
||||
|
||||
if (esp_netif_get_ip6_global(this->eth_netif_, &ip6_addr) == 0 &&
|
||||
esp_netif_ip6_get_addr_type(&ip6_addr) == ESP_IP6_ADDR_IS_GLOBAL) {
|
||||
ESP_LOGCONFIG(TAG, "IPv6 Addr (Global): " IPV6STR, IPV62STR(ip6_addr));
|
||||
}
|
||||
#if USE_NETWORK_IPV6
|
||||
struct esp_ip6_addr if_ip6s[CONFIG_LWIP_IPV6_NUM_ADDRESSES];
|
||||
uint8_t count = 0;
|
||||
count = esp_netif_get_all_ip6(this->eth_netif_, if_ip6s);
|
||||
assert(count <= CONFIG_LWIP_IPV6_NUM_ADDRESSES);
|
||||
for (int i = 0; i < count; i++) {
|
||||
ESP_LOGCONFIG(TAG, " IPv6: " IPV6STR, IPV62STR(if_ip6s[i]));
|
||||
}
|
||||
#endif /* ENABLE_IPV6 */
|
||||
#endif /* USE_NETWORK_IPV6 */
|
||||
|
||||
esp_err_t err;
|
||||
|
||||
@ -393,15 +503,25 @@ void EthernetComponent::dump_connect_params_() {
|
||||
ESP_LOGCONFIG(TAG, " Link Speed: %u", speed == ETH_SPEED_100M ? 100 : 10);
|
||||
}
|
||||
|
||||
#ifdef USE_ETHERNET_SPI
|
||||
void EthernetComponent::set_clk_pin(uint8_t clk_pin) { this->clk_pin_ = clk_pin; }
|
||||
void EthernetComponent::set_miso_pin(uint8_t miso_pin) { this->miso_pin_ = miso_pin; }
|
||||
void EthernetComponent::set_mosi_pin(uint8_t mosi_pin) { this->mosi_pin_ = mosi_pin; }
|
||||
void EthernetComponent::set_cs_pin(uint8_t cs_pin) { this->cs_pin_ = cs_pin; }
|
||||
void EthernetComponent::set_interrupt_pin(uint8_t interrupt_pin) { this->interrupt_pin_ = interrupt_pin; }
|
||||
void EthernetComponent::set_reset_pin(uint8_t reset_pin) { this->reset_pin_ = reset_pin; }
|
||||
void EthernetComponent::set_clock_speed(int clock_speed) { this->clock_speed_ = clock_speed; }
|
||||
#else
|
||||
void EthernetComponent::set_phy_addr(uint8_t phy_addr) { this->phy_addr_ = phy_addr; }
|
||||
void EthernetComponent::set_power_pin(int power_pin) { this->power_pin_ = power_pin; }
|
||||
void EthernetComponent::set_mdc_pin(uint8_t mdc_pin) { this->mdc_pin_ = mdc_pin; }
|
||||
void EthernetComponent::set_mdio_pin(uint8_t mdio_pin) { this->mdio_pin_ = mdio_pin; }
|
||||
void EthernetComponent::set_type(EthernetType type) { this->type_ = type; }
|
||||
void EthernetComponent::set_clk_mode(emac_rmii_clock_mode_t clk_mode, emac_rmii_clock_gpio_t clk_gpio) {
|
||||
this->clk_mode_ = clk_mode;
|
||||
this->clk_gpio_ = clk_gpio;
|
||||
}
|
||||
#endif
|
||||
void EthernetComponent::set_type(EthernetType type) { this->type_ = type; }
|
||||
void EthernetComponent::set_manual_ip(const ManualIP &manual_ip) { this->manual_ip_ = manual_ip; }
|
||||
|
||||
std::string EthernetComponent::get_use_address() const {
|
||||
@ -428,6 +548,7 @@ bool EthernetComponent::powerdown() {
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifndef USE_ETHERNET_SPI
|
||||
void EthernetComponent::ksz8081_set_clock_reference_(esp_eth_mac_t *mac) {
|
||||
#define KSZ80XX_PC2R_REG_ADDR (0x1F)
|
||||
|
||||
@ -458,6 +579,7 @@ void EthernetComponent::ksz8081_set_clock_reference_(esp_eth_mac_t *mac) {
|
||||
|
||||
#undef KSZ80XX_PC2R_REG_ADDR
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace ethernet
|
||||
} // namespace esphome
|
||||
|
@ -23,6 +23,7 @@ enum EthernetType {
|
||||
ETHERNET_TYPE_JL1101,
|
||||
ETHERNET_TYPE_KSZ8081,
|
||||
ETHERNET_TYPE_KSZ8081RNA,
|
||||
ETHERNET_TYPE_W5500,
|
||||
};
|
||||
|
||||
struct ManualIP {
|
||||
@ -50,15 +51,25 @@ class EthernetComponent : public Component {
|
||||
void on_shutdown() override { powerdown(); }
|
||||
bool is_connected();
|
||||
|
||||
#ifdef USE_ETHERNET_SPI
|
||||
void set_clk_pin(uint8_t clk_pin);
|
||||
void set_miso_pin(uint8_t miso_pin);
|
||||
void set_mosi_pin(uint8_t mosi_pin);
|
||||
void set_cs_pin(uint8_t cs_pin);
|
||||
void set_interrupt_pin(uint8_t interrupt_pin);
|
||||
void set_reset_pin(uint8_t reset_pin);
|
||||
void set_clock_speed(int clock_speed);
|
||||
#else
|
||||
void set_phy_addr(uint8_t phy_addr);
|
||||
void set_power_pin(int power_pin);
|
||||
void set_mdc_pin(uint8_t mdc_pin);
|
||||
void set_mdio_pin(uint8_t mdio_pin);
|
||||
void set_type(EthernetType type);
|
||||
void set_clk_mode(emac_rmii_clock_mode_t clk_mode, emac_rmii_clock_gpio_t clk_gpio);
|
||||
#endif
|
||||
void set_type(EthernetType type);
|
||||
void set_manual_ip(const ManualIP &manual_ip);
|
||||
|
||||
network::IPAddress get_ip_address();
|
||||
network::IPAddresses get_ip_addresses();
|
||||
std::string get_use_address() const;
|
||||
void set_use_address(const std::string &use_address);
|
||||
bool powerdown();
|
||||
@ -76,19 +87,30 @@ class EthernetComponent : public Component {
|
||||
void ksz8081_set_clock_reference_(esp_eth_mac_t *mac);
|
||||
|
||||
std::string use_address_;
|
||||
#ifdef USE_ETHERNET_SPI
|
||||
uint8_t clk_pin_;
|
||||
uint8_t miso_pin_;
|
||||
uint8_t mosi_pin_;
|
||||
uint8_t cs_pin_;
|
||||
uint8_t interrupt_pin_;
|
||||
int reset_pin_{-1};
|
||||
int phy_addr_spi_{-1};
|
||||
int clock_speed_;
|
||||
#else
|
||||
uint8_t phy_addr_{0};
|
||||
int power_pin_{-1};
|
||||
uint8_t mdc_pin_{23};
|
||||
uint8_t mdio_pin_{18};
|
||||
EthernetType type_{ETHERNET_TYPE_UNKNOWN};
|
||||
emac_rmii_clock_mode_t clk_mode_{EMAC_CLK_EXT_IN};
|
||||
emac_rmii_clock_gpio_t clk_gpio_{EMAC_CLK_IN_GPIO};
|
||||
#endif
|
||||
EthernetType type_{ETHERNET_TYPE_UNKNOWN};
|
||||
optional<ManualIP> manual_ip_{};
|
||||
|
||||
bool started_{false};
|
||||
bool connected_{false};
|
||||
bool got_ipv4_address_{false};
|
||||
#if LWIP_IPV6
|
||||
bool got_ipv6_{false};
|
||||
uint8_t ipv6_count_{0};
|
||||
#endif /* LWIP_IPV6 */
|
||||
EthernetComponentState state_{EthernetComponentState::STOPPED};
|
||||
|
@ -12,19 +12,30 @@ namespace ethernet_info {
|
||||
class IPAddressEthernetInfo : public PollingComponent, public text_sensor::TextSensor {
|
||||
public:
|
||||
void update() override {
|
||||
auto ip = ethernet::global_eth_component->get_ip_address();
|
||||
if (ip != this->last_ip_) {
|
||||
this->last_ip_ = ip;
|
||||
this->publish_state(network::IPAddress(ip).str());
|
||||
auto ips = ethernet::global_eth_component->get_ip_addresses();
|
||||
if (ips != this->last_ips_) {
|
||||
this->last_ips_ = ips;
|
||||
this->publish_state(ips[0].str());
|
||||
uint8_t sensor = 0;
|
||||
for (auto &ip : ips) {
|
||||
if (ip.is_set()) {
|
||||
if (this->ip_sensors_[sensor] != nullptr) {
|
||||
this->ip_sensors_[sensor]->publish_state(ip.str());
|
||||
}
|
||||
sensor++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
float get_setup_priority() const override { return setup_priority::ETHERNET; }
|
||||
std::string unique_id() override { return get_mac_address() + "-ethernetinfo"; }
|
||||
void dump_config() override;
|
||||
void add_ip_sensors(uint8_t index, text_sensor::TextSensor *s) { this->ip_sensors_[index] = s; }
|
||||
|
||||
protected:
|
||||
network::IPAddress last_ip_;
|
||||
network::IPAddresses last_ips_;
|
||||
std::array<text_sensor::TextSensor *, 5> ip_sensors_;
|
||||
};
|
||||
|
||||
} // namespace ethernet_info
|
||||
|
@ -18,17 +18,25 @@ CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_IP_ADDRESS): text_sensor.text_sensor_schema(
|
||||
IPAddressEsthernetInfo, entity_category=ENTITY_CATEGORY_DIAGNOSTIC
|
||||
).extend(cv.polling_component_schema("1s"))
|
||||
)
|
||||
.extend(cv.polling_component_schema("1s"))
|
||||
.extend(
|
||||
{
|
||||
cv.Optional(f"address_{x}"): text_sensor.text_sensor_schema(
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
)
|
||||
for x in range(5)
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def setup_conf(config, key):
|
||||
if key in config:
|
||||
conf = config[key]
|
||||
var = await text_sensor.new_text_sensor(conf)
|
||||
await cg.register_component(var, conf)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
await setup_conf(config, CONF_IP_ADDRESS)
|
||||
if conf := config.get(CONF_IP_ADDRESS):
|
||||
ip_info = await text_sensor.new_text_sensor(config[CONF_IP_ADDRESS])
|
||||
await cg.register_component(ip_info, config[CONF_IP_ADDRESS])
|
||||
for x in range(5):
|
||||
if sensor_conf := conf.get(f"address_{x}"):
|
||||
sens = await text_sensor.new_text_sensor(sensor_conf)
|
||||
cg.add(ip_info.add_ip_sensors(x, sens))
|
||||
|
@ -336,6 +336,8 @@ void FingerprintGrowComponent::aura_led_control(uint8_t state, uint8_t speed, ui
|
||||
}
|
||||
|
||||
uint8_t FingerprintGrowComponent::send_command_() {
|
||||
while (this->available())
|
||||
this->read();
|
||||
this->write((uint8_t) (START_CODE >> 8));
|
||||
this->write((uint8_t) (START_CODE & 0xFF));
|
||||
this->write(this->address_[0]);
|
||||
|
@ -66,8 +66,14 @@ class FT5x06Touchscreen : public touchscreen::Touchscreen, public i2c::I2CDevice
|
||||
return;
|
||||
}
|
||||
// reading the chip registers to get max x/y does not seem to work.
|
||||
this->x_raw_max_ = this->display_->get_width();
|
||||
this->y_raw_max_ = this->display_->get_height();
|
||||
if (this->display_ != nullptr) {
|
||||
if (this->x_raw_max_ == this->x_raw_min_) {
|
||||
this->x_raw_max_ = this->display_->get_native_width();
|
||||
}
|
||||
if (this->y_raw_max_ == this->y_raw_min_) {
|
||||
this->x_raw_max_ = this->display_->get_native_height();
|
||||
}
|
||||
}
|
||||
esph_log_config(TAG, "FT5x06 Touchscreen setup complete");
|
||||
}
|
||||
|
||||
|
@ -12,21 +12,23 @@
|
||||
// Reference: https://focuslcds.com/content/FT6236.pdf
|
||||
namespace esphome {
|
||||
namespace ft63x6 {
|
||||
static const uint8_t FT6X36_ADDR_DEVICE_MODE = 0x00;
|
||||
|
||||
static const uint8_t FT63X6_ADDR_TD_STATUS = 0x02;
|
||||
static const uint8_t FT63X6_ADDR_TOUCH1_STATE = 0x03;
|
||||
static const uint8_t FT63X6_ADDR_TOUCH1_X = 0x03;
|
||||
static const uint8_t FT63X6_ADDR_TOUCH1_ID = 0x05;
|
||||
static const uint8_t FT63X6_ADDR_TOUCH1_Y = 0x05;
|
||||
static const uint8_t FT63X6_ADDR_TOUCH1_WEIGHT = 0x07;
|
||||
static const uint8_t FT63X6_ADDR_TOUCH1_MISC = 0x08;
|
||||
static const uint8_t FT6X36_ADDR_THRESHHOLD = 0x80;
|
||||
static const uint8_t FT6X36_ADDR_TOUCHRATE_ACTIVE = 0x88;
|
||||
static const uint8_t FT63X6_ADDR_CHIP_ID = 0xA3;
|
||||
|
||||
static const uint8_t FT63X6_ADDR_TOUCH2_STATE = 0x09;
|
||||
static const uint8_t FT63X6_ADDR_TOUCH2_X = 0x09;
|
||||
static const uint8_t FT63X6_ADDR_TOUCH2_ID = 0x0B;
|
||||
static const uint8_t FT63X6_ADDR_TOUCH2_Y = 0x0B;
|
||||
|
||||
static const char *const TAG = "FT63X6Touchscreen";
|
||||
static const char *const TAG = "FT63X6";
|
||||
|
||||
void FT63X6Touchscreen::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up FT63X6Touchscreen Touchscreen...");
|
||||
ESP_LOGCONFIG(TAG, "Setting up FT63X6 Touchscreen...");
|
||||
if (this->interrupt_pin_ != nullptr) {
|
||||
this->interrupt_pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
|
||||
this->interrupt_pin_->setup();
|
||||
@ -35,10 +37,9 @@ void FT63X6Touchscreen::setup() {
|
||||
|
||||
if (this->reset_pin_ != nullptr) {
|
||||
this->reset_pin_->setup();
|
||||
this->hard_reset_();
|
||||
}
|
||||
|
||||
this->hard_reset_();
|
||||
|
||||
// Get touch resolution
|
||||
if (this->x_raw_max_ == this->x_raw_min_) {
|
||||
this->x_raw_max_ = 320;
|
||||
@ -46,6 +47,15 @@ void FT63X6Touchscreen::setup() {
|
||||
if (this->y_raw_max_ == this->y_raw_min_) {
|
||||
this->y_raw_max_ = 480;
|
||||
}
|
||||
uint8_t chip_id = this->read_byte_(FT63X6_ADDR_CHIP_ID);
|
||||
if (chip_id != 0) {
|
||||
ESP_LOGI(TAG, "FT6336U touch driver started chipid: %d", chip_id);
|
||||
} else {
|
||||
ESP_LOGE(TAG, "FT6336U touch driver failed to start");
|
||||
}
|
||||
this->write_byte(FT6X36_ADDR_DEVICE_MODE, 0x00);
|
||||
this->write_byte(FT6X36_ADDR_THRESHHOLD, this->threshold_);
|
||||
this->write_byte(FT6X36_ADDR_TOUCHRATE_ACTIVE, 0x0E);
|
||||
}
|
||||
|
||||
void FT63X6Touchscreen::hard_reset_() {
|
||||
@ -65,28 +75,61 @@ void FT63X6Touchscreen::dump_config() {
|
||||
}
|
||||
|
||||
void FT63X6Touchscreen::update_touches() {
|
||||
uint8_t data[15];
|
||||
uint16_t touch_id, x, y;
|
||||
|
||||
if (!this->read_bytes(0x00, (uint8_t *) data, 15)) {
|
||||
ESP_LOGE(TAG, "Failed to read touch data");
|
||||
this->skip_update_ = true;
|
||||
uint8_t touches = this->read_touch_number_();
|
||||
if ((touches == 0x00) || (touches == 0xff)) {
|
||||
// ESP_LOGD(TAG, "No touches detected");
|
||||
return;
|
||||
}
|
||||
|
||||
if (((data[FT63X6_ADDR_TOUCH1_STATE] >> 6) & 0x01) == 0) {
|
||||
touch_id = data[FT63X6_ADDR_TOUCH1_ID] >> 4; // id1 = 0 or 1
|
||||
x = encode_uint16(data[FT63X6_ADDR_TOUCH1_X] & 0x0F, data[FT63X6_ADDR_TOUCH1_X + 1]);
|
||||
y = encode_uint16(data[FT63X6_ADDR_TOUCH1_Y] & 0x0F, data[FT63X6_ADDR_TOUCH1_Y + 1]);
|
||||
this->add_raw_touch_position_(touch_id, x, y);
|
||||
}
|
||||
if (((data[FT63X6_ADDR_TOUCH2_STATE] >> 6) & 0x01) == 0) {
|
||||
touch_id = data[FT63X6_ADDR_TOUCH2_ID] >> 4; // id1 = 0 or 1
|
||||
x = encode_uint16(data[FT63X6_ADDR_TOUCH2_X] & 0x0F, data[FT63X6_ADDR_TOUCH2_X + 1]);
|
||||
y = encode_uint16(data[FT63X6_ADDR_TOUCH2_Y] & 0x0F, data[FT63X6_ADDR_TOUCH2_Y + 1]);
|
||||
this->add_raw_touch_position_(touch_id, x, y);
|
||||
ESP_LOGV(TAG, "Touches found: %d", touches);
|
||||
|
||||
for (auto point = 0; point < touches; point++) {
|
||||
if (((this->read_touch_event_(point)) & 0x01) == 0) { // checking event flag bit 6 if it is null
|
||||
touch_id = this->read_touch_id_(point); // id1 = 0 or 1
|
||||
x = this->read_touch_x_(point);
|
||||
y = this->read_touch_y_(point);
|
||||
if ((x == 0) && (y == 0)) {
|
||||
ESP_LOGW(TAG, "Reporting a (0,0) touch on %d", touch_id);
|
||||
}
|
||||
this->add_raw_touch_position_(touch_id, x, y, this->read_touch_weight_(point));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t FT63X6Touchscreen::read_touch_number_() { return this->read_byte_(FT63X6_ADDR_TD_STATUS) & 0x0F; }
|
||||
// Touch 1 functions
|
||||
uint16_t FT63X6Touchscreen::read_touch_x_(uint8_t touch) {
|
||||
uint8_t read_buf[2];
|
||||
read_buf[0] = this->read_byte_(FT63X6_ADDR_TOUCH1_X + (touch * 6));
|
||||
read_buf[1] = this->read_byte_(FT63X6_ADDR_TOUCH1_X + 1 + (touch * 6));
|
||||
return ((read_buf[0] & 0x0f) << 8) | read_buf[1];
|
||||
}
|
||||
uint16_t FT63X6Touchscreen::read_touch_y_(uint8_t touch) {
|
||||
uint8_t read_buf[2];
|
||||
read_buf[0] = this->read_byte_(FT63X6_ADDR_TOUCH1_Y + (touch * 6));
|
||||
read_buf[1] = this->read_byte_(FT63X6_ADDR_TOUCH1_Y + 1 + (touch * 6));
|
||||
return ((read_buf[0] & 0x0f) << 8) | read_buf[1];
|
||||
}
|
||||
uint8_t FT63X6Touchscreen::read_touch_event_(uint8_t touch) {
|
||||
return this->read_byte_(FT63X6_ADDR_TOUCH1_X + (touch * 6)) >> 6;
|
||||
}
|
||||
uint8_t FT63X6Touchscreen::read_touch_id_(uint8_t touch) {
|
||||
return this->read_byte_(FT63X6_ADDR_TOUCH1_ID + (touch * 6)) >> 4;
|
||||
}
|
||||
uint8_t FT63X6Touchscreen::read_touch_weight_(uint8_t touch) {
|
||||
return this->read_byte_(FT63X6_ADDR_TOUCH1_WEIGHT + (touch * 6));
|
||||
}
|
||||
uint8_t FT63X6Touchscreen::read_touch_misc_(uint8_t touch) {
|
||||
return this->read_byte_(FT63X6_ADDR_TOUCH1_MISC + (touch * 6)) >> 4;
|
||||
}
|
||||
|
||||
uint8_t FT63X6Touchscreen::read_byte_(uint8_t addr) {
|
||||
uint8_t byte = 0;
|
||||
this->read_byte(addr, &byte);
|
||||
return byte;
|
||||
}
|
||||
|
||||
} // namespace ft63x6
|
||||
} // namespace esphome
|
||||
|
@ -16,6 +16,8 @@ namespace ft63x6 {
|
||||
|
||||
using namespace touchscreen;
|
||||
|
||||
static const uint8_t FT6X36_DEFAULT_THRESHOLD = 22;
|
||||
|
||||
class FT63X6Touchscreen : public Touchscreen, public i2c::I2CDevice {
|
||||
public:
|
||||
void setup() override;
|
||||
@ -23,18 +25,26 @@ class FT63X6Touchscreen : public Touchscreen, public i2c::I2CDevice {
|
||||
|
||||
void set_interrupt_pin(InternalGPIOPin *pin) { this->interrupt_pin_ = pin; }
|
||||
void set_reset_pin(GPIOPin *pin) { this->reset_pin_ = pin; }
|
||||
void set_threshold(uint8_t threshold) { this->threshold_ = threshold; }
|
||||
|
||||
protected:
|
||||
void hard_reset_();
|
||||
uint8_t read_byte_(uint8_t addr);
|
||||
void update_touches() override;
|
||||
|
||||
InternalGPIOPin *interrupt_pin_{nullptr};
|
||||
GPIOPin *reset_pin_{nullptr};
|
||||
uint8_t threshold_{FT6X36_DEFAULT_THRESHOLD};
|
||||
|
||||
uint8_t read_touch_count_();
|
||||
uint16_t read_touch_coordinate_(uint8_t coordinate);
|
||||
uint8_t read_touch_id_(uint8_t id_address);
|
||||
uint8_t read_touch_number_();
|
||||
|
||||
uint16_t read_touch_x_(uint8_t touch);
|
||||
uint16_t read_touch_y_(uint8_t touch);
|
||||
uint8_t read_touch_event_(uint8_t touch);
|
||||
uint8_t read_touch_id_(uint8_t touch);
|
||||
uint8_t read_touch_weight_(uint8_t touch);
|
||||
uint8_t read_touch_misc_(uint8_t touch);
|
||||
|
||||
uint8_t read_byte_(uint8_t addr);
|
||||
};
|
||||
|
||||
} // namespace ft63x6
|
||||
|
@ -3,7 +3,7 @@ import esphome.config_validation as cv
|
||||
|
||||
from esphome import pins
|
||||
from esphome.components import i2c, touchscreen
|
||||
from esphome.const import CONF_ID, CONF_INTERRUPT_PIN, CONF_RESET_PIN
|
||||
from esphome.const import CONF_ID, CONF_INTERRUPT_PIN, CONF_RESET_PIN, CONF_THRESHOLD
|
||||
|
||||
CODEOWNERS = ["@gpambrozio"]
|
||||
DEPENDENCIES = ["i2c"]
|
||||
@ -26,6 +26,7 @@ CONFIG_SCHEMA = touchscreen.TOUCHSCREEN_SCHEMA.extend(
|
||||
pins.internal_gpio_input_pin_schema
|
||||
),
|
||||
cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema,
|
||||
cv.Optional(CONF_THRESHOLD): cv.uint8_t,
|
||||
}
|
||||
).extend(i2c.i2c_device_schema(0x38))
|
||||
)
|
||||
|
@ -48,9 +48,13 @@ void GT911Touchscreen::setup() {
|
||||
if (err == i2c::ERROR_OK) {
|
||||
err = this->read(data, sizeof(data));
|
||||
if (err == i2c::ERROR_OK) {
|
||||
this->x_raw_max_ = encode_uint16(data[1], data[0]);
|
||||
this->y_raw_max_ = encode_uint16(data[3], data[2]);
|
||||
esph_log_d(TAG, "Read max_x/max_y %d/%d", this->x_raw_max_, this->y_raw_max_);
|
||||
if (this->x_raw_max_ == this->x_raw_min_) {
|
||||
this->x_raw_max_ = encode_uint16(data[1], data[0]);
|
||||
}
|
||||
if (this->y_raw_max_ == this->y_raw_min_) {
|
||||
this->y_raw_max_ = encode_uint16(data[3], data[2]);
|
||||
}
|
||||
esph_log_d(TAG, "calibration max_x/max_y %d/%d", this->x_raw_max_, this->y_raw_max_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
70
esphome/components/haier/binary_sensor/__init__.py
Normal file
70
esphome/components/haier/binary_sensor/__init__.py
Normal file
@ -0,0 +1,70 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import binary_sensor
|
||||
from esphome.const import (
|
||||
ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
ICON_FAN,
|
||||
ICON_RADIATOR,
|
||||
)
|
||||
from ..climate import (
|
||||
CONF_HAIER_ID,
|
||||
HonClimate,
|
||||
)
|
||||
|
||||
BinarySensorTypeEnum = HonClimate.enum("SubBinarySensorType", True)
|
||||
|
||||
# Haier sensors
|
||||
CONF_OUTDOOR_FAN_STATUS = "outdoor_fan_status"
|
||||
CONF_DEFROST_STATUS = "defrost_status"
|
||||
CONF_COMPRESSOR_STATUS = "compressor_status"
|
||||
CONF_INDOOR_FAN_STATUS = "indoor_fan_status"
|
||||
CONF_FOUR_WAY_VALVE_STATUS = "four_way_valve_status"
|
||||
CONF_INDOOR_ELECTRIC_HEATING_STATUS = "indoor_electric_heating_status"
|
||||
|
||||
# Additional icons
|
||||
ICON_SNOWFLAKE_THERMOMETER = "mdi:snowflake-thermometer"
|
||||
ICON_HVAC = "mdi:hvac"
|
||||
ICON_VALVE = "mdi:valve"
|
||||
|
||||
SENSOR_TYPES = {
|
||||
CONF_OUTDOOR_FAN_STATUS: binary_sensor.binary_sensor_schema(
|
||||
icon=ICON_FAN,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
CONF_DEFROST_STATUS: binary_sensor.binary_sensor_schema(
|
||||
icon=ICON_SNOWFLAKE_THERMOMETER,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
CONF_COMPRESSOR_STATUS: binary_sensor.binary_sensor_schema(
|
||||
icon=ICON_HVAC,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
CONF_INDOOR_FAN_STATUS: binary_sensor.binary_sensor_schema(
|
||||
icon=ICON_FAN,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
CONF_FOUR_WAY_VALVE_STATUS: binary_sensor.binary_sensor_schema(
|
||||
icon=ICON_VALVE,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
CONF_INDOOR_ELECTRIC_HEATING_STATUS: binary_sensor.binary_sensor_schema(
|
||||
icon=ICON_RADIATOR,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
}
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_HAIER_ID): cv.use_id(HonClimate),
|
||||
}
|
||||
).extend({cv.Optional(type): schema for type, schema in SENSOR_TYPES.items()})
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
paren = await cg.get_variable(config[CONF_HAIER_ID])
|
||||
|
||||
for type, _ in SENSOR_TYPES.items():
|
||||
if conf := config.get(type):
|
||||
sens = await binary_sensor.new_binary_sensor(conf)
|
||||
binary_sensor_type = getattr(BinarySensorTypeEnum, type.upper())
|
||||
cg.add(paren.set_sub_binary_sensor(binary_sensor_type, sens))
|
@ -2,7 +2,7 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
import esphome.final_validate as fv
|
||||
from esphome.components import uart, sensor, climate, logger
|
||||
from esphome.components import uart, climate, logger
|
||||
from esphome import automation
|
||||
from esphome.const import (
|
||||
CONF_BEEPER,
|
||||
@ -21,10 +21,6 @@ from esphome.const import (
|
||||
CONF_TRIGGER_ID,
|
||||
CONF_VISUAL,
|
||||
CONF_WIFI,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
ICON_THERMOMETER,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_CELSIUS,
|
||||
)
|
||||
from esphome.components.climate import (
|
||||
ClimateMode,
|
||||
@ -42,7 +38,6 @@ PROTOCOL_CURRENT_TEMPERATURE_STEP = 0.5
|
||||
PROTOCOL_CONTROL_PACKET_SIZE = 10
|
||||
|
||||
CODEOWNERS = ["@paveldn"]
|
||||
AUTO_LOAD = ["sensor"]
|
||||
DEPENDENCIES = ["climate", "uart"]
|
||||
CONF_ALTERNATIVE_SWING_CONTROL = "alternative_swing_control"
|
||||
CONF_ANSWER_TIMEOUT = "answer_timeout"
|
||||
@ -58,7 +53,6 @@ CONF_WIFI_SIGNAL = "wifi_signal"
|
||||
|
||||
PROTOCOL_HON = "HON"
|
||||
PROTOCOL_SMARTAIR2 = "SMARTAIR2"
|
||||
PROTOCOLS_SUPPORTED = [PROTOCOL_HON, PROTOCOL_SMARTAIR2]
|
||||
|
||||
haier_ns = cg.esphome_ns.namespace("haier")
|
||||
HaierClimateBase = haier_ns.class_(
|
||||
@ -67,6 +61,7 @@ HaierClimateBase = haier_ns.class_(
|
||||
HonClimate = haier_ns.class_("HonClimate", HaierClimateBase)
|
||||
Smartair2Climate = haier_ns.class_("Smartair2Climate", HaierClimateBase)
|
||||
|
||||
CONF_HAIER_ID = "haier_id"
|
||||
|
||||
AirflowVerticalDirection = haier_ns.enum("AirflowVerticalDirection", True)
|
||||
AIRFLOW_VERTICAL_DIRECTION_OPTIONS = {
|
||||
@ -239,12 +234,8 @@ CONFIG_SCHEMA = cv.All(
|
||||
): cv.ensure_list(
|
||||
cv.enum(SUPPORTED_CLIMATE_PRESETS_HON_OPTIONS, upper=True)
|
||||
),
|
||||
cv.Optional(CONF_OUTDOOR_TEMPERATURE): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_CELSIUS,
|
||||
icon=ICON_THERMOMETER,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
cv.Optional(CONF_OUTDOOR_TEMPERATURE): cv.invalid(
|
||||
f"The {CONF_OUTDOOR_TEMPERATURE} option is deprecated, use a sensor for a haier platform instead"
|
||||
),
|
||||
cv.Optional(CONF_ON_ALARM_START): automation.validate_automation(
|
||||
{
|
||||
@ -463,9 +454,6 @@ async def to_code(config):
|
||||
cg.add(var.set_beeper_state(config[CONF_BEEPER]))
|
||||
if CONF_DISPLAY in config:
|
||||
cg.add(var.set_display_state(config[CONF_DISPLAY]))
|
||||
if CONF_OUTDOOR_TEMPERATURE in config:
|
||||
sens = await sensor.new_sensor(config[CONF_OUTDOOR_TEMPERATURE])
|
||||
cg.add(var.set_outdoor_temperature_sensor(sens))
|
||||
if CONF_SUPPORTED_MODES in config:
|
||||
cg.add(var.set_supported_modes(config[CONF_SUPPORTED_MODES]))
|
||||
if CONF_SUPPORTED_SWING_MODES in config:
|
||||
|
@ -2,6 +2,7 @@
|
||||
#include <string>
|
||||
#include "esphome/components/climate/climate.h"
|
||||
#include "esphome/components/uart/uart.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "hon_climate.h"
|
||||
#include "hon_packet.h"
|
||||
|
||||
@ -51,10 +52,9 @@ hon_protocol::HorizontalSwingMode get_horizontal_swing_mode(AirflowHorizontalDir
|
||||
}
|
||||
|
||||
HonClimate::HonClimate()
|
||||
: cleaning_status_(CleaningState::NO_CLEANING),
|
||||
got_valid_outdoor_temp_(false),
|
||||
active_alarms_{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
|
||||
outdoor_sensor_(nullptr) {
|
||||
: cleaning_status_(CleaningState::NO_CLEANING), got_valid_outdoor_temp_(false), active_alarms_{0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00,
|
||||
0x00, 0x00} {
|
||||
last_status_message_ = std::unique_ptr<uint8_t[]>(new uint8_t[sizeof(hon_protocol::HaierPacketControl)]);
|
||||
this->fan_mode_speed_ = (uint8_t) hon_protocol::FanMode::FAN_MID;
|
||||
this->other_modes_fan_speed_ = (uint8_t) hon_protocol::FanMode::FAN_AUTO;
|
||||
@ -66,8 +66,6 @@ void HonClimate::set_beeper_state(bool state) { this->beeper_status_ = state; }
|
||||
|
||||
bool HonClimate::get_beeper_state() const { return this->beeper_status_; }
|
||||
|
||||
void HonClimate::set_outdoor_temperature_sensor(esphome::sensor::Sensor *sensor) { this->outdoor_sensor_ = sensor; }
|
||||
|
||||
AirflowVerticalDirection HonClimate::get_vertical_airflow() const { return this->vertical_direction_; };
|
||||
|
||||
void HonClimate::set_vertical_airflow(AirflowVerticalDirection direction) {
|
||||
@ -368,7 +366,14 @@ void HonClimate::process_phase(std::chrono::steady_clock::time_point now) {
|
||||
if (this->can_send_message() && this->is_message_interval_exceeded_(now)) {
|
||||
static const haier_protocol::HaierMessage STATUS_REQUEST(
|
||||
haier_protocol::FrameType::CONTROL, (uint16_t) hon_protocol::SubcommandsControl::GET_USER_DATA);
|
||||
this->send_message_(STATUS_REQUEST, this->use_crc_);
|
||||
static const haier_protocol::HaierMessage BIG_DATA_REQUEST(
|
||||
haier_protocol::FrameType::CONTROL, (uint16_t) hon_protocol::SubcommandsControl::GET_BIG_DATA);
|
||||
if ((this->protocol_phase_ == ProtocolPhases::SENDING_FIRST_STATUS_REQUEST) ||
|
||||
(!this->should_get_big_data_())) {
|
||||
this->send_message_(STATUS_REQUEST, this->use_crc_);
|
||||
} else {
|
||||
this->send_message_(BIG_DATA_REQUEST, this->use_crc_);
|
||||
}
|
||||
this->last_status_request_ = now;
|
||||
}
|
||||
break;
|
||||
@ -685,9 +690,87 @@ void HonClimate::process_alarm_message_(const uint8_t *packet, uint8_t size, boo
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef USE_SENSOR
|
||||
void HonClimate::set_sub_sensor(SubSensorType type, sensor::Sensor *sens) {
|
||||
if (type < SubSensorType::SUB_SENSOR_TYPE_COUNT) {
|
||||
if (type >= SubSensorType::BIG_DATA_FRAME_SUB_SENSORS) {
|
||||
if ((this->sub_sensors_[(size_t) type] != nullptr) && (sens == nullptr)) {
|
||||
this->big_data_sensors_--;
|
||||
} else if ((this->sub_sensors_[(size_t) type] == nullptr) && (sens != nullptr)) {
|
||||
this->big_data_sensors_++;
|
||||
}
|
||||
}
|
||||
this->sub_sensors_[(size_t) type] = sens;
|
||||
}
|
||||
}
|
||||
|
||||
void HonClimate::update_sub_sensor_(SubSensorType type, float value) {
|
||||
if (type < SubSensorType::SUB_SENSOR_TYPE_COUNT) {
|
||||
size_t index = (size_t) type;
|
||||
if ((this->sub_sensors_[index] != nullptr) &&
|
||||
((!this->sub_sensors_[index]->has_state()) || (this->sub_sensors_[index]->raw_state != value)))
|
||||
this->sub_sensors_[index]->publish_state(value);
|
||||
}
|
||||
}
|
||||
#endif // USE_SENSOR
|
||||
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
void HonClimate::set_sub_binary_sensor(SubBinarySensorType type, binary_sensor::BinarySensor *sens) {
|
||||
if (type < SubBinarySensorType::SUB_BINARY_SENSOR_TYPE_COUNT) {
|
||||
if ((this->sub_binary_sensors_[(size_t) type] != nullptr) && (sens == nullptr)) {
|
||||
this->big_data_sensors_--;
|
||||
} else if ((this->sub_binary_sensors_[(size_t) type] == nullptr) && (sens != nullptr)) {
|
||||
this->big_data_sensors_++;
|
||||
}
|
||||
this->sub_binary_sensors_[(size_t) type] = sens;
|
||||
}
|
||||
}
|
||||
|
||||
void HonClimate::update_sub_binary_sensor_(SubBinarySensorType type, uint8_t value) {
|
||||
if (value < 2) {
|
||||
bool converted_value = value == 1;
|
||||
size_t index = (size_t) type;
|
||||
if ((this->sub_binary_sensors_[index] != nullptr) && ((!this->sub_binary_sensors_[index]->has_state()) ||
|
||||
(this->sub_binary_sensors_[index]->state != converted_value)))
|
||||
this->sub_binary_sensors_[index]->publish_state(converted_value);
|
||||
}
|
||||
}
|
||||
#endif // USE_BINARY_SENSOR
|
||||
|
||||
haier_protocol::HandlerError HonClimate::process_status_message_(const uint8_t *packet_buffer, uint8_t size) {
|
||||
if (size < hon_protocol::HAIER_STATUS_FRAME_SIZE + this->extra_control_packet_bytes_)
|
||||
size_t expected_size = 2 + sizeof(hon_protocol::HaierPacketControl) + sizeof(hon_protocol::HaierPacketSensors) +
|
||||
this->extra_control_packet_bytes_;
|
||||
if (size < expected_size)
|
||||
return haier_protocol::HandlerError::WRONG_MESSAGE_STRUCTURE;
|
||||
uint16_t subtype = (((uint16_t) packet_buffer[0]) << 8) + packet_buffer[1];
|
||||
if ((subtype == 0x7D01) && (size >= expected_size + 4 + sizeof(hon_protocol::HaierPacketBigData))) {
|
||||
// Got BigData packet
|
||||
const hon_protocol::HaierPacketBigData *bd_packet =
|
||||
(const hon_protocol::HaierPacketBigData *) (&packet_buffer[expected_size + 4]);
|
||||
#ifdef USE_SENSOR
|
||||
this->update_sub_sensor_(SubSensorType::INDOOR_COIL_TEMPERATURE, bd_packet->indoor_coil_temperature / 2.0 - 20);
|
||||
this->update_sub_sensor_(SubSensorType::OUTDOOR_COIL_TEMPERATURE, bd_packet->outdoor_coil_temperature - 64);
|
||||
this->update_sub_sensor_(SubSensorType::OUTDOOR_DEFROST_TEMPERATURE, bd_packet->outdoor_coil_temperature - 64);
|
||||
this->update_sub_sensor_(SubSensorType::OUTDOOR_IN_AIR_TEMPERATURE, bd_packet->outdoor_in_air_temperature - 64);
|
||||
this->update_sub_sensor_(SubSensorType::OUTDOOR_OUT_AIR_TEMPERATURE, bd_packet->outdoor_out_air_temperature - 64);
|
||||
this->update_sub_sensor_(SubSensorType::POWER, encode_uint16(bd_packet->power[0], bd_packet->power[1]));
|
||||
this->update_sub_sensor_(SubSensorType::COMPRESSOR_FREQUENCY, bd_packet->compressor_frequency);
|
||||
this->update_sub_sensor_(SubSensorType::COMPRESSOR_CURRENT,
|
||||
encode_uint16(bd_packet->compressor_current[0], bd_packet->compressor_current[1]) / 10.0);
|
||||
this->update_sub_sensor_(
|
||||
SubSensorType::EXPANSION_VALVE_OPEN_DEGREE,
|
||||
encode_uint16(bd_packet->expansion_valve_open_degree[0], bd_packet->expansion_valve_open_degree[1]) / 4095.0);
|
||||
#endif // USE_SENSOR
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
this->update_sub_binary_sensor_(SubBinarySensorType::OUTDOOR_FAN_STATUS, bd_packet->outdoor_fan_status);
|
||||
this->update_sub_binary_sensor_(SubBinarySensorType::DEFROST_STATUS, bd_packet->defrost_status);
|
||||
this->update_sub_binary_sensor_(SubBinarySensorType::COMPRESSOR_STATUS, bd_packet->compressor_status);
|
||||
this->update_sub_binary_sensor_(SubBinarySensorType::INDOOR_FAN_STATUS, bd_packet->indoor_fan_status);
|
||||
this->update_sub_binary_sensor_(SubBinarySensorType::FOUR_WAY_VALVE_STATUS, bd_packet->four_way_valve_status);
|
||||
this->update_sub_binary_sensor_(SubBinarySensorType::INDOOR_ELECTRIC_HEATING_STATUS,
|
||||
bd_packet->indoor_electric_heating_status);
|
||||
#endif // USE_BINARY_SENSOR
|
||||
}
|
||||
struct {
|
||||
hon_protocol::HaierPacketControl control;
|
||||
hon_protocol::HaierPacketSensors sensors;
|
||||
@ -699,13 +782,17 @@ haier_protocol::HandlerError HonClimate::process_status_message_(const uint8_t *
|
||||
if (packet.sensors.error_status != 0) {
|
||||
ESP_LOGW(TAG, "HVAC error, code=0x%02X", packet.sensors.error_status);
|
||||
}
|
||||
if ((this->outdoor_sensor_ != nullptr) &&
|
||||
#ifdef USE_SENSOR
|
||||
if ((this->sub_sensors_[(size_t) SubSensorType::OUTDOOR_TEMPERATURE] != nullptr) &&
|
||||
(this->got_valid_outdoor_temp_ || (packet.sensors.outdoor_temperature > 0))) {
|
||||
this->got_valid_outdoor_temp_ = true;
|
||||
float otemp = (float) (packet.sensors.outdoor_temperature + PROTOCOL_OUTDOOR_TEMPERATURE_OFFSET);
|
||||
if ((!this->outdoor_sensor_->has_state()) || (this->outdoor_sensor_->get_raw_state() != otemp))
|
||||
this->outdoor_sensor_->publish_state(otemp);
|
||||
this->update_sub_sensor_(SubSensorType::OUTDOOR_TEMPERATURE,
|
||||
(float) (packet.sensors.outdoor_temperature + PROTOCOL_OUTDOOR_TEMPERATURE_OFFSET));
|
||||
}
|
||||
if ((this->sub_sensors_[(size_t) SubSensorType::HUMIDITY] != nullptr) && (packet.sensors.room_humidity <= 100)) {
|
||||
this->update_sub_sensor_(SubSensorType::HUMIDITY, (float) packet.sensors.room_humidity);
|
||||
}
|
||||
#endif // USE_SENSOR
|
||||
bool should_publish = false;
|
||||
{
|
||||
// Extra modes/presets
|
||||
@ -1009,21 +1096,22 @@ void HonClimate::fill_control_messages_queue_() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (quiet_mode_buf[1] != 0xFF) {
|
||||
auto presets = this->traits_.get_supported_presets();
|
||||
if ((quiet_mode_buf[1] != 0xFF) && ((presets.find(climate::ClimatePreset::CLIMATE_PRESET_ECO) != presets.end()))) {
|
||||
this->control_messages_queue_.push(
|
||||
haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
|
||||
(uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER +
|
||||
(uint8_t) hon_protocol::DataParameters::QUIET_MODE,
|
||||
quiet_mode_buf, 2));
|
||||
}
|
||||
if (fast_mode_buf[1] != 0xFF) {
|
||||
if ((fast_mode_buf[1] != 0xFF) && ((presets.find(climate::ClimatePreset::CLIMATE_PRESET_BOOST) != presets.end()))) {
|
||||
this->control_messages_queue_.push(
|
||||
haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
|
||||
(uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER +
|
||||
(uint8_t) hon_protocol::DataParameters::FAST_MODE,
|
||||
fast_mode_buf, 2));
|
||||
}
|
||||
if (away_mode_buf[1] != 0xFF) {
|
||||
if ((away_mode_buf[1] != 0xFF) && ((presets.find(climate::ClimatePreset::CLIMATE_PRESET_AWAY) != presets.end()))) {
|
||||
this->control_messages_queue_.push(
|
||||
haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
|
||||
(uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER +
|
||||
@ -1032,7 +1120,7 @@ void HonClimate::fill_control_messages_queue_() {
|
||||
}
|
||||
}
|
||||
// Target temperature
|
||||
if (climate_control.target_temperature.has_value()) {
|
||||
if (climate_control.target_temperature.has_value() && (this->mode != ClimateMode::CLIMATE_MODE_FAN_ONLY)) {
|
||||
uint8_t buffer[2] = {0x00, 0x00};
|
||||
buffer[1] = ((uint8_t) climate_control.target_temperature.value()) - 16;
|
||||
this->control_messages_queue_.push(
|
||||
@ -1119,12 +1207,24 @@ bool HonClimate::prepare_pending_action() {
|
||||
|
||||
void HonClimate::process_protocol_reset() {
|
||||
HaierClimateBase::process_protocol_reset();
|
||||
if (this->outdoor_sensor_ != nullptr) {
|
||||
this->outdoor_sensor_->publish_state(NAN);
|
||||
#ifdef USE_SENSOR
|
||||
for (auto &sub_sensor : this->sub_sensors_) {
|
||||
if ((sub_sensor != nullptr) && sub_sensor->has_state())
|
||||
sub_sensor->publish_state(NAN);
|
||||
}
|
||||
#endif // USE_SENSOR
|
||||
this->got_valid_outdoor_temp_ = false;
|
||||
this->hvac_hardware_info_.reset();
|
||||
}
|
||||
|
||||
bool HonClimate::should_get_big_data_() {
|
||||
if (this->big_data_sensors_ > 0) {
|
||||
static uint8_t counter = 0;
|
||||
counter = (counter + 1) % 3;
|
||||
return counter == 1;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace haier
|
||||
} // namespace esphome
|
||||
|
@ -1,7 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#ifdef USE_SENSOR
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#endif
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
#include "esphome/components/binary_sensor/binary_sensor.h"
|
||||
#endif
|
||||
#include "esphome/core/automation.h"
|
||||
#include "haier_base.h"
|
||||
|
||||
@ -34,6 +39,48 @@ enum class CleaningState : uint8_t {
|
||||
enum class HonControlMethod { MONITOR_ONLY = 0, SET_GROUP_PARAMETERS, SET_SINGLE_PARAMETER };
|
||||
|
||||
class HonClimate : public HaierClimateBase {
|
||||
#ifdef USE_SENSOR
|
||||
public:
|
||||
enum class SubSensorType {
|
||||
// Used data based sensors
|
||||
OUTDOOR_TEMPERATURE = 0,
|
||||
HUMIDITY,
|
||||
// Big data based sensors
|
||||
INDOOR_COIL_TEMPERATURE,
|
||||
OUTDOOR_COIL_TEMPERATURE,
|
||||
OUTDOOR_DEFROST_TEMPERATURE,
|
||||
OUTDOOR_IN_AIR_TEMPERATURE,
|
||||
OUTDOOR_OUT_AIR_TEMPERATURE,
|
||||
POWER,
|
||||
COMPRESSOR_FREQUENCY,
|
||||
COMPRESSOR_CURRENT,
|
||||
EXPANSION_VALVE_OPEN_DEGREE,
|
||||
SUB_SENSOR_TYPE_COUNT,
|
||||
BIG_DATA_FRAME_SUB_SENSORS = INDOOR_COIL_TEMPERATURE,
|
||||
};
|
||||
void set_sub_sensor(SubSensorType type, sensor::Sensor *sens);
|
||||
|
||||
protected:
|
||||
void update_sub_sensor_(SubSensorType type, float value);
|
||||
sensor::Sensor *sub_sensors_[(size_t) SubSensorType::SUB_SENSOR_TYPE_COUNT]{nullptr};
|
||||
#endif
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
public:
|
||||
enum class SubBinarySensorType {
|
||||
OUTDOOR_FAN_STATUS = 0,
|
||||
DEFROST_STATUS,
|
||||
COMPRESSOR_STATUS,
|
||||
INDOOR_FAN_STATUS,
|
||||
FOUR_WAY_VALVE_STATUS,
|
||||
INDOOR_ELECTRIC_HEATING_STATUS,
|
||||
SUB_BINARY_SENSOR_TYPE_COUNT,
|
||||
};
|
||||
void set_sub_binary_sensor(SubBinarySensorType type, binary_sensor::BinarySensor *sens);
|
||||
|
||||
protected:
|
||||
void update_sub_binary_sensor_(SubBinarySensorType type, uint8_t value);
|
||||
binary_sensor::BinarySensor *sub_binary_sensors_[(size_t) SubBinarySensorType::SUB_BINARY_SENSOR_TYPE_COUNT]{nullptr};
|
||||
#endif
|
||||
public:
|
||||
HonClimate();
|
||||
HonClimate(const HonClimate &) = delete;
|
||||
@ -42,7 +89,6 @@ class HonClimate : public HaierClimateBase {
|
||||
void dump_config() override;
|
||||
void set_beeper_state(bool state);
|
||||
bool get_beeper_state() const;
|
||||
void set_outdoor_temperature_sensor(esphome::sensor::Sensor *sensor);
|
||||
AirflowVerticalDirection get_vertical_airflow() const;
|
||||
void set_vertical_airflow(AirflowVerticalDirection direction);
|
||||
AirflowHorizontalDirection get_horizontal_airflow() const;
|
||||
@ -64,6 +110,7 @@ class HonClimate : public HaierClimateBase {
|
||||
haier_protocol::HaierMessage get_power_message(bool state) override;
|
||||
bool prepare_pending_action() override;
|
||||
void process_protocol_reset() override;
|
||||
bool should_get_big_data_();
|
||||
|
||||
// Answers handlers
|
||||
haier_protocol::HandlerError get_device_version_answer_handler_(haier_protocol::FrameType request_type,
|
||||
@ -106,12 +153,12 @@ class HonClimate : public HaierClimateBase {
|
||||
uint8_t active_alarms_[8];
|
||||
int extra_control_packet_bytes_;
|
||||
HonControlMethod control_method_;
|
||||
esphome::sensor::Sensor *outdoor_sensor_;
|
||||
std::queue<haier_protocol::HaierMessage> control_messages_queue_;
|
||||
CallbackManager<void(uint8_t, const char *)> alarm_start_callback_{};
|
||||
CallbackManager<void(uint8_t, const char *)> alarm_end_callback_{};
|
||||
float active_alarm_count_{NAN};
|
||||
std::chrono::steady_clock::time_point last_alarm_request_;
|
||||
int big_data_sensors_{0};
|
||||
};
|
||||
|
||||
class HaierAlarmStartTrigger : public Trigger<uint8_t, const char *> {
|
||||
|
@ -55,18 +55,18 @@ enum class FanMode : uint8_t { FAN_HIGH = 0x01, FAN_MID = 0x02, FAN_LOW = 0x03,
|
||||
|
||||
struct HaierPacketControl {
|
||||
// Control bytes starts here
|
||||
// 10
|
||||
// 1
|
||||
uint8_t set_point; // Target temperature with 16°C offset (0x00 = 16°C)
|
||||
// 11
|
||||
// 2
|
||||
uint8_t vertical_swing_mode : 4; // See enum VerticalSwingMode
|
||||
uint8_t : 0;
|
||||
// 12
|
||||
// 3
|
||||
uint8_t fan_mode : 3; // See enum FanMode
|
||||
uint8_t special_mode : 2; // See enum SpecialMode
|
||||
uint8_t ac_mode : 3; // See enum ConditioningMode
|
||||
// 13
|
||||
// 4
|
||||
uint8_t : 8;
|
||||
// 14
|
||||
// 5
|
||||
uint8_t ten_degree : 1; // 10 degree status
|
||||
uint8_t display_status : 1; // If 0 disables AC's display
|
||||
uint8_t half_degree : 1; // Use half degree
|
||||
@ -75,7 +75,7 @@ struct HaierPacketControl {
|
||||
uint8_t use_fahrenheit : 1; // Use Fahrenheit instead of Celsius
|
||||
uint8_t : 1;
|
||||
uint8_t steri_clean : 1;
|
||||
// 15
|
||||
// 6
|
||||
uint8_t ac_power : 1; // Is ac on or off
|
||||
uint8_t health_mode : 1; // Health mode (negative ions) on or off
|
||||
uint8_t electric_heating_status : 1; // Electric heating status
|
||||
@ -84,16 +84,16 @@ struct HaierPacketControl {
|
||||
uint8_t sleep_mode : 1; // Sleep mode
|
||||
uint8_t lock_remote : 1; // Disable remote
|
||||
uint8_t beeper_status : 1; // If 1 disables AC's command feedback beeper (need to be set on every control command)
|
||||
// 16
|
||||
// 7
|
||||
uint8_t target_humidity; // Target humidity (0=30% .. 3C=90%, step = 1%)
|
||||
// 17
|
||||
// 8
|
||||
uint8_t horizontal_swing_mode : 3; // See enum HorizontalSwingMode
|
||||
uint8_t : 3;
|
||||
uint8_t human_sensing_status : 2; // Human sensing status
|
||||
// 18
|
||||
// 9
|
||||
uint8_t change_filter : 1; // Filter need replacement
|
||||
uint8_t : 0;
|
||||
// 19
|
||||
// 10
|
||||
uint8_t fresh_air_status : 1; // Fresh air status
|
||||
uint8_t humidification_status : 1; // Humidification status
|
||||
uint8_t pm2p5_cleaning_status : 1; // PM2.5 cleaning status
|
||||
@ -105,40 +105,68 @@ struct HaierPacketControl {
|
||||
};
|
||||
|
||||
struct HaierPacketSensors {
|
||||
// 20
|
||||
// 11
|
||||
uint8_t room_temperature; // 0.5°C step
|
||||
// 21
|
||||
// 12
|
||||
uint8_t room_humidity; // 0%-100% with 1% step
|
||||
// 22
|
||||
// 13
|
||||
uint8_t outdoor_temperature; // 1°C step, -64°C offset (0=-64°C)
|
||||
// 23
|
||||
// 14
|
||||
uint8_t pm2p5_level : 2; // Indoor PM2.5 grade (00: Excellent, 01: good, 02: Medium, 03: Bad)
|
||||
uint8_t air_quality : 2; // Air quality grade (00: Excellent, 01: good, 02: Medium, 03: Bad)
|
||||
uint8_t human_sensing : 2; // Human presence result (00: N/A, 01: not detected, 02: One, 03: Multiple)
|
||||
uint8_t : 1;
|
||||
uint8_t ac_type : 1; // 00 - Heat and cool, 01 - Cool only)
|
||||
// 24
|
||||
// 15
|
||||
uint8_t error_status; // See enum ErrorStatus
|
||||
// 25
|
||||
// 16
|
||||
uint8_t operation_source : 2; // who is controlling AC (00: Other, 01: Remote control, 02: Button, 03: ESP)
|
||||
uint8_t operation_mode_hk : 2; // Homekit only, operation mode (00: Cool, 01: Dry, 02: Heat, 03: Fan)
|
||||
uint8_t : 3;
|
||||
uint8_t err_confirmation : 1; // If 1 clear error status
|
||||
// 26
|
||||
// 17
|
||||
uint16_t total_cleaning_time; // Cleaning cumulative time (1h step)
|
||||
// 28
|
||||
// 19
|
||||
uint16_t indoor_pm2p5_value; // Indoor PM2.5 value (0 ug/m3 - 4095 ug/m3, 1 ug/m3 step)
|
||||
// 30
|
||||
// 21
|
||||
uint16_t outdoor_pm2p5_value; // Outdoor PM2.5 value (0 ug/m3 - 4095 ug/m3, 1 ug/m3 step)
|
||||
// 32
|
||||
// 23
|
||||
uint16_t ch2o_value; // Formaldehyde value (0 ug/m3 - 10000 ug/m3, 1 ug/m3 step)
|
||||
// 34
|
||||
// 25
|
||||
uint16_t voc_value; // VOC value (Volatile Organic Compounds) (0 ug/m3 - 1023 ug/m3, 1 ug/m3 step)
|
||||
// 36
|
||||
// 27
|
||||
uint16_t co2_value; // CO2 value (0 PPM - 10000 PPM, 1 PPM step)
|
||||
};
|
||||
|
||||
constexpr size_t HAIER_STATUS_FRAME_SIZE = 2 + sizeof(HaierPacketControl) + sizeof(HaierPacketSensors);
|
||||
struct HaierPacketBigData {
|
||||
// 29
|
||||
uint8_t power[2]; // AC power consumption (0W - 65535W, 1W step)
|
||||
// 31
|
||||
uint8_t indoor_coil_temperature; // 0.5°C step, -20°C offset (0=-20°C)
|
||||
// 32
|
||||
uint8_t outdoor_out_air_temperature; // 1°C step, -64°C offset (0=-64°C)
|
||||
// 33
|
||||
uint8_t outdoor_coil_temperature; // 1°C step, -64°C offset (0=-64°C)
|
||||
// 34
|
||||
uint8_t outdoor_in_air_temperature; // 1°C step, -64°C offset (0=-64°C)
|
||||
// 35
|
||||
uint8_t outdoor_defrost_temperature; // 1°C step, -64°C offset (0=-64°C)
|
||||
// 36
|
||||
uint8_t compressor_frequency; // 1Hz step, 0Hz - 127Hz
|
||||
// 37
|
||||
uint8_t compressor_current[2]; // 0.1A step, 0.0A - 51.1A (0x0000 - 0x01FF)
|
||||
// 39
|
||||
uint8_t outdoor_fan_status : 2; // 0 - off, 1 - on, 2 - information not available
|
||||
uint8_t defrost_status : 2; // 0 - off, 1 - on, 2 - information not available
|
||||
uint8_t : 0;
|
||||
// 40
|
||||
uint8_t compressor_status : 2; // 0 - off, 1 - on, 2 - information not available
|
||||
uint8_t indoor_fan_status : 2; // 0 - off, 1 - on, 2 - information not available
|
||||
uint8_t four_way_valve_status : 2; // 0 - off, 1 - on, 2 - information not available
|
||||
uint8_t indoor_electric_heating_status : 2; // 0 - off, 1 - on, 2 - information not available
|
||||
// 41
|
||||
uint8_t expansion_valve_open_degree[2]; // 0 - 4095
|
||||
};
|
||||
|
||||
struct DeviceVersionAnswer {
|
||||
char protocol_version[8];
|
||||
|
151
esphome/components/haier/sensor/__init__.py
Normal file
151
esphome/components/haier/sensor/__init__.py
Normal file
@ -0,0 +1,151 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import sensor
|
||||
from esphome.const import (
|
||||
CONF_POWER,
|
||||
CONF_HUMIDITY,
|
||||
DEVICE_CLASS_CURRENT,
|
||||
DEVICE_CLASS_FREQUENCY,
|
||||
DEVICE_CLASS_HUMIDITY,
|
||||
DEVICE_CLASS_POWER,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
ICON_CURRENT_AC,
|
||||
ICON_FLASH,
|
||||
ICON_GAUGE,
|
||||
ICON_HEATING_COIL,
|
||||
ICON_PULSE,
|
||||
ICON_THERMOMETER,
|
||||
ICON_WATER_PERCENT,
|
||||
ICON_WEATHER_WINDY,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_AMPERE,
|
||||
UNIT_CELSIUS,
|
||||
UNIT_HERTZ,
|
||||
UNIT_PERCENT,
|
||||
UNIT_WATT,
|
||||
)
|
||||
from ..climate import (
|
||||
CONF_HAIER_ID,
|
||||
HonClimate,
|
||||
)
|
||||
|
||||
SensorTypeEnum = HonClimate.enum("SubSensorType", True)
|
||||
|
||||
# Haier sensors
|
||||
CONF_COMPRESSOR_CURRENT = "compressor_current"
|
||||
CONF_COMPRESSOR_FREQUENCY = "compressor_frequency"
|
||||
CONF_EXPANSION_VALVE_OPEN_DEGREE = "expansion_valve_open_degree"
|
||||
CONF_INDOOR_COIL_TEMPERATURE = "indoor_coil_temperature"
|
||||
CONF_OUTDOOR_COIL_TEMPERATURE = "outdoor_coil_temperature"
|
||||
CONF_OUTDOOR_DEFROST_TEMPERATURE = "outdoor_defrost_temperature"
|
||||
CONF_OUTDOOR_IN_AIR_TEMPERATURE = "outdoor_in_air_temperature"
|
||||
CONF_OUTDOOR_OUT_AIR_TEMPERATURE = "outdoor_out_air_temperature"
|
||||
CONF_OUTDOOR_TEMPERATURE = "outdoor_temperature"
|
||||
|
||||
# Additional icons
|
||||
ICON_SNOWFLAKE_THERMOMETER = "mdi:snowflake-thermometer"
|
||||
|
||||
SENSOR_TYPES = {
|
||||
CONF_COMPRESSOR_CURRENT: sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_AMPERE,
|
||||
icon=ICON_CURRENT_AC,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_CURRENT,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
CONF_COMPRESSOR_FREQUENCY: sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_HERTZ,
|
||||
icon=ICON_PULSE,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_FREQUENCY,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
CONF_EXPANSION_VALVE_OPEN_DEGREE: sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_PERCENT,
|
||||
icon=ICON_GAUGE,
|
||||
accuracy_decimals=2,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
CONF_HUMIDITY: sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_PERCENT,
|
||||
icon=ICON_WATER_PERCENT,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_HUMIDITY,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
CONF_INDOOR_COIL_TEMPERATURE: sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_CELSIUS,
|
||||
icon=ICON_HEATING_COIL,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
CONF_OUTDOOR_COIL_TEMPERATURE: sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_CELSIUS,
|
||||
icon=ICON_HEATING_COIL,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
CONF_OUTDOOR_DEFROST_TEMPERATURE: sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_CELSIUS,
|
||||
icon=ICON_SNOWFLAKE_THERMOMETER,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
CONF_OUTDOOR_IN_AIR_TEMPERATURE: sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_CELSIUS,
|
||||
icon=ICON_WEATHER_WINDY,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
CONF_OUTDOOR_OUT_AIR_TEMPERATURE: sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_CELSIUS,
|
||||
icon=ICON_WEATHER_WINDY,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
CONF_OUTDOOR_TEMPERATURE: sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_CELSIUS,
|
||||
icon=ICON_THERMOMETER,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
CONF_POWER: sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_WATT,
|
||||
icon=ICON_FLASH,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_POWER,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
}
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_HAIER_ID): cv.use_id(HonClimate),
|
||||
}
|
||||
).extend({cv.Optional(type): schema for type, schema in SENSOR_TYPES.items()})
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
paren = await cg.get_variable(config[CONF_HAIER_ID])
|
||||
|
||||
for type, _ in SENSOR_TYPES.items():
|
||||
if conf := config.get(type):
|
||||
sens = await sensor.new_sensor(conf)
|
||||
sensor_type = getattr(SensorTypeEnum, type.upper())
|
||||
cg.add(paren.set_sub_sensor(sensor_type, sens))
|
@ -119,4 +119,4 @@ def to_code(config):
|
||||
cg.add_library("tonia/HeatpumpIR", "1.0.23")
|
||||
|
||||
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"""
|
||||
|
||||
CODEOWNERS = ["@Benichou34"]
|
||||
|
@ -1,2 +1,3 @@
|
||||
"""Support for Honeywell ABP2"""
|
||||
|
||||
CODEOWNERS = ["@jpfaff"]
|
||||
|
@ -4,6 +4,7 @@ from esphome.const import (
|
||||
KEY_TARGET_FRAMEWORK,
|
||||
KEY_TARGET_PLATFORM,
|
||||
PLATFORM_HOST,
|
||||
CONF_MAC_ADDRESS,
|
||||
)
|
||||
from esphome.core import CORE
|
||||
from esphome.helpers import IS_MACOS
|
||||
@ -28,13 +29,18 @@ def set_core_data(config):
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.Schema({}),
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_MAC_ADDRESS, default="98:35:69:ab:f6:79"): cv.mac_address,
|
||||
}
|
||||
),
|
||||
set_core_data,
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
cg.add_build_flag("-DUSE_HOST")
|
||||
cg.add_define("USE_ESPHOME_HOST_MAC_ADDRESS", config[CONF_MAC_ADDRESS].parts)
|
||||
cg.add_build_flag("-std=c++17")
|
||||
cg.add_build_flag("-lsodium")
|
||||
if IS_MACOS:
|
||||
|
@ -24,7 +24,7 @@ void ArduinoI2CBus::setup() {
|
||||
}
|
||||
next_bus_num++;
|
||||
#elif defined(USE_ESP8266)
|
||||
wire_ = &Wire; // NOLINT(cppcoreguidelines-prefer-member-initializer)
|
||||
wire_ = new TwoWire(); // NOLINT(cppcoreguidelines-owning-memory)
|
||||
#elif defined(USE_RP2040)
|
||||
static bool first = true;
|
||||
if (first) {
|
||||
@ -35,6 +35,16 @@ void ArduinoI2CBus::setup() {
|
||||
}
|
||||
#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
|
||||
wire_->setSDA(this->sda_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_));
|
||||
#endif
|
||||
wire_->setClock(frequency_);
|
||||
initialized_ = true;
|
||||
if (this->scan_) {
|
||||
ESP_LOGV(TAG, "Scanning i2c bus for active devices...");
|
||||
this->i2c_scan_();
|
||||
}
|
||||
}
|
||||
|
||||
void ArduinoI2CBus::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "I2C Bus:");
|
||||
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) {
|
||||
#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
|
||||
// should log them
|
||||
if (!initialized_) {
|
||||
@ -120,6 +130,10 @@ ErrorCode ArduinoI2CBus::readv(uint8_t address, ReadBuffer *buffers, size_t cnt)
|
||||
return ERROR_OK;
|
||||
}
|
||||
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
|
||||
// should log them
|
||||
if (!initialized_) {
|
||||
@ -164,7 +178,7 @@ ErrorCode ArduinoI2CBus::writev(uint8_t address, WriteBuffer *buffers, size_t cn
|
||||
return ERROR_UNKNOWN;
|
||||
case 2:
|
||||
case 3:
|
||||
ESP_LOGVV(TAG, "TX failed: not acknowledged");
|
||||
ESP_LOGVV(TAG, "TX failed: not acknowledged: %d", status);
|
||||
return ERROR_NOT_ACKNOWLEDGED;
|
||||
case 5:
|
||||
ESP_LOGVV(TAG, "TX failed: timeout");
|
||||
|
@ -30,6 +30,7 @@ class ArduinoI2CBus : public I2CBus, public Component {
|
||||
|
||||
private:
|
||||
void recover_();
|
||||
void set_pins_and_clock_();
|
||||
RecoveryCode recovery_result_;
|
||||
|
||||
protected:
|
||||
|
@ -21,8 +21,13 @@ std::string ImprovBase::get_formatted_next_url_() {
|
||||
// Ip address
|
||||
pos = this->next_url_.find("{{ip_address}}");
|
||||
if (pos != std::string::npos) {
|
||||
std::string ip = network::get_ip_address().str();
|
||||
copy.replace(pos, 14, ip);
|
||||
for (auto &ip : network::get_ip_addresses()) {
|
||||
if (ip.is_ip4()) {
|
||||
std::string ipa = ip.str();
|
||||
copy.replace(pos, 14, ipa);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return copy;
|
||||
|
@ -155,9 +155,13 @@ std::vector<uint8_t> ImprovSerialComponent::build_rpc_settings_response_(improv:
|
||||
urls.push_back(this->get_formatted_next_url_());
|
||||
}
|
||||
#ifdef USE_WEBSERVER
|
||||
auto ip = wifi::global_wifi_component->wifi_sta_ip();
|
||||
std::string webserver_url = "http://" + ip.str() + ":" + to_string(USE_WEBSERVER_PORT);
|
||||
urls.push_back(webserver_url);
|
||||
for (auto &ip : wifi::global_wifi_component->wifi_sta_ip_addresses()) {
|
||||
if (ip.is_ip4()) {
|
||||
std::string webserver_url = "http://" + ip.str() + ":" + to_string(USE_WEBSERVER_PORT);
|
||||
urls.push_back(webserver_url);
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
std::vector<uint8_t> data = improv::build_rpc_response(command, urls, false);
|
||||
return data;
|
||||
@ -192,7 +196,7 @@ bool ImprovSerialComponent::parse_improv_payload_(improv::ImprovCommand &command
|
||||
this->connecting_sta_ = sta;
|
||||
|
||||
wifi::global_wifi_component->set_sta(sta);
|
||||
wifi::global_wifi_component->start_scanning();
|
||||
wifi::global_wifi_component->start_connecting(sta, false);
|
||||
this->set_state_(improv::STATE_PROVISIONING);
|
||||
ESP_LOGD(TAG, "Received Improv wifi settings ssid=%s, password=" LOG_SECRET("%s"), command.ssid.c_str(),
|
||||
command.password.c_str());
|
||||
|
@ -0,0 +1 @@
|
||||
CODEOWNERS = ["@Sergio303", "@latonita"]
|
@ -33,31 +33,37 @@ static const uint8_t INA226_REGISTER_POWER = 0x03;
|
||||
static const uint8_t INA226_REGISTER_CURRENT = 0x04;
|
||||
static const uint8_t INA226_REGISTER_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() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up INA226...");
|
||||
// Config Register
|
||||
// 0bx000000000000000 << 15 RESET Bit (1 -> trigger reset)
|
||||
if (!this->write_byte_16(INA226_REGISTER_CONFIG, 0x8000)) {
|
||||
|
||||
ConfigurationRegister config;
|
||||
|
||||
config.reset = 1;
|
||||
if (!this->write_byte_16(INA226_REGISTER_CONFIG, config.raw)) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
config |= 0b0000000000100000;
|
||||
config.shunt_voltage_conversion_time = this->adc_time_;
|
||||
|
||||
// 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();
|
||||
return;
|
||||
}
|
||||
@ -87,6 +93,9 @@ void INA226Component::dump_config() {
|
||||
}
|
||||
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(" ", "Shunt Voltage", this->shunt_voltage_sensor_);
|
||||
LOG_SENSOR(" ", "Current", this->current_sensor_);
|
||||
@ -102,7 +111,9 @@ void INA226Component::update() {
|
||||
this->status_set_warning();
|
||||
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);
|
||||
}
|
||||
|
||||
@ -112,7 +123,9 @@ void INA226Component::update() {
|
||||
this->status_set_warning();
|
||||
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);
|
||||
}
|
||||
|
||||
@ -122,7 +135,9 @@ void INA226Component::update() {
|
||||
this->status_set_warning();
|
||||
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);
|
||||
}
|
||||
|
||||
@ -139,5 +154,12 @@ void INA226Component::update() {
|
||||
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 esphome
|
||||
|
@ -7,6 +7,40 @@
|
||||
namespace esphome {
|
||||
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 {
|
||||
public:
|
||||
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_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_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; }
|
||||
@ -24,11 +61,15 @@ class INA226Component : public PollingComponent, public i2c::I2CDevice {
|
||||
protected:
|
||||
float shunt_resistance_ohm_;
|
||||
float max_current_a_;
|
||||
AdcTime adc_time_{AdcTime::ADC_TIME_1100US};
|
||||
AdcAvgSamples adc_avg_samples_{AdcAvgSamples::ADC_AVG_SAMPLES_4};
|
||||
uint32_t calibration_lsb_;
|
||||
sensor::Sensor *bus_voltage_sensor_{nullptr};
|
||||
sensor::Sensor *shunt_voltage_sensor_{nullptr};
|
||||
sensor::Sensor *current_sensor_{nullptr};
|
||||
sensor::Sensor *power_sensor_{nullptr};
|
||||
|
||||
int32_t twos_complement_(int32_t val, uint8_t bits);
|
||||
};
|
||||
|
||||
} // namespace ina226
|
||||
|
@ -20,11 +20,44 @@ from esphome.const import (
|
||||
|
||||
DEPENDENCIES = ["i2c"]
|
||||
|
||||
CONF_ADC_AVERAGING = "adc_averaging"
|
||||
CONF_ADC_TIME = "adc_time"
|
||||
|
||||
ina226_ns = cg.esphome_ns.namespace("ina226")
|
||||
INA226Component = ina226_ns.class_(
|
||||
"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 = (
|
||||
cv.Schema(
|
||||
{
|
||||
@ -59,6 +92,10 @@ CONFIG_SCHEMA = (
|
||||
cv.Optional(CONF_MAX_CURRENT, default=3.2): cv.All(
|
||||
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"))
|
||||
@ -72,8 +109,9 @@ async def to_code(config):
|
||||
await i2c.register_i2c_device(var, config)
|
||||
|
||||
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_adc_time(config[CONF_ADC_TIME]))
|
||||
cg.add(var.set_adc_avg_samples(config[CONF_ADC_AVERAGING]))
|
||||
|
||||
if CONF_BUS_VOLTAGE in config:
|
||||
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.const import (
|
||||
CONF_BATTERY_LEVEL,
|
||||
CONF_EXTERNAL_TEMPERATURE,
|
||||
CONF_HUMIDITY,
|
||||
CONF_MAC_ADDRESS,
|
||||
CONF_TEMPERATURE,
|
||||
@ -19,8 +20,6 @@ from esphome.const import (
|
||||
CODEOWNERS = ["@fkirill"]
|
||||
DEPENDENCIES = ["esp32_ble_tracker"]
|
||||
|
||||
CONF_EXTERNAL_TEMPERATURE = "external_temperature"
|
||||
|
||||
inkbird_ibsth1_mini_ns = cg.esphome_ns.namespace("inkbird_ibsth1_mini")
|
||||
InkbirdIbstH1Mini = inkbird_ibsth1_mini_ns.class_(
|
||||
"InkbirdIbstH1Mini", esp32_ble_tracker.ESPBTDeviceListener, cg.Component
|
||||
|
@ -120,6 +120,7 @@ void LightState::loop() {
|
||||
// Apply transformer (if any)
|
||||
if (this->transformer_ != nullptr) {
|
||||
auto values = this->transformer_->apply();
|
||||
this->is_transformer_active_ = true;
|
||||
if (values.has_value()) {
|
||||
this->current_values = *values;
|
||||
this->output_->update_state(this);
|
||||
@ -131,6 +132,7 @@ void LightState::loop() {
|
||||
this->current_values = this->transformer_->get_target_values();
|
||||
|
||||
this->transformer_->stop();
|
||||
this->is_transformer_active_ = false;
|
||||
this->transformer_ = nullptr;
|
||||
this->target_state_reached_callback_.call();
|
||||
}
|
||||
@ -214,6 +216,8 @@ void LightState::current_values_as_ct(float *color_temperature, float *white_bri
|
||||
this->gamma_correct_);
|
||||
}
|
||||
|
||||
bool LightState::is_transformer_active() { return this->is_transformer_active_; }
|
||||
|
||||
void LightState::start_effect_(uint32_t effect_index) {
|
||||
this->stop_effect_();
|
||||
if (effect_index == 0)
|
||||
@ -263,6 +267,7 @@ void LightState::start_flash_(const LightColorValues &target, uint32_t length, b
|
||||
}
|
||||
|
||||
void LightState::set_immediately_(const LightColorValues &target, bool set_remote_values) {
|
||||
this->is_transformer_active_ = false;
|
||||
this->transformer_ = nullptr;
|
||||
this->current_values = target;
|
||||
if (set_remote_values) {
|
||||
|
@ -144,6 +144,17 @@ class LightState : public EntityBase, public Component {
|
||||
|
||||
void current_values_as_ct(float *color_temperature, float *white_brightness);
|
||||
|
||||
/**
|
||||
* Indicator if a transformer (e.g. transition) is active. This is useful
|
||||
* for effects e.g. at the start of the apply() method, add a check like:
|
||||
*
|
||||
* if (this->state_->is_transformer_active()) {
|
||||
* // Something is already running.
|
||||
* return;
|
||||
* }
|
||||
*/
|
||||
bool is_transformer_active();
|
||||
|
||||
protected:
|
||||
friend LightOutput;
|
||||
friend LightCall;
|
||||
@ -203,6 +214,9 @@ class LightState : public EntityBase, public Component {
|
||||
LightRestoreMode restore_mode_;
|
||||
/// List of effects for this light.
|
||||
std::vector<LightEffect *> effects_;
|
||||
|
||||
// for effects, true if a transformer (transition) is active.
|
||||
bool is_transformer_active_ = false;
|
||||
};
|
||||
|
||||
} // namespace light
|
||||
|
@ -3,6 +3,8 @@
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/hal.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace esphome {
|
||||
namespace lightwaverf {
|
||||
|
||||
|
@ -38,9 +38,14 @@ void LilygoT547Touchscreen::setup() {
|
||||
}
|
||||
|
||||
this->write_register(POWER_REGISTER, WAKEUP_CMD, 1);
|
||||
|
||||
this->x_raw_max_ = this->get_width_();
|
||||
this->y_raw_max_ = this->get_height_();
|
||||
if (this->display_ != nullptr) {
|
||||
if (this->x_raw_max_ == this->x_raw_min_) {
|
||||
this->x_raw_max_ = this->display_->get_native_width();
|
||||
}
|
||||
if (this->y_raw_max_ == this->y_raw_min_) {
|
||||
this->x_raw_max_ = this->display_->get_native_height();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LilygoT547Touchscreen::update_touches() {
|
||||
|
@ -140,7 +140,9 @@ def uart_selection(value):
|
||||
raise cv.Invalid(f"Arduino framework does not support {value}.")
|
||||
variant = get_esp32_variant()
|
||||
if CORE.using_esp_idf and variant == VARIANT_ESP32C3 and value == USB_CDC:
|
||||
raise cv.Invalid(f"esp idf variant {variant} does not support {value}.")
|
||||
raise cv.Invalid(
|
||||
f"{value} is not supported for variant {variant} when using ESP-IDF."
|
||||
)
|
||||
if variant in UART_SELECTION_ESP32:
|
||||
return cv.one_of(*UART_SELECTION_ESP32[variant], upper=True)(value)
|
||||
if CORE.is_esp8266:
|
||||
@ -292,6 +294,16 @@ async def to_code(config):
|
||||
add_idf_sdkconfig_option("CONFIG_ESP_CONSOLE_USB_CDC", True)
|
||||
elif config[CONF_HARDWARE_UART] == USB_SERIAL_JTAG:
|
||||
add_idf_sdkconfig_option("CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG", True)
|
||||
try:
|
||||
uart_selection(USB_SERIAL_JTAG)
|
||||
cg.add_define("USE_LOGGER_USB_SERIAL_JTAG")
|
||||
except cv.Invalid:
|
||||
pass
|
||||
try:
|
||||
uart_selection(USB_CDC)
|
||||
cg.add_define("USE_LOGGER_USB_CDC")
|
||||
except cv.Invalid:
|
||||
pass
|
||||
|
||||
try:
|
||||
uart_selection(USB_SERIAL_JTAG)
|
||||
|
@ -161,26 +161,6 @@ Logger::Logger(uint32_t baud_rate, size_t tx_buffer_size) : baud_rate_(baud_rate
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef USE_USB_CDC
|
||||
#ifndef USE_ZEPHYR
|
||||
void Logger::loop() {
|
||||
#ifdef USE_ARDUINO
|
||||
if (this->uart_ != UART_SELECTION_USB_CDC) {
|
||||
return;
|
||||
}
|
||||
static bool opened = false;
|
||||
if (opened == Serial) {
|
||||
return;
|
||||
}
|
||||
if (false == opened) {
|
||||
App.schedule_dump_config();
|
||||
}
|
||||
opened = !opened;
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
void Logger::set_baud_rate(uint32_t baud_rate) { this->baud_rate_ = baud_rate; }
|
||||
void Logger::set_log_level(const std::string &tag, int log_level) {
|
||||
this->log_levels_.push_back(LogLevelOverride{tag, log_level});
|
||||
|
@ -41,16 +41,14 @@ enum UARTSelection {
|
||||
#else
|
||||
UART_SELECTION_UART0 = 0,
|
||||
#endif
|
||||
#ifndef USE_NRF52
|
||||
UART_SELECTION_UART1,
|
||||
#endif
|
||||
#if defined(USE_LIBRETINY) || defined(USE_ESP32_VARIANT_ESP32)
|
||||
UART_SELECTION_UART2,
|
||||
#endif
|
||||
#ifdef USE_USB_CDC
|
||||
#ifdef USE_LOGGER_USB_CDC
|
||||
UART_SELECTION_USB_CDC,
|
||||
#endif
|
||||
#ifdef USE_USB_SERIAL_JTAG
|
||||
#ifdef USE_LOGGER_USB_SERIAL_JTAG
|
||||
UART_SELECTION_USB_SERIAL_JTAG,
|
||||
#endif
|
||||
#ifdef USE_ESP8266
|
||||
|
@ -8,7 +8,7 @@
|
||||
#ifdef USE_ESP_IDF
|
||||
#include <driver/uart.h>
|
||||
|
||||
#ifdef USE_USB_SERIAL_JTAG
|
||||
#ifdef USE_LOGGER_USB_SERIAL_JTAG
|
||||
#include <driver/usb_serial_jtag.h>
|
||||
#include <esp_vfs_dev.h>
|
||||
#include <esp_vfs_usb_serial_jtag.h>
|
||||
@ -32,7 +32,7 @@ static const char *const TAG = "logger";
|
||||
|
||||
#ifdef USE_ESP_IDF
|
||||
|
||||
#ifdef USE_USB_SERIAL_JTAG
|
||||
#ifdef USE_LOGGER_USB_SERIAL_JTAG
|
||||
static void init_usb_serial_jtag_() {
|
||||
setvbuf(stdin, NULL, _IONBF, 0); // Disable buffering on stdin
|
||||
|
||||
@ -103,7 +103,7 @@ void Logger::pre_setup() {
|
||||
break;
|
||||
#endif
|
||||
|
||||
#ifdef USE_USB_CDC
|
||||
#ifdef USE_LOGGER_USB_CDC
|
||||
case UART_SELECTION_USB_CDC:
|
||||
this->hw_serial_ = &Serial;
|
||||
#if ARDUINO_USB_CDC_ON_BOOT
|
||||
@ -134,7 +134,7 @@ void Logger::pre_setup() {
|
||||
this->uart_num_ = -1;
|
||||
break;
|
||||
#endif
|
||||
#ifdef USE_USB_SERIAL_JTAG
|
||||
#ifdef USE_LOGGER_USB_SERIAL_JTAG
|
||||
case UART_SELECTION_USB_SERIAL_JTAG:
|
||||
this->uart_num_ = -1;
|
||||
init_usb_serial_jtag_();
|
||||
@ -177,6 +177,8 @@ void HOT Logger::write_msg_(const char *msg) {
|
||||
uart_write_bytes(this->uart_num_, "\n", 1);
|
||||
}
|
||||
}
|
||||
#else
|
||||
void HOT Logger::write_msg_(const char *msg) { this->hw_serial_->println(msg); }
|
||||
#endif
|
||||
|
||||
const char *const UART_SELECTIONS[] = {
|
||||
@ -184,10 +186,10 @@ const char *const UART_SELECTIONS[] = {
|
||||
#ifdef USE_ESP32_VARIANT_ESP32
|
||||
"UART2",
|
||||
#endif
|
||||
#ifdef USE_USB_CDC
|
||||
#ifdef USE_LOGGER_USB_CDC
|
||||
"USB_CDC",
|
||||
#endif
|
||||
#ifdef USE_USB_SERIAL_JTAG
|
||||
#ifdef USE_LOGGER_USB_SERIAL_JTAG
|
||||
"USB_SERIAL_JTAG",
|
||||
#endif
|
||||
};
|
||||
|
@ -1,7 +1,4 @@
|
||||
#ifdef USE_ESP8266
|
||||
#ifndef USE_ARDUINO
|
||||
#error "Only ARDUINO is supported"
|
||||
#endif
|
||||
#include "logger.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
@ -37,6 +34,8 @@ void Logger::pre_setup() {
|
||||
ESP_LOGI(TAG, "Log initialized");
|
||||
}
|
||||
|
||||
void HOT Logger::write_msg_(const char *msg) { this->hw_serial_->println(msg); }
|
||||
|
||||
const char *const UART_SELECTIONS[] = {"UART0", "UART1", "UART0_SWAP"};
|
||||
|
||||
const char *Logger::get_uart_selection_() { return UART_SELECTIONS[this->uart_]; }
|
||||
|
22
esphome/components/logger/logger_host.cpp
Normal file
22
esphome/components/logger/logger_host.cpp
Normal file
@ -0,0 +1,22 @@
|
||||
#if defined(USE_HOST)
|
||||
#include "logger.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace logger {
|
||||
|
||||
void HOT Logger::write_msg_(const char *msg) {
|
||||
time_t rawtime;
|
||||
struct tm *timeinfo;
|
||||
char buffer[80];
|
||||
|
||||
time(&rawtime);
|
||||
timeinfo = localtime(&rawtime);
|
||||
strftime(buffer, sizeof buffer, "[%H:%M:%S]", timeinfo);
|
||||
fputs(buffer, stdout);
|
||||
puts(msg);
|
||||
}
|
||||
|
||||
} // namespace logger
|
||||
} // namespace esphome
|
||||
|
||||
#endif
|
@ -50,6 +50,8 @@ void Logger::pre_setup() {
|
||||
ESP_LOGI(TAG, "Log initialized");
|
||||
}
|
||||
|
||||
void HOT Logger::write_msg_(const char *msg) { this->hw_serial_->println(msg); }
|
||||
|
||||
const char *const UART_SELECTIONS[] = {"DEFAULT", "UART0", "UART1", "UART2"};
|
||||
|
||||
const char *Logger::get_uart_selection_() { return UART_SELECTIONS[this->uart_]; }
|
||||
|
@ -1,7 +1,4 @@
|
||||
#ifdef USE_RP2040
|
||||
#ifndef USE_ARDUINO
|
||||
#error "Only ARDUINO is supported"
|
||||
#endif
|
||||
#include "logger.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
@ -31,6 +28,8 @@ void Logger::pre_setup() {
|
||||
ESP_LOGI(TAG, "Log initialized");
|
||||
}
|
||||
|
||||
void HOT Logger::write_msg_(const char *msg) { this->hw_serial_->println(msg); }
|
||||
|
||||
const char *const UART_SELECTIONS[] = {"UART0", "UART1", "USB_CDC"};
|
||||
|
||||
const char *Logger::get_uart_selection_() { return UART_SELECTIONS[this->uart_]; }
|
||||
|
@ -8,10 +8,23 @@ namespace 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 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 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 value = 0;
|
||||
|
||||
@ -58,7 +71,7 @@ void LTR390Component::read_als_() {
|
||||
uint32_t als = *val;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@ -74,7 +87,7 @@ void LTR390Component::read_uvs_() {
|
||||
uint32_t uv = *val;
|
||||
|
||||
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) {
|
||||
@ -132,12 +145,13 @@ void LTR390Component::setup() {
|
||||
// Set gain
|
||||
this->reg(LTR390_GAIN) = gain_;
|
||||
|
||||
// Set resolution
|
||||
uint8_t res = this->reg(LTR390_MEAS_RATE).get();
|
||||
// resolution is in bits 5-7
|
||||
res &= ~0b01110000;
|
||||
res |= res << 4;
|
||||
this->reg(LTR390_MEAS_RATE) = res;
|
||||
// Set resolution and measurement rate
|
||||
this->reg(LTR390_MEAS_RATE) = RESOLUTION_SETTING[this->res_];
|
||||
|
||||
// Set sensitivity by linearly scaling against known value in the datasheet
|
||||
float gain_scale = GAINVALUES[this->gain_] / GAIN_MAX;
|
||||
float intg_scale = (RESOLUTIONVALUE[this->res_] * 100) / INTG_MAX;
|
||||
this->sensitivity_ = SENSITIVITY_MAX * gain_scale * intg_scale;
|
||||
|
||||
// Set sensor read state
|
||||
this->reading_ = false;
|
||||
|
@ -17,14 +17,6 @@ enum LTR390CTRL {
|
||||
};
|
||||
|
||||
// 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
|
||||
enum LTR390MODE {
|
||||
LTR390_MODE_ALS,
|
||||
@ -81,6 +73,7 @@ class LTR390Component : public PollingComponent, public i2c::I2CDevice {
|
||||
|
||||
LTR390GAIN gain_;
|
||||
LTR390RESOLUTION res_;
|
||||
float sensitivity_;
|
||||
float wfac_;
|
||||
|
||||
sensor::Sensor *light_sensor_{nullptr};
|
||||
|
@ -8,6 +8,7 @@ from esphome.const import (
|
||||
CONF_RESOLUTION,
|
||||
UNIT_LUX,
|
||||
ICON_BRIGHTNESS_5,
|
||||
DEVICE_CLASS_EMPTY,
|
||||
DEVICE_CLASS_ILLUMINANCE,
|
||||
)
|
||||
|
||||
@ -61,22 +62,22 @@ CONFIG_SCHEMA = cv.All(
|
||||
unit_of_measurement=UNIT_COUNTS,
|
||||
icon=ICON_BRIGHTNESS_5,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_ILLUMINANCE,
|
||||
device_class=DEVICE_CLASS_EMPTY,
|
||||
),
|
||||
cv.Optional(CONF_UV_INDEX): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_UVI,
|
||||
icon=ICON_BRIGHTNESS_5,
|
||||
accuracy_decimals=5,
|
||||
device_class=DEVICE_CLASS_ILLUMINANCE,
|
||||
device_class=DEVICE_CLASS_EMPTY,
|
||||
),
|
||||
cv.Optional(CONF_UV): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_COUNTS,
|
||||
icon=ICON_BRIGHTNESS_5,
|
||||
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_RESOLUTION, default=18): cv.enum(RES_OPTIONS),
|
||||
cv.Optional(CONF_GAIN, default="X18"): cv.enum(GAIN_OPTIONS),
|
||||
cv.Optional(CONF_RESOLUTION, default=20): cv.enum(RES_OPTIONS),
|
||||
cv.Optional(CONF_WINDOW_CORRECTION_FACTOR, default=1.0): cv.float_range(
|
||||
min=1.0
|
||||
),
|
||||
|
367
esphome/components/micro_wake_word/__init__.py
Normal file
367
esphome/components/micro_wake_word/__init__.py
Normal file
@ -0,0 +1,367 @@
|
||||
import logging
|
||||
|
||||
import json
|
||||
import hashlib
|
||||
from urllib.parse import urljoin
|
||||
from pathlib import Path
|
||||
import requests
|
||||
|
||||
import esphome.config_validation as cv
|
||||
import esphome.codegen as cg
|
||||
|
||||
from esphome.core import CORE, HexInt, EsphomeError
|
||||
|
||||
from esphome.components import esp32, microphone
|
||||
from esphome import automation, git, external_files
|
||||
from esphome.automation import register_action, register_condition
|
||||
|
||||
|
||||
from esphome.const import (
|
||||
__version__,
|
||||
CONF_ID,
|
||||
CONF_MICROPHONE,
|
||||
CONF_MODEL,
|
||||
CONF_URL,
|
||||
CONF_FILE,
|
||||
CONF_PATH,
|
||||
CONF_REF,
|
||||
CONF_REFRESH,
|
||||
CONF_TYPE,
|
||||
CONF_USERNAME,
|
||||
CONF_PASSWORD,
|
||||
CONF_RAW_DATA_ID,
|
||||
TYPE_GIT,
|
||||
TYPE_LOCAL,
|
||||
)
|
||||
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
CODEOWNERS = ["@kahrendt", "@jesserockz"]
|
||||
DEPENDENCIES = ["microphone"]
|
||||
DOMAIN = "micro_wake_word"
|
||||
|
||||
CONF_PROBABILITY_CUTOFF = "probability_cutoff"
|
||||
CONF_SLIDING_WINDOW_AVERAGE_SIZE = "sliding_window_average_size"
|
||||
CONF_ON_WAKE_WORD_DETECTED = "on_wake_word_detected"
|
||||
|
||||
TYPE_HTTP = "http"
|
||||
|
||||
micro_wake_word_ns = cg.esphome_ns.namespace("micro_wake_word")
|
||||
|
||||
MicroWakeWord = micro_wake_word_ns.class_("MicroWakeWord", cg.Component)
|
||||
|
||||
StartAction = micro_wake_word_ns.class_("StartAction", automation.Action)
|
||||
StopAction = micro_wake_word_ns.class_("StopAction", automation.Action)
|
||||
|
||||
IsRunningCondition = micro_wake_word_ns.class_(
|
||||
"IsRunningCondition", automation.Condition
|
||||
)
|
||||
|
||||
|
||||
def _validate_json_filename(value):
|
||||
value = cv.string(value)
|
||||
if not value.endswith(".json"):
|
||||
raise cv.Invalid("Manifest filename must end with .json")
|
||||
return value
|
||||
|
||||
|
||||
def _process_git_source(config):
|
||||
repo_dir, _ = git.clone_or_update(
|
||||
url=config[CONF_URL],
|
||||
ref=config.get(CONF_REF),
|
||||
refresh=config[CONF_REFRESH],
|
||||
domain=DOMAIN,
|
||||
username=config.get(CONF_USERNAME),
|
||||
password=config.get(CONF_PASSWORD),
|
||||
)
|
||||
|
||||
if not (repo_dir / config[CONF_FILE]).exists():
|
||||
raise cv.Invalid("File does not exist in repository")
|
||||
|
||||
return config
|
||||
|
||||
|
||||
CV_GIT_SCHEMA = cv.GIT_SCHEMA
|
||||
if isinstance(CV_GIT_SCHEMA, dict):
|
||||
CV_GIT_SCHEMA = cv.Schema(CV_GIT_SCHEMA)
|
||||
|
||||
GIT_SCHEMA = cv.All(
|
||||
CV_GIT_SCHEMA.extend(
|
||||
{
|
||||
cv.Required(CONF_FILE): _validate_json_filename,
|
||||
cv.Optional(CONF_REFRESH, default="1d"): cv.All(
|
||||
cv.string, cv.source_refresh
|
||||
),
|
||||
}
|
||||
),
|
||||
_process_git_source,
|
||||
)
|
||||
|
||||
KEY_WAKE_WORD = "wake_word"
|
||||
KEY_AUTHOR = "author"
|
||||
KEY_WEBSITE = "website"
|
||||
KEY_VERSION = "version"
|
||||
KEY_MICRO = "micro"
|
||||
KEY_MINIMUM_ESPHOME_VERSION = "minimum_esphome_version"
|
||||
|
||||
MANIFEST_SCHEMA_V1 = cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_TYPE): "micro",
|
||||
cv.Required(KEY_WAKE_WORD): cv.string,
|
||||
cv.Required(KEY_AUTHOR): cv.string,
|
||||
cv.Required(KEY_WEBSITE): cv.url,
|
||||
cv.Required(KEY_VERSION): cv.All(cv.int_, 1),
|
||||
cv.Required(CONF_MODEL): cv.string,
|
||||
cv.Required(KEY_MICRO): cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_PROBABILITY_CUTOFF): cv.float_,
|
||||
cv.Required(CONF_SLIDING_WINDOW_AVERAGE_SIZE): cv.positive_int,
|
||||
cv.Optional(KEY_MINIMUM_ESPHOME_VERSION): cv.All(
|
||||
cv.version_number, cv.validate_esphome_version
|
||||
),
|
||||
}
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def _compute_local_file_path(config: dict) -> Path:
|
||||
url = config[CONF_URL]
|
||||
h = hashlib.new("sha256")
|
||||
h.update(url.encode())
|
||||
key = h.hexdigest()[:8]
|
||||
base_dir = external_files.compute_local_file_dir(DOMAIN)
|
||||
return base_dir / key
|
||||
|
||||
|
||||
def _download_file(url: str, path: Path) -> bytes:
|
||||
if not external_files.has_remote_file_changed(url, path):
|
||||
_LOGGER.debug("Remote file has not changed, skipping download")
|
||||
return path.read_bytes()
|
||||
|
||||
try:
|
||||
req = requests.get(
|
||||
url,
|
||||
timeout=external_files.NETWORK_TIMEOUT,
|
||||
headers={"User-agent": f"ESPHome/{__version__} (https://esphome.io)"},
|
||||
)
|
||||
req.raise_for_status()
|
||||
except requests.exceptions.RequestException as e:
|
||||
raise cv.Invalid(f"Could not download file from {url}: {e}") from e
|
||||
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.write_bytes(req.content)
|
||||
return req.content
|
||||
|
||||
|
||||
def _process_http_source(config):
|
||||
url = config[CONF_URL]
|
||||
path = _compute_local_file_path(config)
|
||||
|
||||
json_path = path / "manifest.json"
|
||||
|
||||
json_contents = _download_file(url, json_path)
|
||||
|
||||
manifest_data = json.loads(json_contents)
|
||||
if not isinstance(manifest_data, dict):
|
||||
raise cv.Invalid("Manifest file must contain a JSON object")
|
||||
|
||||
try:
|
||||
MANIFEST_SCHEMA_V1(manifest_data)
|
||||
except cv.Invalid as e:
|
||||
raise cv.Invalid(f"Invalid manifest file: {e}") from e
|
||||
|
||||
model = manifest_data[CONF_MODEL]
|
||||
model_url = urljoin(url, model)
|
||||
|
||||
model_path = path / model
|
||||
|
||||
_download_file(str(model_url), model_path)
|
||||
|
||||
return config
|
||||
|
||||
|
||||
HTTP_SCHEMA = cv.All(
|
||||
{
|
||||
cv.Required(CONF_URL): cv.url,
|
||||
},
|
||||
_process_http_source,
|
||||
)
|
||||
|
||||
LOCAL_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_PATH): cv.All(_validate_json_filename, cv.file_),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def _validate_source_model_name(value):
|
||||
if not isinstance(value, str):
|
||||
raise cv.Invalid("Model name must be a string")
|
||||
|
||||
if value.endswith(".json"):
|
||||
raise cv.Invalid("Model name must not end with .json")
|
||||
|
||||
return MODEL_SOURCE_SCHEMA(
|
||||
{
|
||||
CONF_TYPE: TYPE_HTTP,
|
||||
CONF_URL: f"https://github.com/esphome/micro-wake-word-models/raw/main/models/{value}.json",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def _validate_source_shorthand(value):
|
||||
if not isinstance(value, str):
|
||||
raise cv.Invalid("Shorthand only for strings")
|
||||
|
||||
try: # Test for model name
|
||||
return _validate_source_model_name(value)
|
||||
except cv.Invalid:
|
||||
pass
|
||||
|
||||
try: # Test for local path
|
||||
return MODEL_SOURCE_SCHEMA({CONF_TYPE: TYPE_LOCAL, CONF_PATH: value})
|
||||
except cv.Invalid:
|
||||
pass
|
||||
|
||||
try: # Test for http url
|
||||
return MODEL_SOURCE_SCHEMA({CONF_TYPE: TYPE_HTTP, CONF_URL: value})
|
||||
except cv.Invalid:
|
||||
pass
|
||||
|
||||
git_file = git.GitFile.from_shorthand(value)
|
||||
|
||||
conf = {
|
||||
CONF_TYPE: TYPE_GIT,
|
||||
CONF_URL: git_file.git_url,
|
||||
CONF_FILE: git_file.filename,
|
||||
}
|
||||
if git_file.ref:
|
||||
conf[CONF_REF] = git_file.ref
|
||||
|
||||
try:
|
||||
return MODEL_SOURCE_SCHEMA(conf)
|
||||
except cv.Invalid as e:
|
||||
raise cv.Invalid(
|
||||
f"Could not find file '{git_file.filename}' in the repository. Please make sure it exists."
|
||||
) from e
|
||||
|
||||
|
||||
MODEL_SOURCE_SCHEMA = cv.Any(
|
||||
_validate_source_shorthand,
|
||||
cv.typed_schema(
|
||||
{
|
||||
TYPE_GIT: GIT_SCHEMA,
|
||||
TYPE_LOCAL: LOCAL_SCHEMA,
|
||||
TYPE_HTTP: HTTP_SCHEMA,
|
||||
}
|
||||
),
|
||||
msg="Not a valid model name, local path, http(s) url, or github shorthand",
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(MicroWakeWord),
|
||||
cv.GenerateID(CONF_MICROPHONE): cv.use_id(microphone.Microphone),
|
||||
cv.Optional(CONF_PROBABILITY_CUTOFF): cv.percentage,
|
||||
cv.Optional(CONF_SLIDING_WINDOW_AVERAGE_SIZE): cv.positive_int,
|
||||
cv.Optional(CONF_ON_WAKE_WORD_DETECTED): automation.validate_automation(
|
||||
single=True
|
||||
),
|
||||
cv.Required(CONF_MODEL): MODEL_SOURCE_SCHEMA,
|
||||
cv.GenerateID(CONF_RAW_DATA_ID): cv.declare_id(cg.uint8),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA),
|
||||
cv.only_with_esp_idf,
|
||||
)
|
||||
|
||||
|
||||
def _load_model_data(manifest_path: Path):
|
||||
with open(manifest_path, encoding="utf-8") as f:
|
||||
manifest = json.load(f)
|
||||
|
||||
try:
|
||||
MANIFEST_SCHEMA_V1(manifest)
|
||||
except cv.Invalid as e:
|
||||
raise EsphomeError(f"Invalid manifest file: {e}") from e
|
||||
|
||||
model_path = urljoin(str(manifest_path), manifest[CONF_MODEL])
|
||||
|
||||
with open(model_path, "rb") as f:
|
||||
model = f.read()
|
||||
|
||||
return manifest, model
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
|
||||
mic = await cg.get_variable(config[CONF_MICROPHONE])
|
||||
cg.add(var.set_microphone(mic))
|
||||
|
||||
if on_wake_word_detection_config := config.get(CONF_ON_WAKE_WORD_DETECTED):
|
||||
await automation.build_automation(
|
||||
var.get_wake_word_detected_trigger(),
|
||||
[(cg.std_string, "wake_word")],
|
||||
on_wake_word_detection_config,
|
||||
)
|
||||
|
||||
esp32.add_idf_component(
|
||||
name="esp-tflite-micro",
|
||||
repo="https://github.com/espressif/esp-tflite-micro",
|
||||
)
|
||||
|
||||
cg.add_build_flag("-DTF_LITE_STATIC_MEMORY")
|
||||
cg.add_build_flag("-DTF_LITE_DISABLE_X86_NEON")
|
||||
cg.add_build_flag("-DESP_NN")
|
||||
|
||||
model_config = config.get(CONF_MODEL)
|
||||
data = []
|
||||
if model_config[CONF_TYPE] == TYPE_GIT:
|
||||
# compute path to model file
|
||||
key = f"{model_config[CONF_URL]}@{model_config.get(CONF_REF)}"
|
||||
base_dir = Path(CORE.data_dir) / DOMAIN
|
||||
h = hashlib.new("sha256")
|
||||
h.update(key.encode())
|
||||
file: Path = base_dir / h.hexdigest()[:8] / model_config[CONF_FILE]
|
||||
|
||||
elif model_config[CONF_TYPE] == TYPE_LOCAL:
|
||||
file = model_config[CONF_PATH]
|
||||
|
||||
elif model_config[CONF_TYPE] == TYPE_HTTP:
|
||||
file = _compute_local_file_path(model_config) / "manifest.json"
|
||||
|
||||
manifest, data = _load_model_data(file)
|
||||
|
||||
rhs = [HexInt(x) for x in data]
|
||||
prog_arr = cg.progmem_array(config[CONF_RAW_DATA_ID], rhs)
|
||||
cg.add(var.set_model_start(prog_arr))
|
||||
|
||||
probability_cutoff = config.get(
|
||||
CONF_PROBABILITY_CUTOFF, manifest[KEY_MICRO][CONF_PROBABILITY_CUTOFF]
|
||||
)
|
||||
cg.add(var.set_probability_cutoff(probability_cutoff))
|
||||
sliding_window_average_size = config.get(
|
||||
CONF_SLIDING_WINDOW_AVERAGE_SIZE,
|
||||
manifest[KEY_MICRO][CONF_SLIDING_WINDOW_AVERAGE_SIZE],
|
||||
)
|
||||
cg.add(var.set_sliding_window_average_size(sliding_window_average_size))
|
||||
|
||||
cg.add(var.set_wake_word(manifest[KEY_WAKE_WORD]))
|
||||
|
||||
|
||||
MICRO_WAKE_WORD_ACTION_SCHEMA = cv.Schema({cv.GenerateID(): cv.use_id(MicroWakeWord)})
|
||||
|
||||
|
||||
@register_action("micro_wake_word.start", StartAction, MICRO_WAKE_WORD_ACTION_SCHEMA)
|
||||
@register_action("micro_wake_word.stop", StopAction, MICRO_WAKE_WORD_ACTION_SCHEMA)
|
||||
@register_condition(
|
||||
"micro_wake_word.is_running", IsRunningCondition, MICRO_WAKE_WORD_ACTION_SCHEMA
|
||||
)
|
||||
async def micro_wake_word_action_to_code(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
return var
|
@ -0,0 +1,493 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_ESP_IDF
|
||||
|
||||
// Converted audio_preprocessor_int8.tflite
|
||||
// From https://github.com/tensorflow/tflite-micro/tree/main/tensorflow/lite/micro/examples/micro_speech/models accessed
|
||||
// January 2024
|
||||
//
|
||||
// Copyright 2023 The TensorFlow Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
namespace esphome {
|
||||
namespace micro_wake_word {
|
||||
|
||||
const unsigned char G_AUDIO_PREPROCESSOR_INT8_TFLITE[] = {
|
||||
0x1c, 0x00, 0x00, 0x00, 0x54, 0x46, 0x4c, 0x33, 0x14, 0x00, 0x20, 0x00, 0x1c, 0x00, 0x18, 0x00, 0x14, 0x00, 0x10,
|
||||
0x00, 0x0c, 0x00, 0x00, 0x00, 0x08, 0x00, 0x04, 0x00, 0x14, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x88, 0x00,
|
||||
0x00, 0x00, 0xe0, 0x00, 0x00, 0x00, 0x80, 0x0e, 0x00, 0x00, 0x90, 0x0e, 0x00, 0x00, 0xcc, 0x1f, 0x00, 0x00, 0x03,
|
||||
0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0xe2, 0xeb, 0xff, 0xff, 0x0c, 0x00, 0x00, 0x00,
|
||||
0x1c, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x73, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67,
|
||||
0x5f, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x94, 0xff,
|
||||
0xff, 0xff, 0x2a, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x6f, 0x75, 0x74, 0x70, 0x75,
|
||||
0x74, 0x5f, 0x30, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0xc2, 0xf5, 0xff, 0xff,
|
||||
0x04, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x61, 0x75, 0x64, 0x69, 0x6f, 0x5f, 0x66, 0x72, 0x61, 0x6d, 0x65,
|
||||
0x00, 0x02, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0xdc, 0xff, 0xff, 0xff, 0x2d, 0x00,
|
||||
0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x43, 0x4f, 0x4e, 0x56, 0x45, 0x52, 0x53, 0x49, 0x4f,
|
||||
0x4e, 0x5f, 0x4d, 0x45, 0x54, 0x41, 0x44, 0x41, 0x54, 0x41, 0x00, 0x08, 0x00, 0x0c, 0x00, 0x08, 0x00, 0x04, 0x00,
|
||||
0x08, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x6d, 0x69, 0x6e,
|
||||
0x5f, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x00, 0x2e, 0x00,
|
||||
0x00, 0x00, 0x9c, 0x0d, 0x00, 0x00, 0x94, 0x0d, 0x00, 0x00, 0xc4, 0x09, 0x00, 0x00, 0x6c, 0x09, 0x00, 0x00, 0x48,
|
||||
0x09, 0x00, 0x00, 0x34, 0x09, 0x00, 0x00, 0x20, 0x09, 0x00, 0x00, 0x0c, 0x09, 0x00, 0x00, 0xf8, 0x08, 0x00, 0x00,
|
||||
0xec, 0x07, 0x00, 0x00, 0x88, 0x07, 0x00, 0x00, 0x24, 0x07, 0x00, 0x00, 0xc0, 0x06, 0x00, 0x00, 0x38, 0x04, 0x00,
|
||||
0x00, 0xb0, 0x01, 0x00, 0x00, 0x9c, 0x01, 0x00, 0x00, 0x88, 0x01, 0x00, 0x00, 0x74, 0x01, 0x00, 0x00, 0x60, 0x01,
|
||||
0x00, 0x00, 0x4c, 0x01, 0x00, 0x00, 0x44, 0x01, 0x00, 0x00, 0x3c, 0x01, 0x00, 0x00, 0x34, 0x01, 0x00, 0x00, 0x2c,
|
||||
0x01, 0x00, 0x00, 0x24, 0x01, 0x00, 0x00, 0x1c, 0x01, 0x00, 0x00, 0x14, 0x01, 0x00, 0x00, 0x0c, 0x01, 0x00, 0x00,
|
||||
0x04, 0x01, 0x00, 0x00, 0xfc, 0x00, 0x00, 0x00, 0xf4, 0x00, 0x00, 0x00, 0xec, 0x00, 0x00, 0x00, 0xe4, 0x00, 0x00,
|
||||
0x00, 0xdc, 0x00, 0x00, 0x00, 0xd4, 0x00, 0x00, 0x00, 0xcc, 0x00, 0x00, 0x00, 0xc4, 0x00, 0x00, 0x00, 0xbc, 0x00,
|
||||
0x00, 0x00, 0xb4, 0x00, 0x00, 0x00, 0xac, 0x00, 0x00, 0x00, 0xa4, 0x00, 0x00, 0x00, 0x9c, 0x00, 0x00, 0x00, 0x94,
|
||||
0x00, 0x00, 0x00, 0x8c, 0x00, 0x00, 0x00, 0x6c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0xf2, 0xf6, 0xff, 0xff,
|
||||
0x04, 0x00, 0x00, 0x00, 0x58, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x08, 0x00, 0x0c, 0x00, 0x08, 0x00, 0x04,
|
||||
0x00, 0x08, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x08, 0x00, 0x0c, 0x00, 0x08, 0x00,
|
||||
0x07, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x0a, 0x00, 0x10, 0x00, 0x0c, 0x00, 0x08, 0x00, 0x04, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
|
||||
0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x32, 0x2e, 0x31, 0x32, 0x2e, 0x30, 0x00,
|
||||
0x00, 0x56, 0xf7, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x32, 0x2e, 0x38, 0x2e, 0x30, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd4, 0xe1, 0xff, 0xff, 0xd8, 0xe1, 0xff, 0xff, 0xdc,
|
||||
0xe1, 0xff, 0xff, 0xe0, 0xe1, 0xff, 0xff, 0xe4, 0xe1, 0xff, 0xff, 0xe8, 0xe1, 0xff, 0xff, 0xec, 0xe1, 0xff, 0xff,
|
||||
0xf0, 0xe1, 0xff, 0xff, 0xf4, 0xe1, 0xff, 0xff, 0xf8, 0xe1, 0xff, 0xff, 0xfc, 0xe1, 0xff, 0xff, 0x00, 0xe2, 0xff,
|
||||
0xff, 0x04, 0xe2, 0xff, 0xff, 0x08, 0xe2, 0xff, 0xff, 0x0c, 0xe2, 0xff, 0xff, 0x10, 0xe2, 0xff, 0xff, 0x14, 0xe2,
|
||||
0xff, 0xff, 0x18, 0xe2, 0xff, 0xff, 0x1c, 0xe2, 0xff, 0xff, 0x20, 0xe2, 0xff, 0xff, 0x24, 0xe2, 0xff, 0xff, 0x28,
|
||||
0xe2, 0xff, 0xff, 0x2c, 0xe2, 0xff, 0xff, 0x30, 0xe2, 0xff, 0xff, 0xd2, 0xf7, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00,
|
||||
0x04, 0x00, 0x00, 0x00, 0x9a, 0x02, 0x00, 0x00, 0xe2, 0xf7, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00,
|
||||
0x00, 0x00, 0x01, 0x00, 0x00, 0xf2, 0xf7, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x80, 0xff,
|
||||
0xff, 0xff, 0x02, 0xf8, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x12,
|
||||
0xf8, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x00, 0x00, 0x22, 0xf8, 0xff, 0xff,
|
||||
0x04, 0x00, 0x00, 0x00, 0x78, 0x02, 0x00, 0x00, 0x00, 0x00, 0x61, 0x05, 0x00, 0x00, 0x00, 0x00, 0x23, 0x0b, 0x41,
|
||||
0x01, 0x00, 0x00, 0x00, 0x00, 0xb3, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x74, 0x0e, 0x80, 0x05,
|
||||
0x00, 0x00, 0x00, 0x00, 0xd1, 0x0c, 0x63, 0x04, 0x00, 0x00, 0x00, 0x00, 0x34, 0x0c, 0x3f, 0x04, 0x00, 0x00, 0x00,
|
||||
0x00, 0x81, 0x0c, 0xf7, 0x04, 0x00, 0x00, 0x00, 0x00, 0x9f, 0x0d, 0x77, 0x06, 0x00, 0x00, 0x00, 0x00, 0x7b, 0x0f,
|
||||
0xa9, 0x08, 0x01, 0x02, 0x7f, 0x0b, 0x22, 0x05, 0x00, 0x00, 0x00, 0x00, 0xe9, 0x0e, 0xd1, 0x08, 0xdb, 0x02, 0x00,
|
||||
0x00, 0x00, 0x00, 0x03, 0x0d, 0x4a, 0x07, 0xad, 0x01, 0x2c, 0x0c, 0xc6, 0x06, 0x79, 0x01, 0x00, 0x00, 0x00, 0x00,
|
||||
0x45, 0x0c, 0x29, 0x07, 0x23, 0x02, 0x34, 0x0d, 0x5b, 0x08, 0x96, 0x03, 0x00, 0x00, 0x00, 0x00, 0xe5, 0x0e, 0x48,
|
||||
0x0a, 0xbd, 0x05, 0x45, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xde, 0x0c, 0x88, 0x08, 0x43, 0x04,
|
||||
0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe9, 0x0b, 0xd3, 0x07, 0xcb, 0x03, 0xd2, 0x0f, 0xe7,
|
||||
0x0b, 0x09, 0x08, 0x39, 0x04, 0x76, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xbf, 0x0c, 0x14, 0x09,
|
||||
0x75, 0x05, 0xe2, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5a, 0x0e, 0xdd, 0x0a, 0x6b, 0x07, 0x03,
|
||||
0x04, 0xa6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x53, 0x0d, 0x09, 0x0a, 0xc9, 0x06, 0x93, 0x03, 0x65, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x41, 0x0d, 0x25, 0x0a, 0x12, 0x07, 0x07, 0x04, 0x05, 0x01, 0x00, 0x00, 0x00,
|
||||
0x00, 0x0a, 0x0e, 0x17, 0x0b, 0x2c, 0x08, 0x49, 0x05, 0x6d, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x98, 0x0f, 0xcb, 0x0c, 0x04, 0x0a, 0x44, 0x07, 0x8b, 0x04, 0xd8, 0x01, 0x00, 0x00, 0x00, 0x00, 0x2c, 0x0f, 0x87,
|
||||
0x0c, 0xe7, 0x09, 0x4e, 0x07, 0xba, 0x04, 0x2d, 0x02, 0x00, 0x00, 0x00, 0x00, 0xa5, 0x0f, 0x23, 0x0d, 0xa7, 0x0a,
|
||||
0x30, 0x08, 0xbe, 0x05, 0x52, 0x03, 0xeb, 0x00, 0x89, 0x0e, 0x2c, 0x0c, 0xd4, 0x09, 0x81, 0x07, 0x33, 0x05, 0xe9,
|
||||
0x02, 0xa5, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x0e, 0x29, 0x0c, 0xf1, 0x09, 0xbe, 0x07, 0x90, 0x05, 0x65, 0x03,
|
||||
0x3f, 0x01, 0x1d, 0x0f, 0xff, 0x0c, 0xe5, 0x0a, 0xcf, 0x08, 0xbc, 0x06, 0xae, 0x04, 0xa3, 0x02, 0x9c, 0x00, 0x99,
|
||||
0x0e, 0x99, 0x0c, 0x9d, 0x0a, 0xa4, 0x08, 0xaf, 0x06, 0xbd, 0x04, 0xcf, 0x02, 0xe4, 0x00, 0xfc, 0x0e, 0x17, 0x0d,
|
||||
0x36, 0x0b, 0x57, 0x09, 0x7c, 0x07, 0xa4, 0x05, 0xcf, 0x03, 0xfd, 0x01, 0x2e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x62, 0x0e, 0x98, 0x0c, 0xd2, 0x0a, 0x0e, 0x09, 0x4d, 0x07, 0x8f, 0x05, 0xd4, 0x03, 0x1b, 0x02,
|
||||
0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb1, 0x0e, 0x00, 0x0d, 0x52, 0x0b, 0xa6, 0x09, 0xfd, 0x07, 0x56, 0x06, 0xb1,
|
||||
0x04, 0x0f, 0x03, 0x6f, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd2, 0x0f, 0x37, 0x0e, 0x9e, 0x0c,
|
||||
0x08, 0x0b, 0x73, 0x09, 0xe1, 0x07, 0x52, 0x06, 0xc4, 0x04, 0x38, 0x03, 0xaf, 0x01, 0x28, 0x00, 0xa3, 0x0e, 0x1f,
|
||||
0x0d, 0x9e, 0x0b, 0x1f, 0x0a, 0xa2, 0x08, 0x27, 0x07, 0xae, 0x05, 0x37, 0x04, 0xc2, 0x02, 0x4e, 0x01, 0x00, 0x00,
|
||||
0x00, 0x00, 0xdd, 0x0f, 0x6d, 0x0e, 0xff, 0x0c, 0x93, 0x0b, 0x29, 0x0a, 0xc1, 0x08, 0x5a, 0x07, 0xf5, 0x05, 0x92,
|
||||
0x04, 0x30, 0x03, 0xd1, 0x01, 0x73, 0x00, 0x16, 0x0f, 0xbc, 0x0d, 0x62, 0x0c, 0x0b, 0x0b, 0xb5, 0x09, 0x61, 0x08,
|
||||
0x0e, 0x07, 0xbd, 0x05, 0x6d, 0x04, 0x1f, 0x03, 0xd3, 0x01, 0x88, 0x00, 0x3e, 0x0f, 0xf6, 0x0d, 0xaf, 0x0c, 0x6a,
|
||||
0x0b, 0x27, 0x0a, 0xe4, 0x08, 0xa3, 0x07, 0x64, 0x06, 0x26, 0x05, 0xe9, 0x03, 0xae, 0x02, 0x74, 0x01, 0x3b, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x0f, 0xce, 0x0d, 0x99, 0x0c, 0x66, 0x0b, 0x34, 0x0a, 0x03,
|
||||
0x09, 0xd3, 0x07, 0xa5, 0x06, 0x78, 0x05, 0x4c, 0x04, 0x22, 0x03, 0xf8, 0x01, 0xd0, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0xa9, 0x0f, 0x83, 0x0e, 0x5f, 0x0d, 0x3b, 0x0c, 0x19, 0x0b, 0xf8, 0x09, 0xd8, 0x08, 0xb9, 0x07, 0x9b, 0x06, 0x7e,
|
||||
0x05, 0x63, 0x04, 0x48, 0x03, 0x2f, 0x02, 0x17, 0x01, 0x00, 0x00, 0x00, 0x00, 0xa6, 0xfa, 0xff, 0xff, 0x04, 0x00,
|
||||
0x00, 0x00, 0x78, 0x02, 0x00, 0x00, 0x00, 0x00, 0x9e, 0x0a, 0x00, 0x00, 0x00, 0x00, 0xdc, 0x04, 0xbe, 0x0e, 0x00,
|
||||
0x00, 0x00, 0x00, 0x4c, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8b, 0x01, 0x7f, 0x0a, 0x00, 0x00,
|
||||
0x00, 0x00, 0x2e, 0x03, 0x9c, 0x0b, 0x00, 0x00, 0x00, 0x00, 0xcb, 0x03, 0xc0, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x7e,
|
||||
0x03, 0x08, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x60, 0x02, 0x88, 0x09, 0x00, 0x00, 0x00, 0x00, 0x84, 0x00, 0x56, 0x07,
|
||||
0xfe, 0x0d, 0x80, 0x04, 0xdd, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x16, 0x01, 0x2e, 0x07, 0x24, 0x0d, 0x00, 0x00, 0x00,
|
||||
0x00, 0xfc, 0x02, 0xb5, 0x08, 0x52, 0x0e, 0xd3, 0x03, 0x39, 0x09, 0x86, 0x0e, 0x00, 0x00, 0x00, 0x00, 0xba, 0x03,
|
||||
0xd6, 0x08, 0xdc, 0x0d, 0xcb, 0x02, 0xa4, 0x07, 0x69, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x1a, 0x01, 0xb7, 0x05, 0x42,
|
||||
0x0a, 0xba, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x03, 0x77, 0x07, 0xbc, 0x0b, 0xf1, 0x0f,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, 0x04, 0x2c, 0x08, 0x34, 0x0c, 0x2d, 0x00, 0x18, 0x04, 0xf6,
|
||||
0x07, 0xc6, 0x0b, 0x89, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x03, 0xeb, 0x06, 0x8a, 0x0a,
|
||||
0x1d, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa5, 0x01, 0x22, 0x05, 0x94, 0x08, 0xfc, 0x0b, 0x59,
|
||||
0x0f, 0x00, 0x00, 0x00, 0x00, 0xac, 0x02, 0xf6, 0x05, 0x36, 0x09, 0x6c, 0x0c, 0x9a, 0x0f, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0xbe, 0x02, 0xda, 0x05, 0xed, 0x08, 0xf8, 0x0b, 0xfa, 0x0e, 0x00, 0x00, 0x00, 0x00, 0xf5,
|
||||
0x01, 0xe8, 0x04, 0xd3, 0x07, 0xb6, 0x0a, 0x92, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x67, 0x00,
|
||||
0x34, 0x03, 0xfb, 0x05, 0xbb, 0x08, 0x74, 0x0b, 0x27, 0x0e, 0x00, 0x00, 0x00, 0x00, 0xd3, 0x00, 0x78, 0x03, 0x18,
|
||||
0x06, 0xb1, 0x08, 0x45, 0x0b, 0xd2, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x5a, 0x00, 0xdc, 0x02, 0x58, 0x05, 0xcf, 0x07,
|
||||
0x41, 0x0a, 0xad, 0x0c, 0x14, 0x0f, 0x76, 0x01, 0xd3, 0x03, 0x2b, 0x06, 0x7e, 0x08, 0xcc, 0x0a, 0x16, 0x0d, 0x5a,
|
||||
0x0f, 0x00, 0x00, 0x00, 0x00, 0x9b, 0x01, 0xd6, 0x03, 0x0e, 0x06, 0x41, 0x08, 0x6f, 0x0a, 0x9a, 0x0c, 0xc0, 0x0e,
|
||||
0xe2, 0x00, 0x00, 0x03, 0x1a, 0x05, 0x30, 0x07, 0x43, 0x09, 0x51, 0x0b, 0x5c, 0x0d, 0x63, 0x0f, 0x66, 0x01, 0x66,
|
||||
0x03, 0x62, 0x05, 0x5b, 0x07, 0x50, 0x09, 0x42, 0x0b, 0x30, 0x0d, 0x1b, 0x0f, 0x03, 0x01, 0xe8, 0x02, 0xc9, 0x04,
|
||||
0xa8, 0x06, 0x83, 0x08, 0x5b, 0x0a, 0x30, 0x0c, 0x02, 0x0e, 0xd1, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x9d, 0x01, 0x67, 0x03, 0x2d, 0x05, 0xf1, 0x06, 0xb2, 0x08, 0x70, 0x0a, 0x2b, 0x0c, 0xe4, 0x0d, 0x9a, 0x0f,
|
||||
0x00, 0x00, 0x00, 0x00, 0x4e, 0x01, 0xff, 0x02, 0xad, 0x04, 0x59, 0x06, 0x02, 0x08, 0xa9, 0x09, 0x4e, 0x0b, 0xf0,
|
||||
0x0c, 0x90, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2d, 0x00, 0xc8, 0x01, 0x61, 0x03, 0xf7, 0x04,
|
||||
0x8c, 0x06, 0x1e, 0x08, 0xad, 0x09, 0x3b, 0x0b, 0xc7, 0x0c, 0x50, 0x0e, 0xd7, 0x0f, 0x5c, 0x01, 0xe0, 0x02, 0x61,
|
||||
0x04, 0xe0, 0x05, 0x5d, 0x07, 0xd8, 0x08, 0x51, 0x0a, 0xc8, 0x0b, 0x3d, 0x0d, 0xb1, 0x0e, 0x00, 0x00, 0x00, 0x00,
|
||||
0x22, 0x00, 0x92, 0x01, 0x00, 0x03, 0x6c, 0x04, 0xd6, 0x05, 0x3e, 0x07, 0xa5, 0x08, 0x0a, 0x0a, 0x6d, 0x0b, 0xcf,
|
||||
0x0c, 0x2e, 0x0e, 0x8c, 0x0f, 0xe9, 0x00, 0x43, 0x02, 0x9d, 0x03, 0xf4, 0x04, 0x4a, 0x06, 0x9e, 0x07, 0xf1, 0x08,
|
||||
0x42, 0x0a, 0x92, 0x0b, 0xe0, 0x0c, 0x2c, 0x0e, 0x77, 0x0f, 0xc1, 0x00, 0x09, 0x02, 0x50, 0x03, 0x95, 0x04, 0xd8,
|
||||
0x05, 0x1b, 0x07, 0x5c, 0x08, 0x9b, 0x09, 0xd9, 0x0a, 0x16, 0x0c, 0x51, 0x0d, 0x8b, 0x0e, 0xc4, 0x0f, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfb, 0x00, 0x31, 0x02, 0x66, 0x03, 0x99, 0x04, 0xcb, 0x05, 0xfc, 0x06, 0x2c,
|
||||
0x08, 0x5a, 0x09, 0x87, 0x0a, 0xb3, 0x0b, 0xdd, 0x0c, 0x07, 0x0e, 0x2f, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x56, 0x00,
|
||||
0x7c, 0x01, 0xa0, 0x02, 0xc4, 0x03, 0xe6, 0x04, 0x07, 0x06, 0x27, 0x07, 0x46, 0x08, 0x64, 0x09, 0x81, 0x0a, 0x9c,
|
||||
0x0b, 0xb7, 0x0c, 0xd0, 0x0d, 0xe8, 0x0e, 0x00, 0x10, 0x00, 0x00, 0x2a, 0xfd, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00,
|
||||
0x52, 0x00, 0x00, 0x00, 0x04, 0x00, 0x06, 0x00, 0x08, 0x00, 0x08, 0x00, 0x0a, 0x00, 0x0c, 0x00, 0x0e, 0x00, 0x10,
|
||||
0x00, 0x12, 0x00, 0x16, 0x00, 0x18, 0x00, 0x1a, 0x00, 0x1e, 0x00, 0x20, 0x00, 0x24, 0x00, 0x26, 0x00, 0x2a, 0x00,
|
||||
0x2e, 0x00, 0x32, 0x00, 0x36, 0x00, 0x3a, 0x00, 0x40, 0x00, 0x44, 0x00, 0x4a, 0x00, 0x4e, 0x00, 0x54, 0x00, 0x5a,
|
||||
0x00, 0x62, 0x00, 0x68, 0x00, 0x70, 0x00, 0x78, 0x00, 0x80, 0x00, 0x88, 0x00, 0x92, 0x00, 0x9a, 0x00, 0xa6, 0x00,
|
||||
0xb0, 0x00, 0xbc, 0x00, 0xc8, 0x00, 0xd4, 0x00, 0xe2, 0x00, 0x00, 0x00, 0x8a, 0xfd, 0xff, 0xff, 0x04, 0x00, 0x00,
|
||||
0x00, 0x52, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x08, 0x00, 0x0c, 0x00, 0x10, 0x00, 0x14, 0x00, 0x18, 0x00,
|
||||
0x1c, 0x00, 0x20, 0x00, 0x24, 0x00, 0x28, 0x00, 0x2c, 0x00, 0x30, 0x00, 0x34, 0x00, 0x38, 0x00, 0x3c, 0x00, 0x44,
|
||||
0x00, 0x4c, 0x00, 0x50, 0x00, 0x58, 0x00, 0x60, 0x00, 0x68, 0x00, 0x70, 0x00, 0x78, 0x00, 0x80, 0x00, 0x88, 0x00,
|
||||
0x90, 0x00, 0x98, 0x00, 0xa0, 0x00, 0xa8, 0x00, 0xb0, 0x00, 0xb8, 0x00, 0xc4, 0x00, 0xd0, 0x00, 0xdc, 0x00, 0xe8,
|
||||
0x00, 0xf4, 0x00, 0x00, 0x01, 0x0c, 0x01, 0x1c, 0x01, 0x2c, 0x01, 0x00, 0x00, 0xea, 0xfd, 0xff, 0xff, 0x04, 0x00,
|
||||
0x00, 0x00, 0x52, 0x00, 0x00, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04,
|
||||
0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x08, 0x00,
|
||||
0x08, 0x00, 0x04, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08,
|
||||
0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x0c, 0x00, 0x0c, 0x00, 0x0c, 0x00, 0x0c, 0x00,
|
||||
0x0c, 0x00, 0x0c, 0x00, 0x0c, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x00, 0x00, 0x4a, 0xfe, 0xff, 0xff, 0x04,
|
||||
0x00, 0x00, 0x00, 0xfa, 0x00, 0x00, 0x00, 0x7c, 0x7f, 0x79, 0x7f, 0x76, 0x7f, 0xfa, 0xff, 0x00, 0x00, 0x00, 0x00,
|
||||
0x70, 0x7f, 0xf4, 0xff, 0x00, 0x00, 0x00, 0x00, 0x64, 0x7f, 0xe9, 0xff, 0xfe, 0xff, 0x00, 0x00, 0x4b, 0x7f, 0xd0,
|
||||
0xff, 0x00, 0x00, 0x00, 0x00, 0x1b, 0x7f, 0xa0, 0xff, 0x00, 0x00, 0x00, 0x00, 0xbb, 0x7e, 0x42, 0xff, 0x00, 0x00,
|
||||
0x00, 0x00, 0xfd, 0x7d, 0x86, 0xfe, 0x04, 0x00, 0x00, 0x00, 0x87, 0x7c, 0x1d, 0xfd, 0x12, 0x00, 0x00, 0x00, 0xb6,
|
||||
0x79, 0x7f, 0xfa, 0x3e, 0x00, 0x00, 0x00, 0x73, 0x74, 0xf9, 0xf5, 0xca, 0x00, 0x00, 0x00, 0x36, 0x6b, 0x33, 0xef,
|
||||
0x32, 0x02, 0x00, 0x00, 0x9b, 0x5c, 0x87, 0xe7, 0xce, 0x04, 0x00, 0x00, 0xf0, 0x48, 0xde, 0xe2, 0xa0, 0x07, 0x00,
|
||||
0x00, 0x6e, 0x33, 0x8a, 0xe4, 0xa4, 0x08, 0x00, 0x00, 0x9c, 0x20, 0x22, 0xeb, 0x4c, 0x07, 0x00, 0x00, 0x0a, 0x13,
|
||||
0x7d, 0xf2, 0x02, 0x05, 0x00, 0x00, 0x89, 0x0a, 0x17, 0xf8, 0x06, 0x03, 0x00, 0x00, 0xa6, 0x05, 0xa0, 0xfb, 0xb4,
|
||||
0x01, 0x00, 0x00, 0xfa, 0x02, 0xac, 0xfd, 0xe8, 0x00, 0x00, 0x00, 0x8e, 0x01, 0xc7, 0xfe, 0x7a, 0x00, 0x00, 0x00,
|
||||
0xcf, 0x00, 0x5c, 0xff, 0x40, 0x00, 0x00, 0x00, 0x6b, 0x00, 0xab, 0xff, 0x22, 0x00, 0x00, 0x00, 0x38, 0x00, 0xd3,
|
||||
0xff, 0x12, 0x00, 0x00, 0x00, 0x1d, 0x00, 0xea, 0xff, 0x08, 0x00, 0x00, 0x00, 0x0f, 0x00, 0xf3, 0xff, 0x06, 0x00,
|
||||
0x00, 0x00, 0x08, 0x00, 0xf8, 0xff, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0xfe, 0xff, 0x00, 0x00, 0x00, 0x00, 0x02,
|
||||
0x00, 0xfd, 0xff, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0xfd, 0xff,
|
||||
0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x52, 0xff, 0xff, 0xff, 0x04, 0x00, 0x00,
|
||||
0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x62, 0xff, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00,
|
||||
0x00, 0x00, 0xf1, 0x00, 0x00, 0x00, 0x72, 0xff, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01,
|
||||
0x00, 0x00, 0x00, 0x82, 0xff, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x4d, 0x01, 0x00, 0x00,
|
||||
0x92, 0xff, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb2, 0xff, 0xff, 0xff, 0x04, 0x00,
|
||||
0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x08, 0x00,
|
||||
0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0xc0, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
|
||||
0x00, 0x02, 0x00, 0x04, 0x00, 0x05, 0x00, 0x07, 0x00, 0x0a, 0x00, 0x0d, 0x00, 0x10, 0x00, 0x13, 0x00, 0x17, 0x00,
|
||||
0x1b, 0x00, 0x20, 0x00, 0x25, 0x00, 0x2a, 0x00, 0x30, 0x00, 0x35, 0x00, 0x3c, 0x00, 0x42, 0x00, 0x49, 0x00, 0x51,
|
||||
0x00, 0x58, 0x00, 0x60, 0x00, 0x68, 0x00, 0x71, 0x00, 0x7a, 0x00, 0x83, 0x00, 0x8d, 0x00, 0x97, 0x00, 0xa1, 0x00,
|
||||
0xac, 0x00, 0xb7, 0x00, 0xc2, 0x00, 0xcd, 0x00, 0xd9, 0x00, 0xe5, 0x00, 0xf2, 0x00, 0xff, 0x00, 0x0c, 0x01, 0x19,
|
||||
0x01, 0x27, 0x01, 0x35, 0x01, 0x43, 0x01, 0x52, 0x01, 0x61, 0x01, 0x70, 0x01, 0x7f, 0x01, 0x8f, 0x01, 0x9f, 0x01,
|
||||
0xaf, 0x01, 0xc0, 0x01, 0xd1, 0x01, 0xe2, 0x01, 0xf3, 0x01, 0x05, 0x02, 0x17, 0x02, 0x29, 0x02, 0x3c, 0x02, 0x4e,
|
||||
0x02, 0x61, 0x02, 0x75, 0x02, 0x88, 0x02, 0x9c, 0x02, 0xb0, 0x02, 0xc4, 0x02, 0xd8, 0x02, 0xed, 0x02, 0x02, 0x03,
|
||||
0x17, 0x03, 0x2c, 0x03, 0x41, 0x03, 0x57, 0x03, 0x6d, 0x03, 0x83, 0x03, 0x99, 0x03, 0xb0, 0x03, 0xc7, 0x03, 0xdd,
|
||||
0x03, 0xf4, 0x03, 0x0c, 0x04, 0x23, 0x04, 0x3b, 0x04, 0x52, 0x04, 0x6a, 0x04, 0x82, 0x04, 0x9a, 0x04, 0xb3, 0x04,
|
||||
0xcb, 0x04, 0xe4, 0x04, 0xfd, 0x04, 0x16, 0x05, 0x2f, 0x05, 0x48, 0x05, 0x61, 0x05, 0x7a, 0x05, 0x94, 0x05, 0xad,
|
||||
0x05, 0xc7, 0x05, 0xe1, 0x05, 0xfb, 0x05, 0x15, 0x06, 0x2f, 0x06, 0x49, 0x06, 0x63, 0x06, 0x7e, 0x06, 0x98, 0x06,
|
||||
0xb2, 0x06, 0xcd, 0x06, 0xe7, 0x06, 0x02, 0x07, 0x1d, 0x07, 0x37, 0x07, 0x52, 0x07, 0x6d, 0x07, 0x87, 0x07, 0xa2,
|
||||
0x07, 0xbd, 0x07, 0xd8, 0x07, 0xf3, 0x07, 0x0d, 0x08, 0x28, 0x08, 0x43, 0x08, 0x5e, 0x08, 0x79, 0x08, 0x93, 0x08,
|
||||
0xae, 0x08, 0xc9, 0x08, 0xe3, 0x08, 0xfe, 0x08, 0x19, 0x09, 0x33, 0x09, 0x4e, 0x09, 0x68, 0x09, 0x82, 0x09, 0x9d,
|
||||
0x09, 0xb7, 0x09, 0xd1, 0x09, 0xeb, 0x09, 0x05, 0x0a, 0x1f, 0x0a, 0x39, 0x0a, 0x53, 0x0a, 0x6c, 0x0a, 0x86, 0x0a,
|
||||
0x9f, 0x0a, 0xb8, 0x0a, 0xd1, 0x0a, 0xea, 0x0a, 0x03, 0x0b, 0x1c, 0x0b, 0x35, 0x0b, 0x4d, 0x0b, 0x66, 0x0b, 0x7e,
|
||||
0x0b, 0x96, 0x0b, 0xae, 0x0b, 0xc5, 0x0b, 0xdd, 0x0b, 0xf4, 0x0b, 0x0c, 0x0c, 0x23, 0x0c, 0x39, 0x0c, 0x50, 0x0c,
|
||||
0x67, 0x0c, 0x7d, 0x0c, 0x93, 0x0c, 0xa9, 0x0c, 0xbf, 0x0c, 0xd4, 0x0c, 0xe9, 0x0c, 0xfe, 0x0c, 0x13, 0x0d, 0x28,
|
||||
0x0d, 0x3c, 0x0d, 0x50, 0x0d, 0x64, 0x0d, 0x78, 0x0d, 0x8b, 0x0d, 0x9f, 0x0d, 0xb2, 0x0d, 0xc4, 0x0d, 0xd7, 0x0d,
|
||||
0xe9, 0x0d, 0xfb, 0x0d, 0x0d, 0x0e, 0x1e, 0x0e, 0x2f, 0x0e, 0x40, 0x0e, 0x51, 0x0e, 0x61, 0x0e, 0x71, 0x0e, 0x81,
|
||||
0x0e, 0x90, 0x0e, 0x9f, 0x0e, 0xae, 0x0e, 0xbd, 0x0e, 0xcb, 0x0e, 0xd9, 0x0e, 0xe7, 0x0e, 0xf4, 0x0e, 0x01, 0x0f,
|
||||
0x0e, 0x0f, 0x1b, 0x0f, 0x27, 0x0f, 0x33, 0x0f, 0x3e, 0x0f, 0x49, 0x0f, 0x54, 0x0f, 0x5f, 0x0f, 0x69, 0x0f, 0x73,
|
||||
0x0f, 0x7d, 0x0f, 0x86, 0x0f, 0x8f, 0x0f, 0x98, 0x0f, 0xa0, 0x0f, 0xa8, 0x0f, 0xaf, 0x0f, 0xb7, 0x0f, 0xbe, 0x0f,
|
||||
0xc4, 0x0f, 0xcb, 0x0f, 0xd0, 0x0f, 0xd6, 0x0f, 0xdb, 0x0f, 0xe0, 0x0f, 0xe5, 0x0f, 0xe9, 0x0f, 0xed, 0x0f, 0xf0,
|
||||
0x0f, 0xf3, 0x0f, 0xf6, 0x0f, 0xf9, 0x0f, 0xfb, 0x0f, 0xfc, 0x0f, 0xfe, 0x0f, 0xff, 0x0f, 0x00, 0x10, 0x00, 0x10,
|
||||
0x00, 0x10, 0x00, 0x10, 0xff, 0x0f, 0xfe, 0x0f, 0xfc, 0x0f, 0xfb, 0x0f, 0xf9, 0x0f, 0xf6, 0x0f, 0xf3, 0x0f, 0xf0,
|
||||
0x0f, 0xed, 0x0f, 0xe9, 0x0f, 0xe5, 0x0f, 0xe0, 0x0f, 0xdb, 0x0f, 0xd6, 0x0f, 0xd0, 0x0f, 0xcb, 0x0f, 0xc4, 0x0f,
|
||||
0xbe, 0x0f, 0xb7, 0x0f, 0xaf, 0x0f, 0xa8, 0x0f, 0xa0, 0x0f, 0x98, 0x0f, 0x8f, 0x0f, 0x86, 0x0f, 0x7d, 0x0f, 0x73,
|
||||
0x0f, 0x69, 0x0f, 0x5f, 0x0f, 0x54, 0x0f, 0x49, 0x0f, 0x3e, 0x0f, 0x33, 0x0f, 0x27, 0x0f, 0x1b, 0x0f, 0x0e, 0x0f,
|
||||
0x01, 0x0f, 0xf4, 0x0e, 0xe7, 0x0e, 0xd9, 0x0e, 0xcb, 0x0e, 0xbd, 0x0e, 0xae, 0x0e, 0x9f, 0x0e, 0x90, 0x0e, 0x81,
|
||||
0x0e, 0x71, 0x0e, 0x61, 0x0e, 0x51, 0x0e, 0x40, 0x0e, 0x2f, 0x0e, 0x1e, 0x0e, 0x0d, 0x0e, 0xfb, 0x0d, 0xe9, 0x0d,
|
||||
0xd7, 0x0d, 0xc4, 0x0d, 0xb2, 0x0d, 0x9f, 0x0d, 0x8b, 0x0d, 0x78, 0x0d, 0x64, 0x0d, 0x50, 0x0d, 0x3c, 0x0d, 0x28,
|
||||
0x0d, 0x13, 0x0d, 0xfe, 0x0c, 0xe9, 0x0c, 0xd4, 0x0c, 0xbf, 0x0c, 0xa9, 0x0c, 0x93, 0x0c, 0x7d, 0x0c, 0x67, 0x0c,
|
||||
0x50, 0x0c, 0x39, 0x0c, 0x23, 0x0c, 0x0c, 0x0c, 0xf4, 0x0b, 0xdd, 0x0b, 0xc5, 0x0b, 0xae, 0x0b, 0x96, 0x0b, 0x7e,
|
||||
0x0b, 0x66, 0x0b, 0x4d, 0x0b, 0x35, 0x0b, 0x1c, 0x0b, 0x03, 0x0b, 0xea, 0x0a, 0xd1, 0x0a, 0xb8, 0x0a, 0x9f, 0x0a,
|
||||
0x86, 0x0a, 0x6c, 0x0a, 0x53, 0x0a, 0x39, 0x0a, 0x1f, 0x0a, 0x05, 0x0a, 0xeb, 0x09, 0xd1, 0x09, 0xb7, 0x09, 0x9d,
|
||||
0x09, 0x82, 0x09, 0x68, 0x09, 0x4e, 0x09, 0x33, 0x09, 0x19, 0x09, 0xfe, 0x08, 0xe3, 0x08, 0xc9, 0x08, 0xae, 0x08,
|
||||
0x93, 0x08, 0x79, 0x08, 0x5e, 0x08, 0x43, 0x08, 0x28, 0x08, 0x0d, 0x08, 0xf3, 0x07, 0xd8, 0x07, 0xbd, 0x07, 0xa2,
|
||||
0x07, 0x87, 0x07, 0x6d, 0x07, 0x52, 0x07, 0x37, 0x07, 0x1d, 0x07, 0x02, 0x07, 0xe7, 0x06, 0xcd, 0x06, 0xb2, 0x06,
|
||||
0x98, 0x06, 0x7e, 0x06, 0x63, 0x06, 0x49, 0x06, 0x2f, 0x06, 0x15, 0x06, 0xfb, 0x05, 0xe1, 0x05, 0xc7, 0x05, 0xad,
|
||||
0x05, 0x94, 0x05, 0x7a, 0x05, 0x61, 0x05, 0x48, 0x05, 0x2f, 0x05, 0x16, 0x05, 0xfd, 0x04, 0xe4, 0x04, 0xcb, 0x04,
|
||||
0xb3, 0x04, 0x9a, 0x04, 0x82, 0x04, 0x6a, 0x04, 0x52, 0x04, 0x3b, 0x04, 0x23, 0x04, 0x0c, 0x04, 0xf4, 0x03, 0xdd,
|
||||
0x03, 0xc7, 0x03, 0xb0, 0x03, 0x99, 0x03, 0x83, 0x03, 0x6d, 0x03, 0x57, 0x03, 0x41, 0x03, 0x2c, 0x03, 0x17, 0x03,
|
||||
0x02, 0x03, 0xed, 0x02, 0xd8, 0x02, 0xc4, 0x02, 0xb0, 0x02, 0x9c, 0x02, 0x88, 0x02, 0x75, 0x02, 0x61, 0x02, 0x4e,
|
||||
0x02, 0x3c, 0x02, 0x29, 0x02, 0x17, 0x02, 0x05, 0x02, 0xf3, 0x01, 0xe2, 0x01, 0xd1, 0x01, 0xc0, 0x01, 0xaf, 0x01,
|
||||
0x9f, 0x01, 0x8f, 0x01, 0x7f, 0x01, 0x70, 0x01, 0x61, 0x01, 0x52, 0x01, 0x43, 0x01, 0x35, 0x01, 0x27, 0x01, 0x19,
|
||||
0x01, 0x0c, 0x01, 0xff, 0x00, 0xf2, 0x00, 0xe5, 0x00, 0xd9, 0x00, 0xcd, 0x00, 0xc2, 0x00, 0xb7, 0x00, 0xac, 0x00,
|
||||
0xa1, 0x00, 0x97, 0x00, 0x8d, 0x00, 0x83, 0x00, 0x7a, 0x00, 0x71, 0x00, 0x68, 0x00, 0x60, 0x00, 0x58, 0x00, 0x51,
|
||||
0x00, 0x49, 0x00, 0x42, 0x00, 0x3c, 0x00, 0x35, 0x00, 0x30, 0x00, 0x2a, 0x00, 0x25, 0x00, 0x20, 0x00, 0x1b, 0x00,
|
||||
0x17, 0x00, 0x13, 0x00, 0x10, 0x00, 0x0d, 0x00, 0x0a, 0x00, 0x07, 0x00, 0x05, 0x00, 0x04, 0x00, 0x02, 0x00, 0x01,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0xee, 0xff, 0xff, 0x38, 0xee, 0xff, 0xff, 0x0f, 0x00, 0x00, 0x00, 0x4d, 0x4c,
|
||||
0x49, 0x52, 0x20, 0x43, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x74, 0x65, 0x64, 0x2e, 0x00, 0x01, 0x00, 0x00, 0x00, 0x14,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x18, 0x00, 0x14, 0x00, 0x10, 0x00, 0x0c, 0x00, 0x08, 0x00, 0x04, 0x00,
|
||||
0x0e, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0xf4, 0x05, 0x00, 0x00, 0xf8, 0x05, 0x00,
|
||||
0x00, 0xfc, 0x05, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x6d, 0x61, 0x69, 0x6e, 0x00, 0x00, 0x00, 0x00, 0x16, 0x00,
|
||||
0x00, 0x00, 0xa0, 0x05, 0x00, 0x00, 0x68, 0x05, 0x00, 0x00, 0x24, 0x05, 0x00, 0x00, 0xc8, 0x04, 0x00, 0x00, 0x70,
|
||||
0x04, 0x00, 0x00, 0x4c, 0x04, 0x00, 0x00, 0x10, 0x04, 0x00, 0x00, 0xc8, 0x03, 0x00, 0x00, 0xa4, 0x03, 0x00, 0x00,
|
||||
0x4c, 0x03, 0x00, 0x00, 0x14, 0x03, 0x00, 0x00, 0x10, 0x02, 0x00, 0x00, 0xc8, 0x01, 0x00, 0x00, 0x6c, 0x01, 0x00,
|
||||
0x00, 0x48, 0x01, 0x00, 0x00, 0x14, 0x01, 0x00, 0x00, 0xe0, 0x00, 0x00, 0x00, 0xac, 0x00, 0x00, 0x00, 0x78, 0x00,
|
||||
0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0xf6, 0xfa, 0xff, 0xff, 0x0c,
|
||||
0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x2a, 0x00, 0x00, 0x00,
|
||||
0x01, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x16, 0xfb, 0xff, 0xff, 0x0c, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00,
|
||||
0x00, 0x11, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x28, 0x00,
|
||||
0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x3a, 0xfb, 0xff, 0xff, 0x0c, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x10,
|
||||
0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00,
|
||||
0x0e, 0x00, 0x00, 0x00, 0xa6, 0xfc, 0xff, 0xff, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x10, 0x00, 0x00,
|
||||
0x00, 0x14, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x68, 0xef, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00, 0x27, 0x00,
|
||||
0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0xd6, 0xfc, 0xff, 0xff, 0x14,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1d, 0x10, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00,
|
||||
0x98, 0xef, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00,
|
||||
0x00, 0x12, 0x00, 0x00, 0x00, 0x06, 0xfd, 0xff, 0xff, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x10, 0x00,
|
||||
0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0xc8, 0xef, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00, 0x25,
|
||||
0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x36, 0xfd, 0xff, 0xff,
|
||||
0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x15, 0x10, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00,
|
||||
0x00, 0xf8, 0xef, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x23, 0x00,
|
||||
0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x1e, 0xfc, 0xff, 0xff, 0x0c, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x05,
|
||||
0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00,
|
||||
0x84, 0xfc, 0xff, 0xff, 0x10, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x44, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00,
|
||||
0x00, 0x30, 0x00, 0x00, 0x00, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5f, 0x63, 0x6f, 0x72, 0x72, 0x65, 0x63, 0x74, 0x69,
|
||||
0x6f, 0x6e, 0x5f, 0x62, 0x69, 0x74, 0x73, 0x00, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x5f, 0x73, 0x63, 0x61, 0x6c,
|
||||
0x65, 0x00, 0x02, 0x24, 0x0f, 0x02, 0x01, 0x02, 0x03, 0x40, 0x04, 0x04, 0x04, 0x24, 0x01, 0x01, 0x00, 0x00, 0x00,
|
||||
0x22, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0xdc, 0xfc, 0xff, 0xff, 0x10, 0x00, 0x00,
|
||||
0x00, 0x24, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x73, 0x6e,
|
||||
0x72, 0x5f, 0x73, 0x68, 0x69, 0x66, 0x74, 0x00, 0x01, 0x0b, 0x01, 0x01, 0x01, 0x06, 0x04, 0x02, 0x24, 0x01, 0x01,
|
||||
0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
|
||||
0x08, 0x00, 0x00, 0x00, 0x20, 0xfd, 0xff, 0xff, 0x10, 0x00, 0x00, 0x00, 0xe4, 0x00, 0x00, 0x00, 0xec, 0x00, 0x00,
|
||||
0x00, 0x0a, 0x00, 0x00, 0x00, 0xd2, 0x00, 0x00, 0x00, 0x61, 0x6c, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x74, 0x65, 0x5f,
|
||||
0x6f, 0x6e, 0x65, 0x5f, 0x6d, 0x69, 0x6e, 0x75, 0x73, 0x5f, 0x73, 0x6d, 0x6f, 0x6f, 0x74, 0x68, 0x69, 0x6e, 0x67,
|
||||
0x00, 0x61, 0x6c, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x74, 0x65, 0x5f, 0x73, 0x6d, 0x6f, 0x6f, 0x74, 0x68, 0x69, 0x6e,
|
||||
0x67, 0x00, 0x63, 0x6c, 0x61, 0x6d, 0x70, 0x69, 0x6e, 0x67, 0x00, 0x6d, 0x69, 0x6e, 0x5f, 0x73, 0x69, 0x67, 0x6e,
|
||||
0x61, 0x6c, 0x5f, 0x72, 0x65, 0x6d, 0x61, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x00, 0x6e, 0x75, 0x6d, 0x5f, 0x63, 0x68,
|
||||
0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x00, 0x6f, 0x6e, 0x65, 0x5f, 0x6d, 0x69, 0x6e, 0x75, 0x73, 0x5f, 0x73, 0x6d,
|
||||
0x6f, 0x6f, 0x74, 0x68, 0x69, 0x6e, 0x67, 0x00, 0x73, 0x6d, 0x6f, 0x6f, 0x74, 0x68, 0x69, 0x6e, 0x67, 0x00, 0x73,
|
||||
0x6d, 0x6f, 0x6f, 0x74, 0x68, 0x69, 0x6e, 0x67, 0x5f, 0x62, 0x69, 0x74, 0x73, 0x00, 0x73, 0x70, 0x65, 0x63, 0x74,
|
||||
0x72, 0x61, 0x6c, 0x5f, 0x73, 0x75, 0x62, 0x74, 0x72, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x62, 0x69, 0x74,
|
||||
0x73, 0x00, 0x09, 0xa5, 0x88, 0x75, 0x6d, 0x59, 0x4d, 0x3a, 0x31, 0x23, 0x09, 0x00, 0x01, 0x00, 0x09, 0x00, 0x29,
|
||||
0x3c, 0xd7, 0x03, 0x00, 0x00, 0x33, 0x03, 0x28, 0x00, 0x67, 0x3e, 0x99, 0x01, 0x0a, 0x00, 0x0e, 0x00, 0x05, 0x05,
|
||||
0x69, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x1b, 0x25, 0x01, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00,
|
||||
0x00, 0x20, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x20, 0xfe, 0xff, 0xff, 0x10, 0x00,
|
||||
0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x01, 0x00, 0x00, 0x24, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
|
||||
0x1d, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x54, 0xfe, 0xff, 0xff, 0x10, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00,
|
||||
0x00, 0x2c, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x6e, 0x75, 0x6d, 0x5f, 0x63, 0x68,
|
||||
0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x00, 0x01, 0x0e, 0x01, 0x01, 0x01, 0x28, 0x04, 0x02, 0x24, 0x01, 0x00, 0x01,
|
||||
0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00,
|
||||
0x0c, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x62, 0xfe, 0xff,
|
||||
0xff, 0x0c, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x1c, 0x00,
|
||||
0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0xca, 0xff, 0xff, 0xff, 0x14, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x0a, 0x10, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x8c, 0xf2, 0xff, 0xff,
|
||||
0x01, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00,
|
||||
0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x18, 0x00, 0x14, 0x00, 0x10, 0x00, 0x0c, 0x00, 0x0b, 0x00,
|
||||
0x04, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x10, 0x00, 0x00, 0x00, 0x14,
|
||||
0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0xd0, 0xf2, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00,
|
||||
0x04, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00,
|
||||
0x00, 0xfe, 0xfe, 0xff, 0xff, 0x0c, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x00,
|
||||
0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x64, 0xff, 0xff, 0xff, 0x10,
|
||||
0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00,
|
||||
0x65, 0x6e, 0x64, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x00, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x69, 0x6e, 0x64,
|
||||
0x65, 0x78, 0x00, 0x02, 0x17, 0x0e, 0x00, 0x03, 0x00, 0x01, 0x00, 0x02, 0x00, 0xf1, 0x00, 0x05, 0x00, 0x05, 0x05,
|
||||
0x06, 0x25, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x17,
|
||||
0x00, 0x00, 0x00, 0xb8, 0xff, 0xff, 0xff, 0x10, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00,
|
||||
0x03, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x54, 0x00, 0x66, 0x66, 0x74, 0x5f, 0x6c, 0x65, 0x6e, 0x67, 0x74,
|
||||
0x68, 0x00, 0x02, 0x0e, 0x0d, 0x02, 0x00, 0x01, 0x00, 0x02, 0x00, 0x07, 0x00, 0x00, 0x02, 0x05, 0x05, 0x06, 0x25,
|
||||
0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x10,
|
||||
0x00, 0x14, 0x00, 0x10, 0x00, 0x0c, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x10, 0x00, 0x00, 0x00,
|
||||
0x10, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x24, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x16, 0x00,
|
||||
0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x10, 0x00, 0x0c, 0x00, 0x08,
|
||||
0x00, 0x04, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
|
||||
0x01, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00,
|
||||
0x00, 0x10, 0x00, 0x10, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x10, 0x00,
|
||||
0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x73,
|
||||
0x68, 0x69, 0x66, 0x74, 0x00, 0x01, 0x07, 0x01, 0x01, 0x01, 0x0c, 0x04, 0x02, 0x24, 0x01, 0x01, 0x00, 0x00, 0x00,
|
||||
0x13, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
|
||||
0x00, 0x2a, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x00, 0x00, 0xc4, 0x0a,
|
||||
0x00, 0x00, 0x74, 0x0a, 0x00, 0x00, 0x3c, 0x0a, 0x00, 0x00, 0x04, 0x0a, 0x00, 0x00, 0xd0, 0x09, 0x00, 0x00, 0x88,
|
||||
0x09, 0x00, 0x00, 0x40, 0x09, 0x00, 0x00, 0xfc, 0x08, 0x00, 0x00, 0xb8, 0x08, 0x00, 0x00, 0x6c, 0x08, 0x00, 0x00,
|
||||
0x20, 0x08, 0x00, 0x00, 0xd4, 0x07, 0x00, 0x00, 0x88, 0x07, 0x00, 0x00, 0x3c, 0x07, 0x00, 0x00, 0xf8, 0x06, 0x00,
|
||||
0x00, 0xb8, 0x06, 0x00, 0x00, 0x84, 0x06, 0x00, 0x00, 0x50, 0x06, 0x00, 0x00, 0x1c, 0x06, 0x00, 0x00, 0xd8, 0x05,
|
||||
0x00, 0x00, 0xa0, 0x05, 0x00, 0x00, 0x58, 0x05, 0x00, 0x00, 0x14, 0x05, 0x00, 0x00, 0xd8, 0x04, 0x00, 0x00, 0x98,
|
||||
0x04, 0x00, 0x00, 0x60, 0x04, 0x00, 0x00, 0x20, 0x04, 0x00, 0x00, 0xe8, 0x03, 0x00, 0x00, 0xb0, 0x03, 0x00, 0x00,
|
||||
0x6c, 0x03, 0x00, 0x00, 0x1c, 0x03, 0x00, 0x00, 0xc4, 0x02, 0x00, 0x00, 0x68, 0x02, 0x00, 0x00, 0x2c, 0x02, 0x00,
|
||||
0x00, 0xe4, 0x01, 0x00, 0x00, 0xac, 0x01, 0x00, 0x00, 0x78, 0x01, 0x00, 0x00, 0x44, 0x01, 0x00, 0x00, 0x08, 0x01,
|
||||
0x00, 0x00, 0xd0, 0x00, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0xfe,
|
||||
0xf5, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x09, 0x20, 0x00, 0x00, 0x00, 0x44, 0xf5, 0xff, 0xff, 0x11, 0x00, 0x00, 0x00, 0x50, 0x61, 0x72,
|
||||
0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x65, 0x64, 0x43, 0x61, 0x6c, 0x6c, 0x3a, 0x30, 0x00, 0x00, 0x00, 0x01, 0x00,
|
||||
0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x3e, 0xf6, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14,
|
||||
0x00, 0x00, 0x00, 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x1c, 0x00, 0x00, 0x00, 0x84, 0xf5, 0xff, 0xff,
|
||||
0x0d, 0x00, 0x00, 0x00, 0x63, 0x6c, 0x69, 0x70, 0x5f, 0x62, 0x79, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x00, 0x00,
|
||||
0x00, 0x01, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x7a, 0xf6, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00,
|
||||
0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x24, 0x00, 0x00, 0x00, 0xc0,
|
||||
0xf5, 0xff, 0xff, 0x15, 0x00, 0x00, 0x00, 0x63, 0x6c, 0x69, 0x70, 0x5f, 0x62, 0x79, 0x5f, 0x76, 0x61, 0x6c, 0x75,
|
||||
0x65, 0x2f, 0x4d, 0x69, 0x6e, 0x69, 0x6d, 0x75, 0x6d, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00,
|
||||
0x00, 0xbe, 0xf6, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x28, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x14, 0x00, 0x00, 0x00, 0x04, 0xf6, 0xff, 0xff, 0x05, 0x00, 0x00, 0x00, 0x61,
|
||||
0x64, 0x64, 0x5f, 0x31, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0xf2, 0xf6, 0xff, 0xff,
|
||||
0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x02, 0x18, 0x00, 0x00, 0x00, 0x38, 0xf6, 0xff, 0xff, 0x0b, 0x00, 0x00, 0x00, 0x54, 0x72, 0x75, 0x6e, 0x63, 0x61,
|
||||
0x74, 0x65, 0x44, 0x69, 0x76, 0x00, 0x01, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x2a, 0xf7, 0xff, 0xff, 0x00,
|
||||
0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02,
|
||||
0x10, 0x00, 0x00, 0x00, 0x70, 0xf6, 0xff, 0xff, 0x03, 0x00, 0x00, 0x00, 0x61, 0x64, 0x64, 0x00, 0x01, 0x00, 0x00,
|
||||
0x00, 0x28, 0x00, 0x00, 0x00, 0x5a, 0xf7, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00,
|
||||
0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x10, 0x00, 0x00, 0x00, 0xa0, 0xf6, 0xff, 0xff, 0x03,
|
||||
0x00, 0x00, 0x00, 0x6d, 0x75, 0x6c, 0x00, 0x01, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x8a, 0xf7, 0xff, 0xff,
|
||||
0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x02, 0x14, 0x00, 0x00, 0x00, 0xd0, 0xf6, 0xff, 0xff, 0x06, 0x00, 0x00, 0x00, 0x43, 0x61, 0x73, 0x74, 0x5f, 0x32,
|
||||
0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0xbe, 0xf7, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14,
|
||||
0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x24, 0x00, 0x00, 0x00,
|
||||
0x04, 0xf7, 0xff, 0xff, 0x16, 0x00, 0x00, 0x00, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x5f, 0x66, 0x69, 0x6c, 0x74,
|
||||
0x65, 0x72, 0x5f, 0x62, 0x61, 0x6e, 0x6b, 0x5f, 0x6c, 0x6f, 0x67, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x28, 0x00,
|
||||
0x00, 0x00, 0x02, 0xf8, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x22,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x18, 0x00, 0x00, 0x00, 0x48, 0xf7, 0xff, 0xff, 0x0b, 0x00, 0x00, 0x00,
|
||||
0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x5f, 0x70, 0x63, 0x61, 0x6e, 0x00, 0x01, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00,
|
||||
0x00, 0x3a, 0xf8, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x21, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x38, 0x00, 0x00, 0x00, 0x80, 0xf7, 0xff, 0xff, 0x28, 0x00, 0x00, 0x00, 0x73,
|
||||
0x69, 0x67, 0x6e, 0x61, 0x6c, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x5f, 0x62, 0x61, 0x6e, 0x6b, 0x5f, 0x73,
|
||||
0x70, 0x65, 0x63, 0x74, 0x72, 0x61, 0x6c, 0x5f, 0x73, 0x75, 0x62, 0x74, 0x72, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e,
|
||||
0x31, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x92, 0xf8, 0xff, 0xff, 0x00, 0x00,
|
||||
0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x34,
|
||||
0x00, 0x00, 0x00, 0xd8, 0xf7, 0xff, 0xff, 0x27, 0x00, 0x00, 0x00, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x5f, 0x66,
|
||||
0x69, 0x6c, 0x74, 0x65, 0x72, 0x5f, 0x62, 0x61, 0x6e, 0x6b, 0x5f, 0x73, 0x70, 0x65, 0x63, 0x74, 0x72, 0x61, 0x6c,
|
||||
0x5f, 0x73, 0x75, 0x62, 0x74, 0x72, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x01, 0x00, 0x00, 0x00, 0x28, 0x00,
|
||||
0x00, 0x00, 0xe6, 0xf8, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x1f,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x2c, 0x00, 0x00, 0x00, 0x2c, 0xf8, 0xff, 0xff, 0x1e, 0x00, 0x00, 0x00,
|
||||
0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x5f, 0x62, 0x61, 0x6e, 0x6b, 0x5f,
|
||||
0x73, 0x71, 0x75, 0x61, 0x72, 0x65, 0x5f, 0x72, 0x6f, 0x6f, 0x74, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x28, 0x00,
|
||||
0x00, 0x00, 0x32, 0xf9, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x1e,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x20, 0x00, 0x00, 0x00, 0x78, 0xf8, 0xff, 0xff, 0x12, 0x00, 0x00, 0x00,
|
||||
0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x5f, 0x62, 0x61, 0x6e, 0x6b, 0x00,
|
||||
0x00, 0x01, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x72, 0xf9, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00,
|
||||
0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x14, 0x00, 0x00, 0x00, 0xb8,
|
||||
0xf8, 0xff, 0xff, 0x06, 0x00, 0x00, 0x00, 0x43, 0x61, 0x73, 0x74, 0x5f, 0x31, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
|
||||
0x01, 0x01, 0x00, 0x00, 0xa6, 0xf9, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00,
|
||||
0x00, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x14, 0x00, 0x00, 0x00, 0xec, 0xf8, 0xff, 0xff, 0x06, 0x00,
|
||||
0x00, 0x00, 0x63, 0x6f, 0x6e, 0x63, 0x61, 0x74, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0xda,
|
||||
0xf9, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x02, 0x1c, 0x00, 0x00, 0x00, 0x20, 0xf9, 0xff, 0xff, 0x0d, 0x00, 0x00, 0x00, 0x73, 0x74, 0x72,
|
||||
0x69, 0x64, 0x65, 0x64, 0x5f, 0x73, 0x6c, 0x69, 0x63, 0x65, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xec, 0x00,
|
||||
0x00, 0x00, 0x16, 0xfa, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x1a,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x14, 0x00, 0x00, 0x00, 0x5c, 0xf9, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00,
|
||||
0x43, 0x61, 0x73, 0x74, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x4a, 0xfa, 0xff,
|
||||
0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x0f, 0x1c, 0x00, 0x00, 0x00, 0x90, 0xf9, 0xff, 0xff, 0x0d, 0x00, 0x00, 0x00, 0x73, 0x69, 0x67, 0x6e, 0x61,
|
||||
0x6c, 0x5f, 0x65, 0x6e, 0x65, 0x72, 0x67, 0x79, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
|
||||
0x86, 0xfa, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x07, 0x18, 0x00, 0x00, 0x00, 0xcc, 0xf9, 0xff, 0xff, 0x0b, 0x00, 0x00, 0x00, 0x73, 0x69,
|
||||
0x67, 0x6e, 0x61, 0x6c, 0x5f, 0x72, 0x66, 0x66, 0x74, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x02, 0x00, 0x00, 0xbe,
|
||||
0xfa, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x02, 0x24, 0x00, 0x00, 0x00, 0x04, 0xfa, 0xff, 0xff, 0x16, 0x00, 0x00, 0x00, 0x73, 0x69, 0x67,
|
||||
0x6e, 0x61, 0x6c, 0x5f, 0x66, 0x66, 0x74, 0x5f, 0x61, 0x75, 0x74, 0x6f, 0x5f, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x31,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0xfa, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14,
|
||||
0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x24, 0x00, 0x00, 0x00, 0x44, 0xfa, 0xff, 0xff,
|
||||
0x15, 0x00, 0x00, 0x00, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x5f, 0x66, 0x66, 0x74, 0x5f, 0x61, 0x75, 0x74, 0x6f,
|
||||
0x5f, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xe0, 0x01, 0x00, 0x00, 0x42, 0xfb,
|
||||
0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x07, 0x14, 0x00, 0x00, 0x00, 0x88, 0xfa, 0xff, 0xff, 0x07, 0x00, 0x00, 0x00, 0x52, 0x65, 0x73, 0x68,
|
||||
0x61, 0x70, 0x65, 0x00, 0x01, 0x00, 0x00, 0x00, 0xe0, 0x01, 0x00, 0x00, 0x76, 0xfb, 0xff, 0xff, 0x00, 0x00, 0x00,
|
||||
0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x1c, 0x00,
|
||||
0x00, 0x00, 0xbc, 0xfa, 0xff, 0xff, 0x0d, 0x00, 0x00, 0x00, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x5f, 0x77, 0x69,
|
||||
0x6e, 0x64, 0x6f, 0x77, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xe0, 0x01, 0x00, 0x00,
|
||||
0xb6, 0xfb, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x02, 0x14, 0x00, 0x00, 0x00, 0xfc, 0xfa, 0xff, 0xff, 0x07, 0x00, 0x00, 0x00, 0x43, 0x6f,
|
||||
0x6e, 0x73, 0x74, 0x5f, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe6, 0xfb, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14,
|
||||
0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x14, 0x00, 0x00, 0x00,
|
||||
0x2c, 0xfb, 0xff, 0xff, 0x06, 0x00, 0x00, 0x00, 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x16, 0xfc, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x11, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x14, 0x00, 0x00, 0x00, 0x5c, 0xfb, 0xff, 0xff, 0x07, 0x00, 0x00, 0x00, 0x43,
|
||||
0x6f, 0x6e, 0x73, 0x74, 0x5f, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0xfc, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01,
|
||||
0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x1c, 0x00, 0x00,
|
||||
0x00, 0x8c, 0xfb, 0xff, 0xff, 0x0d, 0x00, 0x00, 0x00, 0x52, 0x65, 0x73, 0x68, 0x61, 0x70, 0x65, 0x2f, 0x73, 0x68,
|
||||
0x61, 0x70, 0x65, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x82, 0xfc, 0xff, 0xff, 0x00,
|
||||
0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02,
|
||||
0x24, 0x00, 0x00, 0x00, 0xc8, 0xfb, 0xff, 0xff, 0x17, 0x00, 0x00, 0x00, 0x63, 0x6c, 0x69, 0x70, 0x5f, 0x62, 0x79,
|
||||
0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x2f, 0x4d, 0x69, 0x6e, 0x69, 0x6d, 0x75, 0x6d, 0x2f, 0x79, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0xc2, 0xfc, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x0e,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x28, 0x00, 0x00, 0x00, 0x08, 0xfc, 0xff, 0xff, 0x18, 0x00, 0x00, 0x00,
|
||||
0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x5f, 0x62, 0x61, 0x6e, 0x6b, 0x2f,
|
||||
0x43, 0x6f, 0x6e, 0x73, 0x74, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x3c, 0x01, 0x00, 0x00, 0x0a, 0xfd,
|
||||
0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x07, 0x28, 0x00, 0x00, 0x00, 0x50, 0xfc, 0xff, 0xff, 0x1a, 0x00, 0x00, 0x00, 0x73, 0x69, 0x67, 0x6e,
|
||||
0x61, 0x6c, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x5f, 0x62, 0x61, 0x6e, 0x6b, 0x2f, 0x43, 0x6f, 0x6e, 0x73,
|
||||
0x74, 0x5f, 0x31, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x3c, 0x01, 0x00, 0x00, 0x52, 0xfd, 0xff, 0xff, 0x00, 0x00,
|
||||
0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x28,
|
||||
0x00, 0x00, 0x00, 0x98, 0xfc, 0xff, 0xff, 0x1a, 0x00, 0x00, 0x00, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x5f, 0x66,
|
||||
0x69, 0x6c, 0x74, 0x65, 0x72, 0x5f, 0x62, 0x61, 0x6e, 0x6b, 0x2f, 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x5f, 0x32, 0x00,
|
||||
0x00, 0x01, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x9a, 0xfd, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00,
|
||||
0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x28, 0x00, 0x00, 0x00, 0xe0,
|
||||
0xfc, 0xff, 0xff, 0x1a, 0x00, 0x00, 0x00, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65,
|
||||
0x72, 0x5f, 0x62, 0x61, 0x6e, 0x6b, 0x2f, 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x5f, 0x33, 0x00, 0x00, 0x01, 0x00, 0x00,
|
||||
0x00, 0x29, 0x00, 0x00, 0x00, 0xe2, 0xfd, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00,
|
||||
0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x28, 0x00, 0x00, 0x00, 0x28, 0xfd, 0xff, 0xff, 0x1a,
|
||||
0x00, 0x00, 0x00, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x5f, 0x62, 0x61,
|
||||
0x6e, 0x6b, 0x2f, 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x5f, 0x34, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00,
|
||||
0x00, 0x2a, 0xfe, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x09, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x20, 0x00, 0x00, 0x00, 0x70, 0xfd, 0xff, 0xff, 0x11, 0x00, 0x00, 0x00, 0x73,
|
||||
0x69, 0x67, 0x6e, 0x61, 0x6c, 0x5f, 0x70, 0x63, 0x61, 0x6e, 0x2f, 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x00, 0x00, 0x00,
|
||||
0x01, 0x00, 0x00, 0x00, 0x7d, 0x00, 0x00, 0x00, 0x6a, 0xfe, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00,
|
||||
0x00, 0x14, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x20, 0x00, 0x00, 0x00, 0xb0, 0xfd,
|
||||
0xff, 0xff, 0x13, 0x00, 0x00, 0x00, 0x73, 0x74, 0x72, 0x69, 0x64, 0x65, 0x64, 0x5f, 0x73, 0x6c, 0x69, 0x63, 0x65,
|
||||
0x2f, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xaa, 0xfe, 0xff, 0xff,
|
||||
0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x02, 0x24, 0x00, 0x00, 0x00, 0xf0, 0xfd, 0xff, 0xff, 0x15, 0x00, 0x00, 0x00, 0x73, 0x74, 0x72, 0x69, 0x64, 0x65,
|
||||
0x64, 0x5f, 0x73, 0x6c, 0x69, 0x63, 0x65, 0x2f, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x5f, 0x31, 0x00, 0x00, 0x00, 0x01,
|
||||
0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xee, 0xfe, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00,
|
||||
0x14, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x24, 0x00, 0x00, 0x00, 0x34, 0xfe, 0xff,
|
||||
0xff, 0x15, 0x00, 0x00, 0x00, 0x73, 0x74, 0x72, 0x69, 0x64, 0x65, 0x64, 0x5f, 0x73, 0x6c, 0x69, 0x63, 0x65, 0x2f,
|
||||
0x73, 0x74, 0x61, 0x63, 0x6b, 0x5f, 0x32, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x32,
|
||||
0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x02, 0x14, 0x00, 0x00, 0x00, 0x78, 0xfe, 0xff, 0xff, 0x06, 0x00, 0x00, 0x00, 0x43, 0x61, 0x73,
|
||||
0x74, 0x5f, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x62, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00,
|
||||
0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x14, 0x00, 0x00, 0x00, 0xa8,
|
||||
0xfe, 0xff, 0xff, 0x05, 0x00, 0x00, 0x00, 0x7a, 0x65, 0x72, 0x6f, 0x73, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
|
||||
0x05, 0x00, 0x00, 0x00, 0x96, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00,
|
||||
0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x14, 0x00, 0x00, 0x00, 0xdc, 0xfe, 0xff, 0xff, 0x07, 0x00,
|
||||
0x00, 0x00, 0x7a, 0x65, 0x72, 0x6f, 0x73, 0x5f, 0x31, 0x00, 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0xca,
|
||||
0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x07, 0x14, 0x00, 0x00, 0x00, 0x10, 0xff, 0xff, 0xff, 0x05, 0x00, 0x00, 0x00, 0x43, 0x6f, 0x6e,
|
||||
0x73, 0x74, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xe0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x16, 0x00, 0x1c, 0x00,
|
||||
0x18, 0x00, 0x17, 0x00, 0x10, 0x00, 0x0c, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x16,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x07, 0x2c, 0x00, 0x00, 0x00, 0x5c, 0xff, 0xff, 0xff, 0x1d, 0x00, 0x00, 0x00, 0x73, 0x65, 0x72,
|
||||
0x76, 0x69, 0x6e, 0x67, 0x5f, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x61, 0x75, 0x64, 0x69, 0x6f, 0x5f,
|
||||
0x66, 0x72, 0x61, 0x6d, 0x65, 0x3a, 0x30, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xe0,
|
||||
0x01, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x1c, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0xc8, 0x01, 0x00, 0x00,
|
||||
0xa4, 0x01, 0x00, 0x00, 0x7c, 0x01, 0x00, 0x00, 0x68, 0x01, 0x00, 0x00, 0x4c, 0x01, 0x00, 0x00, 0x3c, 0x01, 0x00,
|
||||
0x00, 0x10, 0x01, 0x00, 0x00, 0xdc, 0x00, 0x00, 0x00, 0xa0, 0x00, 0x00, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x50, 0x00,
|
||||
0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x04,
|
||||
0x00, 0x00, 0x00, 0x50, 0xfe, 0xff, 0xff, 0x37, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x37, 0x5c, 0xfe, 0xff, 0xff,
|
||||
0x39, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x39, 0x68, 0xfe, 0xff, 0xff, 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x2a, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x00, 0x00, 0x7c, 0xfe, 0xff, 0xff, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x12, 0x70, 0xfe, 0xff, 0xff, 0x20, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x13,
|
||||
0x00, 0x00, 0x00, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x42, 0x61, 0x6e, 0x6b,
|
||||
0x4c, 0x6f, 0x67, 0x00, 0x98, 0xfe, 0xff, 0xff, 0x20, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x20, 0x0a, 0x00, 0x00, 0x00, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x50, 0x43, 0x41, 0x4e, 0x00, 0x00, 0xb8, 0xfe,
|
||||
0xff, 0xff, 0x20, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x23, 0x00, 0x00, 0x00, 0x53,
|
||||
0x69, 0x67, 0x6e, 0x61, 0x6c, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x42, 0x61, 0x6e, 0x6b, 0x53, 0x70, 0x65, 0x63,
|
||||
0x74, 0x72, 0x61, 0x6c, 0x53, 0x75, 0x62, 0x74, 0x72, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x00, 0xf0, 0xfe, 0xff,
|
||||
0xff, 0x20, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x1a, 0x00, 0x00, 0x00, 0x53, 0x69,
|
||||
0x67, 0x6e, 0x61, 0x6c, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x42, 0x61, 0x6e, 0x6b, 0x53, 0x71, 0x75, 0x61, 0x72,
|
||||
0x65, 0x52, 0x6f, 0x6f, 0x74, 0x00, 0x00, 0x20, 0xff, 0xff, 0xff, 0x20, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x20, 0x10, 0x00, 0x00, 0x00, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x46, 0x69, 0x6c, 0x74, 0x65,
|
||||
0x72, 0x42, 0x61, 0x6e, 0x6b, 0x00, 0x00, 0x00, 0x00, 0x60, 0xff, 0xff, 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x02, 0x6c, 0xff, 0xff, 0xff, 0x2d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2d, 0x0c, 0x00, 0x10, 0x00, 0x0f,
|
||||
0x00, 0x00, 0x00, 0x08, 0x00, 0x04, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x35, 0x7c, 0xff, 0xff, 0xff, 0x20, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x20, 0x0c, 0x00, 0x00, 0x00, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x45, 0x6e, 0x65, 0x72, 0x67, 0x79, 0x00, 0x00,
|
||||
0x00, 0x00, 0xa0, 0xff, 0xff, 0xff, 0x20, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x0a,
|
||||
0x00, 0x00, 0x00, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x52, 0x66, 0x66, 0x74, 0x00, 0x00, 0xc0, 0xff, 0xff, 0xff,
|
||||
0x20, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x12, 0x00, 0x00, 0x00, 0x53, 0x69, 0x67,
|
||||
0x6e, 0x61, 0x6c, 0x46, 0x66, 0x74, 0x41, 0x75, 0x74, 0x6f, 0x53, 0x63, 0x61, 0x6c, 0x65, 0x00, 0x00, 0x0c, 0x00,
|
||||
0x0c, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x16, 0x0c, 0x00, 0x10, 0x00, 0x0f, 0x00, 0x08, 0x00, 0x00, 0x00, 0x04, 0x00, 0x0c, 0x00, 0x00, 0x00,
|
||||
0x20, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x0c, 0x00, 0x00, 0x00, 0x53, 0x69, 0x67,
|
||||
0x6e, 0x61, 0x6c, 0x57, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x00, 0x00, 0x00, 0x00};
|
||||
|
||||
} // namespace micro_wake_word
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ESP_IDF
|
521
esphome/components/micro_wake_word/micro_wake_word.cpp
Normal file
521
esphome/components/micro_wake_word/micro_wake_word.cpp
Normal file
@ -0,0 +1,521 @@
|
||||
#include "micro_wake_word.h"
|
||||
|
||||
/**
|
||||
* This is a workaround until we can figure out a way to get
|
||||
* the tflite-micro idf component code available in CI
|
||||
*
|
||||
* */
|
||||
//
|
||||
#ifndef CLANG_TIDY
|
||||
|
||||
#ifdef USE_ESP_IDF
|
||||
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#include "audio_preprocessor_int8_model_data.h"
|
||||
|
||||
#include <tensorflow/lite/core/c/common.h>
|
||||
#include <tensorflow/lite/micro/micro_interpreter.h>
|
||||
#include <tensorflow/lite/micro/micro_mutable_op_resolver.h>
|
||||
|
||||
#include <cmath>
|
||||
|
||||
namespace esphome {
|
||||
namespace micro_wake_word {
|
||||
|
||||
static const char *const TAG = "micro_wake_word";
|
||||
|
||||
static const size_t SAMPLE_RATE_HZ = 16000; // 16 kHz
|
||||
static const size_t BUFFER_LENGTH = 500; // 0.5 seconds
|
||||
static const size_t BUFFER_SIZE = SAMPLE_RATE_HZ / 1000 * BUFFER_LENGTH;
|
||||
static const size_t INPUT_BUFFER_SIZE = 32 * SAMPLE_RATE_HZ / 1000; // 32ms * 16kHz / 1000ms
|
||||
|
||||
float MicroWakeWord::get_setup_priority() const { return setup_priority::AFTER_CONNECTION; }
|
||||
|
||||
static const LogString *micro_wake_word_state_to_string(State state) {
|
||||
switch (state) {
|
||||
case State::IDLE:
|
||||
return LOG_STR("IDLE");
|
||||
case State::START_MICROPHONE:
|
||||
return LOG_STR("START_MICROPHONE");
|
||||
case State::STARTING_MICROPHONE:
|
||||
return LOG_STR("STARTING_MICROPHONE");
|
||||
case State::DETECTING_WAKE_WORD:
|
||||
return LOG_STR("DETECTING_WAKE_WORD");
|
||||
case State::STOP_MICROPHONE:
|
||||
return LOG_STR("STOP_MICROPHONE");
|
||||
case State::STOPPING_MICROPHONE:
|
||||
return LOG_STR("STOPPING_MICROPHONE");
|
||||
default:
|
||||
return LOG_STR("UNKNOWN");
|
||||
}
|
||||
}
|
||||
|
||||
void MicroWakeWord::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "microWakeWord:");
|
||||
ESP_LOGCONFIG(TAG, " Wake Word: %s", this->get_wake_word().c_str());
|
||||
ESP_LOGCONFIG(TAG, " Probability cutoff: %.3f", this->probability_cutoff_);
|
||||
ESP_LOGCONFIG(TAG, " Sliding window size: %d", this->sliding_window_average_size_);
|
||||
}
|
||||
|
||||
void MicroWakeWord::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up microWakeWord...");
|
||||
|
||||
if (!this->initialize_models()) {
|
||||
ESP_LOGE(TAG, "Failed to initialize models");
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
ExternalRAMAllocator<int16_t> allocator(ExternalRAMAllocator<int16_t>::ALLOW_FAILURE);
|
||||
this->input_buffer_ = allocator.allocate(INPUT_BUFFER_SIZE * sizeof(int16_t));
|
||||
if (this->input_buffer_ == nullptr) {
|
||||
ESP_LOGW(TAG, "Could not allocate input buffer");
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
this->ring_buffer_ = RingBuffer::create(BUFFER_SIZE * sizeof(int16_t));
|
||||
if (this->ring_buffer_ == nullptr) {
|
||||
ESP_LOGW(TAG, "Could not allocate ring buffer");
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGCONFIG(TAG, "Micro Wake Word initialized");
|
||||
}
|
||||
|
||||
int MicroWakeWord::read_microphone_() {
|
||||
size_t bytes_read = this->microphone_->read(this->input_buffer_, INPUT_BUFFER_SIZE * sizeof(int16_t));
|
||||
if (bytes_read == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t bytes_written = this->ring_buffer_->write((void *) this->input_buffer_, bytes_read);
|
||||
if (bytes_written != bytes_read) {
|
||||
ESP_LOGW(TAG, "Failed to write some data to ring buffer (written=%d, expected=%d)", bytes_written, bytes_read);
|
||||
}
|
||||
return bytes_written;
|
||||
}
|
||||
|
||||
void MicroWakeWord::loop() {
|
||||
switch (this->state_) {
|
||||
case State::IDLE:
|
||||
break;
|
||||
case State::START_MICROPHONE:
|
||||
ESP_LOGD(TAG, "Starting Microphone");
|
||||
this->microphone_->start();
|
||||
this->set_state_(State::STARTING_MICROPHONE);
|
||||
this->high_freq_.start();
|
||||
break;
|
||||
case State::STARTING_MICROPHONE:
|
||||
if (this->microphone_->is_running()) {
|
||||
this->set_state_(State::DETECTING_WAKE_WORD);
|
||||
}
|
||||
break;
|
||||
case State::DETECTING_WAKE_WORD:
|
||||
this->read_microphone_();
|
||||
if (this->detect_wake_word_()) {
|
||||
ESP_LOGD(TAG, "Wake Word Detected");
|
||||
this->detected_ = true;
|
||||
this->set_state_(State::STOP_MICROPHONE);
|
||||
}
|
||||
break;
|
||||
case State::STOP_MICROPHONE:
|
||||
ESP_LOGD(TAG, "Stopping Microphone");
|
||||
this->microphone_->stop();
|
||||
this->set_state_(State::STOPPING_MICROPHONE);
|
||||
this->high_freq_.stop();
|
||||
break;
|
||||
case State::STOPPING_MICROPHONE:
|
||||
if (this->microphone_->is_stopped()) {
|
||||
this->set_state_(State::IDLE);
|
||||
if (this->detected_) {
|
||||
this->detected_ = false;
|
||||
this->wake_word_detected_trigger_->trigger(this->wake_word_);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void MicroWakeWord::start() {
|
||||
if (this->is_failed()) {
|
||||
ESP_LOGW(TAG, "Wake word component is marked as failed. Please check setup logs");
|
||||
return;
|
||||
}
|
||||
if (this->state_ != State::IDLE) {
|
||||
ESP_LOGW(TAG, "Wake word is already running");
|
||||
return;
|
||||
}
|
||||
this->set_state_(State::START_MICROPHONE);
|
||||
}
|
||||
|
||||
void MicroWakeWord::stop() {
|
||||
if (this->state_ == State::IDLE) {
|
||||
ESP_LOGW(TAG, "Wake word is already stopped");
|
||||
return;
|
||||
}
|
||||
if (this->state_ == State::STOPPING_MICROPHONE) {
|
||||
ESP_LOGW(TAG, "Wake word is already stopping");
|
||||
return;
|
||||
}
|
||||
this->set_state_(State::STOP_MICROPHONE);
|
||||
}
|
||||
|
||||
void MicroWakeWord::set_state_(State state) {
|
||||
ESP_LOGD(TAG, "State changed from %s to %s", LOG_STR_ARG(micro_wake_word_state_to_string(this->state_)),
|
||||
LOG_STR_ARG(micro_wake_word_state_to_string(state)));
|
||||
this->state_ = state;
|
||||
}
|
||||
|
||||
bool MicroWakeWord::initialize_models() {
|
||||
ExternalRAMAllocator<uint8_t> arena_allocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE);
|
||||
ExternalRAMAllocator<int8_t> features_allocator(ExternalRAMAllocator<int8_t>::ALLOW_FAILURE);
|
||||
ExternalRAMAllocator<int16_t> audio_samples_allocator(ExternalRAMAllocator<int16_t>::ALLOW_FAILURE);
|
||||
|
||||
this->streaming_tensor_arena_ = arena_allocator.allocate(STREAMING_MODEL_ARENA_SIZE);
|
||||
if (this->streaming_tensor_arena_ == nullptr) {
|
||||
ESP_LOGE(TAG, "Could not allocate the streaming model's tensor arena.");
|
||||
return false;
|
||||
}
|
||||
|
||||
this->streaming_var_arena_ = arena_allocator.allocate(STREAMING_MODEL_VARIABLE_ARENA_SIZE);
|
||||
if (this->streaming_var_arena_ == nullptr) {
|
||||
ESP_LOGE(TAG, "Could not allocate the streaming model variable's tensor arena.");
|
||||
return false;
|
||||
}
|
||||
|
||||
this->preprocessor_tensor_arena_ = arena_allocator.allocate(PREPROCESSOR_ARENA_SIZE);
|
||||
if (this->preprocessor_tensor_arena_ == nullptr) {
|
||||
ESP_LOGE(TAG, "Could not allocate the audio preprocessor model's tensor arena.");
|
||||
return false;
|
||||
}
|
||||
|
||||
this->new_features_data_ = features_allocator.allocate(PREPROCESSOR_FEATURE_SIZE);
|
||||
if (this->new_features_data_ == nullptr) {
|
||||
ESP_LOGE(TAG, "Could not allocate the audio features buffer.");
|
||||
return false;
|
||||
}
|
||||
|
||||
this->preprocessor_audio_buffer_ = audio_samples_allocator.allocate(SAMPLE_DURATION_COUNT);
|
||||
if (this->preprocessor_audio_buffer_ == nullptr) {
|
||||
ESP_LOGE(TAG, "Could not allocate the audio preprocessor's buffer.");
|
||||
return false;
|
||||
}
|
||||
|
||||
this->preprocessor_stride_buffer_ = audio_samples_allocator.allocate(HISTORY_SAMPLES_TO_KEEP);
|
||||
if (this->preprocessor_stride_buffer_ == nullptr) {
|
||||
ESP_LOGE(TAG, "Could not allocate the audio preprocessor's stride buffer.");
|
||||
return false;
|
||||
}
|
||||
|
||||
this->preprocessor_model_ = tflite::GetModel(G_AUDIO_PREPROCESSOR_INT8_TFLITE);
|
||||
if (this->preprocessor_model_->version() != TFLITE_SCHEMA_VERSION) {
|
||||
ESP_LOGE(TAG, "Wake word's audio preprocessor model's schema is not supported");
|
||||
return false;
|
||||
}
|
||||
|
||||
this->streaming_model_ = tflite::GetModel(this->model_start_);
|
||||
if (this->streaming_model_->version() != TFLITE_SCHEMA_VERSION) {
|
||||
ESP_LOGE(TAG, "Wake word's streaming model's schema is not supported");
|
||||
return false;
|
||||
}
|
||||
|
||||
static tflite::MicroMutableOpResolver<18> preprocessor_op_resolver;
|
||||
static tflite::MicroMutableOpResolver<14> streaming_op_resolver;
|
||||
|
||||
if (!this->register_preprocessor_ops_(preprocessor_op_resolver))
|
||||
return false;
|
||||
if (!this->register_streaming_ops_(streaming_op_resolver))
|
||||
return false;
|
||||
|
||||
tflite::MicroAllocator *ma =
|
||||
tflite::MicroAllocator::Create(this->streaming_var_arena_, STREAMING_MODEL_VARIABLE_ARENA_SIZE);
|
||||
this->mrv_ = tflite::MicroResourceVariables::Create(ma, 15);
|
||||
|
||||
static tflite::MicroInterpreter static_preprocessor_interpreter(
|
||||
this->preprocessor_model_, preprocessor_op_resolver, this->preprocessor_tensor_arena_, PREPROCESSOR_ARENA_SIZE);
|
||||
|
||||
static tflite::MicroInterpreter static_streaming_interpreter(this->streaming_model_, streaming_op_resolver,
|
||||
this->streaming_tensor_arena_,
|
||||
STREAMING_MODEL_ARENA_SIZE, this->mrv_);
|
||||
|
||||
this->preprocessor_interperter_ = &static_preprocessor_interpreter;
|
||||
this->streaming_interpreter_ = &static_streaming_interpreter;
|
||||
|
||||
// Allocate tensors for each models.
|
||||
if (this->preprocessor_interperter_->AllocateTensors() != kTfLiteOk) {
|
||||
ESP_LOGE(TAG, "Failed to allocate tensors for the audio preprocessor");
|
||||
return false;
|
||||
}
|
||||
if (this->streaming_interpreter_->AllocateTensors() != kTfLiteOk) {
|
||||
ESP_LOGE(TAG, "Failed to allocate tensors for the streaming model");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Verify input tensor matches expected values
|
||||
TfLiteTensor *input = this->streaming_interpreter_->input(0);
|
||||
if ((input->dims->size != 3) || (input->dims->data[0] != 1) || (input->dims->data[0] != 1) ||
|
||||
(input->dims->data[1] != 1) || (input->dims->data[2] != PREPROCESSOR_FEATURE_SIZE)) {
|
||||
ESP_LOGE(TAG, "Wake word detection model tensor input dimensions is not 1x1x%u", input->dims->data[2]);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (input->type != kTfLiteInt8) {
|
||||
ESP_LOGE(TAG, "Wake word detection model tensor input is not int8.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Verify output tensor matches expected values
|
||||
TfLiteTensor *output = this->streaming_interpreter_->output(0);
|
||||
if ((output->dims->size != 2) || (output->dims->data[0] != 1) || (output->dims->data[1] != 1)) {
|
||||
ESP_LOGE(TAG, "Wake word detection model tensor output dimensions is not 1x1.");
|
||||
}
|
||||
|
||||
if (output->type != kTfLiteUInt8) {
|
||||
ESP_LOGE(TAG, "Wake word detection model tensor input is not uint8.");
|
||||
return false;
|
||||
}
|
||||
|
||||
this->recent_streaming_probabilities_.resize(this->sliding_window_average_size_, 0.0);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MicroWakeWord::update_features_() {
|
||||
// Retrieve strided audio samples
|
||||
int16_t *audio_samples = nullptr;
|
||||
if (!this->stride_audio_samples_(&audio_samples)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Compute the features for the newest audio samples
|
||||
if (!this->generate_single_feature_(audio_samples, SAMPLE_DURATION_COUNT, this->new_features_data_)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
float MicroWakeWord::perform_streaming_inference_() {
|
||||
TfLiteTensor *input = this->streaming_interpreter_->input(0);
|
||||
|
||||
size_t bytes_to_copy = input->bytes;
|
||||
|
||||
memcpy((void *) (tflite::GetTensorData<int8_t>(input)), (const void *) (this->new_features_data_), bytes_to_copy);
|
||||
|
||||
uint32_t prior_invoke = millis();
|
||||
|
||||
TfLiteStatus invoke_status = this->streaming_interpreter_->Invoke();
|
||||
if (invoke_status != kTfLiteOk) {
|
||||
ESP_LOGW(TAG, "Streaming Interpreter Invoke failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
ESP_LOGV(TAG, "Streaming Inference Latency=%u ms", (millis() - prior_invoke));
|
||||
|
||||
TfLiteTensor *output = this->streaming_interpreter_->output(0);
|
||||
|
||||
return static_cast<float>(output->data.uint8[0]) / 255.0;
|
||||
}
|
||||
|
||||
bool MicroWakeWord::detect_wake_word_() {
|
||||
// Preprocess the newest audio samples into features
|
||||
if (!this->update_features_()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Perform inference
|
||||
uint32_t streaming_size = micros();
|
||||
float streaming_prob = this->perform_streaming_inference_();
|
||||
|
||||
// Add the most recent probability to the sliding window
|
||||
this->recent_streaming_probabilities_[this->last_n_index_] = streaming_prob;
|
||||
++this->last_n_index_;
|
||||
if (this->last_n_index_ == this->sliding_window_average_size_)
|
||||
this->last_n_index_ = 0;
|
||||
|
||||
float sum = 0.0;
|
||||
for (auto &prob : this->recent_streaming_probabilities_) {
|
||||
sum += prob;
|
||||
}
|
||||
|
||||
float sliding_window_average = sum / static_cast<float>(this->sliding_window_average_size_);
|
||||
|
||||
// Ensure we have enough samples since the last positive detection
|
||||
this->ignore_windows_ = std::min(this->ignore_windows_ + 1, 0);
|
||||
if (this->ignore_windows_ < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Detect the wake word if the sliding window average is above the cutoff
|
||||
if (sliding_window_average > this->probability_cutoff_) {
|
||||
this->ignore_windows_ = -MIN_SLICES_BEFORE_DETECTION;
|
||||
for (auto &prob : this->recent_streaming_probabilities_) {
|
||||
prob = 0;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void MicroWakeWord::set_sliding_window_average_size(size_t size) {
|
||||
this->sliding_window_average_size_ = size;
|
||||
this->recent_streaming_probabilities_.resize(this->sliding_window_average_size_, 0.0);
|
||||
}
|
||||
|
||||
bool MicroWakeWord::slice_available_() {
|
||||
size_t available = this->ring_buffer_->available();
|
||||
|
||||
size_t free = this->ring_buffer_->free();
|
||||
|
||||
if (free < NEW_SAMPLES_TO_GET * sizeof(int16_t)) {
|
||||
// If the ring buffer is within one audio slice of being full, then wake word detection will have issues.
|
||||
// If this is constantly occuring, then some possibilities why are
|
||||
// 1) there are too many other slow components configured
|
||||
// 2) the ESP32 isn't fast enough; e.g., an ESP32 is much slower than an ESP32-S3 at inferences.
|
||||
// 3) the model is too large
|
||||
// 4) the model uses operations that are not optimized
|
||||
ESP_LOGW(TAG,
|
||||
"Audio buffer is nearly full. Wake word detection may be less accurate and have slower reponse times. "
|
||||
#if !defined(USE_ESP32_VARIANT_ESP32S3)
|
||||
"microWakeWord is designed for the ESP32-S3. The current platform is too slow for this model."
|
||||
#endif
|
||||
);
|
||||
}
|
||||
|
||||
return available > (NEW_SAMPLES_TO_GET * sizeof(int16_t));
|
||||
}
|
||||
|
||||
bool MicroWakeWord::stride_audio_samples_(int16_t **audio_samples) {
|
||||
if (!this->slice_available_()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Copy 320 bytes (160 samples over 10 ms) into preprocessor_audio_buffer_ from history in
|
||||
// preprocessor_stride_buffer_
|
||||
memcpy((void *) (this->preprocessor_audio_buffer_), (void *) (this->preprocessor_stride_buffer_),
|
||||
HISTORY_SAMPLES_TO_KEEP * sizeof(int16_t));
|
||||
|
||||
// Copy 640 bytes (320 samples over 20 ms) from the ring buffer
|
||||
// The first 320 bytes (160 samples over 10 ms) will be from history
|
||||
size_t bytes_read = this->ring_buffer_->read((void *) (this->preprocessor_audio_buffer_ + HISTORY_SAMPLES_TO_KEEP),
|
||||
NEW_SAMPLES_TO_GET * sizeof(int16_t), pdMS_TO_TICKS(200));
|
||||
|
||||
if (bytes_read == 0) {
|
||||
ESP_LOGE(TAG, "Could not read data from Ring Buffer");
|
||||
} else if (bytes_read < NEW_SAMPLES_TO_GET * sizeof(int16_t)) {
|
||||
ESP_LOGD(TAG, "Partial Read of Data by Model");
|
||||
ESP_LOGD(TAG, "Could only read %d bytes when required %d bytes ", bytes_read,
|
||||
(int) (NEW_SAMPLES_TO_GET * sizeof(int16_t)));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Copy the last 320 bytes (160 samples over 10 ms) from the audio buffer into history stride buffer for the next
|
||||
// iteration
|
||||
memcpy((void *) (this->preprocessor_stride_buffer_), (void *) (this->preprocessor_audio_buffer_ + NEW_SAMPLES_TO_GET),
|
||||
HISTORY_SAMPLES_TO_KEEP * sizeof(int16_t));
|
||||
|
||||
*audio_samples = this->preprocessor_audio_buffer_;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MicroWakeWord::generate_single_feature_(const int16_t *audio_data, const int audio_data_size,
|
||||
int8_t feature_output[PREPROCESSOR_FEATURE_SIZE]) {
|
||||
TfLiteTensor *input = this->preprocessor_interperter_->input(0);
|
||||
TfLiteTensor *output = this->preprocessor_interperter_->output(0);
|
||||
std::copy_n(audio_data, audio_data_size, tflite::GetTensorData<int16_t>(input));
|
||||
|
||||
if (this->preprocessor_interperter_->Invoke() != kTfLiteOk) {
|
||||
ESP_LOGE(TAG, "Failed to preprocess audio for local wake word.");
|
||||
return false;
|
||||
}
|
||||
std::memcpy(feature_output, tflite::GetTensorData<int8_t>(output), PREPROCESSOR_FEATURE_SIZE * sizeof(int8_t));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MicroWakeWord::register_preprocessor_ops_(tflite::MicroMutableOpResolver<18> &op_resolver) {
|
||||
if (op_resolver.AddReshape() != kTfLiteOk)
|
||||
return false;
|
||||
if (op_resolver.AddCast() != kTfLiteOk)
|
||||
return false;
|
||||
if (op_resolver.AddStridedSlice() != kTfLiteOk)
|
||||
return false;
|
||||
if (op_resolver.AddConcatenation() != kTfLiteOk)
|
||||
return false;
|
||||
if (op_resolver.AddMul() != kTfLiteOk)
|
||||
return false;
|
||||
if (op_resolver.AddAdd() != kTfLiteOk)
|
||||
return false;
|
||||
if (op_resolver.AddDiv() != kTfLiteOk)
|
||||
return false;
|
||||
if (op_resolver.AddMinimum() != kTfLiteOk)
|
||||
return false;
|
||||
if (op_resolver.AddMaximum() != kTfLiteOk)
|
||||
return false;
|
||||
if (op_resolver.AddWindow() != kTfLiteOk)
|
||||
return false;
|
||||
if (op_resolver.AddFftAutoScale() != kTfLiteOk)
|
||||
return false;
|
||||
if (op_resolver.AddRfft() != kTfLiteOk)
|
||||
return false;
|
||||
if (op_resolver.AddEnergy() != kTfLiteOk)
|
||||
return false;
|
||||
if (op_resolver.AddFilterBank() != kTfLiteOk)
|
||||
return false;
|
||||
if (op_resolver.AddFilterBankSquareRoot() != kTfLiteOk)
|
||||
return false;
|
||||
if (op_resolver.AddFilterBankSpectralSubtraction() != kTfLiteOk)
|
||||
return false;
|
||||
if (op_resolver.AddPCAN() != kTfLiteOk)
|
||||
return false;
|
||||
if (op_resolver.AddFilterBankLog() != kTfLiteOk)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MicroWakeWord::register_streaming_ops_(tflite::MicroMutableOpResolver<14> &op_resolver) {
|
||||
if (op_resolver.AddCallOnce() != kTfLiteOk)
|
||||
return false;
|
||||
if (op_resolver.AddVarHandle() != kTfLiteOk)
|
||||
return false;
|
||||
if (op_resolver.AddReshape() != kTfLiteOk)
|
||||
return false;
|
||||
if (op_resolver.AddReadVariable() != kTfLiteOk)
|
||||
return false;
|
||||
if (op_resolver.AddStridedSlice() != kTfLiteOk)
|
||||
return false;
|
||||
if (op_resolver.AddConcatenation() != kTfLiteOk)
|
||||
return false;
|
||||
if (op_resolver.AddAssignVariable() != kTfLiteOk)
|
||||
return false;
|
||||
if (op_resolver.AddConv2D() != kTfLiteOk)
|
||||
return false;
|
||||
if (op_resolver.AddMul() != kTfLiteOk)
|
||||
return false;
|
||||
if (op_resolver.AddAdd() != kTfLiteOk)
|
||||
return false;
|
||||
if (op_resolver.AddMean() != kTfLiteOk)
|
||||
return false;
|
||||
if (op_resolver.AddFullyConnected() != kTfLiteOk)
|
||||
return false;
|
||||
if (op_resolver.AddLogistic() != kTfLiteOk)
|
||||
return false;
|
||||
if (op_resolver.AddQuantize() != kTfLiteOk)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace micro_wake_word
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ESP_IDF
|
||||
|
||||
#endif // CLANG_TIDY
|
207
esphome/components/micro_wake_word/micro_wake_word.h
Normal file
207
esphome/components/micro_wake_word/micro_wake_word.h
Normal file
@ -0,0 +1,207 @@
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* This is a workaround until we can figure out a way to get
|
||||
* the tflite-micro idf component code available in CI
|
||||
*
|
||||
* */
|
||||
//
|
||||
#ifndef CLANG_TIDY
|
||||
|
||||
#ifdef USE_ESP_IDF
|
||||
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/ring_buffer.h"
|
||||
|
||||
#include "esphome/components/microphone/microphone.h"
|
||||
|
||||
#include <tensorflow/lite/core/c/common.h>
|
||||
#include <tensorflow/lite/micro/micro_interpreter.h>
|
||||
#include <tensorflow/lite/micro/micro_mutable_op_resolver.h>
|
||||
|
||||
namespace esphome {
|
||||
namespace micro_wake_word {
|
||||
|
||||
// The following are dictated by the preprocessor model
|
||||
//
|
||||
// The number of features the audio preprocessor generates per slice
|
||||
static const uint8_t PREPROCESSOR_FEATURE_SIZE = 40;
|
||||
// How frequently the preprocessor generates a new set of features
|
||||
static const uint8_t FEATURE_STRIDE_MS = 20;
|
||||
// Duration of each slice used as input into the preprocessor
|
||||
static const uint8_t FEATURE_DURATION_MS = 30;
|
||||
// Audio sample frequency in hertz
|
||||
static const uint16_t AUDIO_SAMPLE_FREQUENCY = 16000;
|
||||
// The number of old audio samples that are saved to be part of the next feature window
|
||||
static const uint16_t HISTORY_SAMPLES_TO_KEEP =
|
||||
((FEATURE_DURATION_MS - FEATURE_STRIDE_MS) * (AUDIO_SAMPLE_FREQUENCY / 1000));
|
||||
// The number of new audio samples to receive to be included with the next feature window
|
||||
static const uint16_t NEW_SAMPLES_TO_GET = (FEATURE_STRIDE_MS * (AUDIO_SAMPLE_FREQUENCY / 1000));
|
||||
// The total number of audio samples included in the feature window
|
||||
static const uint16_t SAMPLE_DURATION_COUNT = FEATURE_DURATION_MS * AUDIO_SAMPLE_FREQUENCY / 1000;
|
||||
// Number of bytes in memory needed for the preprocessor arena
|
||||
static const uint32_t PREPROCESSOR_ARENA_SIZE = 9528;
|
||||
|
||||
// The following configure the streaming wake word model
|
||||
//
|
||||
// The number of audio slices to process before accepting a positive detection
|
||||
static const uint8_t MIN_SLICES_BEFORE_DETECTION = 74;
|
||||
|
||||
// Number of bytes in memory needed for the streaming wake word model
|
||||
static const uint32_t STREAMING_MODEL_ARENA_SIZE = 64000;
|
||||
static const uint32_t STREAMING_MODEL_VARIABLE_ARENA_SIZE = 1024;
|
||||
|
||||
enum State {
|
||||
IDLE,
|
||||
START_MICROPHONE,
|
||||
STARTING_MICROPHONE,
|
||||
DETECTING_WAKE_WORD,
|
||||
STOP_MICROPHONE,
|
||||
STOPPING_MICROPHONE,
|
||||
};
|
||||
|
||||
class MicroWakeWord : public Component {
|
||||
public:
|
||||
void setup() override;
|
||||
void loop() override;
|
||||
float get_setup_priority() const override;
|
||||
void dump_config() override;
|
||||
|
||||
void start();
|
||||
void stop();
|
||||
|
||||
bool is_running() const { return this->state_ != State::IDLE; }
|
||||
|
||||
bool initialize_models();
|
||||
|
||||
std::string get_wake_word() { return this->wake_word_; }
|
||||
|
||||
// Increasing either of these will reduce the rate of false acceptances while increasing the false rejection rate
|
||||
void set_probability_cutoff(float probability_cutoff) { this->probability_cutoff_ = probability_cutoff; }
|
||||
void set_sliding_window_average_size(size_t size);
|
||||
|
||||
void set_microphone(microphone::Microphone *microphone) { this->microphone_ = microphone; }
|
||||
|
||||
Trigger<std::string> *get_wake_word_detected_trigger() const { return this->wake_word_detected_trigger_; }
|
||||
|
||||
void set_model_start(const uint8_t *model_start) { this->model_start_ = model_start; }
|
||||
void set_wake_word(const std::string &wake_word) { this->wake_word_ = wake_word; }
|
||||
|
||||
protected:
|
||||
void set_state_(State state);
|
||||
int read_microphone_();
|
||||
|
||||
const uint8_t *model_start_;
|
||||
std::string wake_word_;
|
||||
|
||||
microphone::Microphone *microphone_{nullptr};
|
||||
Trigger<std::string> *wake_word_detected_trigger_ = new Trigger<std::string>();
|
||||
State state_{State::IDLE};
|
||||
HighFrequencyLoopRequester high_freq_;
|
||||
|
||||
std::unique_ptr<RingBuffer> ring_buffer_;
|
||||
|
||||
int16_t *input_buffer_;
|
||||
|
||||
const tflite::Model *preprocessor_model_{nullptr};
|
||||
const tflite::Model *streaming_model_{nullptr};
|
||||
tflite::MicroInterpreter *streaming_interpreter_{nullptr};
|
||||
tflite::MicroInterpreter *preprocessor_interperter_{nullptr};
|
||||
|
||||
std::vector<float> recent_streaming_probabilities_;
|
||||
size_t last_n_index_{0};
|
||||
|
||||
float probability_cutoff_{0.5};
|
||||
size_t sliding_window_average_size_{10};
|
||||
|
||||
// When the wake word detection first starts or after the word has been detected once, we ignore this many audio
|
||||
// feature slices before accepting a positive detection again
|
||||
int16_t ignore_windows_{-MIN_SLICES_BEFORE_DETECTION};
|
||||
|
||||
uint8_t *streaming_var_arena_{nullptr};
|
||||
uint8_t *streaming_tensor_arena_{nullptr};
|
||||
uint8_t *preprocessor_tensor_arena_{nullptr};
|
||||
int8_t *new_features_data_{nullptr};
|
||||
|
||||
tflite::MicroResourceVariables *mrv_{nullptr};
|
||||
|
||||
// Stores audio fed into feature generator preprocessor
|
||||
int16_t *preprocessor_audio_buffer_;
|
||||
int16_t *preprocessor_stride_buffer_;
|
||||
|
||||
bool detected_{false};
|
||||
|
||||
/** Detects if wake word has been said
|
||||
*
|
||||
* If enough audio samples are available, it will generate one slice of new features.
|
||||
* If the streaming model predicts the wake word, then the nonstreaming model confirms it.
|
||||
* @param ring_Buffer Ring buffer containing raw audio samples
|
||||
* @return True if the wake word is detected, false otherwise
|
||||
*/
|
||||
bool detect_wake_word_();
|
||||
|
||||
/// @brief Returns true if there are enough audio samples in the buffer to generate another slice of features
|
||||
bool slice_available_();
|
||||
|
||||
/** Shifts previous feature slices over by one and generates a new slice of features
|
||||
*
|
||||
* @param ring_buffer ring buffer containing raw audio samples
|
||||
* @return True if a new slice of features was generated, false otherwise
|
||||
*/
|
||||
bool update_features_();
|
||||
|
||||
/** Generates features from audio samples
|
||||
*
|
||||
* Adapted from TFLite micro speech example
|
||||
* @param audio_data Pointer to array with the audio samples
|
||||
* @param audio_data_size The number of samples to use as input to the preprocessor model
|
||||
* @param feature_output Array that will store the features
|
||||
* @return True if successful, false otherwise.
|
||||
*/
|
||||
bool generate_single_feature_(const int16_t *audio_data, int audio_data_size,
|
||||
int8_t feature_output[PREPROCESSOR_FEATURE_SIZE]);
|
||||
|
||||
/** Performs inference over the most recent feature slice with the streaming model
|
||||
*
|
||||
* @return Probability of the wake word between 0.0 and 1.0
|
||||
*/
|
||||
float perform_streaming_inference_();
|
||||
|
||||
/** Strides the audio samples by keeping the last 10 ms of the previous slice
|
||||
*
|
||||
* Adapted from the TFLite micro speech example
|
||||
* @param ring_buffer Ring buffer containing raw audio samples
|
||||
* @param audio_samples Pointer to an array that will store the strided audio samples
|
||||
* @return True if successful, false otherwise
|
||||
*/
|
||||
bool stride_audio_samples_(int16_t **audio_samples);
|
||||
|
||||
/// @brief Returns true if successfully registered the preprocessor's TensorFlow operations
|
||||
bool register_preprocessor_ops_(tflite::MicroMutableOpResolver<18> &op_resolver);
|
||||
|
||||
/// @brief Returns true if successfully registered the streaming model's TensorFlow operations
|
||||
bool register_streaming_ops_(tflite::MicroMutableOpResolver<14> &op_resolver);
|
||||
};
|
||||
|
||||
template<typename... Ts> class StartAction : public Action<Ts...>, public Parented<MicroWakeWord> {
|
||||
public:
|
||||
void play(Ts... x) override { this->parent_->start(); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class StopAction : public Action<Ts...>, public Parented<MicroWakeWord> {
|
||||
public:
|
||||
void play(Ts... x) override { this->parent_->stop(); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class IsRunningCondition : public Condition<Ts...>, public Parented<MicroWakeWord> {
|
||||
public:
|
||||
bool check(Ts... x) override { return this->parent_->is_running(); }
|
||||
};
|
||||
|
||||
} // namespace micro_wake_word
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ESP_IDF
|
||||
|
||||
#endif // CLANG_TIDY
|
@ -91,8 +91,13 @@ void MQTTClientComponent::send_device_info_() {
|
||||
this->publish_json(
|
||||
topic,
|
||||
[](JsonObject root) {
|
||||
auto ip = network::get_ip_address();
|
||||
root["ip"] = ip.str();
|
||||
uint8_t index = 0;
|
||||
for (auto &ip : network::get_ip_addresses()) {
|
||||
if (ip.is_set()) {
|
||||
root["ip" + (index == 0 ? "" : esphome::to_string(index))] = ip.str();
|
||||
index++;
|
||||
}
|
||||
}
|
||||
root["name"] = App.get_name();
|
||||
#ifdef USE_API
|
||||
root["port"] = api::global_api_server->get_port();
|
||||
@ -159,14 +164,13 @@ void MQTTClientComponent::start_dnslookup_() {
|
||||
this->dns_resolve_error_ = false;
|
||||
this->dns_resolved_ = false;
|
||||
ip_addr_t addr;
|
||||
#if defined(USE_ESP32) || defined(USE_LIBRETINY)
|
||||
#if USE_NETWORK_IPV6
|
||||
err_t err = dns_gethostbyname_addrtype(this->credentials_.address.c_str(), &addr,
|
||||
MQTTClientComponent::dns_found_callback, this, LWIP_DNS_ADDRTYPE_IPV6_IPV4);
|
||||
#else
|
||||
err_t err = dns_gethostbyname_addrtype(this->credentials_.address.c_str(), &addr,
|
||||
MQTTClientComponent::dns_found_callback, this, LWIP_DNS_ADDRTYPE_IPV4);
|
||||
#endif
|
||||
#ifdef USE_ESP8266
|
||||
err_t err = dns_gethostbyname(this->credentials_.address.c_str(), &addr,
|
||||
esphome::mqtt::MQTTClientComponent::dns_found_callback, this);
|
||||
#endif
|
||||
#endif /* USE_NETWORK_IPV6 */
|
||||
switch (err) {
|
||||
case ERR_OK: {
|
||||
// Got IP immediately
|
||||
|
@ -1,6 +1,8 @@
|
||||
#include "mqtt_text_sensor.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#include "mqtt_const.h"
|
||||
|
||||
#ifdef USE_MQTT
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
|
||||
@ -13,6 +15,8 @@ using namespace esphome::text_sensor;
|
||||
|
||||
MQTTTextSensor::MQTTTextSensor(TextSensor *sensor) : sensor_(sensor) {}
|
||||
void MQTTTextSensor::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) {
|
||||
if (!this->sensor_->get_device_class().empty())
|
||||
root[MQTT_DEVICE_CLASS] = this->sensor_->get_device_class();
|
||||
config.command_topic = false;
|
||||
}
|
||||
void MQTTTextSensor::setup() {
|
||||
|
1
esphome/components/ms8607/__init__.py
Normal file
1
esphome/components/ms8607/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
CODEOWNERS = ["@e28eta"]
|
444
esphome/components/ms8607/ms8607.cpp
Normal file
444
esphome/components/ms8607/ms8607.cpp
Normal file
@ -0,0 +1,444 @@
|
||||
#include "ms8607.h"
|
||||
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ms8607 {
|
||||
|
||||
/// TAG used for logging calls
|
||||
static const char *const TAG = "ms8607";
|
||||
|
||||
/// Reset the Pressure/Temperature sensor
|
||||
static const uint8_t MS8607_PT_CMD_RESET = 0x1E;
|
||||
|
||||
/// Beginning of PROM register addresses. Same for both i2c addresses. Each address has 16 bits of data, and
|
||||
/// PROM addresses step by two, so the LSB is always 0
|
||||
static const uint8_t MS8607_PROM_START = 0xA0;
|
||||
/// Last PROM register address.
|
||||
static const uint8_t MS8607_PROM_END = 0xAE;
|
||||
/// Number of PROM registers.
|
||||
static const uint8_t MS8607_PROM_COUNT = (MS8607_PROM_END - MS8607_PROM_START) >> 1;
|
||||
|
||||
/// Reset the Humidity sensor
|
||||
static const uint8_t MS8607_CMD_H_RESET = 0xFE;
|
||||
/// Read relative humidity, without holding i2c master
|
||||
static const uint8_t MS8607_CMD_H_MEASURE_NO_HOLD = 0xF5;
|
||||
/// Temperature correction coefficient for Relative Humidity from datasheet
|
||||
static const float MS8607_H_TEMP_COEFFICIENT = -0.18;
|
||||
|
||||
/// Read the converted analog value, either D1 (pressure) or D2 (temperature)
|
||||
static const uint8_t MS8607_CMD_ADC_READ = 0x00;
|
||||
|
||||
// TODO: allow OSR to be turned down for speed and/or lower power consumption via configuration.
|
||||
// ms8607 supports 6 different settings
|
||||
|
||||
/// Request conversion of analog D1 (pressure) with OSR=8192 (highest oversampling ratio). Takes maximum of 17.2ms
|
||||
static const uint8_t MS8607_CMD_CONV_D1_OSR_8K = 0x4A;
|
||||
/// Request conversion of analog D2 (temperature) with OSR=8192 (highest oversampling ratio). Takes maximum of 17.2ms
|
||||
static const uint8_t MS8607_CMD_CONV_D2_OSR_8K = 0x5A;
|
||||
|
||||
enum class MS8607Component::ErrorCode {
|
||||
/// Component hasn't failed (yet?)
|
||||
NONE = 0,
|
||||
/// Both the Pressure/Temperature address and the Humidity address failed to reset
|
||||
PTH_RESET_FAILED = 1,
|
||||
/// Asking the Pressure/Temperature sensor to reset failed
|
||||
PT_RESET_FAILED = 2,
|
||||
/// Asking the Humidity sensor to reset failed
|
||||
H_RESET_FAILED = 3,
|
||||
/// Reading the PROM calibration values failed
|
||||
PROM_READ_FAILED = 4,
|
||||
/// The PROM calibration values failed the CRC check
|
||||
PROM_CRC_FAILED = 5,
|
||||
};
|
||||
|
||||
enum class MS8607Component::SetupStatus {
|
||||
/// This component has not successfully reset the PT & H devices
|
||||
NEEDS_RESET,
|
||||
/// Reset commands succeeded, need to wait >= 15ms to read PROM
|
||||
NEEDS_PROM_READ,
|
||||
/// Successfully read PROM and ready to update sensors
|
||||
SUCCESSFUL,
|
||||
};
|
||||
|
||||
static uint8_t crc4(uint16_t *buffer, size_t length);
|
||||
static uint8_t hsensor_crc_check(uint16_t value);
|
||||
|
||||
void MS8607Component::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up MS8607...");
|
||||
this->error_code_ = ErrorCode::NONE;
|
||||
this->setup_status_ = SetupStatus::NEEDS_RESET;
|
||||
|
||||
// I do not know why the device sometimes NACKs the reset command, but
|
||||
// try 3 times in case it's a transitory issue on this boot
|
||||
this->set_retry(
|
||||
"reset", 5, 3,
|
||||
[this](const uint8_t remaining_setup_attempts) {
|
||||
ESP_LOGD(TAG, "Resetting both I2C addresses: 0x%02X, 0x%02X", this->address_,
|
||||
this->humidity_device_->get_address());
|
||||
// I believe sending the reset command to both addresses is preferable to
|
||||
// skipping humidity if PT fails for some reason.
|
||||
// However, only consider the reset successful if they both ACK
|
||||
bool const pt_successful = this->write_bytes(MS8607_PT_CMD_RESET, nullptr, 0);
|
||||
bool const h_successful = this->humidity_device_->write_bytes(MS8607_CMD_H_RESET, nullptr, 0);
|
||||
|
||||
if (!(pt_successful && h_successful)) {
|
||||
ESP_LOGE(TAG, "Resetting I2C devices failed");
|
||||
if (!pt_successful && !h_successful) {
|
||||
this->error_code_ = ErrorCode::PTH_RESET_FAILED;
|
||||
} else if (!pt_successful) {
|
||||
this->error_code_ = ErrorCode::PT_RESET_FAILED;
|
||||
} else {
|
||||
this->error_code_ = ErrorCode::H_RESET_FAILED;
|
||||
}
|
||||
|
||||
if (remaining_setup_attempts > 0) {
|
||||
this->status_set_error();
|
||||
} else {
|
||||
this->mark_failed();
|
||||
}
|
||||
return RetryResult::RETRY;
|
||||
}
|
||||
|
||||
this->setup_status_ = SetupStatus::NEEDS_PROM_READ;
|
||||
this->error_code_ = ErrorCode::NONE;
|
||||
this->status_clear_error();
|
||||
|
||||
// 15ms delay matches datasheet, Adafruit_MS8607 & SparkFun_PHT_MS8607_Arduino_Library
|
||||
this->set_timeout("prom-read", 15, [this]() {
|
||||
if (this->read_calibration_values_from_prom_()) {
|
||||
this->setup_status_ = SetupStatus::SUCCESSFUL;
|
||||
this->status_clear_error();
|
||||
} else {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
return RetryResult::DONE;
|
||||
},
|
||||
5.0f); // executes at now, +5ms, +25ms
|
||||
}
|
||||
|
||||
void MS8607Component::update() {
|
||||
if (this->setup_status_ != SetupStatus::SUCCESSFUL) {
|
||||
// setup is still occurring, either because reset had to retry or due to the 15ms
|
||||
// delay needed between reset & reading the PROM values
|
||||
return;
|
||||
}
|
||||
|
||||
// Updating happens async and sequentially.
|
||||
// Temperature, then pressure, then humidity
|
||||
this->request_read_temperature_();
|
||||
}
|
||||
|
||||
void MS8607Component::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "MS8607:");
|
||||
LOG_I2C_DEVICE(this);
|
||||
// LOG_I2C_DEVICE doesn't work for humidity, the `address_` is protected. Log using get_address()
|
||||
ESP_LOGCONFIG(TAG, " Address: 0x%02X", this->humidity_device_->get_address());
|
||||
if (this->is_failed()) {
|
||||
ESP_LOGE(TAG, "Communication with MS8607 failed.");
|
||||
switch (this->error_code_) {
|
||||
case ErrorCode::PT_RESET_FAILED:
|
||||
ESP_LOGE(TAG, "Temperature/Pressure RESET failed");
|
||||
break;
|
||||
case ErrorCode::H_RESET_FAILED:
|
||||
ESP_LOGE(TAG, "Humidity RESET failed");
|
||||
break;
|
||||
case ErrorCode::PTH_RESET_FAILED:
|
||||
ESP_LOGE(TAG, "Temperature/Pressure && Humidity RESET failed");
|
||||
break;
|
||||
case ErrorCode::PROM_READ_FAILED:
|
||||
ESP_LOGE(TAG, "Reading PROM failed");
|
||||
break;
|
||||
case ErrorCode::PROM_CRC_FAILED:
|
||||
ESP_LOGE(TAG, "PROM values failed CRC");
|
||||
break;
|
||||
case ErrorCode::NONE:
|
||||
default:
|
||||
ESP_LOGE(TAG, "Error reason unknown %u", static_cast<uint8_t>(this->error_code_));
|
||||
break;
|
||||
}
|
||||
}
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
|
||||
LOG_SENSOR(" ", "Pressure", this->pressure_sensor_);
|
||||
LOG_SENSOR(" ", "Humidity", this->humidity_sensor_);
|
||||
}
|
||||
|
||||
bool MS8607Component::read_calibration_values_from_prom_() {
|
||||
ESP_LOGD(TAG, "Reading calibration values from PROM");
|
||||
|
||||
uint16_t buffer[MS8607_PROM_COUNT];
|
||||
bool successful = true;
|
||||
|
||||
for (uint8_t idx = 0; idx < MS8607_PROM_COUNT; ++idx) {
|
||||
uint8_t const address_to_read = MS8607_PROM_START + (idx * 2);
|
||||
successful &= this->read_byte_16(address_to_read, &buffer[idx]);
|
||||
}
|
||||
|
||||
if (!successful) {
|
||||
ESP_LOGE(TAG, "Reading calibration values from PROM failed");
|
||||
this->error_code_ = ErrorCode::PROM_READ_FAILED;
|
||||
return false;
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, "Checking CRC of calibration values from PROM");
|
||||
uint8_t const expected_crc = (buffer[0] & 0xF000) >> 12; // first 4 bits
|
||||
buffer[0] &= 0x0FFF; // strip CRC from buffer, in order to run CRC
|
||||
uint8_t const actual_crc = crc4(buffer, MS8607_PROM_COUNT);
|
||||
|
||||
if (expected_crc != actual_crc) {
|
||||
ESP_LOGE(TAG, "Incorrect CRC value. Provided value 0x%01X != calculated value 0x%01X", expected_crc, actual_crc);
|
||||
this->error_code_ = ErrorCode::PROM_CRC_FAILED;
|
||||
return false;
|
||||
}
|
||||
|
||||
this->calibration_values_.pressure_sensitivity = buffer[1];
|
||||
this->calibration_values_.pressure_offset = buffer[2];
|
||||
this->calibration_values_.pressure_sensitivity_temperature_coefficient = buffer[3];
|
||||
this->calibration_values_.pressure_offset_temperature_coefficient = buffer[4];
|
||||
this->calibration_values_.reference_temperature = buffer[5];
|
||||
this->calibration_values_.temperature_coefficient_of_temperature = buffer[6];
|
||||
ESP_LOGD(TAG, "Finished reading calibration values");
|
||||
|
||||
// Skipping reading Humidity PROM, since it doesn't have anything interesting for us
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
CRC-4 algorithm from datasheet. It operates on a buffer of 16-bit values, one byte at a time, using a 16-bit
|
||||
value to collect the CRC result into.
|
||||
|
||||
The provided/expected CRC value must already be zeroed out from the buffer.
|
||||
*/
|
||||
static uint8_t crc4(uint16_t *buffer, size_t length) {
|
||||
uint16_t crc_remainder = 0;
|
||||
|
||||
// algorithm to add a byte into the crc
|
||||
auto apply_crc = [&crc_remainder](uint8_t next) {
|
||||
crc_remainder ^= next;
|
||||
for (uint8_t bit = 8; bit > 0; --bit) {
|
||||
if (crc_remainder & 0x8000) {
|
||||
crc_remainder = (crc_remainder << 1) ^ 0x3000;
|
||||
} else {
|
||||
crc_remainder = (crc_remainder << 1);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// add all the bytes
|
||||
for (size_t idx = 0; idx < length; ++idx) {
|
||||
for (auto byte : decode_value(buffer[idx])) {
|
||||
apply_crc(byte);
|
||||
}
|
||||
}
|
||||
// For the MS8607 CRC, add a pair of zeros to shift the last byte from `buffer` through
|
||||
apply_crc(0);
|
||||
apply_crc(0);
|
||||
|
||||
return (crc_remainder >> 12) & 0xF; // only the most significant 4 bits
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Calculates CRC value for the provided humidity (+ status bits) value
|
||||
*
|
||||
* CRC-8 check comes from other MS8607 libraries on github. I did not find it in the datasheet,
|
||||
* and it differs from the crc8 implementation that's already part of esphome.
|
||||
*
|
||||
* @param value two byte humidity sensor value read from i2c
|
||||
* @return uint8_t computed crc value
|
||||
*/
|
||||
static uint8_t hsensor_crc_check(uint16_t value) {
|
||||
uint32_t polynom = 0x988000; // x^8 + x^5 + x^4 + 1
|
||||
uint32_t msb = 0x800000;
|
||||
uint32_t mask = 0xFF8000;
|
||||
uint32_t result = (uint32_t) value << 8; // Pad with zeros as specified in spec
|
||||
|
||||
while (msb != 0x80) {
|
||||
// Check if msb of current value is 1 and apply XOR mask
|
||||
if (result & msb) {
|
||||
result = ((result ^ polynom) & mask) | (result & ~mask);
|
||||
}
|
||||
|
||||
// Shift by one
|
||||
msb >>= 1;
|
||||
mask >>= 1;
|
||||
polynom >>= 1;
|
||||
}
|
||||
return result & 0xFF;
|
||||
}
|
||||
|
||||
void MS8607Component::request_read_temperature_() {
|
||||
// Tell MS8607 to start ADC conversion of temperature sensor
|
||||
if (!this->write_bytes(MS8607_CMD_CONV_D2_OSR_8K, nullptr, 0)) {
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
|
||||
auto f = std::bind(&MS8607Component::read_temperature_, this);
|
||||
// datasheet says 17.2ms max conversion time at OSR 8192
|
||||
this->set_timeout("temperature", 20, f);
|
||||
}
|
||||
|
||||
void MS8607Component::read_temperature_() {
|
||||
uint8_t bytes[3]; // 24 bits
|
||||
if (!this->read_bytes(MS8607_CMD_ADC_READ, bytes, 3)) {
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
|
||||
const uint32_t d2_raw_temperature = encode_uint32(0, bytes[0], bytes[1], bytes[2]);
|
||||
this->request_read_pressure_(d2_raw_temperature);
|
||||
}
|
||||
|
||||
void MS8607Component::request_read_pressure_(uint32_t d2_raw_temperature) {
|
||||
if (!this->write_bytes(MS8607_CMD_CONV_D1_OSR_8K, nullptr, 0)) {
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
|
||||
auto f = std::bind(&MS8607Component::read_pressure_, this, d2_raw_temperature);
|
||||
// datasheet says 17.2ms max conversion time at OSR 8192
|
||||
this->set_timeout("pressure", 20, f);
|
||||
}
|
||||
|
||||
void MS8607Component::read_pressure_(uint32_t d2_raw_temperature) {
|
||||
uint8_t bytes[3]; // 24 bits
|
||||
if (!this->read_bytes(MS8607_CMD_ADC_READ, bytes, 3)) {
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
const uint32_t d1_raw_pressure = encode_uint32(0, bytes[0], bytes[1], bytes[2]);
|
||||
this->calculate_values_(d2_raw_temperature, d1_raw_pressure);
|
||||
}
|
||||
|
||||
void MS8607Component::request_read_humidity_(float temperature_float) {
|
||||
if (!this->humidity_device_->write_bytes(MS8607_CMD_H_MEASURE_NO_HOLD, nullptr, 0)) {
|
||||
ESP_LOGW(TAG, "Request to measure humidity failed");
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
|
||||
auto f = std::bind(&MS8607Component::read_humidity_, this, temperature_float);
|
||||
// datasheet says 15.89ms max conversion time at OSR 8192
|
||||
this->set_timeout("humidity", 20, f);
|
||||
}
|
||||
|
||||
void MS8607Component::read_humidity_(float temperature_float) {
|
||||
uint8_t bytes[3];
|
||||
if (!this->humidity_device_->read_bytes_raw(bytes, 3)) {
|
||||
ESP_LOGW(TAG, "Failed to read the measured humidity value");
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
|
||||
// "the measurement is stored into 14 bits. The two remaining LSBs are used for transmitting status information.
|
||||
// Bit1 of the two LSBS must be set to '1'. Bit0 is currently not assigned"
|
||||
uint16_t humidity = encode_uint16(bytes[0], bytes[1]);
|
||||
uint8_t const expected_crc = bytes[2];
|
||||
uint8_t const actual_crc = hsensor_crc_check(humidity);
|
||||
if (expected_crc != actual_crc) {
|
||||
ESP_LOGE(TAG, "Incorrect Humidity CRC value. Provided value 0x%01X != calculated value 0x%01X", expected_crc,
|
||||
actual_crc);
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
if (!(humidity & 0x2)) {
|
||||
// data sheet says Bit1 should always set, but nothing about what happens if it isn't
|
||||
ESP_LOGE(TAG, "Humidity status bit was not set to 1?");
|
||||
}
|
||||
humidity &= ~(0b11); // strip status & unassigned bits from data
|
||||
|
||||
// map 16 bit humidity value into range [-6%, 118%]
|
||||
float const humidity_partial = double(humidity) / (1 << 16);
|
||||
float const humidity_percentage = lerp(humidity_partial, -6.0, 118.0);
|
||||
float const compensated_humidity_percentage =
|
||||
humidity_percentage + (20 - temperature_float) * MS8607_H_TEMP_COEFFICIENT;
|
||||
ESP_LOGD(TAG, "Compensated for temperature, humidity=%.2f%%", compensated_humidity_percentage);
|
||||
|
||||
if (this->humidity_sensor_ != nullptr) {
|
||||
this->humidity_sensor_->publish_state(compensated_humidity_percentage);
|
||||
}
|
||||
this->status_clear_warning();
|
||||
}
|
||||
|
||||
void MS8607Component::calculate_values_(uint32_t d2_raw_temperature, uint32_t d1_raw_pressure) {
|
||||
// Perform the first order pressure/temperature calculation
|
||||
|
||||
// d_t: "difference between actual and reference temperature" = D2 - [C5] * 2**8
|
||||
const int32_t d_t = int32_t(d2_raw_temperature) - (int32_t(this->calibration_values_.reference_temperature) << 8);
|
||||
// actual temperature as hundredths of degree celsius in range [-4000, 8500]
|
||||
// 2000 + d_t * [C6] / (2**23)
|
||||
int32_t temperature =
|
||||
2000 + ((int64_t(d_t) * this->calibration_values_.temperature_coefficient_of_temperature) >> 23);
|
||||
|
||||
// offset at actual temperature. [C2] * (2**17) + (d_t * [C4] / (2**6))
|
||||
int64_t pressure_offset = (int64_t(this->calibration_values_.pressure_offset) << 17) +
|
||||
((int64_t(d_t) * this->calibration_values_.pressure_offset_temperature_coefficient) >> 6);
|
||||
// sensitivity at actual temperature. [C1] * (2**16) + ([C3] * d_t) / (2**7)
|
||||
int64_t pressure_sensitivity =
|
||||
(int64_t(this->calibration_values_.pressure_sensitivity) << 16) +
|
||||
((int64_t(d_t) * this->calibration_values_.pressure_sensitivity_temperature_coefficient) >> 7);
|
||||
|
||||
// Perform the second order compensation, for non-linearity over temperature range
|
||||
const int64_t d_t_squared = int64_t(d_t) * d_t;
|
||||
int64_t temperature_2 = 0;
|
||||
int32_t pressure_offset_2 = 0;
|
||||
int32_t pressure_sensitivity_2 = 0;
|
||||
if (temperature < 2000) {
|
||||
// (TEMP - 2000)**2 / 2**4
|
||||
const int32_t low_temperature_adjustment = (temperature - 2000) * (temperature - 2000) >> 4;
|
||||
|
||||
// T2 = 3 * (d_t**2) / 2**33
|
||||
temperature_2 = (3 * d_t_squared) >> 33;
|
||||
// OFF2 = 61 * (TEMP-2000)**2 / 2**4
|
||||
pressure_offset_2 = 61 * low_temperature_adjustment;
|
||||
// SENS2 = 29 * (TEMP-2000)**2 / 2**4
|
||||
pressure_sensitivity_2 = 29 * low_temperature_adjustment;
|
||||
|
||||
if (temperature < -1500) {
|
||||
// (TEMP+1500)**2
|
||||
const int32_t very_low_temperature_adjustment = (temperature + 1500) * (temperature + 1500);
|
||||
|
||||
// OFF2 = OFF2 + 17 * (TEMP+1500)**2
|
||||
pressure_offset_2 += 17 * very_low_temperature_adjustment;
|
||||
// SENS2 = SENS2 + 9 * (TEMP+1500)**2
|
||||
pressure_sensitivity_2 += 9 * very_low_temperature_adjustment;
|
||||
}
|
||||
} else {
|
||||
// T2 = 5 * (d_t**2) / 2**38
|
||||
temperature_2 = (5 * d_t_squared) >> 38;
|
||||
}
|
||||
|
||||
temperature -= temperature_2;
|
||||
pressure_offset -= pressure_offset_2;
|
||||
pressure_sensitivity -= pressure_sensitivity_2;
|
||||
|
||||
// Temperature compensated pressure. [1000, 120000] => [10.00 mbar, 1200.00 mbar]
|
||||
const int32_t pressure = (((d1_raw_pressure * pressure_sensitivity) >> 21) - pressure_offset) >> 15;
|
||||
|
||||
const float temperature_float = temperature / 100.0f;
|
||||
const float pressure_float = pressure / 100.0f;
|
||||
ESP_LOGD(TAG, "Temperature=%0.2f°C, Pressure=%0.2fhPa", temperature_float, pressure_float);
|
||||
|
||||
if (this->temperature_sensor_ != nullptr) {
|
||||
this->temperature_sensor_->publish_state(temperature_float);
|
||||
}
|
||||
if (this->pressure_sensor_ != nullptr) {
|
||||
this->pressure_sensor_->publish_state(pressure_float); // hPa aka mbar
|
||||
}
|
||||
this->status_clear_warning();
|
||||
|
||||
if (this->humidity_sensor_ != nullptr) {
|
||||
// now that we have temperature (to compensate the humidity with), kick off that read
|
||||
this->request_read_humidity_(temperature_float);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ms8607
|
||||
} // namespace esphome
|
109
esphome/components/ms8607/ms8607.h
Normal file
109
esphome/components/ms8607/ms8607.h
Normal file
@ -0,0 +1,109 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ms8607 {
|
||||
|
||||
/**
|
||||
Class for I2CDevice used to communicate with the Humidity sensor
|
||||
on the chip. See MS8607Component instead
|
||||
*/
|
||||
class MS8607HumidityDevice : public i2c::I2CDevice {
|
||||
public:
|
||||
uint8_t get_address() { return address_; }
|
||||
};
|
||||
|
||||
/**
|
||||
Temperature, pressure, and humidity sensor.
|
||||
|
||||
By default, the MS8607 measures sensors at the highest resolution.
|
||||
A potential enhancement would be to expose the resolution as a configurable
|
||||
setting. A lower resolution speeds up ADC conversion time & uses less power.
|
||||
|
||||
Datasheet:
|
||||
https://www.te.com/commerce/DocumentDelivery/DDEController?Action=showdoc&DocId=Data+Sheet%7FMS8607-02BA01%7FB3%7Fpdf%7FEnglish%7FENG_DS_MS8607-02BA01_B3.pdf%7FCAT-BLPS0018
|
||||
|
||||
Other implementations:
|
||||
- https://github.com/TEConnectivity/MS8607_Generic_C_Driver
|
||||
- https://github.com/adafruit/Adafruit_MS8607
|
||||
- https://github.com/sparkfun/SparkFun_PHT_MS8607_Arduino_Library
|
||||
*/
|
||||
class MS8607Component : public PollingComponent, public i2c::I2CDevice {
|
||||
public:
|
||||
virtual ~MS8607Component() = default;
|
||||
void setup() override;
|
||||
void update() override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override { return setup_priority::DATA; };
|
||||
|
||||
void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; }
|
||||
void set_pressure_sensor(sensor::Sensor *pressure_sensor) { pressure_sensor_ = pressure_sensor; }
|
||||
void set_humidity_sensor(sensor::Sensor *humidity_sensor) { humidity_sensor_ = humidity_sensor; }
|
||||
void set_humidity_device(MS8607HumidityDevice *humidity_device) { humidity_device_ = humidity_device; }
|
||||
|
||||
protected:
|
||||
/**
|
||||
Read and store the Pressure & Temperature calibration settings from the PROM.
|
||||
Intended to be called during setup(), this will set the `failure_reason_`
|
||||
*/
|
||||
bool read_calibration_values_from_prom_();
|
||||
|
||||
/// Start async temperature read
|
||||
void request_read_temperature_();
|
||||
/// Process async temperature read
|
||||
void read_temperature_();
|
||||
/// start async pressure read
|
||||
void request_read_pressure_(uint32_t raw_temperature);
|
||||
/// process async pressure read
|
||||
void read_pressure_(uint32_t raw_temperature);
|
||||
/// start async humidity read
|
||||
void request_read_humidity_(float temperature_float);
|
||||
/// process async humidity read
|
||||
void read_humidity_(float temperature_float);
|
||||
/// use raw temperature & pressure to calculate & publish values
|
||||
void calculate_values_(uint32_t raw_temperature, uint32_t raw_pressure);
|
||||
|
||||
sensor::Sensor *temperature_sensor_;
|
||||
sensor::Sensor *pressure_sensor_;
|
||||
sensor::Sensor *humidity_sensor_;
|
||||
|
||||
/** I2CDevice object to communicate with secondary I2C address for the humidity sensor
|
||||
*
|
||||
* The MS8607 only has one set of I2C pins, despite using two different addresses.
|
||||
*
|
||||
* Default address for humidity is 0x40
|
||||
*/
|
||||
MS8607HumidityDevice *humidity_device_;
|
||||
|
||||
/// This device's pressure & temperature calibration values, read from PROM
|
||||
struct CalibrationValues {
|
||||
/// Pressure sensitivity | SENS-T1. [C1]
|
||||
uint16_t pressure_sensitivity;
|
||||
/// Temperature coefficient of pressure sensitivity | TCS. [C3]
|
||||
uint16_t pressure_sensitivity_temperature_coefficient;
|
||||
/// Pressure offset | OFF-T1. [C2]
|
||||
uint16_t pressure_offset;
|
||||
/// Temperature coefficient of pressure offset | TCO. [C4]
|
||||
uint16_t pressure_offset_temperature_coefficient;
|
||||
/// Reference temperature | T-REF. [C5]
|
||||
uint16_t reference_temperature;
|
||||
/// Temperature coefficient of the temperature | TEMPSENS. [C6]
|
||||
uint16_t temperature_coefficient_of_temperature;
|
||||
} calibration_values_;
|
||||
|
||||
/// Possible failure reasons of this component
|
||||
enum class ErrorCode;
|
||||
/// Keep track of the reason why this component failed, to augment the dumped config
|
||||
ErrorCode error_code_;
|
||||
|
||||
/// Current progress through required component setup
|
||||
enum class SetupStatus;
|
||||
/// Current step in the multi-step & possibly delayed setup() process
|
||||
SetupStatus setup_status_;
|
||||
};
|
||||
|
||||
} // namespace ms8607
|
||||
} // namespace esphome
|
83
esphome/components/ms8607/sensor.py
Normal file
83
esphome/components/ms8607/sensor.py
Normal file
@ -0,0 +1,83 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import i2c, sensor
|
||||
from esphome.const import (
|
||||
CONF_HUMIDITY,
|
||||
CONF_ID,
|
||||
CONF_PRESSURE,
|
||||
CONF_TEMPERATURE,
|
||||
DEVICE_CLASS_HUMIDITY,
|
||||
DEVICE_CLASS_PRESSURE,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_CELSIUS,
|
||||
UNIT_HECTOPASCAL,
|
||||
UNIT_PERCENT,
|
||||
)
|
||||
|
||||
DEPENDENCIES = ["i2c"]
|
||||
|
||||
ms8607_ns = cg.esphome_ns.namespace("ms8607")
|
||||
MS8607Component = ms8607_ns.class_(
|
||||
"MS8607Component", cg.PollingComponent, i2c.I2CDevice
|
||||
)
|
||||
|
||||
CONF_HUMIDITY_I2C_ID = "humidity_i2c_id"
|
||||
MS8607HumidityDevice = ms8607_ns.class_("MS8607HumidityDevice", i2c.I2CDevice)
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(MS8607Component),
|
||||
cv.Required(CONF_TEMPERATURE): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_CELSIUS,
|
||||
accuracy_decimals=2, # Resolution: 0.01
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Required(CONF_PRESSURE): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_HECTOPASCAL,
|
||||
accuracy_decimals=2, # Resolution: 0.016
|
||||
device_class=DEVICE_CLASS_PRESSURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Required(CONF_HUMIDITY): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_PERCENT,
|
||||
accuracy_decimals=2, # Resolution: 0.04
|
||||
device_class=DEVICE_CLASS_HUMIDITY,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
)
|
||||
.extend(
|
||||
{
|
||||
cv.GenerateID(CONF_HUMIDITY_I2C_ID): cv.declare_id(
|
||||
MS8607HumidityDevice
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(i2c.i2c_device_schema(0x40)), # default address for humidity
|
||||
}
|
||||
)
|
||||
.extend(cv.polling_component_schema("60s"))
|
||||
.extend(i2c.i2c_device_schema(0x76)) # default address for temp/pressure
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
|
||||
if temperature_config := config.get(CONF_TEMPERATURE):
|
||||
sens = await sensor.new_sensor(temperature_config)
|
||||
cg.add(var.set_temperature_sensor(sens))
|
||||
|
||||
if pressure_config := config.get(CONF_PRESSURE):
|
||||
sens = await sensor.new_sensor(pressure_config)
|
||||
cg.add(var.set_pressure_sensor(sens))
|
||||
|
||||
if humidity_config := config.get(CONF_HUMIDITY):
|
||||
sens = await sensor.new_sensor(humidity_config)
|
||||
cg.add(var.set_humidity_sensor(sens))
|
||||
humidity_device = cg.new_Pvariable(humidity_config[CONF_HUMIDITY_I2C_ID])
|
||||
await i2c.register_i2c_device(humidity_device, humidity_config)
|
||||
cg.add(var.set_humidity_device(humidity_device))
|
@ -5,6 +5,7 @@ from esphome.components.esp32 import add_idf_sdkconfig_option
|
||||
|
||||
from esphome.const import (
|
||||
CONF_ENABLE_IPV6,
|
||||
CONF_MIN_IPV6_ADDR_COUNT,
|
||||
)
|
||||
|
||||
CODEOWNERS = ["@esphome/core"]
|
||||
@ -16,12 +17,14 @@ IPAddress = network_ns.class_("IPAddress")
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_ENABLE_IPV6, default=False): cv.boolean,
|
||||
cv.Optional(CONF_MIN_IPV6_ADDR_COUNT, default=0): cv.positive_int,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
cg.add_define("ENABLE_IPV6", config[CONF_ENABLE_IPV6])
|
||||
cg.add_define("USE_NETWORK_IPV6", config[CONF_ENABLE_IPV6])
|
||||
cg.add_define("USE_NETWORK_MIN_IPV6_ADDR_COUNT", config[CONF_MIN_IPV6_ADDR_COUNT])
|
||||
if CORE.using_esp_idf:
|
||||
add_idf_sdkconfig_option("CONFIG_LWIP_IPV6", config[CONF_ENABLE_IPV6])
|
||||
add_idf_sdkconfig_option(
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user