mirror of
https://github.com/esphome/esphome.git
synced 2025-02-24 20:12:30 +01:00
Merge branch 'dev' into nvds-status-info
This commit is contained in:
commit
387dbf3367
307 changed files with 8074 additions and 1008 deletions
|
@ -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
|
||||
|
@ -223,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
|
||||
|
@ -364,6 +367,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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -118,7 +118,7 @@ void CSE7766Component::parse_data_() {
|
|||
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];
|
||||
uint16_t cf_pulses = (this->raw_data_[21] << 8) + this->raw_data_[22];
|
||||
|
||||
bool have_power = adj & 0x10;
|
||||
bool have_current = adj & 0x20;
|
||||
|
@ -132,8 +132,19 @@ void CSE7766Component::parse_data_() {
|
|||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
uint16_t cf_diff = cf_pulses - this->cf_pulses_last_;
|
||||
this->cf_pulses_total_ += cf_diff;
|
||||
this->cf_pulses_last_ = cf_pulses;
|
||||
energy = this->cf_pulses_total_ * float(power_coeff) / 1000000.0f / 3600.0f;
|
||||
this->energy_sensor_->publish_state(energy);
|
||||
}
|
||||
|
||||
float power = 0.0f;
|
||||
float energy = 0.0f;
|
||||
if (power_cycle_exceeds_range) {
|
||||
// Datasheet: power cycle exceeding range means active power is 0
|
||||
if (this->power_sensor_ != nullptr) {
|
||||
|
@ -144,27 +155,6 @@ void CSE7766Component::parse_data_() {
|
|||
if (this->power_sensor_ != nullptr) {
|
||||
this->power_sensor_->publish_state(power);
|
||||
}
|
||||
|
||||
// Add CF pulses to the total energy only if we have Power coefficient to multiply by
|
||||
|
||||
if (this->cf_pulses_last_ == 0) {
|
||||
this->cf_pulses_last_ = cf_pulses;
|
||||
}
|
||||
|
||||
uint32_t cf_diff;
|
||||
if (cf_pulses < this->cf_pulses_last_) {
|
||||
cf_diff = cf_pulses + (0x10000 - this->cf_pulses_last_);
|
||||
} else {
|
||||
cf_diff = cf_pulses - this->cf_pulses_last_;
|
||||
}
|
||||
this->cf_pulses_last_ = cf_pulses;
|
||||
|
||||
energy = cf_diff * float(power_coeff) / 1000000.0f / 3600.0f;
|
||||
this->energy_total_ += energy;
|
||||
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);
|
||||
}
|
||||
|
||||
float current = 0.0f;
|
||||
|
@ -183,6 +173,32 @@ void CSE7766Component::parse_data_() {
|
|||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
@ -215,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);
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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))
|
|
@ -1,2 +1,3 @@
|
|||
"""Support for Honeywell HumidIcon HIH"""
|
||||
|
||||
CODEOWNERS = ["@Benichou34"]
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
"""Support for Honeywell ABP2"""
|
||||
|
||||
CODEOWNERS = ["@jpfaff"]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -127,6 +127,10 @@ def uart_selection(value):
|
|||
if CORE.using_arduino and value.upper() in ESP_ARDUINO_UNSUPPORTED_USB_UARTS:
|
||||
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"{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:
|
||||
|
@ -274,6 +278,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
|
||||
|
||||
# Register at end for safe mode
|
||||
await cg.register_component(log, config)
|
||||
|
|
|
@ -1,28 +1,6 @@
|
|||
#include "logger.h"
|
||||
#include <cinttypes>
|
||||
|
||||
#ifdef USE_ESP_IDF
|
||||
#include <driver/uart.h>
|
||||
|
||||
#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32S3) || \
|
||||
defined(USE_ESP32_VARIANT_ESP32H2)
|
||||
#include <driver/usb_serial_jtag.h>
|
||||
#include <esp_vfs_dev.h>
|
||||
#include <esp_vfs_usb_serial_jtag.h>
|
||||
#endif
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "esp_idf_version.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <fcntl.h>
|
||||
|
||||
#endif // USE_ESP_IDF
|
||||
|
||||
#if defined(USE_ESP32_FRAMEWORK_ARDUINO) || defined(USE_ESP_IDF)
|
||||
#include <esp_log.h>
|
||||
#endif // USE_ESP32_FRAMEWORK_ARDUINO || USE_ESP_IDF
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
|
@ -106,58 +84,6 @@ void Logger::log_vprintf_(int level, const char *tag, int line, const __FlashStr
|
|||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_ESP_IDF
|
||||
void Logger::init_uart_() {
|
||||
uart_config_t uart_config{};
|
||||
uart_config.baud_rate = (int) baud_rate_;
|
||||
uart_config.data_bits = UART_DATA_8_BITS;
|
||||
uart_config.parity = UART_PARITY_DISABLE;
|
||||
uart_config.stop_bits = UART_STOP_BITS_1;
|
||||
uart_config.flow_ctrl = UART_HW_FLOWCTRL_DISABLE;
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
|
||||
uart_config.source_clk = UART_SCLK_DEFAULT;
|
||||
#endif
|
||||
uart_param_config(this->uart_num_, &uart_config);
|
||||
const int uart_buffer_size = tx_buffer_size_;
|
||||
// Install UART driver using an event queue here
|
||||
uart_driver_install(this->uart_num_, uart_buffer_size, uart_buffer_size, 10, nullptr, 0);
|
||||
}
|
||||
|
||||
#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
|
||||
void Logger::init_usb_cdc_() {}
|
||||
#endif
|
||||
|
||||
#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32S3) || \
|
||||
defined(USE_ESP32_VARIANT_ESP32H2)
|
||||
void Logger::init_usb_serial_jtag_() {
|
||||
setvbuf(stdin, NULL, _IONBF, 0); // Disable buffering on stdin
|
||||
|
||||
// Minicom, screen, idf_monitor send CR when ENTER key is pressed
|
||||
esp_vfs_dev_usb_serial_jtag_set_rx_line_endings(ESP_LINE_ENDINGS_CR);
|
||||
// Move the caret to the beginning of the next line on '\n'
|
||||
esp_vfs_dev_usb_serial_jtag_set_tx_line_endings(ESP_LINE_ENDINGS_CRLF);
|
||||
|
||||
// Enable non-blocking mode on stdin and stdout
|
||||
fcntl(fileno(stdout), F_SETFL, 0);
|
||||
fcntl(fileno(stdin), F_SETFL, 0);
|
||||
|
||||
usb_serial_jtag_driver_config_t usb_serial_jtag_config{};
|
||||
usb_serial_jtag_config.rx_buffer_size = 512;
|
||||
usb_serial_jtag_config.tx_buffer_size = 512;
|
||||
|
||||
esp_err_t ret = ESP_OK;
|
||||
// Install USB-SERIAL-JTAG driver for interrupt-driven reads and writes
|
||||
ret = usb_serial_jtag_driver_install(&usb_serial_jtag_config);
|
||||
if (ret != ESP_OK) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Tell vfs to use usb-serial-jtag driver
|
||||
esp_vfs_usb_serial_jtag_use_driver();
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
int HOT Logger::level_for(const char *tag) {
|
||||
// Uses std::vector<> for low memory footprint, though the vector
|
||||
// could be sorted to minimize lookup times. This feature isn't used that
|
||||
|
@ -169,6 +95,7 @@ int HOT Logger::level_for(const char *tag) {
|
|||
}
|
||||
return ESPHOME_LOG_LEVEL;
|
||||
}
|
||||
|
||||
void HOT Logger::log_message_(int level, const char *tag, int offset) {
|
||||
// remove trailing newline
|
||||
if (this->tx_buffer_[this->tx_buffer_at_ - 1] == '\n') {
|
||||
|
@ -178,28 +105,9 @@ void HOT Logger::log_message_(int level, const char *tag, int offset) {
|
|||
this->set_null_terminator_();
|
||||
|
||||
const char *msg = this->tx_buffer_ + offset;
|
||||
|
||||
if (this->baud_rate_ > 0) {
|
||||
#ifdef USE_ARDUINO
|
||||
this->hw_serial_->println(msg);
|
||||
#endif // USE_ARDUINO
|
||||
#ifdef USE_ESP_IDF
|
||||
if (
|
||||
#if defined(USE_ESP32_VARIANT_ESP32S2)
|
||||
this->uart_ == UART_SELECTION_USB_CDC
|
||||
#elif defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32H2)
|
||||
this->uart_ == UART_SELECTION_USB_SERIAL_JTAG
|
||||
#elif defined(USE_ESP32_VARIANT_ESP32S3)
|
||||
this->uart_ == UART_SELECTION_USB_CDC || this->uart_ == UART_SELECTION_USB_SERIAL_JTAG
|
||||
#else
|
||||
/* DISABLES CODE */ (false) // NOLINT
|
||||
#endif
|
||||
) {
|
||||
puts(msg);
|
||||
} else {
|
||||
uart_write_bytes(this->uart_num_, msg, strlen(msg));
|
||||
uart_write_bytes(this->uart_num_, "\n", 1);
|
||||
}
|
||||
#endif
|
||||
this->write_msg_(msg);
|
||||
}
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
@ -211,17 +119,6 @@ void HOT Logger::log_message_(int level, const char *tag, int offset) {
|
|||
if (xPortGetFreeHeapSize() < 2048)
|
||||
return;
|
||||
#endif
|
||||
#ifdef USE_HOST
|
||||
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);
|
||||
#endif
|
||||
|
||||
this->log_callback_.call(level, tag, msg);
|
||||
}
|
||||
|
@ -231,179 +128,6 @@ Logger::Logger(uint32_t baud_rate, size_t tx_buffer_size) : baud_rate_(baud_rate
|
|||
this->tx_buffer_ = new char[this->tx_buffer_size_ + 1]; // NOLINT
|
||||
}
|
||||
|
||||
#ifndef USE_LIBRETINY
|
||||
void Logger::pre_setup() {
|
||||
if (this->baud_rate_ > 0) {
|
||||
#ifdef USE_ARDUINO
|
||||
switch (this->uart_) {
|
||||
case UART_SELECTION_UART0:
|
||||
#ifdef USE_ESP8266
|
||||
case UART_SELECTION_UART0_SWAP:
|
||||
#endif
|
||||
#ifdef USE_RP2040
|
||||
this->hw_serial_ = &Serial1;
|
||||
Serial1.begin(this->baud_rate_);
|
||||
#else
|
||||
#if ARDUINO_USB_CDC_ON_BOOT
|
||||
this->hw_serial_ = &Serial0;
|
||||
Serial0.begin(this->baud_rate_);
|
||||
#else
|
||||
this->hw_serial_ = &Serial;
|
||||
Serial.begin(this->baud_rate_);
|
||||
#endif
|
||||
#endif
|
||||
#ifdef USE_ESP8266
|
||||
if (this->uart_ == UART_SELECTION_UART0_SWAP) {
|
||||
Serial.swap();
|
||||
}
|
||||
Serial.setDebugOutput(ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE);
|
||||
#endif
|
||||
break;
|
||||
case UART_SELECTION_UART1:
|
||||
#ifdef USE_RP2040
|
||||
this->hw_serial_ = &Serial2;
|
||||
Serial2.begin(this->baud_rate_);
|
||||
#else
|
||||
this->hw_serial_ = &Serial1;
|
||||
Serial1.begin(this->baud_rate_);
|
||||
#endif
|
||||
#ifdef USE_ESP8266
|
||||
Serial1.setDebugOutput(ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE);
|
||||
#endif
|
||||
break;
|
||||
#if defined(USE_ESP32) && !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32C6) && \
|
||||
!defined(USE_ESP32_VARIANT_ESP32S2) && !defined(USE_ESP32_VARIANT_ESP32S3)
|
||||
case UART_SELECTION_UART2:
|
||||
this->hw_serial_ = &Serial2;
|
||||
Serial2.begin(this->baud_rate_);
|
||||
break;
|
||||
#endif
|
||||
#if defined(USE_ESP32) && \
|
||||
(defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32C3))
|
||||
#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32C3)
|
||||
case UART_SELECTION_USB_CDC:
|
||||
#endif // USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3 || USE_ESP32_VARIANT_ESP32C3
|
||||
#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S3)
|
||||
case UART_SELECTION_USB_SERIAL_JTAG:
|
||||
#endif // USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32S3
|
||||
#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32C3)
|
||||
#if ARDUINO_USB_CDC_ON_BOOT
|
||||
this->hw_serial_ = &Serial;
|
||||
Serial.setTxTimeoutMs(0); // workaround for 2.0.9 crash when there's no data connection
|
||||
Serial.begin(this->baud_rate_);
|
||||
#else
|
||||
this->hw_serial_ = &Serial;
|
||||
Serial.begin(this->baud_rate_);
|
||||
#endif // ARDUINO_USB_CDC_ON_BOOT
|
||||
#endif // USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3 || USE_ESP32_VARIANT_ESP32C3
|
||||
break;
|
||||
#endif // USE_ESP32 && (USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3 || USE_ESP32_VARIANT_ESP32C3)
|
||||
#ifdef USE_RP2040
|
||||
case UART_SELECTION_USB_CDC:
|
||||
this->hw_serial_ = &Serial;
|
||||
Serial.begin(this->baud_rate_);
|
||||
break;
|
||||
#endif // USE_RP2040
|
||||
}
|
||||
#endif // USE_ARDUINO
|
||||
#ifdef USE_ESP_IDF
|
||||
this->uart_num_ = UART_NUM_0;
|
||||
switch (this->uart_) {
|
||||
case UART_SELECTION_UART0:
|
||||
this->uart_num_ = UART_NUM_0;
|
||||
break;
|
||||
case UART_SELECTION_UART1:
|
||||
this->uart_num_ = UART_NUM_1;
|
||||
break;
|
||||
#if !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32C6) && \
|
||||
!defined(USE_ESP32_VARIANT_ESP32S2) && !defined(USE_ESP32_VARIANT_ESP32S3) && !defined(USE_ESP32_VARIANT_ESP32H2)
|
||||
case UART_SELECTION_UART2:
|
||||
this->uart_num_ = UART_NUM_2;
|
||||
break;
|
||||
#endif // !USE_ESP32_VARIANT_ESP32C3 && !USE_ESP32_VARIANT_ESP32S2 && !USE_ESP32_VARIANT_ESP32S3 &&
|
||||
// !USE_ESP32_VARIANT_ESP32H2
|
||||
#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
|
||||
case UART_SELECTION_USB_CDC:
|
||||
this->uart_num_ = -1;
|
||||
this->init_usb_cdc_();
|
||||
break;
|
||||
#endif // USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3
|
||||
#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32S3) || \
|
||||
defined(USE_ESP32_VARIANT_ESP32H2)
|
||||
case UART_SELECTION_USB_SERIAL_JTAG:
|
||||
this->uart_num_ = -1;
|
||||
this->init_usb_serial_jtag_();
|
||||
break;
|
||||
#endif // USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C6 || USE_ESP32_VARIANT_ESP32S3 ||
|
||||
// USE_ESP32_VARIANT_ESP32H2
|
||||
}
|
||||
if (this->uart_num_ >= 0) {
|
||||
this->init_uart_();
|
||||
}
|
||||
#endif // USE_ESP_IDF
|
||||
}
|
||||
#ifdef USE_ESP8266
|
||||
else {
|
||||
uart_set_debug(UART_NO);
|
||||
}
|
||||
#endif // USE_ESP8266
|
||||
|
||||
global_logger = this;
|
||||
#if defined(USE_ESP_IDF) || defined(USE_ESP32_FRAMEWORK_ARDUINO)
|
||||
esp_log_set_vprintf(esp_idf_log_vprintf_);
|
||||
if (ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE) {
|
||||
esp_log_level_set("*", ESP_LOG_VERBOSE);
|
||||
}
|
||||
#endif // USE_ESP_IDF || USE_ESP32_FRAMEWORK_ARDUINO
|
||||
|
||||
ESP_LOGI(TAG, "Log initialized");
|
||||
}
|
||||
#else // USE_LIBRETINY
|
||||
void Logger::pre_setup() {
|
||||
if (this->baud_rate_ > 0) {
|
||||
switch (this->uart_) {
|
||||
#if LT_HW_UART0
|
||||
case UART_SELECTION_UART0:
|
||||
this->hw_serial_ = &Serial0;
|
||||
Serial0.begin(this->baud_rate_);
|
||||
break;
|
||||
#endif
|
||||
#if LT_HW_UART1
|
||||
case UART_SELECTION_UART1:
|
||||
this->hw_serial_ = &Serial1;
|
||||
Serial1.begin(this->baud_rate_);
|
||||
break;
|
||||
#endif
|
||||
#if LT_HW_UART2
|
||||
case UART_SELECTION_UART2:
|
||||
this->hw_serial_ = &Serial2;
|
||||
Serial2.begin(this->baud_rate_);
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
this->hw_serial_ = &Serial;
|
||||
Serial.begin(this->baud_rate_);
|
||||
if (this->uart_ != UART_SELECTION_DEFAULT) {
|
||||
ESP_LOGW(TAG, " The chosen logger UART port is not available on this board."
|
||||
"The default port was used instead.");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// change lt_log() port to match default Serial
|
||||
if (this->uart_ == UART_SELECTION_DEFAULT) {
|
||||
this->uart_ = (UARTSelection) (LT_UART_DEFAULT_SERIAL + 1);
|
||||
lt_log_set_port(LT_UART_DEFAULT_SERIAL);
|
||||
} else {
|
||||
lt_log_set_port(this->uart_ - 1);
|
||||
}
|
||||
}
|
||||
|
||||
global_logger = this;
|
||||
ESP_LOGI(TAG, "Log initialized");
|
||||
}
|
||||
#endif // USE_LIBRETINY
|
||||
|
||||
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});
|
||||
|
@ -418,38 +142,12 @@ void Logger::add_on_log_callback(std::function<void(int, const char *, const cha
|
|||
}
|
||||
float Logger::get_setup_priority() const { return setup_priority::BUS + 500.0f; }
|
||||
const char *const LOG_LEVELS[] = {"NONE", "ERROR", "WARN", "INFO", "CONFIG", "DEBUG", "VERBOSE", "VERY_VERBOSE"};
|
||||
#ifdef USE_ESP32
|
||||
const char *const UART_SELECTIONS[] = {
|
||||
"UART0", "UART1",
|
||||
#if !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32C6) && \
|
||||
!defined(USE_ESP32_VARIANT_ESP32S2) && !defined(USE_ESP32_VARIANT_ESP32S3) && !defined(USE_ESP32_VARIANT_ESP32H2)
|
||||
"UART2",
|
||||
#endif // !USE_ESP32_VARIANT_ESP32C3 && !USE_ESP32_VARINT_ESP32C6 && !USE_ESP32_VARIANT_ESP32S2 &&
|
||||
// !USE_ESP32_VARIANT_ESP32S3 && !USE_ESP32_VARIANT_ESP32H2
|
||||
#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
|
||||
"USB_CDC",
|
||||
#endif // USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3
|
||||
#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S3)
|
||||
"USB_SERIAL_JTAG",
|
||||
#endif // USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32S3
|
||||
};
|
||||
#endif // USE_ESP32
|
||||
#ifdef USE_ESP8266
|
||||
const char *const UART_SELECTIONS[] = {"UART0", "UART1", "UART0_SWAP"};
|
||||
#endif // USE_ESP8266
|
||||
#ifdef USE_RP2040
|
||||
const char *const UART_SELECTIONS[] = {"UART0", "UART1", "USB_CDC"};
|
||||
#endif // USE_RP2040
|
||||
#ifdef USE_LIBRETINY
|
||||
const char *const UART_SELECTIONS[] = {"DEFAULT", "UART0", "UART1", "UART2"};
|
||||
#endif // USE_LIBRETINY
|
||||
|
||||
void Logger::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "Logger:");
|
||||
ESP_LOGCONFIG(TAG, " Level: %s", LOG_LEVELS[ESPHOME_LOG_LEVEL]);
|
||||
ESP_LOGCONFIG(TAG, " Log Baud Rate: %" PRIu32, this->baud_rate_);
|
||||
#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_LIBRETINY)
|
||||
ESP_LOGCONFIG(TAG, " Hardware UART: %s", UART_SELECTIONS[this->uart_]);
|
||||
#endif
|
||||
ESP_LOGCONFIG(TAG, " Hardware UART: %s", get_uart_selection_());
|
||||
|
||||
for (auto &it : this->log_levels_) {
|
||||
ESP_LOGCONFIG(TAG, " Level for '%s': %s", it.tag.c_str(), LOG_LEVELS[it.level]);
|
||||
|
|
|
@ -34,34 +34,22 @@ enum UARTSelection {
|
|||
#ifdef USE_LIBRETINY
|
||||
UART_SELECTION_DEFAULT = 0,
|
||||
UART_SELECTION_UART0,
|
||||
UART_SELECTION_UART1,
|
||||
UART_SELECTION_UART2,
|
||||
#else
|
||||
UART_SELECTION_UART0 = 0,
|
||||
#endif
|
||||
UART_SELECTION_UART1,
|
||||
#if defined(USE_ESP32)
|
||||
#if !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32C6) && \
|
||||
!defined(USE_ESP32_VARIANT_ESP32S2) && !defined(USE_ESP32_VARIANT_ESP32S3) && !defined(USE_ESP32_VARIANT_ESP32H2)
|
||||
#if defined(USE_LIBRETINY) || defined(USE_ESP32_VARIANT_ESP32)
|
||||
UART_SELECTION_UART2,
|
||||
#endif // !USE_ESP32_VARIANT_ESP32C3 && !USE_ESP32_VARIANT_ESP32C6 && !USE_ESP32_VARIANT_ESP32S2 &&
|
||||
// !USE_ESP32_VARIANT_ESP32S3 && !USE_ESP32_VARIANT_ESP32H2
|
||||
#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) || \
|
||||
(defined(USE_ESP32_VARIANT_ESP32C3) && defined(USE_ARDUINO))
|
||||
#endif
|
||||
#ifdef USE_LOGGER_USB_CDC
|
||||
UART_SELECTION_USB_CDC,
|
||||
#endif // USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3 || USE_ESP32_VARIANT_ESP32C3
|
||||
#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32S3) || \
|
||||
defined(USE_ESP32_VARIANT_ESP32H2)
|
||||
#endif
|
||||
#ifdef USE_LOGGER_USB_SERIAL_JTAG
|
||||
UART_SELECTION_USB_SERIAL_JTAG,
|
||||
#endif // USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32C6 || USE_ESP32_VARIANT_ESP32S3 ||
|
||||
// USE_ESP32_VARIANT_ESP32H2
|
||||
#endif // USE_ESP32
|
||||
#endif
|
||||
#ifdef USE_ESP8266
|
||||
UART_SELECTION_UART0_SWAP,
|
||||
#endif // USE_ESP8266
|
||||
#ifdef USE_RP2040
|
||||
UART_SELECTION_USB_CDC,
|
||||
#endif // USE_RP2040
|
||||
#endif // USE_LIBRETINY
|
||||
};
|
||||
#endif // USE_ESP32 || USE_ESP8266 || USE_RP2040 || USE_LIBRETINY
|
||||
|
||||
|
@ -106,19 +94,10 @@ class Logger : public Component {
|
|||
#endif
|
||||
|
||||
protected:
|
||||
#ifdef USE_ESP_IDF
|
||||
void init_uart_();
|
||||
#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
|
||||
void init_usb_cdc_();
|
||||
#endif
|
||||
#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32S3) || \
|
||||
defined(USE_ESP32_VARIANT_ESP32H2)
|
||||
void init_usb_serial_jtag_();
|
||||
#endif
|
||||
#endif
|
||||
void write_header_(int level, const char *tag, int line);
|
||||
void write_footer_();
|
||||
void log_message_(int level, const char *tag, int offset = 0);
|
||||
void write_msg_(const char *msg);
|
||||
|
||||
inline bool is_buffer_full_() const { return this->tx_buffer_at_ >= this->tx_buffer_size_; }
|
||||
inline int buffer_remaining_capacity_() const { return this->tx_buffer_size_ - this->tx_buffer_at_; }
|
||||
|
@ -158,6 +137,8 @@ class Logger : public Component {
|
|||
va_end(arg);
|
||||
}
|
||||
|
||||
const char *get_uart_selection_();
|
||||
|
||||
uint32_t baud_rate_;
|
||||
char *tx_buffer_{nullptr};
|
||||
int tx_buffer_at_{0};
|
||||
|
|
201
esphome/components/logger/logger_esp32.cpp
Normal file
201
esphome/components/logger/logger_esp32.cpp
Normal file
|
@ -0,0 +1,201 @@
|
|||
#ifdef USE_ESP32
|
||||
#include "logger.h"
|
||||
|
||||
#if defined(USE_ESP32_FRAMEWORK_ARDUINO) || defined(USE_ESP_IDF)
|
||||
#include <esp_log.h>
|
||||
#endif // USE_ESP32_FRAMEWORK_ARDUINO || USE_ESP_IDF
|
||||
|
||||
#ifdef USE_ESP_IDF
|
||||
#include <driver/uart.h>
|
||||
|
||||
#ifdef USE_LOGGER_USB_SERIAL_JTAG
|
||||
#include <driver/usb_serial_jtag.h>
|
||||
#include <esp_vfs_dev.h>
|
||||
#include <esp_vfs_usb_serial_jtag.h>
|
||||
#endif
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "esp_idf_version.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <fcntl.h>
|
||||
|
||||
#endif // USE_ESP_IDF
|
||||
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace logger {
|
||||
|
||||
static const char *const TAG = "logger";
|
||||
|
||||
#ifdef USE_ESP_IDF
|
||||
|
||||
#ifdef USE_LOGGER_USB_SERIAL_JTAG
|
||||
static void init_usb_serial_jtag_() {
|
||||
setvbuf(stdin, NULL, _IONBF, 0); // Disable buffering on stdin
|
||||
|
||||
// Minicom, screen, idf_monitor send CR when ENTER key is pressed
|
||||
esp_vfs_dev_usb_serial_jtag_set_rx_line_endings(ESP_LINE_ENDINGS_CR);
|
||||
// Move the caret to the beginning of the next line on '\n'
|
||||
esp_vfs_dev_usb_serial_jtag_set_tx_line_endings(ESP_LINE_ENDINGS_CRLF);
|
||||
|
||||
// Enable non-blocking mode on stdin and stdout
|
||||
fcntl(fileno(stdout), F_SETFL, 0);
|
||||
fcntl(fileno(stdin), F_SETFL, 0);
|
||||
|
||||
usb_serial_jtag_driver_config_t usb_serial_jtag_config{};
|
||||
usb_serial_jtag_config.rx_buffer_size = 512;
|
||||
usb_serial_jtag_config.tx_buffer_size = 512;
|
||||
|
||||
esp_err_t ret = ESP_OK;
|
||||
// Install USB-SERIAL-JTAG driver for interrupt-driven reads and writes
|
||||
ret = usb_serial_jtag_driver_install(&usb_serial_jtag_config);
|
||||
if (ret != ESP_OK) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Tell vfs to use usb-serial-jtag driver
|
||||
esp_vfs_usb_serial_jtag_use_driver();
|
||||
}
|
||||
#endif
|
||||
|
||||
void init_uart(uart_port_t uart_num, uint32_t baud_rate, int tx_buffer_size) {
|
||||
uart_config_t uart_config{};
|
||||
uart_config.baud_rate = (int) baud_rate;
|
||||
uart_config.data_bits = UART_DATA_8_BITS;
|
||||
uart_config.parity = UART_PARITY_DISABLE;
|
||||
uart_config.stop_bits = UART_STOP_BITS_1;
|
||||
uart_config.flow_ctrl = UART_HW_FLOWCTRL_DISABLE;
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
|
||||
uart_config.source_clk = UART_SCLK_DEFAULT;
|
||||
#endif
|
||||
uart_param_config(uart_num, &uart_config);
|
||||
const int uart_buffer_size = tx_buffer_size;
|
||||
// Install UART driver using an event queue here
|
||||
uart_driver_install(uart_num, uart_buffer_size, uart_buffer_size, 10, nullptr, 0);
|
||||
}
|
||||
|
||||
#endif // USE_ESP_IDF
|
||||
|
||||
void Logger::pre_setup() {
|
||||
if (this->baud_rate_ > 0) {
|
||||
#ifdef USE_ARDUINO
|
||||
switch (this->uart_) {
|
||||
case UART_SELECTION_UART0:
|
||||
#if ARDUINO_USB_CDC_ON_BOOT
|
||||
this->hw_serial_ = &Serial0;
|
||||
Serial0.begin(this->baud_rate_);
|
||||
#else
|
||||
this->hw_serial_ = &Serial;
|
||||
Serial.begin(this->baud_rate_);
|
||||
#endif
|
||||
break;
|
||||
case UART_SELECTION_UART1:
|
||||
this->hw_serial_ = &Serial1;
|
||||
Serial1.begin(this->baud_rate_);
|
||||
break;
|
||||
#ifdef USE_ESP32_VARIANT_ESP32
|
||||
case UART_SELECTION_UART2:
|
||||
this->hw_serial_ = &Serial2;
|
||||
Serial2.begin(this->baud_rate_);
|
||||
break;
|
||||
#endif
|
||||
|
||||
#ifdef USE_LOGGER_USB_CDC
|
||||
case UART_SELECTION_USB_CDC:
|
||||
this->hw_serial_ = &Serial;
|
||||
#if ARDUINO_USB_CDC_ON_BOOT
|
||||
Serial.setTxTimeoutMs(0); // workaround for 2.0.9 crash when there's no data connection
|
||||
#endif
|
||||
Serial.begin(this->baud_rate_);
|
||||
break;
|
||||
#endif
|
||||
}
|
||||
#endif // USE_ARDUINO
|
||||
|
||||
#ifdef USE_ESP_IDF
|
||||
this->uart_num_ = UART_NUM_0;
|
||||
switch (this->uart_) {
|
||||
case UART_SELECTION_UART0:
|
||||
this->uart_num_ = UART_NUM_0;
|
||||
break;
|
||||
case UART_SELECTION_UART1:
|
||||
this->uart_num_ = UART_NUM_1;
|
||||
break;
|
||||
#ifdef USE_ESP32_VARIANT_ESP32
|
||||
case UART_SELECTION_UART2:
|
||||
this->uart_num_ = UART_NUM_2;
|
||||
break;
|
||||
#endif
|
||||
#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
|
||||
case UART_SELECTION_USB_CDC:
|
||||
this->uart_num_ = -1;
|
||||
break;
|
||||
#endif
|
||||
#ifdef USE_LOGGER_USB_SERIAL_JTAG
|
||||
case UART_SELECTION_USB_SERIAL_JTAG:
|
||||
this->uart_num_ = -1;
|
||||
init_usb_serial_jtag_();
|
||||
break;
|
||||
#endif
|
||||
}
|
||||
if (this->uart_num_ >= 0) {
|
||||
init_uart(this->uart_num_, baud_rate_, tx_buffer_size_);
|
||||
}
|
||||
#endif // USE_ESP_IDF
|
||||
}
|
||||
|
||||
global_logger = this;
|
||||
#if defined(USE_ESP_IDF) || defined(USE_ESP32_FRAMEWORK_ARDUINO)
|
||||
esp_log_set_vprintf(esp_idf_log_vprintf_);
|
||||
if (ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE) {
|
||||
esp_log_level_set("*", ESP_LOG_VERBOSE);
|
||||
}
|
||||
#endif // USE_ESP_IDF || USE_ESP32_FRAMEWORK_ARDUINO
|
||||
|
||||
ESP_LOGI(TAG, "Log initialized");
|
||||
}
|
||||
|
||||
#ifdef USE_ESP_IDF
|
||||
void HOT Logger::write_msg_(const char *msg) {
|
||||
if (
|
||||
#if defined(USE_ESP32_VARIANT_ESP32S2)
|
||||
this->uart_ == UART_SELECTION_USB_CDC
|
||||
#elif defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32H2)
|
||||
this->uart_ == UART_SELECTION_USB_SERIAL_JTAG
|
||||
#elif defined(USE_ESP32_VARIANT_ESP32S3)
|
||||
this->uart_ == UART_SELECTION_USB_CDC || this->uart_ == UART_SELECTION_USB_SERIAL_JTAG
|
||||
#else
|
||||
/* DISABLES CODE */ (false) // NOLINT
|
||||
#endif
|
||||
) {
|
||||
puts(msg);
|
||||
} else {
|
||||
uart_write_bytes(this->uart_num_, msg, strlen(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[] = {
|
||||
"UART0", "UART1",
|
||||
#ifdef USE_ESP32_VARIANT_ESP32
|
||||
"UART2",
|
||||
#endif
|
||||
#ifdef USE_LOGGER_USB_CDC
|
||||
"USB_CDC",
|
||||
#endif
|
||||
#ifdef USE_LOGGER_USB_SERIAL_JTAG
|
||||
"USB_SERIAL_JTAG",
|
||||
#endif
|
||||
};
|
||||
|
||||
const char *Logger::get_uart_selection_() { return UART_SELECTIONS[this->uart_]; }
|
||||
|
||||
} // namespace logger
|
||||
} // namespace esphome
|
||||
#endif
|
45
esphome/components/logger/logger_esp8266.cpp
Normal file
45
esphome/components/logger/logger_esp8266.cpp
Normal file
|
@ -0,0 +1,45 @@
|
|||
#ifdef USE_ESP8266
|
||||
#include "logger.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace logger {
|
||||
|
||||
static const char *const TAG = "logger";
|
||||
|
||||
void Logger::pre_setup() {
|
||||
if (this->baud_rate_ > 0) {
|
||||
switch (this->uart_) {
|
||||
case UART_SELECTION_UART0:
|
||||
case UART_SELECTION_UART0_SWAP:
|
||||
this->hw_serial_ = &Serial;
|
||||
Serial.begin(this->baud_rate_);
|
||||
if (this->uart_ == UART_SELECTION_UART0_SWAP) {
|
||||
Serial.swap();
|
||||
}
|
||||
Serial.setDebugOutput(ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE);
|
||||
break;
|
||||
case UART_SELECTION_UART1:
|
||||
this->hw_serial_ = &Serial1;
|
||||
Serial1.begin(this->baud_rate_);
|
||||
Serial1.setDebugOutput(ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
uart_set_debug(UART_NO);
|
||||
}
|
||||
|
||||
global_logger = this;
|
||||
|
||||
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_]; }
|
||||
|
||||
} // namespace logger
|
||||
} // namespace esphome
|
||||
#endif
|
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
|
62
esphome/components/logger/logger_libretiny.cpp
Normal file
62
esphome/components/logger/logger_libretiny.cpp
Normal file
|
@ -0,0 +1,62 @@
|
|||
#ifdef USE_LIBRETINY
|
||||
#include "logger.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace logger {
|
||||
|
||||
static const char *const TAG = "logger";
|
||||
|
||||
void Logger::pre_setup() {
|
||||
if (this->baud_rate_ > 0) {
|
||||
switch (this->uart_) {
|
||||
#if LT_HW_UART0
|
||||
case UART_SELECTION_UART0:
|
||||
this->hw_serial_ = &Serial0;
|
||||
Serial0.begin(this->baud_rate_);
|
||||
break;
|
||||
#endif
|
||||
#if LT_HW_UART1
|
||||
case UART_SELECTION_UART1:
|
||||
this->hw_serial_ = &Serial1;
|
||||
Serial1.begin(this->baud_rate_);
|
||||
break;
|
||||
#endif
|
||||
#if LT_HW_UART2
|
||||
case UART_SELECTION_UART2:
|
||||
this->hw_serial_ = &Serial2;
|
||||
Serial2.begin(this->baud_rate_);
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
this->hw_serial_ = &Serial;
|
||||
Serial.begin(this->baud_rate_);
|
||||
if (this->uart_ != UART_SELECTION_DEFAULT) {
|
||||
ESP_LOGW(TAG, " The chosen logger UART port is not available on this board."
|
||||
"The default port was used instead.");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// change lt_log() port to match default Serial
|
||||
if (this->uart_ == UART_SELECTION_DEFAULT) {
|
||||
this->uart_ = (UARTSelection) (LT_UART_DEFAULT_SERIAL + 1);
|
||||
lt_log_set_port(LT_UART_DEFAULT_SERIAL);
|
||||
} else {
|
||||
lt_log_set_port(this->uart_ - 1);
|
||||
}
|
||||
}
|
||||
|
||||
global_logger = this;
|
||||
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_]; }
|
||||
|
||||
} // namespace logger
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_LIBRETINY
|
39
esphome/components/logger/logger_rp2040.cpp
Normal file
39
esphome/components/logger/logger_rp2040.cpp
Normal file
|
@ -0,0 +1,39 @@
|
|||
#ifdef USE_RP2040
|
||||
#include "logger.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace logger {
|
||||
|
||||
static const char *const TAG = "logger";
|
||||
|
||||
void Logger::pre_setup() {
|
||||
if (this->baud_rate_ > 0) {
|
||||
switch (this->uart_) {
|
||||
case UART_SELECTION_UART0:
|
||||
this->hw_serial_ = &Serial1;
|
||||
Serial1.begin(this->baud_rate_);
|
||||
break;
|
||||
case UART_SELECTION_UART1:
|
||||
this->hw_serial_ = &Serial2;
|
||||
Serial2.begin(this->baud_rate_);
|
||||
break;
|
||||
case UART_SELECTION_USB_CDC:
|
||||
this->hw_serial_ = &Serial;
|
||||
Serial.begin(this->baud_rate_);
|
||||
break;
|
||||
}
|
||||
}
|
||||
global_logger = this;
|
||||
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_]; }
|
||||
|
||||
} // namespace logger
|
||||
} // namespace esphome
|
||||
#endif // USE_RP2040
|
|
@ -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
|
||||
),
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -77,6 +77,13 @@ struct IPAddress {
|
|||
}
|
||||
#endif /* LWIP_IPV6 */
|
||||
IPAddress(esp_ip4_addr_t *other_ip) { memcpy((void *) &ip_addr_, (void *) other_ip, sizeof(esp_ip4_addr_t)); }
|
||||
IPAddress(esp_ip_addr_t *other_ip) {
|
||||
#if LWIP_IPV6
|
||||
memcpy((void *) &ip_addr_, (void *) other_ip, sizeof(ip_addr_));
|
||||
#else
|
||||
memcpy((void *) &ip_addr_, (void *) &other_ip->u_addr.ip4, sizeof(ip_addr_));
|
||||
#endif
|
||||
}
|
||||
operator esp_ip_addr_t() const {
|
||||
esp_ip_addr_t tmp;
|
||||
#if LWIP_IPV6
|
||||
|
@ -128,5 +135,7 @@ struct IPAddress {
|
|||
ip_addr_t ip_addr_;
|
||||
};
|
||||
|
||||
using IPAddresses = std::array<IPAddress, 5>;
|
||||
|
||||
} // namespace network
|
||||
} // namespace esphome
|
||||
|
|
|
@ -37,14 +37,14 @@ bool is_disabled() {
|
|||
return false;
|
||||
}
|
||||
|
||||
network::IPAddress get_ip_address() {
|
||||
network::IPAddresses get_ip_addresses() {
|
||||
#ifdef USE_ETHERNET
|
||||
if (ethernet::global_eth_component != nullptr)
|
||||
return ethernet::global_eth_component->get_ip_address();
|
||||
return ethernet::global_eth_component->get_ip_addresses();
|
||||
#endif
|
||||
#ifdef USE_WIFI
|
||||
if (wifi::global_wifi_component != nullptr)
|
||||
return wifi::global_wifi_component->get_ip_address();
|
||||
return wifi::global_wifi_component->get_ip_addresses();
|
||||
#endif
|
||||
return {};
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ bool is_connected();
|
|||
bool is_disabled();
|
||||
/// Get the active network hostname
|
||||
std::string get_use_address();
|
||||
IPAddress get_ip_address();
|
||||
IPAddresses get_ip_addresses();
|
||||
|
||||
} // namespace network
|
||||
} // namespace esphome
|
||||
|
|
|
@ -12,6 +12,7 @@ from esphome.const import (
|
|||
CONF_PLATFORM,
|
||||
CONF_TRIGGER_ID,
|
||||
CONF_SPEAKER,
|
||||
CONF_GAIN,
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
@ -38,6 +39,7 @@ CONFIG_SCHEMA = cv.All(
|
|||
cv.GenerateID(CONF_ID): cv.declare_id(Rtttl),
|
||||
cv.Optional(CONF_OUTPUT): cv.use_id(FloatOutput),
|
||||
cv.Optional(CONF_SPEAKER): cv.use_id(Speaker),
|
||||
cv.Optional(CONF_GAIN, default="0.6"): cv.percentage,
|
||||
cv.Optional(CONF_ON_FINISHED_PLAYBACK): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
||||
|
@ -98,6 +100,8 @@ async def to_code(config):
|
|||
out = await cg.get_variable(config[CONF_SPEAKER])
|
||||
cg.add(var.set_speaker(out))
|
||||
|
||||
cg.add(var.set_gain(config[CONF_GAIN]))
|
||||
|
||||
for conf in config.get(CONF_ON_FINISHED_PLAYBACK, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [], conf)
|
||||
|
|
|
@ -16,7 +16,7 @@ static const uint16_t NOTES[] = {0, 262, 277, 294, 311, 330, 349, 370,
|
|||
1109, 1175, 1245, 1319, 1397, 1480, 1568, 1661, 1760, 1865, 1976, 2093, 2217,
|
||||
2349, 2489, 2637, 2794, 2960, 3136, 3322, 3520, 3729, 3951};
|
||||
|
||||
static const uint16_t I2S_SPEED = 1600;
|
||||
static const uint16_t I2S_SPEED = 1000;
|
||||
|
||||
#undef HALF_PI
|
||||
static const double HALF_PI = 1.5707963267948966192313216916398;
|
||||
|
@ -136,7 +136,7 @@ void Rtttl::loop() {
|
|||
if (this->samples_per_wave_ != 0 && this->samples_sent_ >= this->samples_gap_) { // Play note//
|
||||
rem = ((this->samples_sent_ << 10) % this->samples_per_wave_) * (360.0 / this->samples_per_wave_);
|
||||
|
||||
int16_t val = 8192 * sin(deg2rad(rem));
|
||||
int16_t val = (49152 * this->gain_) * sin(deg2rad(rem));
|
||||
|
||||
sample[x].left = val;
|
||||
sample[x].right = val;
|
||||
|
@ -269,7 +269,7 @@ void Rtttl::loop() {
|
|||
}
|
||||
if (this->output_freq_ != 0) {
|
||||
this->output_->update_frequency(this->output_freq_);
|
||||
this->output_->set_level(0.5);
|
||||
this->output_->set_level(this->gain_);
|
||||
} else {
|
||||
this->output_->set_level(0.0);
|
||||
}
|
||||
|
@ -278,18 +278,23 @@ void Rtttl::loop() {
|
|||
#ifdef USE_SPEAKER
|
||||
if (this->speaker_ != nullptr) {
|
||||
this->samples_sent_ = 0;
|
||||
this->samples_count_ = (this->sample_rate_ * this->note_duration_) / I2S_SPEED;
|
||||
// Convert from frequency in Hz to high and low samples in fixed point
|
||||
this->samples_gap_ = 0;
|
||||
this->samples_per_wave_ = 0;
|
||||
this->samples_count_ = (this->sample_rate_ * this->note_duration_) / 1600; //(ms);
|
||||
if (need_note_gap) {
|
||||
this->samples_gap_ = (this->sample_rate_ * DOUBLE_NOTE_GAP_MS) / 1600; //(ms);
|
||||
}
|
||||
if (this->output_freq_ != 0) {
|
||||
this->samples_per_wave_ = (this->sample_rate_ << 10) / this->output_freq_;
|
||||
} else {
|
||||
this->samples_per_wave_ = 0;
|
||||
}
|
||||
if (need_note_gap) {
|
||||
this->samples_gap_ = (this->sample_rate_ * DOUBLE_NOTE_GAP_MS) / I2S_SPEED;
|
||||
} else {
|
||||
this->samples_gap_ = 0;
|
||||
|
||||
// make sure there is enough samples to add a full last sinus.
|
||||
uint16_t division = ((this->samples_count_ << 10) / this->samples_per_wave_) + 1;
|
||||
uint16_t x = this->samples_count_;
|
||||
this->samples_count_ = (division * this->samples_per_wave_);
|
||||
ESP_LOGD(TAG, "play time old: %d div: %d new: %d %d", x, division, this->samples_count_, this->samples_per_wave_);
|
||||
this->samples_count_ = this->samples_count_ >> 10;
|
||||
}
|
||||
// Convert from frequency in Hz to high and low samples in fixed point
|
||||
}
|
||||
#endif
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ namespace esphome {
|
|||
namespace rtttl {
|
||||
|
||||
#ifdef USE_SPEAKER
|
||||
static const size_t SAMPLE_BUFFER_SIZE = 256;
|
||||
static const size_t SAMPLE_BUFFER_SIZE = 512;
|
||||
|
||||
struct SpeakerSample {
|
||||
int16_t left{0};
|
||||
|
@ -31,6 +31,13 @@ class Rtttl : public Component {
|
|||
#ifdef USE_SPEAKER
|
||||
void set_speaker(speaker::Speaker *speaker) { this->speaker_ = speaker; }
|
||||
#endif
|
||||
void set_gain(float gain) {
|
||||
if (gain < 0.1f)
|
||||
gain = 0.1f;
|
||||
if (gain > 1.0f)
|
||||
gain = 1.0f;
|
||||
this->gain_ = gain;
|
||||
}
|
||||
void play(std::string rtttl);
|
||||
void stop();
|
||||
void dump_config() override;
|
||||
|
@ -60,6 +67,7 @@ class Rtttl : public Component {
|
|||
uint16_t note_duration_;
|
||||
|
||||
uint32_t output_freq_;
|
||||
float gain_{0.6f};
|
||||
|
||||
#ifdef USE_OUTPUT
|
||||
output::FloatOutput *output_;
|
||||
|
@ -68,13 +76,13 @@ class Rtttl : public Component {
|
|||
void play_output_();
|
||||
|
||||
#ifdef USE_SPEAKER
|
||||
speaker::Speaker *speaker_;
|
||||
void play_speaker_();
|
||||
speaker::Speaker *speaker_{nullptr};
|
||||
int sample_rate_{16000};
|
||||
int samples_per_wave_{0};
|
||||
int samples_sent_{0};
|
||||
int samples_count_{0};
|
||||
int samples_gap_{0};
|
||||
|
||||
#endif
|
||||
|
||||
CallbackManager<void()> on_finished_playback_callback_;
|
||||
|
|
|
@ -252,7 +252,9 @@ ThrottleAverageFilter::ThrottleAverageFilter(uint32_t time_period) : time_period
|
|||
|
||||
optional<float> ThrottleAverageFilter::new_value(float value) {
|
||||
ESP_LOGVV(TAG, "ThrottleAverageFilter(%p)::new_value(value=%f)", this, value);
|
||||
if (!std::isnan(value)) {
|
||||
if (std::isnan(value)) {
|
||||
this->have_nan_ = true;
|
||||
} else {
|
||||
this->sum_ += value;
|
||||
this->n_++;
|
||||
}
|
||||
|
@ -262,12 +264,14 @@ void ThrottleAverageFilter::setup() {
|
|||
this->set_interval("throttle_average", this->time_period_, [this]() {
|
||||
ESP_LOGVV(TAG, "ThrottleAverageFilter(%p)::interval(sum=%f, n=%i)", this, this->sum_, this->n_);
|
||||
if (this->n_ == 0) {
|
||||
this->output(NAN);
|
||||
if (this->have_nan_)
|
||||
this->output(NAN);
|
||||
} else {
|
||||
this->output(this->sum_ / this->n_);
|
||||
this->sum_ = 0.0f;
|
||||
this->n_ = 0;
|
||||
}
|
||||
this->have_nan_ = false;
|
||||
});
|
||||
}
|
||||
float ThrottleAverageFilter::get_setup_priority() const { return setup_priority::HARDWARE; }
|
||||
|
|
|
@ -245,6 +245,7 @@ class ThrottleAverageFilter : public Filter, public Component {
|
|||
uint32_t time_period_;
|
||||
float sum_{0.0f};
|
||||
unsigned int n_{0};
|
||||
bool have_nan_{false};
|
||||
};
|
||||
|
||||
using lambda_filter_t = std::function<optional<float>(float)>;
|
||||
|
|
|
@ -10,15 +10,15 @@ namespace socket {
|
|||
Socket::~Socket() {}
|
||||
|
||||
std::unique_ptr<Socket> socket_ip(int type, int protocol) {
|
||||
#if ENABLE_IPV6
|
||||
#if USE_NETWORK_IPV6
|
||||
return socket(AF_INET6, type, protocol);
|
||||
#else
|
||||
return socket(AF_INET, type, protocol);
|
||||
#endif
|
||||
#endif /* USE_NETWORK_IPV6 */
|
||||
}
|
||||
|
||||
socklen_t set_sockaddr(struct sockaddr *addr, socklen_t addrlen, const std::string &ip_address, uint16_t port) {
|
||||
#if ENABLE_IPV6
|
||||
#if USE_NETWORK_IPV6
|
||||
if (addrlen < sizeof(sockaddr_in6)) {
|
||||
errno = EINVAL;
|
||||
return 0;
|
||||
|
@ -47,11 +47,11 @@ socklen_t set_sockaddr(struct sockaddr *addr, socklen_t addrlen, const std::stri
|
|||
server->sin_addr.s_addr = inet_addr(ip_address.c_str());
|
||||
server->sin_port = htons(port);
|
||||
return sizeof(sockaddr_in);
|
||||
#endif
|
||||
#endif /* USE_NETWORK_IPV6 */
|
||||
}
|
||||
|
||||
socklen_t set_sockaddr_any(struct sockaddr *addr, socklen_t addrlen, uint16_t port) {
|
||||
#if ENABLE_IPV6
|
||||
#if USE_NETWORK_IPV6
|
||||
if (addrlen < sizeof(sockaddr_in6)) {
|
||||
errno = EINVAL;
|
||||
return 0;
|
||||
|
@ -73,7 +73,7 @@ socklen_t set_sockaddr_any(struct sockaddr *addr, socklen_t addrlen, uint16_t po
|
|||
server->sin_addr.s_addr = ESPHOME_INADDR_ANY;
|
||||
server->sin_port = htons(port);
|
||||
return sizeof(sockaddr_in);
|
||||
#endif
|
||||
#endif /* USE_NETWORK_IPV6 */
|
||||
}
|
||||
} // namespace socket
|
||||
} // namespace esphome
|
||||
|
|
|
@ -19,7 +19,7 @@ SpeedFan = speed_ns.class_("SpeedFan", cg.Component, fan.Fan)
|
|||
CONFIG_SCHEMA = fan.FAN_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(SpeedFan),
|
||||
cv.Required(CONF_OUTPUT): cv.use_id(output.FloatOutput),
|
||||
cv.Optional(CONF_OUTPUT): cv.use_id(output.FloatOutput),
|
||||
cv.Optional(CONF_OSCILLATION_OUTPUT): cv.use_id(output.BinaryOutput),
|
||||
cv.Optional(CONF_DIRECTION_OUTPUT): cv.use_id(output.BinaryOutput),
|
||||
cv.Optional(CONF_SPEED): cv.invalid(
|
||||
|
@ -32,11 +32,14 @@ CONFIG_SCHEMA = fan.FAN_SCHEMA.extend(
|
|||
|
||||
|
||||
async def to_code(config):
|
||||
output_ = await cg.get_variable(config[CONF_OUTPUT])
|
||||
var = cg.new_Pvariable(config[CONF_OUTPUT_ID], output_, config[CONF_SPEED_COUNT])
|
||||
var = cg.new_Pvariable(config[CONF_OUTPUT_ID], config[CONF_SPEED_COUNT])
|
||||
await cg.register_component(var, config)
|
||||
await fan.register_fan(var, config)
|
||||
|
||||
if CONF_OUTPUT in config:
|
||||
output_ = await cg.get_variable(config[CONF_OUTPUT])
|
||||
cg.add(var.set_output(output_))
|
||||
|
||||
if CONF_OSCILLATION_OUTPUT in config:
|
||||
oscillation_output = await cg.get_variable(config[CONF_OSCILLATION_OUTPUT])
|
||||
cg.add(var.set_oscillating(oscillation_output))
|
||||
|
|
|
@ -36,9 +36,10 @@ void SpeedFan::control(const fan::FanCall &call) {
|
|||
}
|
||||
|
||||
void SpeedFan::write_state_() {
|
||||
float speed = this->state ? static_cast<float>(this->speed) / static_cast<float>(this->speed_count_) : 0.0f;
|
||||
this->output_->set_level(speed);
|
||||
|
||||
if (this->output_ != nullptr) {
|
||||
float speed = this->state ? static_cast<float>(this->speed) / static_cast<float>(this->speed_count_) : 0.0f;
|
||||
this->output_->set_level(speed);
|
||||
}
|
||||
if (this->oscillating_ != nullptr)
|
||||
this->oscillating_->set_state(this->oscillating);
|
||||
if (this->direction_ != nullptr)
|
||||
|
|
|
@ -12,9 +12,10 @@ namespace speed {
|
|||
|
||||
class SpeedFan : public Component, public fan::Fan {
|
||||
public:
|
||||
SpeedFan(output::FloatOutput *output, int speed_count) : output_(output), speed_count_(speed_count) {}
|
||||
SpeedFan(int speed_count) : speed_count_(speed_count) {}
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
void set_output(output::FloatOutput *output) { this->output_ = output; }
|
||||
void set_oscillating(output::BinaryOutput *oscillating) { this->oscillating_ = oscillating; }
|
||||
void set_direction(output::BinaryOutput *direction) { this->direction_ = direction; }
|
||||
void set_preset_modes(const std::set<std::string> &presets) { this->preset_modes_ = presets; }
|
||||
|
@ -24,7 +25,7 @@ class SpeedFan : public Component, public fan::Fan {
|
|||
void control(const fan::FanCall &call) override;
|
||||
void write_state_();
|
||||
|
||||
output::FloatOutput *output_;
|
||||
output::FloatOutput *output_{nullptr};
|
||||
output::BinaryOutput *oscillating_{nullptr};
|
||||
output::BinaryOutput *direction_{nullptr};
|
||||
int speed_count_{};
|
||||
|
|
|
@ -74,7 +74,8 @@ CONF_FORCE_SW = "force_sw"
|
|||
CONF_INTERFACE = "interface"
|
||||
CONF_INTERFACE_INDEX = "interface_index"
|
||||
|
||||
# RP2040 SPI pin assignments are complicated. Refer to https://datasheets.raspberrypi.com/rp2040/rp2040-datasheet.pdf
|
||||
# RP2040 SPI pin assignments are complicated;
|
||||
# refer to GPIO function select table in https://datasheets.raspberrypi.com/rp2040/rp2040-datasheet.pdf
|
||||
|
||||
RP_SPI_PINSETS = [
|
||||
{
|
||||
|
@ -85,7 +86,7 @@ RP_SPI_PINSETS = [
|
|||
{
|
||||
CONF_MISO_PIN: [8, 12, 24, 28, -1],
|
||||
CONF_CLK_PIN: [10, 14, 26],
|
||||
CONF_MOSI_PIN: [11, 23, 27, -1],
|
||||
CONF_MOSI_PIN: [11, 15, 27, -1],
|
||||
},
|
||||
]
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ import esphome.config_validation as cv
|
|||
from esphome import automation
|
||||
from esphome.components import mqtt
|
||||
from esphome.const import (
|
||||
CONF_DEVICE_CLASS,
|
||||
CONF_ENTITY_CATEGORY,
|
||||
CONF_FILTERS,
|
||||
CONF_ICON,
|
||||
|
@ -14,12 +15,21 @@ from esphome.const import (
|
|||
CONF_STATE,
|
||||
CONF_FROM,
|
||||
CONF_TO,
|
||||
DEVICE_CLASS_DATE,
|
||||
DEVICE_CLASS_EMPTY,
|
||||
DEVICE_CLASS_TIMESTAMP,
|
||||
)
|
||||
from esphome.core import CORE, coroutine_with_priority
|
||||
from esphome.cpp_generator import MockObjClass
|
||||
from esphome.cpp_helpers import setup_entity
|
||||
from esphome.util import Registry
|
||||
|
||||
DEVICE_CLASSES = [
|
||||
DEVICE_CLASS_DATE,
|
||||
DEVICE_CLASS_EMPTY,
|
||||
DEVICE_CLASS_TIMESTAMP,
|
||||
]
|
||||
|
||||
|
||||
IS_PLATFORM_COMPONENT = True
|
||||
|
||||
|
@ -112,10 +122,13 @@ async def map_filter_to_code(config, filter_id):
|
|||
)
|
||||
|
||||
|
||||
validate_device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space="_")
|
||||
|
||||
TEXT_SENSOR_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend(
|
||||
{
|
||||
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTTextSensor),
|
||||
cv.GenerateID(): cv.declare_id(TextSensor),
|
||||
cv.Optional(CONF_DEVICE_CLASS): validate_device_class,
|
||||
cv.Optional(CONF_FILTERS): validate_filters,
|
||||
cv.Optional(CONF_ON_VALUE): automation.validate_automation(
|
||||
{
|
||||
|
@ -140,12 +153,21 @@ def text_sensor_schema(
|
|||
*,
|
||||
icon: str = _UNDEF,
|
||||
entity_category: str = _UNDEF,
|
||||
device_class: str = _UNDEF,
|
||||
) -> cv.Schema:
|
||||
schema = TEXT_SENSOR_SCHEMA
|
||||
if class_ is not _UNDEF:
|
||||
schema = schema.extend({cv.GenerateID(): cv.declare_id(class_)})
|
||||
if icon is not _UNDEF:
|
||||
schema = schema.extend({cv.Optional(CONF_ICON, default=icon): cv.icon})
|
||||
if device_class is not _UNDEF:
|
||||
schema = schema.extend(
|
||||
{
|
||||
cv.Optional(
|
||||
CONF_DEVICE_CLASS, default=device_class
|
||||
): validate_device_class
|
||||
}
|
||||
)
|
||||
if entity_category is not _UNDEF:
|
||||
schema = schema.extend(
|
||||
{
|
||||
|
@ -164,6 +186,9 @@ async def build_filters(config):
|
|||
async def setup_text_sensor_core_(var, config):
|
||||
await setup_entity(var, config)
|
||||
|
||||
if CONF_DEVICE_CLASS in config:
|
||||
cg.add(var.set_device_class(config[CONF_DEVICE_CLASS]))
|
||||
|
||||
if config.get(CONF_FILTERS): # must exist and not be empty
|
||||
filters = await build_filters(config[CONF_FILTERS])
|
||||
cg.add(var.set_filters(filters))
|
||||
|
|
|
@ -13,6 +13,9 @@ namespace text_sensor {
|
|||
#define LOG_TEXT_SENSOR(prefix, type, obj) \
|
||||
if ((obj) != nullptr) { \
|
||||
ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(type), (obj)->get_name().c_str()); \
|
||||
if (!(obj)->get_device_class().empty()) { \
|
||||
ESP_LOGCONFIG(TAG, "%s Device Class: '%s'", prefix, (obj)->get_device_class().c_str()); \
|
||||
} \
|
||||
if (!(obj)->get_icon().empty()) { \
|
||||
ESP_LOGCONFIG(TAG, "%s Icon: '%s'", prefix, (obj)->get_icon().c_str()); \
|
||||
} \
|
||||
|
@ -28,7 +31,7 @@ namespace text_sensor {
|
|||
public: \
|
||||
void set_##name##_text_sensor(text_sensor::TextSensor *text_sensor) { this->name##_text_sensor_ = text_sensor; }
|
||||
|
||||
class TextSensor : public EntityBase {
|
||||
class TextSensor : public EntityBase, public EntityBase_DeviceClass {
|
||||
public:
|
||||
/// Getter-syntax for .state.
|
||||
std::string get_state() const;
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
#include "thermostat_climate.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include <cinttypes>
|
||||
|
||||
namespace esphome {
|
||||
namespace thermostat {
|
||||
|
@ -63,6 +62,15 @@ void ThermostatClimate::setup() {
|
|||
this->publish_state();
|
||||
}
|
||||
|
||||
void ThermostatClimate::loop() {
|
||||
for (auto &timer : this->timer_) {
|
||||
if (timer.active && (timer.started + timer.time < millis())) {
|
||||
timer.active = false;
|
||||
timer.func();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
float ThermostatClimate::cool_deadband() { return this->cooling_deadband_; }
|
||||
float ThermostatClimate::cool_overrun() { return this->cooling_overrun_; }
|
||||
float ThermostatClimate::heat_deadband() { return this->heating_deadband_; }
|
||||
|
@ -439,6 +447,7 @@ void ThermostatClimate::switch_to_action_(climate::ClimateAction action, bool pu
|
|||
this->start_timer_(thermostat::TIMER_FANNING_ON);
|
||||
trig_fan = this->fan_only_action_trigger_;
|
||||
}
|
||||
this->cooling_max_runtime_exceeded_ = false;
|
||||
trig = this->cool_action_trigger_;
|
||||
ESP_LOGVV(TAG, "Switching to COOLING action");
|
||||
action_ready = true;
|
||||
|
@ -452,6 +461,7 @@ void ThermostatClimate::switch_to_action_(climate::ClimateAction action, bool pu
|
|||
this->start_timer_(thermostat::TIMER_FANNING_ON);
|
||||
trig_fan = this->fan_only_action_trigger_;
|
||||
}
|
||||
this->heating_max_runtime_exceeded_ = false;
|
||||
trig = this->heat_action_trigger_;
|
||||
ESP_LOGVV(TAG, "Switching to HEATING action");
|
||||
action_ready = true;
|
||||
|
@ -752,15 +762,15 @@ bool ThermostatClimate::heating_action_ready_() {
|
|||
|
||||
void ThermostatClimate::start_timer_(const ThermostatClimateTimerIndex timer_index) {
|
||||
if (this->timer_duration_(timer_index) > 0) {
|
||||
this->set_timeout(this->timer_[timer_index].name, this->timer_duration_(timer_index),
|
||||
this->timer_cbf_(timer_index));
|
||||
this->timer_[timer_index].started = millis();
|
||||
this->timer_[timer_index].active = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool ThermostatClimate::cancel_timer_(ThermostatClimateTimerIndex timer_index) {
|
||||
auto ret = this->timer_[timer_index].active;
|
||||
this->timer_[timer_index].active = false;
|
||||
return this->cancel_timeout(this->timer_[timer_index].name);
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool ThermostatClimate::timer_active_(ThermostatClimateTimerIndex timer_index) {
|
||||
|
@ -777,7 +787,6 @@ std::function<void()> ThermostatClimate::timer_cbf_(ThermostatClimateTimerIndex
|
|||
|
||||
void ThermostatClimate::cooling_max_run_time_timer_callback_() {
|
||||
ESP_LOGVV(TAG, "cooling_max_run_time timer expired");
|
||||
this->timer_[thermostat::TIMER_COOLING_MAX_RUN_TIME].active = false;
|
||||
this->cooling_max_runtime_exceeded_ = true;
|
||||
this->trigger_supplemental_action_();
|
||||
this->switch_to_supplemental_action_(this->compute_supplemental_action_());
|
||||
|
@ -785,21 +794,18 @@ void ThermostatClimate::cooling_max_run_time_timer_callback_() {
|
|||
|
||||
void ThermostatClimate::cooling_off_timer_callback_() {
|
||||
ESP_LOGVV(TAG, "cooling_off timer expired");
|
||||
this->timer_[thermostat::TIMER_COOLING_OFF].active = false;
|
||||
this->switch_to_action_(this->compute_action_());
|
||||
this->switch_to_supplemental_action_(this->compute_supplemental_action_());
|
||||
}
|
||||
|
||||
void ThermostatClimate::cooling_on_timer_callback_() {
|
||||
ESP_LOGVV(TAG, "cooling_on timer expired");
|
||||
this->timer_[thermostat::TIMER_COOLING_ON].active = false;
|
||||
this->switch_to_action_(this->compute_action_());
|
||||
this->switch_to_supplemental_action_(this->compute_supplemental_action_());
|
||||
}
|
||||
|
||||
void ThermostatClimate::fan_mode_timer_callback_() {
|
||||
ESP_LOGVV(TAG, "fan_mode timer expired");
|
||||
this->timer_[thermostat::TIMER_FAN_MODE].active = false;
|
||||
this->switch_to_fan_mode_(this->fan_mode.value_or(climate::CLIMATE_FAN_ON));
|
||||
if (this->supports_fan_only_action_uses_fan_mode_timer_)
|
||||
this->switch_to_action_(this->compute_action_());
|
||||
|
@ -807,19 +813,16 @@ void ThermostatClimate::fan_mode_timer_callback_() {
|
|||
|
||||
void ThermostatClimate::fanning_off_timer_callback_() {
|
||||
ESP_LOGVV(TAG, "fanning_off timer expired");
|
||||
this->timer_[thermostat::TIMER_FANNING_OFF].active = false;
|
||||
this->switch_to_action_(this->compute_action_());
|
||||
}
|
||||
|
||||
void ThermostatClimate::fanning_on_timer_callback_() {
|
||||
ESP_LOGVV(TAG, "fanning_on timer expired");
|
||||
this->timer_[thermostat::TIMER_FANNING_ON].active = false;
|
||||
this->switch_to_action_(this->compute_action_());
|
||||
}
|
||||
|
||||
void ThermostatClimate::heating_max_run_time_timer_callback_() {
|
||||
ESP_LOGVV(TAG, "heating_max_run_time timer expired");
|
||||
this->timer_[thermostat::TIMER_HEATING_MAX_RUN_TIME].active = false;
|
||||
this->heating_max_runtime_exceeded_ = true;
|
||||
this->trigger_supplemental_action_();
|
||||
this->switch_to_supplemental_action_(this->compute_supplemental_action_());
|
||||
|
@ -827,21 +830,18 @@ void ThermostatClimate::heating_max_run_time_timer_callback_() {
|
|||
|
||||
void ThermostatClimate::heating_off_timer_callback_() {
|
||||
ESP_LOGVV(TAG, "heating_off timer expired");
|
||||
this->timer_[thermostat::TIMER_HEATING_OFF].active = false;
|
||||
this->switch_to_action_(this->compute_action_());
|
||||
this->switch_to_supplemental_action_(this->compute_supplemental_action_());
|
||||
}
|
||||
|
||||
void ThermostatClimate::heating_on_timer_callback_() {
|
||||
ESP_LOGVV(TAG, "heating_on timer expired");
|
||||
this->timer_[thermostat::TIMER_HEATING_ON].active = false;
|
||||
this->switch_to_action_(this->compute_action_());
|
||||
this->switch_to_supplemental_action_(this->compute_supplemental_action_());
|
||||
}
|
||||
|
||||
void ThermostatClimate::idle_on_timer_callback_() {
|
||||
ESP_LOGVV(TAG, "idle_on timer expired");
|
||||
this->timer_[thermostat::TIMER_IDLE_ON].active = false;
|
||||
this->switch_to_action_(this->compute_action_());
|
||||
this->switch_to_supplemental_action_(this->compute_supplemental_action_());
|
||||
}
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/components/climate/climate.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
|
||||
#include <cinttypes>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
|
@ -26,9 +28,9 @@ enum ThermostatClimateTimerIndex : size_t {
|
|||
|
||||
enum OnBootRestoreFrom : size_t { MEMORY = 0, DEFAULT_PRESET = 1 };
|
||||
struct ThermostatClimateTimer {
|
||||
const std::string name;
|
||||
bool active;
|
||||
uint32_t time;
|
||||
uint32_t started;
|
||||
std::function<void()> func;
|
||||
};
|
||||
|
||||
|
@ -59,6 +61,7 @@ class ThermostatClimate : public climate::Climate, public Component {
|
|||
ThermostatClimate();
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
void loop() override;
|
||||
|
||||
void set_default_preset(const std::string &custom_preset);
|
||||
void set_default_preset(climate::ClimatePreset preset);
|
||||
|
@ -441,16 +444,17 @@ class ThermostatClimate : public climate::Climate, public Component {
|
|||
|
||||
/// Climate action timers
|
||||
std::vector<ThermostatClimateTimer> timer_{
|
||||
{"cool_run", false, 0, std::bind(&ThermostatClimate::cooling_max_run_time_timer_callback_, this)},
|
||||
{"cool_off", false, 0, std::bind(&ThermostatClimate::cooling_off_timer_callback_, this)},
|
||||
{"cool_on", false, 0, std::bind(&ThermostatClimate::cooling_on_timer_callback_, this)},
|
||||
{"fan_mode", false, 0, std::bind(&ThermostatClimate::fan_mode_timer_callback_, this)},
|
||||
{"fan_off", false, 0, std::bind(&ThermostatClimate::fanning_off_timer_callback_, this)},
|
||||
{"fan_on", false, 0, std::bind(&ThermostatClimate::fanning_on_timer_callback_, this)},
|
||||
{"heat_run", false, 0, std::bind(&ThermostatClimate::heating_max_run_time_timer_callback_, this)},
|
||||
{"heat_off", false, 0, std::bind(&ThermostatClimate::heating_off_timer_callback_, this)},
|
||||
{"heat_on", false, 0, std::bind(&ThermostatClimate::heating_on_timer_callback_, this)},
|
||||
{"idle_on", false, 0, std::bind(&ThermostatClimate::idle_on_timer_callback_, this)}};
|
||||
{false, 0, 0, std::bind(&ThermostatClimate::cooling_max_run_time_timer_callback_, this)},
|
||||
{false, 0, 0, std::bind(&ThermostatClimate::cooling_off_timer_callback_, this)},
|
||||
{false, 0, 0, std::bind(&ThermostatClimate::cooling_on_timer_callback_, this)},
|
||||
{false, 0, 0, std::bind(&ThermostatClimate::fan_mode_timer_callback_, this)},
|
||||
{false, 0, 0, std::bind(&ThermostatClimate::fanning_off_timer_callback_, this)},
|
||||
{false, 0, 0, std::bind(&ThermostatClimate::fanning_on_timer_callback_, this)},
|
||||
{false, 0, 0, std::bind(&ThermostatClimate::heating_max_run_time_timer_callback_, this)},
|
||||
{false, 0, 0, std::bind(&ThermostatClimate::heating_off_timer_callback_, this)},
|
||||
{false, 0, 0, std::bind(&ThermostatClimate::heating_on_timer_callback_, this)},
|
||||
{false, 0, 0, std::bind(&ThermostatClimate::idle_on_timer_callback_, this)},
|
||||
};
|
||||
|
||||
/// The set of standard preset configurations this thermostat supports (Eg. AWAY, ECO, etc)
|
||||
std::map<climate::ClimatePreset, ThermostatClimateTargetTempConfig> preset_config_{};
|
||||
|
|
|
@ -13,6 +13,7 @@ from esphome.const import (
|
|||
CODEOWNERS = ["@freekode"]
|
||||
|
||||
tm1651_ns = cg.esphome_ns.namespace("tm1651")
|
||||
TM1651Brightness = tm1651_ns.enum("TM1651Brightness")
|
||||
TM1651Display = tm1651_ns.class_("TM1651Display", cg.Component)
|
||||
|
||||
SetLevelPercentAction = tm1651_ns.class_("SetLevelPercentAction", automation.Action)
|
||||
|
@ -24,9 +25,9 @@ TurnOffAction = tm1651_ns.class_("SetLevelPercentAction", automation.Action)
|
|||
CONF_LEVEL_PERCENT = "level_percent"
|
||||
|
||||
TM1651_BRIGHTNESS_OPTIONS = {
|
||||
1: TM1651Display.TM1651_BRIGHTNESS_LOW,
|
||||
2: TM1651Display.TM1651_BRIGHTNESS_MEDIUM,
|
||||
3: TM1651Display.TM1651_BRIGHTNESS_HIGH,
|
||||
1: TM1651Brightness.TM1651_BRIGHTNESS_LOW,
|
||||
2: TM1651Brightness.TM1651_BRIGHTNESS_MEDIUM,
|
||||
3: TM1651Brightness.TM1651_BRIGHTNESS_HIGH,
|
||||
}
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
|
|
|
@ -12,9 +12,9 @@ static const char *const TAG = "tm1651.display";
|
|||
static const uint8_t MAX_INPUT_LEVEL_PERCENT = 100;
|
||||
static const uint8_t TM1651_MAX_LEVEL = 7;
|
||||
|
||||
static const uint8_t TM1651_BRIGHTNESS_LOW = 0;
|
||||
static const uint8_t TM1651_BRIGHTNESS_MEDIUM = 2;
|
||||
static const uint8_t TM1651_BRIGHTNESS_HIGH = 7;
|
||||
static const uint8_t TM1651_BRIGHTNESS_LOW_HW = 0;
|
||||
static const uint8_t TM1651_BRIGHTNESS_MEDIUM_HW = 2;
|
||||
static const uint8_t TM1651_BRIGHTNESS_HIGH_HW = 7;
|
||||
|
||||
void TM1651Display::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up TM1651...");
|
||||
|
@ -78,14 +78,14 @@ uint8_t TM1651Display::calculate_level_(uint8_t new_level) {
|
|||
|
||||
uint8_t TM1651Display::calculate_brightness_(uint8_t new_brightness) {
|
||||
if (new_brightness <= 1) {
|
||||
return TM1651_BRIGHTNESS_LOW;
|
||||
return TM1651_BRIGHTNESS_LOW_HW;
|
||||
} else if (new_brightness == 2) {
|
||||
return TM1651_BRIGHTNESS_MEDIUM;
|
||||
return TM1651_BRIGHTNESS_MEDIUM_HW;
|
||||
} else if (new_brightness >= 3) {
|
||||
return TM1651_BRIGHTNESS_HIGH;
|
||||
return TM1651_BRIGHTNESS_HIGH_HW;
|
||||
}
|
||||
|
||||
return TM1651_BRIGHTNESS_LOW;
|
||||
return TM1651_BRIGHTNESS_LOW_HW;
|
||||
}
|
||||
|
||||
} // namespace tm1651
|
||||
|
|
|
@ -13,6 +13,12 @@
|
|||
namespace esphome {
|
||||
namespace tm1651 {
|
||||
|
||||
enum TM1651Brightness : uint8_t {
|
||||
TM1651_BRIGHTNESS_LOW = 1,
|
||||
TM1651_BRIGHTNESS_MEDIUM = 2,
|
||||
TM1651_BRIGHTNESS_HIGH = 3,
|
||||
};
|
||||
|
||||
class TM1651Display : public Component {
|
||||
public:
|
||||
void set_clk_pin(InternalGPIOPin *pin) { clk_pin_ = pin; }
|
||||
|
@ -24,6 +30,7 @@ class TM1651Display : public Component {
|
|||
void set_level_percent(uint8_t new_level);
|
||||
void set_level(uint8_t new_level);
|
||||
void set_brightness(uint8_t new_brightness);
|
||||
void set_brightness(TM1651Brightness new_brightness) { this->set_brightness(static_cast<uint8_t>(new_brightness)); }
|
||||
|
||||
void turn_on();
|
||||
void turn_off();
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue