mirror of
https://github.com/esphome/esphome.git
synced 2025-09-04 04:12:23 +01:00
Merge branch 'redudant_checks_bluetooth_proxy' into integration
This commit is contained in:
2
.github/workflows/auto-label-pr.yml
vendored
2
.github/workflows/auto-label-pr.yml
vendored
@@ -22,7 +22,7 @@ jobs:
|
||||
if: github.event.action != 'labeled' || github.event.sender.type != 'Bot'
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4.2.2
|
||||
uses: actions/checkout@v5.0.0
|
||||
|
||||
- name: Generate a token
|
||||
id: generate-token
|
||||
|
2
.github/workflows/ci-api-proto.yml
vendored
2
.github/workflows/ci-api-proto.yml
vendored
@@ -21,7 +21,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4.2.2
|
||||
uses: actions/checkout@v5.0.0
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5.6.0
|
||||
with:
|
||||
|
2
.github/workflows/ci-clang-tidy-hash.yml
vendored
2
.github/workflows/ci-clang-tidy-hash.yml
vendored
@@ -20,7 +20,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4.2.2
|
||||
uses: actions/checkout@v5.0.0
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5.6.0
|
||||
|
2
.github/workflows/ci-docker.yml
vendored
2
.github/workflows/ci-docker.yml
vendored
@@ -43,7 +43,7 @@ jobs:
|
||||
- "docker"
|
||||
# - "lint"
|
||||
steps:
|
||||
- uses: actions/checkout@v4.2.2
|
||||
- uses: actions/checkout@v5.0.0
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5.6.0
|
||||
with:
|
||||
|
22
.github/workflows/ci.yml
vendored
22
.github/workflows/ci.yml
vendored
@@ -36,7 +36,7 @@ jobs:
|
||||
cache-key: ${{ steps.cache-key.outputs.key }}
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.2.2
|
||||
uses: actions/checkout@v5.0.0
|
||||
- name: Generate cache-key
|
||||
id: cache-key
|
||||
run: echo key="${{ hashFiles('requirements.txt', 'requirements_test.txt', '.pre-commit-config.yaml') }}" >> $GITHUB_OUTPUT
|
||||
@@ -70,7 +70,7 @@ jobs:
|
||||
if: needs.determine-jobs.outputs.python-linters == 'true'
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.2.2
|
||||
uses: actions/checkout@v5.0.0
|
||||
- name: Restore Python
|
||||
uses: ./.github/actions/restore-python
|
||||
with:
|
||||
@@ -91,7 +91,7 @@ jobs:
|
||||
- common
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.2.2
|
||||
uses: actions/checkout@v5.0.0
|
||||
- name: Restore Python
|
||||
uses: ./.github/actions/restore-python
|
||||
with:
|
||||
@@ -136,7 +136,7 @@ jobs:
|
||||
- common
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.2.2
|
||||
uses: actions/checkout@v5.0.0
|
||||
- name: Restore Python
|
||||
id: restore-python
|
||||
uses: ./.github/actions/restore-python
|
||||
@@ -179,7 +179,7 @@ jobs:
|
||||
component-test-count: ${{ steps.determine.outputs.component-test-count }}
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.2.2
|
||||
uses: actions/checkout@v5.0.0
|
||||
with:
|
||||
# Fetch enough history to find the merge base
|
||||
fetch-depth: 2
|
||||
@@ -214,7 +214,7 @@ jobs:
|
||||
if: needs.determine-jobs.outputs.integration-tests == 'true'
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.2.2
|
||||
uses: actions/checkout@v5.0.0
|
||||
- name: Set up Python 3.13
|
||||
id: python
|
||||
uses: actions/setup-python@v5.6.0
|
||||
@@ -287,7 +287,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.2.2
|
||||
uses: actions/checkout@v5.0.0
|
||||
with:
|
||||
# Need history for HEAD~1 to work for checking changed files
|
||||
fetch-depth: 2
|
||||
@@ -374,7 +374,7 @@ jobs:
|
||||
sudo apt-get install libsdl2-dev
|
||||
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.2.2
|
||||
uses: actions/checkout@v5.0.0
|
||||
- name: Restore Python
|
||||
uses: ./.github/actions/restore-python
|
||||
with:
|
||||
@@ -400,7 +400,7 @@ jobs:
|
||||
matrix: ${{ steps.split.outputs.components }}
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.2.2
|
||||
uses: actions/checkout@v5.0.0
|
||||
- name: Split components into 20 groups
|
||||
id: split
|
||||
run: |
|
||||
@@ -430,7 +430,7 @@ jobs:
|
||||
sudo apt-get install libsdl2-dev
|
||||
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.2.2
|
||||
uses: actions/checkout@v5.0.0
|
||||
- name: Restore Python
|
||||
uses: ./.github/actions/restore-python
|
||||
with:
|
||||
@@ -459,7 +459,7 @@ jobs:
|
||||
if: github.event_name == 'pull_request' && github.base_ref != 'beta' && github.base_ref != 'release'
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.2.2
|
||||
uses: actions/checkout@v5.0.0
|
||||
- name: Restore Python
|
||||
uses: ./.github/actions/restore-python
|
||||
with:
|
||||
|
2
.github/workflows/codeql.yml
vendored
2
.github/workflows/codeql.yml
vendored
@@ -54,7 +54,7 @@ jobs:
|
||||
# your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5.0.0
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
|
8
.github/workflows/release.yml
vendored
8
.github/workflows/release.yml
vendored
@@ -20,7 +20,7 @@ jobs:
|
||||
branch_build: ${{ steps.tag.outputs.branch_build }}
|
||||
deploy_env: ${{ steps.tag.outputs.deploy_env }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4.2.2
|
||||
- uses: actions/checkout@v5.0.0
|
||||
- name: Get tag
|
||||
id: tag
|
||||
# yamllint disable rule:line-length
|
||||
@@ -60,7 +60,7 @@ jobs:
|
||||
contents: read
|
||||
id-token: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4.2.2
|
||||
- uses: actions/checkout@v5.0.0
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5.6.0
|
||||
with:
|
||||
@@ -92,7 +92,7 @@ jobs:
|
||||
os: "ubuntu-24.04-arm"
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4.2.2
|
||||
- uses: actions/checkout@v5.0.0
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5.6.0
|
||||
with:
|
||||
@@ -168,7 +168,7 @@ jobs:
|
||||
- ghcr
|
||||
- dockerhub
|
||||
steps:
|
||||
- uses: actions/checkout@v4.2.2
|
||||
- uses: actions/checkout@v5.0.0
|
||||
|
||||
- name: Download digests
|
||||
uses: actions/download-artifact@v5.0.0
|
||||
|
4
.github/workflows/sync-device-classes.yml
vendored
4
.github/workflows/sync-device-classes.yml
vendored
@@ -13,10 +13,10 @@ jobs:
|
||||
if: github.repository == 'esphome/esphome'
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4.2.2
|
||||
uses: actions/checkout@v5.0.0
|
||||
|
||||
- name: Checkout Home Assistant
|
||||
uses: actions/checkout@v4.2.2
|
||||
uses: actions/checkout@v5.0.0
|
||||
with:
|
||||
repository: home-assistant/core
|
||||
path: lib/home-assistant
|
||||
|
@@ -246,6 +246,7 @@ esphome/components/kuntze/* @ssieb
|
||||
esphome/components/lc709203f/* @ilikecake
|
||||
esphome/components/lcd_menu/* @numo68
|
||||
esphome/components/ld2410/* @regevbr @sebcaps
|
||||
esphome/components/ld2412/* @Rihan9
|
||||
esphome/components/ld2420/* @descipher
|
||||
esphome/components/ld2450/* @hareeshmu
|
||||
esphome/components/ld24xx/* @kbx81
|
||||
|
@@ -7,6 +7,7 @@ from esphome.const import (
|
||||
CONF_DIRECTION,
|
||||
CONF_HYSTERESIS,
|
||||
CONF_ID,
|
||||
CONF_POWER_MODE,
|
||||
CONF_RANGE,
|
||||
)
|
||||
|
||||
@@ -57,7 +58,6 @@ FAST_FILTER = {
|
||||
CONF_RAW_ANGLE = "raw_angle"
|
||||
CONF_RAW_POSITION = "raw_position"
|
||||
CONF_WATCHDOG = "watchdog"
|
||||
CONF_POWER_MODE = "power_mode"
|
||||
CONF_SLOW_FILTER = "slow_filter"
|
||||
CONF_FAST_FILTER = "fast_filter"
|
||||
CONF_START_POSITION = "start_position"
|
||||
|
@@ -24,7 +24,6 @@ AS5600Sensor = as5600_ns.class_("AS5600Sensor", sensor.Sensor, cg.PollingCompone
|
||||
CONF_RAW_ANGLE = "raw_angle"
|
||||
CONF_RAW_POSITION = "raw_position"
|
||||
CONF_WATCHDOG = "watchdog"
|
||||
CONF_POWER_MODE = "power_mode"
|
||||
CONF_SLOW_FILTER = "slow_filter"
|
||||
CONF_FAST_FILTER = "fast_filter"
|
||||
CONF_PWM_FREQUENCY = "pwm_frequency"
|
||||
|
@@ -110,6 +110,8 @@ void ATM90E32Component::update() {
|
||||
|
||||
void ATM90E32Component::setup() {
|
||||
this->spi_setup();
|
||||
this->cs_summary_ = this->cs_->dump_summary();
|
||||
const char *cs = this->cs_summary_.c_str();
|
||||
|
||||
uint16_t mmode0 = 0x87; // 3P4W 50Hz
|
||||
uint16_t high_thresh = 0;
|
||||
@@ -130,9 +132,9 @@ void ATM90E32Component::setup() {
|
||||
mmode0 |= 0 << 1; // sets 1st bit to 0, phase b is not counted into the all-phase sum energy/power (P/Q/S)
|
||||
}
|
||||
|
||||
this->write16_(ATM90E32_REGISTER_SOFTRESET, 0x789A); // Perform soft reset
|
||||
delay(6); // Wait for the minimum 5ms + 1ms
|
||||
this->write16_(ATM90E32_REGISTER_CFGREGACCEN, 0x55AA); // enable register config access
|
||||
this->write16_(ATM90E32_REGISTER_SOFTRESET, 0x789A, false); // Perform soft reset
|
||||
delay(6); // Wait for the minimum 5ms + 1ms
|
||||
this->write16_(ATM90E32_REGISTER_CFGREGACCEN, 0x55AA); // enable register config access
|
||||
if (!this->validate_spi_read_(0x55AA, "setup()")) {
|
||||
ESP_LOGW(TAG, "Could not initialize ATM90E32 IC, check SPI settings");
|
||||
this->mark_failed();
|
||||
@@ -156,16 +158,17 @@ void ATM90E32Component::setup() {
|
||||
|
||||
if (this->enable_offset_calibration_) {
|
||||
// Initialize flash storage for offset calibrations
|
||||
uint32_t o_hash = fnv1_hash(std::string("_offset_calibration_") + this->cs_->dump_summary());
|
||||
uint32_t o_hash = fnv1_hash(std::string("_offset_calibration_") + this->cs_summary_);
|
||||
this->offset_pref_ = global_preferences->make_preference<OffsetCalibration[3]>(o_hash, true);
|
||||
this->restore_offset_calibrations_();
|
||||
|
||||
// Initialize flash storage for power offset calibrations
|
||||
uint32_t po_hash = fnv1_hash(std::string("_power_offset_calibration_") + this->cs_->dump_summary());
|
||||
uint32_t po_hash = fnv1_hash(std::string("_power_offset_calibration_") + this->cs_summary_);
|
||||
this->power_offset_pref_ = global_preferences->make_preference<PowerOffsetCalibration[3]>(po_hash, true);
|
||||
this->restore_power_offset_calibrations_();
|
||||
} else {
|
||||
ESP_LOGI(TAG, "[CALIBRATION] Power & Voltage/Current offset calibration is disabled. Using config file values.");
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] Power & Voltage/Current offset calibration is disabled. Using config file values.",
|
||||
cs);
|
||||
for (uint8_t phase = 0; phase < 3; ++phase) {
|
||||
this->write16_(this->voltage_offset_registers[phase],
|
||||
static_cast<uint16_t>(this->offset_phase_[phase].voltage_offset_));
|
||||
@@ -180,21 +183,18 @@ void ATM90E32Component::setup() {
|
||||
|
||||
if (this->enable_gain_calibration_) {
|
||||
// Initialize flash storage for gain calibration
|
||||
uint32_t g_hash = fnv1_hash(std::string("_gain_calibration_") + this->cs_->dump_summary());
|
||||
uint32_t g_hash = fnv1_hash(std::string("_gain_calibration_") + this->cs_summary_);
|
||||
this->gain_calibration_pref_ = global_preferences->make_preference<GainCalibration[3]>(g_hash, true);
|
||||
this->restore_gain_calibrations_();
|
||||
|
||||
if (this->using_saved_calibrations_) {
|
||||
ESP_LOGI(TAG, "[CALIBRATION] Successfully restored gain calibration from memory.");
|
||||
} else {
|
||||
if (!this->using_saved_calibrations_) {
|
||||
for (uint8_t phase = 0; phase < 3; ++phase) {
|
||||
this->write16_(voltage_gain_registers[phase], this->phase_[phase].voltage_gain_);
|
||||
this->write16_(current_gain_registers[phase], this->phase_[phase].ct_gain_);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ESP_LOGI(TAG, "[CALIBRATION] Gain calibration is disabled. Using config file values.");
|
||||
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] Gain calibration is disabled. Using config file values.", cs);
|
||||
for (uint8_t phase = 0; phase < 3; ++phase) {
|
||||
this->write16_(voltage_gain_registers[phase], this->phase_[phase].voltage_gain_);
|
||||
this->write16_(current_gain_registers[phase], this->phase_[phase].ct_gain_);
|
||||
@@ -213,6 +213,122 @@ void ATM90E32Component::setup() {
|
||||
this->write16_(ATM90E32_REGISTER_CFGREGACCEN, 0x0000); // end configuration
|
||||
}
|
||||
|
||||
void ATM90E32Component::log_calibration_status_() {
|
||||
const char *cs = this->cs_summary_.c_str();
|
||||
|
||||
bool offset_mismatch = false;
|
||||
bool power_mismatch = false;
|
||||
bool gain_mismatch = false;
|
||||
|
||||
for (uint8_t phase = 0; phase < 3; ++phase) {
|
||||
offset_mismatch |= this->offset_calibration_mismatch_[phase];
|
||||
power_mismatch |= this->power_offset_calibration_mismatch_[phase];
|
||||
gain_mismatch |= this->gain_calibration_mismatch_[phase];
|
||||
}
|
||||
|
||||
if (offset_mismatch) {
|
||||
ESP_LOGW(TAG, "[CALIBRATION][%s] ", cs);
|
||||
ESP_LOGW(TAG,
|
||||
"[CALIBRATION][%s] ===================== Offset mismatch: using flash values =====================", cs);
|
||||
ESP_LOGW(TAG, "[CALIBRATION][%s] ------------------------------------------------------------------------------",
|
||||
cs);
|
||||
ESP_LOGW(TAG, "[CALIBRATION][%s] | Phase | offset_voltage | offset_current |", cs);
|
||||
ESP_LOGW(TAG, "[CALIBRATION][%s] | | config | flash | config | flash |", cs);
|
||||
ESP_LOGW(TAG, "[CALIBRATION][%s] ------------------------------------------------------------------------------",
|
||||
cs);
|
||||
for (uint8_t phase = 0; phase < 3; ++phase) {
|
||||
ESP_LOGW(TAG, "[CALIBRATION][%s] | %c | %6d | %6d | %6d | %6d |", cs, 'A' + phase,
|
||||
this->config_offset_phase_[phase].voltage_offset_, this->offset_phase_[phase].voltage_offset_,
|
||||
this->config_offset_phase_[phase].current_offset_, this->offset_phase_[phase].current_offset_);
|
||||
}
|
||||
ESP_LOGW(TAG,
|
||||
"[CALIBRATION][%s] ===============================================================================", cs);
|
||||
}
|
||||
if (power_mismatch) {
|
||||
ESP_LOGW(TAG, "[CALIBRATION][%s] ", cs);
|
||||
ESP_LOGW(TAG,
|
||||
"[CALIBRATION][%s] ================= Power offset mismatch: using flash values =================", cs);
|
||||
ESP_LOGW(TAG, "[CALIBRATION][%s] ------------------------------------------------------------------------------",
|
||||
cs);
|
||||
ESP_LOGW(TAG, "[CALIBRATION][%s] | Phase | offset_active_power|offset_reactive_power|", cs);
|
||||
ESP_LOGW(TAG, "[CALIBRATION][%s] | | config | flash | config | flash |", cs);
|
||||
ESP_LOGW(TAG, "[CALIBRATION][%s] ------------------------------------------------------------------------------",
|
||||
cs);
|
||||
for (uint8_t phase = 0; phase < 3; ++phase) {
|
||||
ESP_LOGW(TAG, "[CALIBRATION][%s] | %c | %6d | %6d | %6d | %6d |", cs, 'A' + phase,
|
||||
this->config_power_offset_phase_[phase].active_power_offset,
|
||||
this->power_offset_phase_[phase].active_power_offset,
|
||||
this->config_power_offset_phase_[phase].reactive_power_offset,
|
||||
this->power_offset_phase_[phase].reactive_power_offset);
|
||||
}
|
||||
ESP_LOGW(TAG,
|
||||
"[CALIBRATION][%s] ===============================================================================", cs);
|
||||
}
|
||||
if (gain_mismatch) {
|
||||
ESP_LOGW(TAG, "[CALIBRATION][%s] ", cs);
|
||||
ESP_LOGW(TAG,
|
||||
"[CALIBRATION][%s] ====================== Gain mismatch: using flash values =====================", cs);
|
||||
ESP_LOGW(TAG, "[CALIBRATION][%s] ------------------------------------------------------------------------------",
|
||||
cs);
|
||||
ESP_LOGW(TAG, "[CALIBRATION][%s] | Phase | voltage_gain | current_gain |", cs);
|
||||
ESP_LOGW(TAG, "[CALIBRATION][%s] | | config | flash | config | flash |", cs);
|
||||
ESP_LOGW(TAG, "[CALIBRATION][%s] ------------------------------------------------------------------------------",
|
||||
cs);
|
||||
for (uint8_t phase = 0; phase < 3; ++phase) {
|
||||
ESP_LOGW(TAG, "[CALIBRATION][%s] | %c | %6u | %6u | %6u | %6u |", cs, 'A' + phase,
|
||||
this->config_gain_phase_[phase].voltage_gain, this->gain_phase_[phase].voltage_gain,
|
||||
this->config_gain_phase_[phase].current_gain, this->gain_phase_[phase].current_gain);
|
||||
}
|
||||
ESP_LOGW(TAG,
|
||||
"[CALIBRATION][%s] ===============================================================================", cs);
|
||||
}
|
||||
if (!this->enable_offset_calibration_) {
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] Power & Voltage/Current offset calibration is disabled. Using config file values.",
|
||||
cs);
|
||||
} else if (this->restored_offset_calibration_ && !offset_mismatch) {
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ", cs);
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ============== Restored offset calibration from memory ==============", cs);
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] --------------------------------------------------------------", cs);
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] | Phase | offset_voltage | offset_current |", cs);
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] --------------------------------------------------------------", cs);
|
||||
for (uint8_t phase = 0; phase < 3; phase++) {
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] | %c | %6d | %6d |", cs, 'A' + phase,
|
||||
this->offset_phase_[phase].voltage_offset_, this->offset_phase_[phase].current_offset_);
|
||||
}
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ==============================================================\\n", cs);
|
||||
}
|
||||
|
||||
if (this->restored_power_offset_calibration_ && !power_mismatch) {
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ", cs);
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ============ Restored power offset calibration from memory ============", cs);
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ---------------------------------------------------------------------", cs);
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] | Phase | offset_active_power | offset_reactive_power |", cs);
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ---------------------------------------------------------------------", cs);
|
||||
for (uint8_t phase = 0; phase < 3; phase++) {
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] | %c | %6d | %6d |", cs, 'A' + phase,
|
||||
this->power_offset_phase_[phase].active_power_offset,
|
||||
this->power_offset_phase_[phase].reactive_power_offset);
|
||||
}
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] =====================================================================\n", cs);
|
||||
}
|
||||
if (!this->enable_gain_calibration_) {
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] Gain calibration is disabled. Using config file values.", cs);
|
||||
} else if (this->restored_gain_calibration_ && !gain_mismatch) {
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ", cs);
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ============ Restoring saved gain calibrations to registers ============", cs);
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ---------------------------------------------------------------------", cs);
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] | Phase | voltage_gain | current_gain |", cs);
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ---------------------------------------------------------------------", cs);
|
||||
for (uint8_t phase = 0; phase < 3; phase++) {
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] | %c | %6u | %6u |", cs, 'A' + phase,
|
||||
this->gain_phase_[phase].voltage_gain, this->gain_phase_[phase].current_gain);
|
||||
}
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] =====================================================================\\n", cs);
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] Gain calibration loaded and verified successfully.\n", cs);
|
||||
}
|
||||
this->calibration_message_printed_ = true;
|
||||
}
|
||||
|
||||
void ATM90E32Component::dump_config() {
|
||||
ESP_LOGCONFIG("", "ATM90E32:");
|
||||
LOG_PIN(" CS Pin: ", this->cs_);
|
||||
@@ -255,6 +371,10 @@ void ATM90E32Component::dump_config() {
|
||||
LOG_SENSOR(" ", "Peak Current C", this->phase_[PHASEC].peak_current_sensor_);
|
||||
LOG_SENSOR(" ", "Frequency", this->freq_sensor_);
|
||||
LOG_SENSOR(" ", "Chip Temp", this->chip_temperature_sensor_);
|
||||
if (this->restored_offset_calibration_ || this->restored_power_offset_calibration_ ||
|
||||
this->restored_gain_calibration_ || !this->enable_offset_calibration_ || !this->enable_gain_calibration_) {
|
||||
this->log_calibration_status_();
|
||||
}
|
||||
}
|
||||
|
||||
float ATM90E32Component::get_setup_priority() const { return setup_priority::IO; }
|
||||
@@ -262,26 +382,35 @@ float ATM90E32Component::get_setup_priority() const { return setup_priority::IO;
|
||||
// R/C registers can conly be cleared after the LastSPIData register is updated (register 78H)
|
||||
// Peakdetect period: 05H. Bit 15:8 are PeakDet_period in ms. 7:0 are Sag_period
|
||||
// Default is 143FH (20ms, 63ms)
|
||||
uint16_t ATM90E32Component::read16_(uint16_t a_register) {
|
||||
uint16_t ATM90E32Component::read16_transaction_(uint16_t a_register) {
|
||||
uint8_t addrh = (1 << 7) | ((a_register >> 8) & 0x03);
|
||||
uint8_t addrl = (a_register & 0xFF);
|
||||
uint8_t data[2];
|
||||
uint16_t output;
|
||||
this->enable();
|
||||
delay_microseconds_safe(1); // min delay between CS low and first SCK is 200ns - 1ms is plenty
|
||||
this->write_byte(addrh);
|
||||
this->write_byte(addrl);
|
||||
this->read_array(data, 2);
|
||||
this->disable();
|
||||
|
||||
output = (uint16_t(data[0] & 0xFF) << 8) | (data[1] & 0xFF);
|
||||
uint8_t data[4] = {addrh, addrl, 0x00, 0x00};
|
||||
this->transfer_array(data, 4);
|
||||
uint16_t output = encode_uint16(data[2], data[3]);
|
||||
ESP_LOGVV(TAG, "read16_ 0x%04" PRIX16 " output 0x%04" PRIX16, a_register, output);
|
||||
return output;
|
||||
}
|
||||
|
||||
uint16_t ATM90E32Component::read16_(uint16_t a_register) {
|
||||
this->enable();
|
||||
delay_microseconds_safe(1); // min delay between CS low and first SCK is 200ns - 1us is plenty
|
||||
uint16_t output = this->read16_transaction_(a_register);
|
||||
delay_microseconds_safe(1); // allow the last clock to propagate before releasing CS
|
||||
this->disable();
|
||||
delay_microseconds_safe(1); // meet minimum CS high time before next transaction
|
||||
return output;
|
||||
}
|
||||
|
||||
int ATM90E32Component::read32_(uint16_t addr_h, uint16_t addr_l) {
|
||||
const uint16_t val_h = this->read16_(addr_h);
|
||||
const uint16_t val_l = this->read16_(addr_l);
|
||||
this->enable();
|
||||
delay_microseconds_safe(1);
|
||||
const uint16_t val_h = this->read16_transaction_(addr_h);
|
||||
delay_microseconds_safe(1);
|
||||
const uint16_t val_l = this->read16_transaction_(addr_l);
|
||||
delay_microseconds_safe(1);
|
||||
this->disable();
|
||||
delay_microseconds_safe(1);
|
||||
const int32_t val = (val_h << 16) | val_l;
|
||||
|
||||
ESP_LOGVV(TAG,
|
||||
@@ -292,13 +421,19 @@ int ATM90E32Component::read32_(uint16_t addr_h, uint16_t addr_l) {
|
||||
return val;
|
||||
}
|
||||
|
||||
void ATM90E32Component::write16_(uint16_t a_register, uint16_t val) {
|
||||
void ATM90E32Component::write16_(uint16_t a_register, uint16_t val, bool validate) {
|
||||
ESP_LOGVV(TAG, "write16_ 0x%04" PRIX16 " val 0x%04" PRIX16, a_register, val);
|
||||
uint8_t addrh = ((a_register >> 8) & 0x03);
|
||||
uint8_t addrl = (a_register & 0xFF);
|
||||
uint8_t data[4] = {addrh, addrl, uint8_t((val >> 8) & 0xFF), uint8_t(val & 0xFF)};
|
||||
this->enable();
|
||||
this->write_byte16(a_register);
|
||||
this->write_byte16(val);
|
||||
delay_microseconds_safe(1); // ensure CS setup time
|
||||
this->write_array(data, 4);
|
||||
delay_microseconds_safe(1); // allow clock to settle before raising CS
|
||||
this->disable();
|
||||
this->validate_spi_read_(val, "write16()");
|
||||
delay_microseconds_safe(1); // ensure minimum CS high time
|
||||
if (validate)
|
||||
this->validate_spi_read_(val, "write16()");
|
||||
}
|
||||
|
||||
float ATM90E32Component::get_local_phase_voltage_(uint8_t phase) { return this->phase_[phase].voltage_; }
|
||||
@@ -441,8 +576,10 @@ float ATM90E32Component::get_chip_temperature_() {
|
||||
}
|
||||
|
||||
void ATM90E32Component::run_gain_calibrations() {
|
||||
const char *cs = this->cs_summary_.c_str();
|
||||
if (!this->enable_gain_calibration_) {
|
||||
ESP_LOGW(TAG, "[CALIBRATION] Gain calibration is disabled! Enable it first with enable_gain_calibration: true");
|
||||
ESP_LOGW(TAG, "[CALIBRATION][%s] Gain calibration is disabled! Enable it first with enable_gain_calibration: true",
|
||||
cs);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -454,12 +591,14 @@ void ATM90E32Component::run_gain_calibrations() {
|
||||
float ref_currents[3] = {this->get_reference_current(0), this->get_reference_current(1),
|
||||
this->get_reference_current(2)};
|
||||
|
||||
ESP_LOGI(TAG, "[CALIBRATION] ");
|
||||
ESP_LOGI(TAG, "[CALIBRATION] ========================= Gain Calibration =========================");
|
||||
ESP_LOGI(TAG, "[CALIBRATION] ---------------------------------------------------------------------");
|
||||
ESP_LOGI(TAG,
|
||||
"[CALIBRATION] | Phase | V_meas (V) | I_meas (A) | V_ref | I_ref | V_gain (old→new) | I_gain (old→new) |");
|
||||
ESP_LOGI(TAG, "[CALIBRATION] ---------------------------------------------------------------------");
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ", cs);
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ========================= Gain Calibration =========================", cs);
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ---------------------------------------------------------------------", cs);
|
||||
ESP_LOGI(
|
||||
TAG,
|
||||
"[CALIBRATION][%s] | Phase | V_meas (V) | I_meas (A) | V_ref | I_ref | V_gain (old→new) | I_gain (old→new) |",
|
||||
cs);
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ---------------------------------------------------------------------", cs);
|
||||
|
||||
for (uint8_t phase = 0; phase < 3; phase++) {
|
||||
float measured_voltage = this->get_phase_voltage_avg_(phase);
|
||||
@@ -476,22 +615,22 @@ void ATM90E32Component::run_gain_calibrations() {
|
||||
|
||||
// Voltage calibration
|
||||
if (ref_voltage <= 0.0f) {
|
||||
ESP_LOGW(TAG, "[CALIBRATION] Phase %s - Skipping voltage calibration: reference voltage is 0.",
|
||||
ESP_LOGW(TAG, "[CALIBRATION][%s] Phase %s - Skipping voltage calibration: reference voltage is 0.", cs,
|
||||
phase_labels[phase]);
|
||||
} else if (measured_voltage == 0.0f) {
|
||||
ESP_LOGW(TAG, "[CALIBRATION] Phase %s - Skipping voltage calibration: measured voltage is 0.",
|
||||
ESP_LOGW(TAG, "[CALIBRATION][%s] Phase %s - Skipping voltage calibration: measured voltage is 0.", cs,
|
||||
phase_labels[phase]);
|
||||
} else {
|
||||
uint32_t new_voltage_gain = static_cast<uint16_t>((ref_voltage / measured_voltage) * current_voltage_gain);
|
||||
if (new_voltage_gain == 0) {
|
||||
ESP_LOGW(TAG, "[CALIBRATION] Phase %s - Voltage gain would be 0. Check reference and measured voltage.",
|
||||
ESP_LOGW(TAG, "[CALIBRATION][%s] Phase %s - Voltage gain would be 0. Check reference and measured voltage.", cs,
|
||||
phase_labels[phase]);
|
||||
} else {
|
||||
if (new_voltage_gain >= 65535) {
|
||||
ESP_LOGW(
|
||||
TAG,
|
||||
"[CALIBRATION] Phase %s - Voltage gain exceeds 65535. You may need a higher output voltage transformer.",
|
||||
phase_labels[phase]);
|
||||
ESP_LOGW(TAG,
|
||||
"[CALIBRATION][%s] Phase %s - Voltage gain exceeds 65535. You may need a higher output voltage "
|
||||
"transformer.",
|
||||
cs, phase_labels[phase]);
|
||||
new_voltage_gain = 65535;
|
||||
}
|
||||
this->gain_phase_[phase].voltage_gain = static_cast<uint16_t>(new_voltage_gain);
|
||||
@@ -501,20 +640,20 @@ void ATM90E32Component::run_gain_calibrations() {
|
||||
|
||||
// Current calibration
|
||||
if (ref_current == 0.0f) {
|
||||
ESP_LOGW(TAG, "[CALIBRATION] Phase %s - Skipping current calibration: reference current is 0.",
|
||||
ESP_LOGW(TAG, "[CALIBRATION][%s] Phase %s - Skipping current calibration: reference current is 0.", cs,
|
||||
phase_labels[phase]);
|
||||
} else if (measured_current == 0.0f) {
|
||||
ESP_LOGW(TAG, "[CALIBRATION] Phase %s - Skipping current calibration: measured current is 0.",
|
||||
ESP_LOGW(TAG, "[CALIBRATION][%s] Phase %s - Skipping current calibration: measured current is 0.", cs,
|
||||
phase_labels[phase]);
|
||||
} else {
|
||||
uint32_t new_current_gain = static_cast<uint16_t>((ref_current / measured_current) * current_current_gain);
|
||||
if (new_current_gain == 0) {
|
||||
ESP_LOGW(TAG, "[CALIBRATION] Phase %s - Current gain would be 0. Check reference and measured current.",
|
||||
ESP_LOGW(TAG, "[CALIBRATION][%s] Phase %s - Current gain would be 0. Check reference and measured current.", cs,
|
||||
phase_labels[phase]);
|
||||
} else {
|
||||
if (new_current_gain >= 65535) {
|
||||
ESP_LOGW(TAG, "[CALIBRATION] Phase %s - Current gain exceeds 65535. You may need to turn up pga gain.",
|
||||
phase_labels[phase]);
|
||||
ESP_LOGW(TAG, "[CALIBRATION][%s] Phase %s - Current gain exceeds 65535. You may need to turn up pga gain.",
|
||||
cs, phase_labels[phase]);
|
||||
new_current_gain = 65535;
|
||||
}
|
||||
this->gain_phase_[phase].current_gain = static_cast<uint16_t>(new_current_gain);
|
||||
@@ -523,13 +662,13 @@ void ATM90E32Component::run_gain_calibrations() {
|
||||
}
|
||||
|
||||
// Final row output
|
||||
ESP_LOGI(TAG, "[CALIBRATION] | %c | %9.2f | %9.4f | %5.2f | %6.4f | %5u → %-5u | %5u → %-5u |",
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] | %c | %9.2f | %9.4f | %5.2f | %6.4f | %5u → %-5u | %5u → %-5u |", cs,
|
||||
'A' + phase, measured_voltage, measured_current, ref_voltage, ref_current, current_voltage_gain,
|
||||
did_voltage ? this->gain_phase_[phase].voltage_gain : current_voltage_gain, current_current_gain,
|
||||
did_current ? this->gain_phase_[phase].current_gain : current_current_gain);
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "[CALIBRATION] =====================================================================\n");
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] =====================================================================\n", cs);
|
||||
|
||||
this->save_gain_calibration_to_memory_();
|
||||
this->write_gains_to_registers_();
|
||||
@@ -537,54 +676,108 @@ void ATM90E32Component::run_gain_calibrations() {
|
||||
}
|
||||
|
||||
void ATM90E32Component::save_gain_calibration_to_memory_() {
|
||||
const char *cs = this->cs_summary_.c_str();
|
||||
bool success = this->gain_calibration_pref_.save(&this->gain_phase_);
|
||||
global_preferences->sync();
|
||||
if (success) {
|
||||
this->using_saved_calibrations_ = true;
|
||||
ESP_LOGI(TAG, "[CALIBRATION] Gain calibration saved to memory.");
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] Gain calibration saved to memory.", cs);
|
||||
} else {
|
||||
this->using_saved_calibrations_ = false;
|
||||
ESP_LOGE(TAG, "[CALIBRATION] Failed to save gain calibration to memory!");
|
||||
ESP_LOGE(TAG, "[CALIBRATION][%s] Failed to save gain calibration to memory!", cs);
|
||||
}
|
||||
}
|
||||
|
||||
void ATM90E32Component::save_offset_calibration_to_memory_() {
|
||||
const char *cs = this->cs_summary_.c_str();
|
||||
bool success = this->offset_pref_.save(&this->offset_phase_);
|
||||
global_preferences->sync();
|
||||
if (success) {
|
||||
this->using_saved_calibrations_ = true;
|
||||
this->restored_offset_calibration_ = true;
|
||||
for (bool &phase : this->offset_calibration_mismatch_)
|
||||
phase = false;
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] Offset calibration saved to memory.", cs);
|
||||
} else {
|
||||
this->using_saved_calibrations_ = false;
|
||||
ESP_LOGE(TAG, "[CALIBRATION][%s] Failed to save offset calibration to memory!", cs);
|
||||
}
|
||||
}
|
||||
|
||||
void ATM90E32Component::save_power_offset_calibration_to_memory_() {
|
||||
const char *cs = this->cs_summary_.c_str();
|
||||
bool success = this->power_offset_pref_.save(&this->power_offset_phase_);
|
||||
global_preferences->sync();
|
||||
if (success) {
|
||||
this->using_saved_calibrations_ = true;
|
||||
this->restored_power_offset_calibration_ = true;
|
||||
for (bool &phase : this->power_offset_calibration_mismatch_)
|
||||
phase = false;
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] Power offset calibration saved to memory.", cs);
|
||||
} else {
|
||||
this->using_saved_calibrations_ = false;
|
||||
ESP_LOGE(TAG, "[CALIBRATION][%s] Failed to save power offset calibration to memory!", cs);
|
||||
}
|
||||
}
|
||||
|
||||
void ATM90E32Component::run_offset_calibrations() {
|
||||
const char *cs = this->cs_summary_.c_str();
|
||||
if (!this->enable_offset_calibration_) {
|
||||
ESP_LOGW(TAG, "[CALIBRATION] Offset calibration is disabled! Enable it first with enable_offset_calibration: true");
|
||||
ESP_LOGW(TAG,
|
||||
"[CALIBRATION][%s] Offset calibration is disabled! Enable it first with enable_offset_calibration: true",
|
||||
cs);
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ", cs);
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ======================== Offset Calibration ========================", cs);
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ------------------------------------------------------------------", cs);
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] | Phase | offset_voltage | offset_current |", cs);
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ------------------------------------------------------------------", cs);
|
||||
|
||||
for (uint8_t phase = 0; phase < 3; phase++) {
|
||||
int16_t voltage_offset = calibrate_offset(phase, true);
|
||||
int16_t current_offset = calibrate_offset(phase, false);
|
||||
|
||||
this->write_offsets_to_registers_(phase, voltage_offset, current_offset);
|
||||
|
||||
ESP_LOGI(TAG, "[CALIBRATION] Phase %c - offset_voltage: %d, offset_current: %d", 'A' + phase, voltage_offset,
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] | %c | %6d | %6d |", cs, 'A' + phase, voltage_offset,
|
||||
current_offset);
|
||||
}
|
||||
|
||||
this->offset_pref_.save(&this->offset_phase_); // Save to flash
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ==================================================================\n", cs);
|
||||
|
||||
this->save_offset_calibration_to_memory_();
|
||||
}
|
||||
|
||||
void ATM90E32Component::run_power_offset_calibrations() {
|
||||
const char *cs = this->cs_summary_.c_str();
|
||||
if (!this->enable_offset_calibration_) {
|
||||
ESP_LOGW(
|
||||
TAG,
|
||||
"[CALIBRATION] Offset power calibration is disabled! Enable it first with enable_offset_calibration: true");
|
||||
"[CALIBRATION][%s] Offset power calibration is disabled! Enable it first with enable_offset_calibration: true",
|
||||
cs);
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ", cs);
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ===================== Power Offset Calibration =====================", cs);
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ---------------------------------------------------------------------", cs);
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] | Phase | offset_active_power | offset_reactive_power |", cs);
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ---------------------------------------------------------------------", cs);
|
||||
|
||||
for (uint8_t phase = 0; phase < 3; ++phase) {
|
||||
int16_t active_offset = calibrate_power_offset(phase, false);
|
||||
int16_t reactive_offset = calibrate_power_offset(phase, true);
|
||||
|
||||
this->write_power_offsets_to_registers_(phase, active_offset, reactive_offset);
|
||||
|
||||
ESP_LOGI(TAG, "[CALIBRATION] Phase %c - offset_active_power: %d, offset_reactive_power: %d", 'A' + phase,
|
||||
active_offset, reactive_offset);
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] | %c | %6d | %6d |", cs, 'A' + phase, active_offset,
|
||||
reactive_offset);
|
||||
}
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] =====================================================================\n", cs);
|
||||
|
||||
this->power_offset_pref_.save(&this->power_offset_phase_); // Save to flash
|
||||
this->save_power_offset_calibration_to_memory_();
|
||||
}
|
||||
|
||||
void ATM90E32Component::write_gains_to_registers_() {
|
||||
@@ -631,102 +824,276 @@ void ATM90E32Component::write_power_offsets_to_registers_(uint8_t phase, int16_t
|
||||
}
|
||||
|
||||
void ATM90E32Component::restore_gain_calibrations_() {
|
||||
if (this->gain_calibration_pref_.load(&this->gain_phase_)) {
|
||||
ESP_LOGI(TAG, "[CALIBRATION] Restoring saved gain calibrations to registers:");
|
||||
|
||||
for (uint8_t phase = 0; phase < 3; phase++) {
|
||||
uint16_t v_gain = this->gain_phase_[phase].voltage_gain;
|
||||
uint16_t i_gain = this->gain_phase_[phase].current_gain;
|
||||
ESP_LOGI(TAG, "[CALIBRATION] Phase %c - Voltage Gain: %u, Current Gain: %u", 'A' + phase, v_gain, i_gain);
|
||||
}
|
||||
|
||||
this->write_gains_to_registers_();
|
||||
|
||||
if (this->verify_gain_writes_()) {
|
||||
this->using_saved_calibrations_ = true;
|
||||
ESP_LOGI(TAG, "[CALIBRATION] Gain calibration loaded and verified successfully.");
|
||||
} else {
|
||||
this->using_saved_calibrations_ = false;
|
||||
ESP_LOGE(TAG, "[CALIBRATION] Gain verification failed! Calibration may not be applied correctly.");
|
||||
}
|
||||
} else {
|
||||
this->using_saved_calibrations_ = false;
|
||||
ESP_LOGW(TAG, "[CALIBRATION] No stored gain calibrations found. Using config file values.");
|
||||
const char *cs = this->cs_summary_.c_str();
|
||||
for (uint8_t i = 0; i < 3; ++i) {
|
||||
this->config_gain_phase_[i].voltage_gain = this->phase_[i].voltage_gain_;
|
||||
this->config_gain_phase_[i].current_gain = this->phase_[i].ct_gain_;
|
||||
this->gain_phase_[i] = this->config_gain_phase_[i];
|
||||
}
|
||||
|
||||
if (this->gain_calibration_pref_.load(&this->gain_phase_)) {
|
||||
bool all_zero = true;
|
||||
bool same_as_config = true;
|
||||
for (uint8_t phase = 0; phase < 3; ++phase) {
|
||||
const auto &cfg = this->config_gain_phase_[phase];
|
||||
const auto &saved = this->gain_phase_[phase];
|
||||
if (saved.voltage_gain != 0 || saved.current_gain != 0)
|
||||
all_zero = false;
|
||||
if (saved.voltage_gain != cfg.voltage_gain || saved.current_gain != cfg.current_gain)
|
||||
same_as_config = false;
|
||||
}
|
||||
|
||||
if (!all_zero && !same_as_config) {
|
||||
for (uint8_t phase = 0; phase < 3; ++phase) {
|
||||
bool mismatch = false;
|
||||
if (this->has_config_voltage_gain_[phase] &&
|
||||
this->gain_phase_[phase].voltage_gain != this->config_gain_phase_[phase].voltage_gain)
|
||||
mismatch = true;
|
||||
if (this->has_config_current_gain_[phase] &&
|
||||
this->gain_phase_[phase].current_gain != this->config_gain_phase_[phase].current_gain)
|
||||
mismatch = true;
|
||||
if (mismatch)
|
||||
this->gain_calibration_mismatch_[phase] = true;
|
||||
}
|
||||
|
||||
this->write_gains_to_registers_();
|
||||
|
||||
if (this->verify_gain_writes_()) {
|
||||
this->using_saved_calibrations_ = true;
|
||||
this->restored_gain_calibration_ = true;
|
||||
return;
|
||||
}
|
||||
|
||||
this->using_saved_calibrations_ = false;
|
||||
ESP_LOGE(TAG, "[CALIBRATION][%s] Gain verification failed! Calibration may not be applied correctly.", cs);
|
||||
}
|
||||
}
|
||||
|
||||
this->using_saved_calibrations_ = false;
|
||||
for (uint8_t i = 0; i < 3; ++i)
|
||||
this->gain_phase_[i] = this->config_gain_phase_[i];
|
||||
this->write_gains_to_registers_();
|
||||
|
||||
ESP_LOGW(TAG, "[CALIBRATION][%s] No stored gain calibrations found. Using config file values.", cs);
|
||||
}
|
||||
|
||||
void ATM90E32Component::restore_offset_calibrations_() {
|
||||
if (this->offset_pref_.load(&this->offset_phase_)) {
|
||||
ESP_LOGI(TAG, "[CALIBRATION] Successfully restored offset calibration from memory.");
|
||||
const char *cs = this->cs_summary_.c_str();
|
||||
for (uint8_t i = 0; i < 3; ++i)
|
||||
this->config_offset_phase_[i] = this->offset_phase_[i];
|
||||
|
||||
bool have_data = this->offset_pref_.load(&this->offset_phase_);
|
||||
bool all_zero = true;
|
||||
if (have_data) {
|
||||
for (auto &phase : this->offset_phase_) {
|
||||
if (phase.voltage_offset_ != 0 || phase.current_offset_ != 0) {
|
||||
all_zero = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (have_data && !all_zero) {
|
||||
this->restored_offset_calibration_ = true;
|
||||
for (uint8_t phase = 0; phase < 3; phase++) {
|
||||
auto &offset = this->offset_phase_[phase];
|
||||
write_offsets_to_registers_(phase, offset.voltage_offset_, offset.current_offset_);
|
||||
ESP_LOGI(TAG, "[CALIBRATION] Phase %c - offset_voltage:: %d, offset_current: %d", 'A' + phase,
|
||||
offset.voltage_offset_, offset.current_offset_);
|
||||
bool mismatch = false;
|
||||
if (this->has_config_voltage_offset_[phase] &&
|
||||
offset.voltage_offset_ != this->config_offset_phase_[phase].voltage_offset_)
|
||||
mismatch = true;
|
||||
if (this->has_config_current_offset_[phase] &&
|
||||
offset.current_offset_ != this->config_offset_phase_[phase].current_offset_)
|
||||
mismatch = true;
|
||||
if (mismatch)
|
||||
this->offset_calibration_mismatch_[phase] = true;
|
||||
}
|
||||
} else {
|
||||
ESP_LOGW(TAG, "[CALIBRATION] No stored offset calibrations found. Using default values.");
|
||||
for (uint8_t phase = 0; phase < 3; phase++)
|
||||
this->offset_phase_[phase] = this->config_offset_phase_[phase];
|
||||
ESP_LOGW(TAG, "[CALIBRATION][%s] No stored offset calibrations found. Using default values.", cs);
|
||||
}
|
||||
|
||||
for (uint8_t phase = 0; phase < 3; phase++) {
|
||||
write_offsets_to_registers_(phase, this->offset_phase_[phase].voltage_offset_,
|
||||
this->offset_phase_[phase].current_offset_);
|
||||
}
|
||||
}
|
||||
|
||||
void ATM90E32Component::restore_power_offset_calibrations_() {
|
||||
if (this->power_offset_pref_.load(&this->power_offset_phase_)) {
|
||||
ESP_LOGI(TAG, "[CALIBRATION] Successfully restored power offset calibration from memory.");
|
||||
const char *cs = this->cs_summary_.c_str();
|
||||
for (uint8_t i = 0; i < 3; ++i)
|
||||
this->config_power_offset_phase_[i] = this->power_offset_phase_[i];
|
||||
|
||||
bool have_data = this->power_offset_pref_.load(&this->power_offset_phase_);
|
||||
bool all_zero = true;
|
||||
if (have_data) {
|
||||
for (auto &phase : this->power_offset_phase_) {
|
||||
if (phase.active_power_offset != 0 || phase.reactive_power_offset != 0) {
|
||||
all_zero = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (have_data && !all_zero) {
|
||||
this->restored_power_offset_calibration_ = true;
|
||||
for (uint8_t phase = 0; phase < 3; ++phase) {
|
||||
auto &offset = this->power_offset_phase_[phase];
|
||||
write_power_offsets_to_registers_(phase, offset.active_power_offset, offset.reactive_power_offset);
|
||||
ESP_LOGI(TAG, "[CALIBRATION] Phase %c - offset_active_power: %d, offset_reactive_power: %d", 'A' + phase,
|
||||
offset.active_power_offset, offset.reactive_power_offset);
|
||||
bool mismatch = false;
|
||||
if (this->has_config_active_power_offset_[phase] &&
|
||||
offset.active_power_offset != this->config_power_offset_phase_[phase].active_power_offset)
|
||||
mismatch = true;
|
||||
if (this->has_config_reactive_power_offset_[phase] &&
|
||||
offset.reactive_power_offset != this->config_power_offset_phase_[phase].reactive_power_offset)
|
||||
mismatch = true;
|
||||
if (mismatch)
|
||||
this->power_offset_calibration_mismatch_[phase] = true;
|
||||
}
|
||||
} else {
|
||||
ESP_LOGW(TAG, "[CALIBRATION] No stored power offsets found. Using default values.");
|
||||
for (uint8_t phase = 0; phase < 3; ++phase)
|
||||
this->power_offset_phase_[phase] = this->config_power_offset_phase_[phase];
|
||||
ESP_LOGW(TAG, "[CALIBRATION][%s] No stored power offsets found. Using default values.", cs);
|
||||
}
|
||||
|
||||
for (uint8_t phase = 0; phase < 3; ++phase) {
|
||||
write_power_offsets_to_registers_(phase, this->power_offset_phase_[phase].active_power_offset,
|
||||
this->power_offset_phase_[phase].reactive_power_offset);
|
||||
}
|
||||
}
|
||||
|
||||
void ATM90E32Component::clear_gain_calibrations() {
|
||||
ESP_LOGI(TAG, "[CALIBRATION] Clearing stored gain calibrations and restoring config-defined values");
|
||||
|
||||
for (int phase = 0; phase < 3; phase++) {
|
||||
gain_phase_[phase].voltage_gain = this->phase_[phase].voltage_gain_;
|
||||
gain_phase_[phase].current_gain = this->phase_[phase].ct_gain_;
|
||||
const char *cs = this->cs_summary_.c_str();
|
||||
if (!this->using_saved_calibrations_) {
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] No stored gain calibrations to clear. Current values:", cs);
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ----------------------------------------------------------", cs);
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] | Phase | voltage_gain | current_gain |", cs);
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ----------------------------------------------------------", cs);
|
||||
for (int phase = 0; phase < 3; phase++) {
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] | %c | %6u | %6u |", cs, 'A' + phase,
|
||||
this->gain_phase_[phase].voltage_gain, this->gain_phase_[phase].current_gain);
|
||||
}
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ==========================================================\n", cs);
|
||||
return;
|
||||
}
|
||||
|
||||
bool success = this->gain_calibration_pref_.save(&this->gain_phase_);
|
||||
this->using_saved_calibrations_ = false;
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] Clearing stored gain calibrations and restoring config-defined values", cs);
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ----------------------------------------------------------", cs);
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] | Phase | voltage_gain | current_gain |", cs);
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ----------------------------------------------------------", cs);
|
||||
|
||||
if (success) {
|
||||
ESP_LOGI(TAG, "[CALIBRATION] Gain calibrations cleared. Config values restored:");
|
||||
for (int phase = 0; phase < 3; phase++) {
|
||||
ESP_LOGI(TAG, "[CALIBRATION] Phase %c - Voltage Gain: %u, Current Gain: %u", 'A' + phase,
|
||||
gain_phase_[phase].voltage_gain, gain_phase_[phase].current_gain);
|
||||
}
|
||||
} else {
|
||||
ESP_LOGE(TAG, "[CALIBRATION] Failed to clear gain calibrations!");
|
||||
for (int phase = 0; phase < 3; phase++) {
|
||||
uint16_t voltage_gain = this->phase_[phase].voltage_gain_;
|
||||
uint16_t current_gain = this->phase_[phase].ct_gain_;
|
||||
|
||||
this->config_gain_phase_[phase].voltage_gain = voltage_gain;
|
||||
this->config_gain_phase_[phase].current_gain = current_gain;
|
||||
this->gain_phase_[phase].voltage_gain = voltage_gain;
|
||||
this->gain_phase_[phase].current_gain = current_gain;
|
||||
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] | %c | %6u | %6u |", cs, 'A' + phase, voltage_gain, current_gain);
|
||||
}
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ==========================================================\n", cs);
|
||||
|
||||
GainCalibration zero_gains[3]{{0, 0}, {0, 0}, {0, 0}};
|
||||
bool success = this->gain_calibration_pref_.save(&zero_gains);
|
||||
global_preferences->sync();
|
||||
|
||||
this->using_saved_calibrations_ = false;
|
||||
this->restored_gain_calibration_ = false;
|
||||
for (bool &phase : this->gain_calibration_mismatch_)
|
||||
phase = false;
|
||||
|
||||
if (!success) {
|
||||
ESP_LOGE(TAG, "[CALIBRATION][%s] Failed to clear gain calibrations!", cs);
|
||||
}
|
||||
|
||||
this->write_gains_to_registers_(); // Apply them to the chip immediately
|
||||
}
|
||||
|
||||
void ATM90E32Component::clear_offset_calibrations() {
|
||||
for (uint8_t phase = 0; phase < 3; phase++) {
|
||||
this->write_offsets_to_registers_(phase, 0, 0);
|
||||
const char *cs = this->cs_summary_.c_str();
|
||||
if (!this->restored_offset_calibration_) {
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] No stored offset calibrations to clear. Current values:", cs);
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] --------------------------------------------------------------", cs);
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] | Phase | offset_voltage | offset_current |", cs);
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] --------------------------------------------------------------", cs);
|
||||
for (uint8_t phase = 0; phase < 3; phase++) {
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] | %c | %6d | %6d |", cs, 'A' + phase,
|
||||
this->offset_phase_[phase].voltage_offset_, this->offset_phase_[phase].current_offset_);
|
||||
}
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ==============================================================\n", cs);
|
||||
return;
|
||||
}
|
||||
|
||||
this->offset_pref_.save(&this->offset_phase_); // Save cleared values to flash memory
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] Clearing stored offset calibrations and restoring config-defined values", cs);
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] --------------------------------------------------------------", cs);
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] | Phase | offset_voltage | offset_current |", cs);
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] --------------------------------------------------------------", cs);
|
||||
|
||||
ESP_LOGI(TAG, "[CALIBRATION] Offsets cleared.");
|
||||
for (uint8_t phase = 0; phase < 3; phase++) {
|
||||
int16_t voltage_offset =
|
||||
this->has_config_voltage_offset_[phase] ? this->config_offset_phase_[phase].voltage_offset_ : 0;
|
||||
int16_t current_offset =
|
||||
this->has_config_current_offset_[phase] ? this->config_offset_phase_[phase].current_offset_ : 0;
|
||||
this->write_offsets_to_registers_(phase, voltage_offset, current_offset);
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] | %c | %6d | %6d |", cs, 'A' + phase, voltage_offset,
|
||||
current_offset);
|
||||
}
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ==============================================================\n", cs);
|
||||
|
||||
OffsetCalibration zero_offsets[3]{{0, 0}, {0, 0}, {0, 0}};
|
||||
this->offset_pref_.save(&zero_offsets); // Clear stored values in flash
|
||||
global_preferences->sync();
|
||||
|
||||
this->restored_offset_calibration_ = false;
|
||||
for (bool &phase : this->offset_calibration_mismatch_)
|
||||
phase = false;
|
||||
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] Offsets cleared.", cs);
|
||||
}
|
||||
|
||||
void ATM90E32Component::clear_power_offset_calibrations() {
|
||||
for (uint8_t phase = 0; phase < 3; phase++) {
|
||||
this->write_power_offsets_to_registers_(phase, 0, 0);
|
||||
const char *cs = this->cs_summary_.c_str();
|
||||
if (!this->restored_power_offset_calibration_) {
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] No stored power offsets to clear. Current values:", cs);
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ---------------------------------------------------------------------", cs);
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] | Phase | offset_active_power | offset_reactive_power |", cs);
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ---------------------------------------------------------------------", cs);
|
||||
for (uint8_t phase = 0; phase < 3; phase++) {
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] | %c | %6d | %6d |", cs, 'A' + phase,
|
||||
this->power_offset_phase_[phase].active_power_offset,
|
||||
this->power_offset_phase_[phase].reactive_power_offset);
|
||||
}
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] =====================================================================\n", cs);
|
||||
return;
|
||||
}
|
||||
|
||||
this->power_offset_pref_.save(&this->power_offset_phase_);
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] Clearing stored power offsets and restoring config-defined values", cs);
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ---------------------------------------------------------------------", cs);
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] | Phase | offset_active_power | offset_reactive_power |", cs);
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] ---------------------------------------------------------------------", cs);
|
||||
|
||||
ESP_LOGI(TAG, "[CALIBRATION] Power offsets cleared.");
|
||||
for (uint8_t phase = 0; phase < 3; phase++) {
|
||||
int16_t active_offset =
|
||||
this->has_config_active_power_offset_[phase] ? this->config_power_offset_phase_[phase].active_power_offset : 0;
|
||||
int16_t reactive_offset = this->has_config_reactive_power_offset_[phase]
|
||||
? this->config_power_offset_phase_[phase].reactive_power_offset
|
||||
: 0;
|
||||
this->write_power_offsets_to_registers_(phase, active_offset, reactive_offset);
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] | %c | %6d | %6d |", cs, 'A' + phase, active_offset,
|
||||
reactive_offset);
|
||||
}
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] =====================================================================\n", cs);
|
||||
|
||||
PowerOffsetCalibration zero_power_offsets[3]{{0, 0}, {0, 0}, {0, 0}};
|
||||
this->power_offset_pref_.save(&zero_power_offsets);
|
||||
global_preferences->sync();
|
||||
|
||||
this->restored_power_offset_calibration_ = false;
|
||||
for (bool &phase : this->power_offset_calibration_mismatch_)
|
||||
phase = false;
|
||||
|
||||
ESP_LOGI(TAG, "[CALIBRATION][%s] Power offsets cleared.", cs);
|
||||
}
|
||||
|
||||
int16_t ATM90E32Component::calibrate_offset(uint8_t phase, bool voltage) {
|
||||
@@ -747,20 +1114,21 @@ int16_t ATM90E32Component::calibrate_offset(uint8_t phase, bool voltage) {
|
||||
|
||||
int16_t ATM90E32Component::calibrate_power_offset(uint8_t phase, bool reactive) {
|
||||
const uint8_t num_reads = 5;
|
||||
uint64_t total_value = 0;
|
||||
int64_t total_value = 0;
|
||||
|
||||
for (uint8_t i = 0; i < num_reads; ++i) {
|
||||
uint32_t reading = reactive ? this->read32_(ATM90E32_REGISTER_QMEAN + phase, ATM90E32_REGISTER_QMEANLSB + phase)
|
||||
: this->read32_(ATM90E32_REGISTER_PMEAN + phase, ATM90E32_REGISTER_PMEANLSB + phase);
|
||||
int32_t reading = reactive ? this->read32_(ATM90E32_REGISTER_QMEAN + phase, ATM90E32_REGISTER_QMEANLSB + phase)
|
||||
: this->read32_(ATM90E32_REGISTER_PMEAN + phase, ATM90E32_REGISTER_PMEANLSB + phase);
|
||||
total_value += reading;
|
||||
}
|
||||
|
||||
const uint32_t average_value = total_value / num_reads;
|
||||
const uint32_t power_offset = ~average_value + 1;
|
||||
int32_t average_value = total_value / num_reads;
|
||||
int32_t power_offset = -average_value;
|
||||
return static_cast<int16_t>(power_offset); // Takes the lower 16 bits
|
||||
}
|
||||
|
||||
bool ATM90E32Component::verify_gain_writes_() {
|
||||
const char *cs = this->cs_summary_.c_str();
|
||||
bool success = true;
|
||||
for (uint8_t phase = 0; phase < 3; phase++) {
|
||||
uint16_t read_voltage = this->read16_(voltage_gain_registers[phase]);
|
||||
@@ -768,7 +1136,7 @@ bool ATM90E32Component::verify_gain_writes_() {
|
||||
|
||||
if (read_voltage != this->gain_phase_[phase].voltage_gain ||
|
||||
read_current != this->gain_phase_[phase].current_gain) {
|
||||
ESP_LOGE(TAG, "[CALIBRATION] Mismatch detected for Phase %s!", phase_labels[phase]);
|
||||
ESP_LOGE(TAG, "[CALIBRATION][%s] Mismatch detected for Phase %s!", cs, phase_labels[phase]);
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
@@ -791,16 +1159,16 @@ void ATM90E32Component::check_phase_status() {
|
||||
status += "Phase Loss; ";
|
||||
|
||||
auto *sensor = this->phase_status_text_sensor_[phase];
|
||||
const char *phase_name = sensor ? sensor->get_name().c_str() : "Unknown Phase";
|
||||
if (sensor == nullptr)
|
||||
continue;
|
||||
|
||||
if (!status.empty()) {
|
||||
status.pop_back(); // remove space
|
||||
status.pop_back(); // remove semicolon
|
||||
ESP_LOGW(TAG, "%s: %s", phase_name, status.c_str());
|
||||
if (sensor != nullptr)
|
||||
sensor->publish_state(status);
|
||||
ESP_LOGW(TAG, "%s: %s", sensor->get_name().c_str(), status.c_str());
|
||||
sensor->publish_state(status);
|
||||
} else {
|
||||
if (sensor != nullptr)
|
||||
sensor->publish_state("Okay");
|
||||
sensor->publish_state("Okay");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -817,9 +1185,12 @@ void ATM90E32Component::check_freq_status() {
|
||||
} else {
|
||||
freq_status = "Normal";
|
||||
}
|
||||
ESP_LOGW(TAG, "Frequency status: %s", freq_status.c_str());
|
||||
|
||||
if (this->freq_status_text_sensor_ != nullptr) {
|
||||
if (freq_status == "Normal") {
|
||||
ESP_LOGD(TAG, "Frequency status: %s", freq_status.c_str());
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Frequency status: %s", freq_status.c_str());
|
||||
}
|
||||
this->freq_status_text_sensor_->publish_state(freq_status);
|
||||
}
|
||||
}
|
||||
|
@@ -61,15 +61,29 @@ class ATM90E32Component : public PollingComponent,
|
||||
this->phase_[phase].harmonic_active_power_sensor_ = obj;
|
||||
}
|
||||
void set_peak_current_sensor(int phase, sensor::Sensor *obj) { this->phase_[phase].peak_current_sensor_ = obj; }
|
||||
void set_volt_gain(int phase, uint16_t gain) { this->phase_[phase].voltage_gain_ = gain; }
|
||||
void set_ct_gain(int phase, uint16_t gain) { this->phase_[phase].ct_gain_ = gain; }
|
||||
void set_voltage_offset(uint8_t phase, int16_t offset) { this->offset_phase_[phase].voltage_offset_ = offset; }
|
||||
void set_current_offset(uint8_t phase, int16_t offset) { this->offset_phase_[phase].current_offset_ = offset; }
|
||||
void set_volt_gain(int phase, uint16_t gain) {
|
||||
this->phase_[phase].voltage_gain_ = gain;
|
||||
this->has_config_voltage_gain_[phase] = true;
|
||||
}
|
||||
void set_ct_gain(int phase, uint16_t gain) {
|
||||
this->phase_[phase].ct_gain_ = gain;
|
||||
this->has_config_current_gain_[phase] = true;
|
||||
}
|
||||
void set_voltage_offset(uint8_t phase, int16_t offset) {
|
||||
this->offset_phase_[phase].voltage_offset_ = offset;
|
||||
this->has_config_voltage_offset_[phase] = true;
|
||||
}
|
||||
void set_current_offset(uint8_t phase, int16_t offset) {
|
||||
this->offset_phase_[phase].current_offset_ = offset;
|
||||
this->has_config_current_offset_[phase] = true;
|
||||
}
|
||||
void set_active_power_offset(uint8_t phase, int16_t offset) {
|
||||
this->power_offset_phase_[phase].active_power_offset = offset;
|
||||
this->has_config_active_power_offset_[phase] = true;
|
||||
}
|
||||
void set_reactive_power_offset(uint8_t phase, int16_t offset) {
|
||||
this->power_offset_phase_[phase].reactive_power_offset = offset;
|
||||
this->has_config_reactive_power_offset_[phase] = true;
|
||||
}
|
||||
void set_freq_sensor(sensor::Sensor *freq_sensor) { freq_sensor_ = freq_sensor; }
|
||||
void set_peak_current_signed(bool flag) { peak_current_signed_ = flag; }
|
||||
@@ -126,8 +140,9 @@ class ATM90E32Component : public PollingComponent,
|
||||
number::Number *ref_currents_[3]{nullptr, nullptr, nullptr};
|
||||
#endif
|
||||
uint16_t read16_(uint16_t a_register);
|
||||
uint16_t read16_transaction_(uint16_t a_register);
|
||||
int read32_(uint16_t addr_h, uint16_t addr_l);
|
||||
void write16_(uint16_t a_register, uint16_t val);
|
||||
void write16_(uint16_t a_register, uint16_t val, bool validate = true);
|
||||
float get_local_phase_voltage_(uint8_t phase);
|
||||
float get_local_phase_current_(uint8_t phase);
|
||||
float get_local_phase_active_power_(uint8_t phase);
|
||||
@@ -159,12 +174,15 @@ class ATM90E32Component : public PollingComponent,
|
||||
void restore_offset_calibrations_();
|
||||
void restore_power_offset_calibrations_();
|
||||
void restore_gain_calibrations_();
|
||||
void save_offset_calibration_to_memory_();
|
||||
void save_gain_calibration_to_memory_();
|
||||
void save_power_offset_calibration_to_memory_();
|
||||
void write_offsets_to_registers_(uint8_t phase, int16_t voltage_offset, int16_t current_offset);
|
||||
void write_power_offsets_to_registers_(uint8_t phase, int16_t p_offset, int16_t q_offset);
|
||||
void write_gains_to_registers_();
|
||||
bool verify_gain_writes_();
|
||||
bool validate_spi_read_(uint16_t expected, const char *context = nullptr);
|
||||
void log_calibration_status_();
|
||||
|
||||
struct ATM90E32Phase {
|
||||
uint16_t voltage_gain_{0};
|
||||
@@ -204,19 +222,33 @@ class ATM90E32Component : public PollingComponent,
|
||||
int16_t current_offset_{0};
|
||||
} offset_phase_[3];
|
||||
|
||||
OffsetCalibration config_offset_phase_[3];
|
||||
|
||||
struct PowerOffsetCalibration {
|
||||
int16_t active_power_offset{0};
|
||||
int16_t reactive_power_offset{0};
|
||||
} power_offset_phase_[3];
|
||||
|
||||
PowerOffsetCalibration config_power_offset_phase_[3];
|
||||
|
||||
struct GainCalibration {
|
||||
uint16_t voltage_gain{1};
|
||||
uint16_t current_gain{1};
|
||||
} gain_phase_[3];
|
||||
|
||||
GainCalibration config_gain_phase_[3];
|
||||
|
||||
bool has_config_voltage_offset_[3]{false, false, false};
|
||||
bool has_config_current_offset_[3]{false, false, false};
|
||||
bool has_config_active_power_offset_[3]{false, false, false};
|
||||
bool has_config_reactive_power_offset_[3]{false, false, false};
|
||||
bool has_config_voltage_gain_[3]{false, false, false};
|
||||
bool has_config_current_gain_[3]{false, false, false};
|
||||
|
||||
ESPPreferenceObject offset_pref_;
|
||||
ESPPreferenceObject power_offset_pref_;
|
||||
ESPPreferenceObject gain_calibration_pref_;
|
||||
std::string cs_summary_;
|
||||
|
||||
sensor::Sensor *freq_sensor_{nullptr};
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
@@ -231,6 +263,13 @@ class ATM90E32Component : public PollingComponent,
|
||||
bool peak_current_signed_{false};
|
||||
bool enable_offset_calibration_{false};
|
||||
bool enable_gain_calibration_{false};
|
||||
bool restored_offset_calibration_{false};
|
||||
bool restored_power_offset_calibration_{false};
|
||||
bool restored_gain_calibration_{false};
|
||||
bool calibration_message_printed_{false};
|
||||
bool offset_calibration_mismatch_[3]{false, false, false};
|
||||
bool power_offset_calibration_mismatch_[3]{false, false, false};
|
||||
bool gain_calibration_mismatch_[3]{false, false, false};
|
||||
};
|
||||
|
||||
} // namespace atm90e32
|
||||
|
@@ -119,7 +119,7 @@ void BluetoothConnection::loop() {
|
||||
|
||||
// Check if we should disable the loop
|
||||
// - For V3_WITH_CACHE: Services are never sent, disable after INIT state
|
||||
// - For other connections: Disable only after service discovery is complete
|
||||
// - For V3_WITHOUT_CACHE: Disable only after service discovery is complete
|
||||
// (send_service_ == DONE_SENDING_SERVICES, which is only set after services are sent)
|
||||
if (this->state_ != espbt::ClientState::INIT && (this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE ||
|
||||
this->send_service_ == DONE_SENDING_SERVICES)) {
|
||||
@@ -146,10 +146,7 @@ void BluetoothConnection::send_service_for_discovery_() {
|
||||
if (this->send_service_ >= this->service_count_) {
|
||||
this->send_service_ = DONE_SENDING_SERVICES;
|
||||
this->proxy_->send_gatt_services_done(this->address_);
|
||||
if (this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE ||
|
||||
this->connection_type_ == espbt::ConnectionType::V3_WITHOUT_CACHE) {
|
||||
this->release_services();
|
||||
}
|
||||
this->release_services();
|
||||
return;
|
||||
}
|
||||
|
||||
|
46
esphome/components/ld2412/__init__.py
Normal file
46
esphome/components/ld2412/__init__.py
Normal file
@@ -0,0 +1,46 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import uart
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ID, CONF_THROTTLE
|
||||
|
||||
AUTO_LOAD = ["ld24xx"]
|
||||
CODEOWNERS = ["@Rihan9"]
|
||||
DEPENDENCIES = ["uart"]
|
||||
MULTI_CONF = True
|
||||
|
||||
LD2412_ns = cg.esphome_ns.namespace("ld2412")
|
||||
LD2412Component = LD2412_ns.class_("LD2412Component", cg.Component, uart.UARTDevice)
|
||||
|
||||
CONF_LD2412_ID = "ld2412_id"
|
||||
|
||||
CONF_MAX_MOVE_DISTANCE = "max_move_distance"
|
||||
CONF_MAX_STILL_DISTANCE = "max_still_distance"
|
||||
CONF_MOVE_THRESHOLDS = [f"g{x}_move_threshold" for x in range(9)]
|
||||
CONF_STILL_THRESHOLDS = [f"g{x}_still_threshold" for x in range(9)]
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(LD2412Component),
|
||||
cv.Optional(CONF_THROTTLE): cv.invalid(
|
||||
f"{CONF_THROTTLE} has been removed; use per-sensor filters, instead"
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(uart.UART_DEVICE_SCHEMA)
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
)
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema(
|
||||
"ld2412",
|
||||
require_tx=True,
|
||||
require_rx=True,
|
||||
parity="NONE",
|
||||
stop_bits=1,
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await uart.register_uart_device(var, config)
|
70
esphome/components/ld2412/binary_sensor.py
Normal file
70
esphome/components/ld2412/binary_sensor.py
Normal file
@@ -0,0 +1,70 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import binary_sensor
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_HAS_MOVING_TARGET,
|
||||
CONF_HAS_STILL_TARGET,
|
||||
CONF_HAS_TARGET,
|
||||
DEVICE_CLASS_MOTION,
|
||||
DEVICE_CLASS_OCCUPANCY,
|
||||
DEVICE_CLASS_RUNNING,
|
||||
ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
ICON_ACCOUNT,
|
||||
ICON_MOTION_SENSOR,
|
||||
)
|
||||
|
||||
from . import CONF_LD2412_ID, LD2412Component
|
||||
|
||||
DEPENDENCIES = ["ld2412"]
|
||||
|
||||
CONF_DYNAMIC_BACKGROUND_CORRECTION_STATUS = "dynamic_background_correction_status"
|
||||
|
||||
CONFIG_SCHEMA = {
|
||||
cv.GenerateID(CONF_LD2412_ID): cv.use_id(LD2412Component),
|
||||
cv.Optional(
|
||||
CONF_DYNAMIC_BACKGROUND_CORRECTION_STATUS
|
||||
): binary_sensor.binary_sensor_schema(
|
||||
device_class=DEVICE_CLASS_RUNNING,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
icon=ICON_ACCOUNT,
|
||||
),
|
||||
cv.Optional(CONF_HAS_TARGET): binary_sensor.binary_sensor_schema(
|
||||
device_class=DEVICE_CLASS_OCCUPANCY,
|
||||
filters=[{"settle": cv.TimePeriod(milliseconds=1000)}],
|
||||
icon=ICON_ACCOUNT,
|
||||
),
|
||||
cv.Optional(CONF_HAS_MOVING_TARGET): binary_sensor.binary_sensor_schema(
|
||||
device_class=DEVICE_CLASS_MOTION,
|
||||
filters=[{"settle": cv.TimePeriod(milliseconds=1000)}],
|
||||
icon=ICON_MOTION_SENSOR,
|
||||
),
|
||||
cv.Optional(CONF_HAS_STILL_TARGET): binary_sensor.binary_sensor_schema(
|
||||
device_class=DEVICE_CLASS_OCCUPANCY,
|
||||
filters=[{"settle": cv.TimePeriod(milliseconds=1000)}],
|
||||
icon=ICON_MOTION_SENSOR,
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
LD2412_component = await cg.get_variable(config[CONF_LD2412_ID])
|
||||
if dynamic_background_correction_status_config := config.get(
|
||||
CONF_DYNAMIC_BACKGROUND_CORRECTION_STATUS
|
||||
):
|
||||
sens = await binary_sensor.new_binary_sensor(
|
||||
dynamic_background_correction_status_config
|
||||
)
|
||||
cg.add(
|
||||
LD2412_component.set_dynamic_background_correction_status_binary_sensor(
|
||||
sens
|
||||
)
|
||||
)
|
||||
if has_target_config := config.get(CONF_HAS_TARGET):
|
||||
sens = await binary_sensor.new_binary_sensor(has_target_config)
|
||||
cg.add(LD2412_component.set_target_binary_sensor(sens))
|
||||
if has_moving_target_config := config.get(CONF_HAS_MOVING_TARGET):
|
||||
sens = await binary_sensor.new_binary_sensor(has_moving_target_config)
|
||||
cg.add(LD2412_component.set_moving_target_binary_sensor(sens))
|
||||
if has_still_target_config := config.get(CONF_HAS_STILL_TARGET):
|
||||
sens = await binary_sensor.new_binary_sensor(has_still_target_config)
|
||||
cg.add(LD2412_component.set_still_target_binary_sensor(sens))
|
74
esphome/components/ld2412/button/__init__.py
Normal file
74
esphome/components/ld2412/button/__init__.py
Normal file
@@ -0,0 +1,74 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import button
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_FACTORY_RESET,
|
||||
CONF_RESTART,
|
||||
DEVICE_CLASS_RESTART,
|
||||
ENTITY_CATEGORY_CONFIG,
|
||||
ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
ICON_DATABASE,
|
||||
ICON_PULSE,
|
||||
ICON_RESTART,
|
||||
ICON_RESTART_ALERT,
|
||||
)
|
||||
|
||||
from .. import CONF_LD2412_ID, LD2412_ns, LD2412Component
|
||||
|
||||
FactoryResetButton = LD2412_ns.class_("FactoryResetButton", button.Button)
|
||||
QueryButton = LD2412_ns.class_("QueryButton", button.Button)
|
||||
RestartButton = LD2412_ns.class_("RestartButton", button.Button)
|
||||
StartDynamicBackgroundCorrectionButton = LD2412_ns.class_(
|
||||
"StartDynamicBackgroundCorrectionButton", button.Button
|
||||
)
|
||||
|
||||
CONF_QUERY_PARAMS = "query_params"
|
||||
CONF_START_DYNAMIC_BACKGROUND_CORRECTION = "start_dynamic_background_correction"
|
||||
|
||||
CONFIG_SCHEMA = {
|
||||
cv.GenerateID(CONF_LD2412_ID): cv.use_id(LD2412Component),
|
||||
cv.Optional(CONF_FACTORY_RESET): button.button_schema(
|
||||
FactoryResetButton,
|
||||
device_class=DEVICE_CLASS_RESTART,
|
||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||
icon=ICON_RESTART_ALERT,
|
||||
),
|
||||
cv.Optional(CONF_QUERY_PARAMS): button.button_schema(
|
||||
QueryButton,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
icon=ICON_DATABASE,
|
||||
),
|
||||
cv.Optional(CONF_RESTART): button.button_schema(
|
||||
RestartButton,
|
||||
device_class=DEVICE_CLASS_RESTART,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
icon=ICON_RESTART,
|
||||
),
|
||||
cv.Optional(CONF_START_DYNAMIC_BACKGROUND_CORRECTION): button.button_schema(
|
||||
StartDynamicBackgroundCorrectionButton,
|
||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||
icon=ICON_PULSE,
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
LD2412_component = await cg.get_variable(config[CONF_LD2412_ID])
|
||||
if factory_reset_config := config.get(CONF_FACTORY_RESET):
|
||||
b = await button.new_button(factory_reset_config)
|
||||
await cg.register_parented(b, config[CONF_LD2412_ID])
|
||||
cg.add(LD2412_component.set_factory_reset_button(b))
|
||||
if query_params_config := config.get(CONF_QUERY_PARAMS):
|
||||
b = await button.new_button(query_params_config)
|
||||
await cg.register_parented(b, config[CONF_LD2412_ID])
|
||||
cg.add(LD2412_component.set_query_button(b))
|
||||
if restart_config := config.get(CONF_RESTART):
|
||||
b = await button.new_button(restart_config)
|
||||
await cg.register_parented(b, config[CONF_LD2412_ID])
|
||||
cg.add(LD2412_component.set_restart_button(b))
|
||||
if start_dynamic_background_correction_config := config.get(
|
||||
CONF_START_DYNAMIC_BACKGROUND_CORRECTION
|
||||
):
|
||||
b = await button.new_button(start_dynamic_background_correction_config)
|
||||
await cg.register_parented(b, config[CONF_LD2412_ID])
|
||||
cg.add(LD2412_component.set_start_dynamic_background_correction_button(b))
|
@@ -0,0 +1,9 @@
|
||||
#include "factory_reset_button.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ld2412 {
|
||||
|
||||
void FactoryResetButton::press_action() { this->parent_->factory_reset(); }
|
||||
|
||||
} // namespace ld2412
|
||||
} // namespace esphome
|
18
esphome/components/ld2412/button/factory_reset_button.h
Normal file
18
esphome/components/ld2412/button/factory_reset_button.h
Normal file
@@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/button/button.h"
|
||||
#include "../ld2412.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ld2412 {
|
||||
|
||||
class FactoryResetButton : public button::Button, public Parented<LD2412Component> {
|
||||
public:
|
||||
FactoryResetButton() = default;
|
||||
|
||||
protected:
|
||||
void press_action() override;
|
||||
};
|
||||
|
||||
} // namespace ld2412
|
||||
} // namespace esphome
|
9
esphome/components/ld2412/button/query_button.cpp
Normal file
9
esphome/components/ld2412/button/query_button.cpp
Normal file
@@ -0,0 +1,9 @@
|
||||
#include "query_button.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ld2412 {
|
||||
|
||||
void QueryButton::press_action() { this->parent_->read_all_info(); }
|
||||
|
||||
} // namespace ld2412
|
||||
} // namespace esphome
|
18
esphome/components/ld2412/button/query_button.h
Normal file
18
esphome/components/ld2412/button/query_button.h
Normal file
@@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/button/button.h"
|
||||
#include "../ld2412.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ld2412 {
|
||||
|
||||
class QueryButton : public button::Button, public Parented<LD2412Component> {
|
||||
public:
|
||||
QueryButton() = default;
|
||||
|
||||
protected:
|
||||
void press_action() override;
|
||||
};
|
||||
|
||||
} // namespace ld2412
|
||||
} // namespace esphome
|
9
esphome/components/ld2412/button/restart_button.cpp
Normal file
9
esphome/components/ld2412/button/restart_button.cpp
Normal file
@@ -0,0 +1,9 @@
|
||||
#include "restart_button.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ld2412 {
|
||||
|
||||
void RestartButton::press_action() { this->parent_->restart_and_read_all_info(); }
|
||||
|
||||
} // namespace ld2412
|
||||
} // namespace esphome
|
18
esphome/components/ld2412/button/restart_button.h
Normal file
18
esphome/components/ld2412/button/restart_button.h
Normal file
@@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/button/button.h"
|
||||
#include "../ld2412.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ld2412 {
|
||||
|
||||
class RestartButton : public button::Button, public Parented<LD2412Component> {
|
||||
public:
|
||||
RestartButton() = default;
|
||||
|
||||
protected:
|
||||
void press_action() override;
|
||||
};
|
||||
|
||||
} // namespace ld2412
|
||||
} // namespace esphome
|
@@ -0,0 +1,11 @@
|
||||
#include "start_dynamic_background_correction_button.h"
|
||||
|
||||
#include "restart_button.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ld2412 {
|
||||
|
||||
void StartDynamicBackgroundCorrectionButton::press_action() { this->parent_->start_dynamic_background_correction(); }
|
||||
|
||||
} // namespace ld2412
|
||||
} // namespace esphome
|
@@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/button/button.h"
|
||||
#include "../ld2412.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ld2412 {
|
||||
|
||||
class StartDynamicBackgroundCorrectionButton : public button::Button, public Parented<LD2412Component> {
|
||||
public:
|
||||
StartDynamicBackgroundCorrectionButton() = default;
|
||||
|
||||
protected:
|
||||
void press_action() override;
|
||||
};
|
||||
|
||||
} // namespace ld2412
|
||||
} // namespace esphome
|
861
esphome/components/ld2412/ld2412.cpp
Normal file
861
esphome/components/ld2412/ld2412.cpp
Normal file
@@ -0,0 +1,861 @@
|
||||
#include "ld2412.h"
|
||||
|
||||
#ifdef USE_NUMBER
|
||||
#include "esphome/components/number/number.h"
|
||||
#endif
|
||||
#ifdef USE_SENSOR
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#endif
|
||||
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ld2412 {
|
||||
|
||||
static const char *const TAG = "ld2412";
|
||||
static const char *const UNKNOWN_MAC = "unknown";
|
||||
static const char *const VERSION_FMT = "%u.%02X.%02X%02X%02X%02X";
|
||||
|
||||
enum BaudRate : uint8_t {
|
||||
BAUD_RATE_9600 = 1,
|
||||
BAUD_RATE_19200 = 2,
|
||||
BAUD_RATE_38400 = 3,
|
||||
BAUD_RATE_57600 = 4,
|
||||
BAUD_RATE_115200 = 5,
|
||||
BAUD_RATE_230400 = 6,
|
||||
BAUD_RATE_256000 = 7,
|
||||
BAUD_RATE_460800 = 8,
|
||||
};
|
||||
|
||||
enum DistanceResolution : uint8_t {
|
||||
DISTANCE_RESOLUTION_0_2 = 0x03,
|
||||
DISTANCE_RESOLUTION_0_5 = 0x01,
|
||||
DISTANCE_RESOLUTION_0_75 = 0x00,
|
||||
};
|
||||
|
||||
enum LightFunction : uint8_t {
|
||||
LIGHT_FUNCTION_OFF = 0x00,
|
||||
LIGHT_FUNCTION_BELOW = 0x01,
|
||||
LIGHT_FUNCTION_ABOVE = 0x02,
|
||||
};
|
||||
|
||||
enum OutPinLevel : uint8_t {
|
||||
OUT_PIN_LEVEL_LOW = 0x01,
|
||||
OUT_PIN_LEVEL_HIGH = 0x00,
|
||||
};
|
||||
|
||||
/*
|
||||
Data Type: 6th byte
|
||||
Target states: 9th byte
|
||||
Moving target distance: 10~11th bytes
|
||||
Moving target energy: 12th byte
|
||||
Still target distance: 13~14th bytes
|
||||
Still target energy: 15th byte
|
||||
Detect distance: 16~17th bytes
|
||||
*/
|
||||
enum PeriodicData : uint8_t {
|
||||
DATA_TYPES = 6,
|
||||
TARGET_STATES = 8,
|
||||
MOVING_TARGET_LOW = 9,
|
||||
MOVING_TARGET_HIGH = 10,
|
||||
MOVING_ENERGY = 11,
|
||||
STILL_TARGET_LOW = 12,
|
||||
STILL_TARGET_HIGH = 13,
|
||||
STILL_ENERGY = 14,
|
||||
MOVING_SENSOR_START = 17,
|
||||
STILL_SENSOR_START = 31,
|
||||
LIGHT_SENSOR = 45,
|
||||
OUT_PIN_SENSOR = 38,
|
||||
};
|
||||
|
||||
enum PeriodicDataValue : uint8_t {
|
||||
HEADER = 0XAA,
|
||||
FOOTER = 0x55,
|
||||
CHECK = 0x00,
|
||||
};
|
||||
|
||||
enum AckData : uint8_t {
|
||||
COMMAND = 6,
|
||||
COMMAND_STATUS = 7,
|
||||
};
|
||||
|
||||
// Memory-efficient lookup tables
|
||||
struct StringToUint8 {
|
||||
const char *str;
|
||||
const uint8_t value;
|
||||
};
|
||||
|
||||
struct Uint8ToString {
|
||||
const uint8_t value;
|
||||
const char *str;
|
||||
};
|
||||
|
||||
constexpr StringToUint8 BAUD_RATES_BY_STR[] = {
|
||||
{"9600", BAUD_RATE_9600}, {"19200", BAUD_RATE_19200}, {"38400", BAUD_RATE_38400},
|
||||
{"57600", BAUD_RATE_57600}, {"115200", BAUD_RATE_115200}, {"230400", BAUD_RATE_230400},
|
||||
{"256000", BAUD_RATE_256000}, {"460800", BAUD_RATE_460800},
|
||||
};
|
||||
|
||||
constexpr StringToUint8 DISTANCE_RESOLUTIONS_BY_STR[] = {
|
||||
{"0.2m", DISTANCE_RESOLUTION_0_2},
|
||||
{"0.5m", DISTANCE_RESOLUTION_0_5},
|
||||
{"0.75m", DISTANCE_RESOLUTION_0_75},
|
||||
};
|
||||
|
||||
constexpr Uint8ToString DISTANCE_RESOLUTIONS_BY_UINT[] = {
|
||||
{DISTANCE_RESOLUTION_0_2, "0.2m"},
|
||||
{DISTANCE_RESOLUTION_0_5, "0.5m"},
|
||||
{DISTANCE_RESOLUTION_0_75, "0.75m"},
|
||||
};
|
||||
|
||||
constexpr StringToUint8 LIGHT_FUNCTIONS_BY_STR[] = {
|
||||
{"off", LIGHT_FUNCTION_OFF},
|
||||
{"below", LIGHT_FUNCTION_BELOW},
|
||||
{"above", LIGHT_FUNCTION_ABOVE},
|
||||
};
|
||||
|
||||
constexpr Uint8ToString LIGHT_FUNCTIONS_BY_UINT[] = {
|
||||
{LIGHT_FUNCTION_OFF, "off"},
|
||||
{LIGHT_FUNCTION_BELOW, "below"},
|
||||
{LIGHT_FUNCTION_ABOVE, "above"},
|
||||
};
|
||||
|
||||
constexpr StringToUint8 OUT_PIN_LEVELS_BY_STR[] = {
|
||||
{"low", OUT_PIN_LEVEL_LOW},
|
||||
{"high", OUT_PIN_LEVEL_HIGH},
|
||||
};
|
||||
|
||||
constexpr Uint8ToString OUT_PIN_LEVELS_BY_UINT[] = {
|
||||
{OUT_PIN_LEVEL_LOW, "low"},
|
||||
{OUT_PIN_LEVEL_HIGH, "high"},
|
||||
};
|
||||
|
||||
// Helper functions for lookups
|
||||
template<size_t N> uint8_t find_uint8(const StringToUint8 (&arr)[N], const std::string &str) {
|
||||
for (const auto &entry : arr) {
|
||||
if (str == entry.str) {
|
||||
return entry.value;
|
||||
}
|
||||
}
|
||||
return 0xFF; // Not found
|
||||
}
|
||||
|
||||
template<size_t N> const char *find_str(const Uint8ToString (&arr)[N], uint8_t value) {
|
||||
for (const auto &entry : arr) {
|
||||
if (value == entry.value) {
|
||||
return entry.str;
|
||||
}
|
||||
}
|
||||
return ""; // Not found
|
||||
}
|
||||
|
||||
static constexpr uint8_t DEFAULT_PRESENCE_TIMEOUT = 5; // Default used when number component is not defined
|
||||
// Commands
|
||||
static constexpr uint8_t CMD_ENABLE_CONF = 0xFF;
|
||||
static constexpr uint8_t CMD_DISABLE_CONF = 0xFE;
|
||||
static constexpr uint8_t CMD_ENABLE_ENG = 0x62;
|
||||
static constexpr uint8_t CMD_DISABLE_ENG = 0x63;
|
||||
static constexpr uint8_t CMD_QUERY_BASIC_CONF = 0x12;
|
||||
static constexpr uint8_t CMD_BASIC_CONF = 0x02;
|
||||
static constexpr uint8_t CMD_QUERY_VERSION = 0xA0;
|
||||
static constexpr uint8_t CMD_QUERY_DISTANCE_RESOLUTION = 0x11;
|
||||
static constexpr uint8_t CMD_SET_DISTANCE_RESOLUTION = 0x01;
|
||||
static constexpr uint8_t CMD_QUERY_LIGHT_CONTROL = 0x1C;
|
||||
static constexpr uint8_t CMD_SET_LIGHT_CONTROL = 0x0C;
|
||||
static constexpr uint8_t CMD_SET_BAUD_RATE = 0xA1;
|
||||
static constexpr uint8_t CMD_QUERY_MAC_ADDRESS = 0xA5;
|
||||
static constexpr uint8_t CMD_FACTORY_RESET = 0xA2;
|
||||
static constexpr uint8_t CMD_RESTART = 0xA3;
|
||||
static constexpr uint8_t CMD_BLUETOOTH = 0xA4;
|
||||
static constexpr uint8_t CMD_DYNAMIC_BACKGROUND_CORRECTION = 0x0B;
|
||||
static constexpr uint8_t CMD_QUERY_DYNAMIC_BACKGROUND_CORRECTION = 0x1B;
|
||||
static constexpr uint8_t CMD_MOTION_GATE_SENS = 0x03;
|
||||
static constexpr uint8_t CMD_QUERY_MOTION_GATE_SENS = 0x13;
|
||||
static constexpr uint8_t CMD_STATIC_GATE_SENS = 0x04;
|
||||
static constexpr uint8_t CMD_QUERY_STATIC_GATE_SENS = 0x14;
|
||||
static constexpr uint8_t CMD_NONE = 0x00;
|
||||
// Commands values
|
||||
static constexpr uint8_t CMD_MAX_MOVE_VALUE = 0x00;
|
||||
static constexpr uint8_t CMD_MAX_STILL_VALUE = 0x01;
|
||||
static constexpr uint8_t CMD_DURATION_VALUE = 0x02;
|
||||
// Bitmasks for target states
|
||||
static constexpr uint8_t MOVE_BITMASK = 0x01;
|
||||
static constexpr uint8_t STILL_BITMASK = 0x02;
|
||||
// Header & Footer size
|
||||
static constexpr uint8_t HEADER_FOOTER_SIZE = 4;
|
||||
// Command Header & Footer
|
||||
static constexpr uint8_t CMD_FRAME_HEADER[HEADER_FOOTER_SIZE] = {0xFD, 0xFC, 0xFB, 0xFA};
|
||||
static constexpr uint8_t CMD_FRAME_FOOTER[HEADER_FOOTER_SIZE] = {0x04, 0x03, 0x02, 0x01};
|
||||
// Data Header & Footer
|
||||
static constexpr uint8_t DATA_FRAME_HEADER[HEADER_FOOTER_SIZE] = {0xF4, 0xF3, 0xF2, 0xF1};
|
||||
static constexpr uint8_t DATA_FRAME_FOOTER[HEADER_FOOTER_SIZE] = {0xF8, 0xF7, 0xF6, 0xF5};
|
||||
// MAC address the module uses when Bluetooth is disabled
|
||||
static constexpr uint8_t NO_MAC[] = {0x08, 0x05, 0x04, 0x03, 0x02, 0x01};
|
||||
|
||||
static inline int two_byte_to_int(char firstbyte, char secondbyte) { return (int16_t) (secondbyte << 8) + firstbyte; }
|
||||
|
||||
static inline bool validate_header_footer(const uint8_t *header_footer, const uint8_t *buffer) {
|
||||
return std::memcmp(header_footer, buffer, HEADER_FOOTER_SIZE) == 0;
|
||||
}
|
||||
|
||||
void LD2412Component::dump_config() {
|
||||
std::string mac_str =
|
||||
mac_address_is_valid(this->mac_address_) ? format_mac_address_pretty(this->mac_address_) : UNKNOWN_MAC;
|
||||
std::string version = str_sprintf(VERSION_FMT, this->version_[1], this->version_[0], this->version_[5],
|
||||
this->version_[4], this->version_[3], this->version_[2]);
|
||||
ESP_LOGCONFIG(TAG,
|
||||
"LD2412:\n"
|
||||
" Firmware version: %s\n"
|
||||
" MAC address: %s",
|
||||
version.c_str(), mac_str.c_str());
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
ESP_LOGCONFIG(TAG, "Binary Sensors:");
|
||||
LOG_BINARY_SENSOR(" ", "DynamicBackgroundCorrectionStatus",
|
||||
this->dynamic_background_correction_status_binary_sensor_);
|
||||
LOG_BINARY_SENSOR(" ", "MovingTarget", this->moving_target_binary_sensor_);
|
||||
LOG_BINARY_SENSOR(" ", "StillTarget", this->still_target_binary_sensor_);
|
||||
LOG_BINARY_SENSOR(" ", "Target", this->target_binary_sensor_);
|
||||
#endif
|
||||
#ifdef USE_SENSOR
|
||||
ESP_LOGCONFIG(TAG, "Sensors:");
|
||||
LOG_SENSOR_WITH_DEDUP_SAFE(" ", "Light", this->light_sensor_);
|
||||
LOG_SENSOR_WITH_DEDUP_SAFE(" ", "DetectionDistance", this->detection_distance_sensor_);
|
||||
LOG_SENSOR_WITH_DEDUP_SAFE(" ", "MovingTargetDistance", this->moving_target_distance_sensor_);
|
||||
LOG_SENSOR_WITH_DEDUP_SAFE(" ", "MovingTargetEnergy", this->moving_target_energy_sensor_);
|
||||
LOG_SENSOR_WITH_DEDUP_SAFE(" ", "StillTargetDistance", this->still_target_distance_sensor_);
|
||||
LOG_SENSOR_WITH_DEDUP_SAFE(" ", "StillTargetEnergy", this->still_target_energy_sensor_);
|
||||
for (auto &s : this->gate_still_sensors_) {
|
||||
LOG_SENSOR_WITH_DEDUP_SAFE(" ", "GateStill", s);
|
||||
}
|
||||
for (auto &s : this->gate_move_sensors_) {
|
||||
LOG_SENSOR_WITH_DEDUP_SAFE(" ", "GateMove", s);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
ESP_LOGCONFIG(TAG, "Text Sensors:");
|
||||
LOG_TEXT_SENSOR(" ", "MAC address", this->mac_text_sensor_);
|
||||
LOG_TEXT_SENSOR(" ", "Version", this->version_text_sensor_);
|
||||
#endif
|
||||
#ifdef USE_NUMBER
|
||||
ESP_LOGCONFIG(TAG, "Numbers:");
|
||||
LOG_NUMBER(" ", "LightThreshold", this->light_threshold_number_);
|
||||
LOG_NUMBER(" ", "MaxDistanceGate", this->max_distance_gate_number_);
|
||||
LOG_NUMBER(" ", "MinDistanceGate", this->min_distance_gate_number_);
|
||||
LOG_NUMBER(" ", "Timeout", this->timeout_number_);
|
||||
for (number::Number *n : this->gate_move_threshold_numbers_) {
|
||||
LOG_NUMBER(" ", "Move Thresholds", n);
|
||||
}
|
||||
for (number::Number *n : this->gate_still_threshold_numbers_) {
|
||||
LOG_NUMBER(" ", "Still Thresholds", n);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_SELECT
|
||||
ESP_LOGCONFIG(TAG, "Selects:");
|
||||
LOG_SELECT(" ", "BaudRate", this->baud_rate_select_);
|
||||
LOG_SELECT(" ", "DistanceResolution", this->distance_resolution_select_);
|
||||
LOG_SELECT(" ", "LightFunction", this->light_function_select_);
|
||||
LOG_SELECT(" ", "OutPinLevel", this->out_pin_level_select_);
|
||||
#endif
|
||||
#ifdef USE_SWITCH
|
||||
ESP_LOGCONFIG(TAG, "Switches:");
|
||||
LOG_SWITCH(" ", "Bluetooth", this->bluetooth_switch_);
|
||||
LOG_SWITCH(" ", "EngineeringMode", this->engineering_mode_switch_);
|
||||
#endif
|
||||
#ifdef USE_BUTTON
|
||||
ESP_LOGCONFIG(TAG, "Buttons:");
|
||||
LOG_BUTTON(" ", "FactoryReset", this->factory_reset_button_);
|
||||
LOG_BUTTON(" ", "Query", this->query_button_);
|
||||
LOG_BUTTON(" ", "Restart", this->restart_button_);
|
||||
LOG_BUTTON(" ", "StartDynamicBackgroundCorrection", this->start_dynamic_background_correction_button_);
|
||||
#endif
|
||||
}
|
||||
|
||||
void LD2412Component::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Running setup");
|
||||
this->read_all_info();
|
||||
}
|
||||
|
||||
void LD2412Component::read_all_info() {
|
||||
this->set_config_mode_(true);
|
||||
this->get_version_();
|
||||
delay(10); // NOLINT
|
||||
this->get_mac_();
|
||||
delay(10); // NOLINT
|
||||
this->get_distance_resolution_();
|
||||
delay(10); // NOLINT
|
||||
this->query_parameters_();
|
||||
delay(10); // NOLINT
|
||||
this->query_dynamic_background_correction_();
|
||||
delay(10); // NOLINT
|
||||
this->query_light_control_();
|
||||
delay(10); // NOLINT
|
||||
#ifdef USE_NUMBER
|
||||
this->get_gate_threshold();
|
||||
delay(10); // NOLINT
|
||||
#endif
|
||||
this->set_config_mode_(false);
|
||||
#ifdef USE_SELECT
|
||||
const auto baud_rate = std::to_string(this->parent_->get_baud_rate());
|
||||
if (this->baud_rate_select_ != nullptr) {
|
||||
this->baud_rate_select_->publish_state(baud_rate);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void LD2412Component::restart_and_read_all_info() {
|
||||
this->set_config_mode_(true);
|
||||
this->restart_();
|
||||
this->set_timeout(1000, [this]() { this->read_all_info(); });
|
||||
}
|
||||
|
||||
void LD2412Component::loop() {
|
||||
while (this->available()) {
|
||||
this->readline_(this->read());
|
||||
}
|
||||
}
|
||||
|
||||
void LD2412Component::send_command_(uint8_t command, const uint8_t *command_value, uint8_t command_value_len) {
|
||||
ESP_LOGV(TAG, "Sending COMMAND %02X", command);
|
||||
// frame header bytes
|
||||
this->write_array(CMD_FRAME_HEADER, HEADER_FOOTER_SIZE);
|
||||
// length bytes
|
||||
uint8_t len = 2;
|
||||
if (command_value != nullptr) {
|
||||
len += command_value_len;
|
||||
}
|
||||
// 2 length bytes (low, high) + 2 command bytes (low, high)
|
||||
uint8_t len_cmd[] = {len, 0x00, command, 0x00};
|
||||
this->write_array(len_cmd, sizeof(len_cmd));
|
||||
|
||||
// command value bytes
|
||||
if (command_value != nullptr) {
|
||||
this->write_array(command_value, command_value_len);
|
||||
}
|
||||
// frame footer bytes
|
||||
this->write_array(CMD_FRAME_FOOTER, HEADER_FOOTER_SIZE);
|
||||
|
||||
if (command != CMD_ENABLE_CONF && command != CMD_DISABLE_CONF) {
|
||||
delay(30); // NOLINT
|
||||
}
|
||||
delay(20); // NOLINT
|
||||
}
|
||||
|
||||
void LD2412Component::handle_periodic_data_() {
|
||||
// 4 frame header bytes + 2 length bytes + 1 data end byte + 1 crc byte + 4 frame footer bytes
|
||||
// data header=0xAA, data footer=0x55, crc=0x00
|
||||
if (this->buffer_pos_ < 12 || !ld2412::validate_header_footer(DATA_FRAME_HEADER, this->buffer_data_) ||
|
||||
this->buffer_data_[7] != HEADER || this->buffer_data_[this->buffer_pos_ - 6] != FOOTER) {
|
||||
return;
|
||||
}
|
||||
/*
|
||||
Data Type: 7th
|
||||
0x01: Engineering mode
|
||||
0x02: Normal mode
|
||||
*/
|
||||
bool engineering_mode = this->buffer_data_[DATA_TYPES] == 0x01;
|
||||
#ifdef USE_SWITCH
|
||||
if (this->engineering_mode_switch_ != nullptr) {
|
||||
this->engineering_mode_switch_->publish_state(engineering_mode);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
/*
|
||||
Target states: 9th
|
||||
0x00 = No target
|
||||
0x01 = Moving targets
|
||||
0x02 = Still targets
|
||||
0x03 = Moving+Still targets
|
||||
*/
|
||||
char target_state = this->buffer_data_[TARGET_STATES];
|
||||
if (this->target_binary_sensor_ != nullptr) {
|
||||
this->target_binary_sensor_->publish_state(target_state != 0x00);
|
||||
}
|
||||
if (this->moving_target_binary_sensor_ != nullptr) {
|
||||
this->moving_target_binary_sensor_->publish_state(target_state & MOVE_BITMASK);
|
||||
}
|
||||
if (this->still_target_binary_sensor_ != nullptr) {
|
||||
this->still_target_binary_sensor_->publish_state(target_state & STILL_BITMASK);
|
||||
}
|
||||
#endif
|
||||
/*
|
||||
Moving target distance: 10~11th bytes
|
||||
Moving target energy: 12th byte
|
||||
Still target distance: 13~14th bytes
|
||||
Still target energy: 15th byte
|
||||
Detect distance: 16~17th bytes
|
||||
*/
|
||||
#ifdef USE_SENSOR
|
||||
SAFE_PUBLISH_SENSOR(
|
||||
this->moving_target_distance_sensor_,
|
||||
ld2412::two_byte_to_int(this->buffer_data_[MOVING_TARGET_LOW], this->buffer_data_[MOVING_TARGET_HIGH]))
|
||||
SAFE_PUBLISH_SENSOR(this->moving_target_energy_sensor_, this->buffer_data_[MOVING_ENERGY])
|
||||
SAFE_PUBLISH_SENSOR(
|
||||
this->still_target_distance_sensor_,
|
||||
ld2412::two_byte_to_int(this->buffer_data_[STILL_TARGET_LOW], this->buffer_data_[STILL_TARGET_HIGH]))
|
||||
SAFE_PUBLISH_SENSOR(this->still_target_energy_sensor_, this->buffer_data_[STILL_ENERGY])
|
||||
if (this->detection_distance_sensor_ != nullptr) {
|
||||
int new_detect_distance = 0;
|
||||
if (target_state != 0x00 && (target_state & MOVE_BITMASK)) {
|
||||
new_detect_distance =
|
||||
ld2412::two_byte_to_int(this->buffer_data_[MOVING_TARGET_LOW], this->buffer_data_[MOVING_TARGET_HIGH]);
|
||||
} else if (target_state != 0x00) {
|
||||
new_detect_distance =
|
||||
ld2412::two_byte_to_int(this->buffer_data_[STILL_TARGET_LOW], this->buffer_data_[STILL_TARGET_HIGH]);
|
||||
}
|
||||
this->detection_distance_sensor_->publish_state_if_not_dup(new_detect_distance);
|
||||
}
|
||||
if (engineering_mode) {
|
||||
/*
|
||||
Moving distance range: 18th byte
|
||||
Still distance range: 19th byte
|
||||
Moving energy: 20~28th bytes
|
||||
*/
|
||||
for (uint8_t i = 0; i < TOTAL_GATES; i++) {
|
||||
SAFE_PUBLISH_SENSOR(this->gate_move_sensors_[i], this->buffer_data_[MOVING_SENSOR_START + i])
|
||||
}
|
||||
/*
|
||||
Still energy: 29~37th bytes
|
||||
*/
|
||||
for (uint8_t i = 0; i < TOTAL_GATES; i++) {
|
||||
SAFE_PUBLISH_SENSOR(this->gate_still_sensors_[i], this->buffer_data_[STILL_SENSOR_START + i])
|
||||
}
|
||||
/*
|
||||
Light sensor: 38th bytes
|
||||
*/
|
||||
SAFE_PUBLISH_SENSOR(this->light_sensor_, this->buffer_data_[LIGHT_SENSOR])
|
||||
} else {
|
||||
for (auto &gate_move_sensor : this->gate_move_sensors_) {
|
||||
SAFE_PUBLISH_SENSOR_UNKNOWN(gate_move_sensor)
|
||||
}
|
||||
for (auto &gate_still_sensor : this->gate_still_sensors_) {
|
||||
SAFE_PUBLISH_SENSOR_UNKNOWN(gate_still_sensor)
|
||||
}
|
||||
SAFE_PUBLISH_SENSOR_UNKNOWN(this->light_sensor_)
|
||||
}
|
||||
#endif
|
||||
// the radar module won't tell us when it's done, so we just have to keep polling...
|
||||
if (this->dynamic_background_correction_active_) {
|
||||
this->set_config_mode_(true);
|
||||
this->query_dynamic_background_correction_();
|
||||
this->set_config_mode_(false);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef USE_NUMBER
|
||||
std::function<void(void)> set_number_value(number::Number *n, float value) {
|
||||
if (n != nullptr && (!n->has_state() || n->state != value)) {
|
||||
n->state = value;
|
||||
return [n, value]() { n->publish_state(value); };
|
||||
}
|
||||
return []() {};
|
||||
}
|
||||
#endif
|
||||
|
||||
bool LD2412Component::handle_ack_data_() {
|
||||
ESP_LOGV(TAG, "Handling ACK DATA for COMMAND %02X", this->buffer_data_[COMMAND]);
|
||||
if (this->buffer_pos_ < 10) {
|
||||
ESP_LOGW(TAG, "Invalid length");
|
||||
return true;
|
||||
}
|
||||
if (!ld2412::validate_header_footer(CMD_FRAME_HEADER, this->buffer_data_)) {
|
||||
ESP_LOGW(TAG, "Invalid header: %s", format_hex_pretty(this->buffer_data_, HEADER_FOOTER_SIZE).c_str());
|
||||
return true;
|
||||
}
|
||||
if (this->buffer_data_[COMMAND_STATUS] != 0x01) {
|
||||
ESP_LOGW(TAG, "Invalid status");
|
||||
return true;
|
||||
}
|
||||
if (this->buffer_data_[8] || this->buffer_data_[9]) {
|
||||
ESP_LOGW(TAG, "Invalid command: %02X, %02X", this->buffer_data_[8], this->buffer_data_[9]);
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (this->buffer_data_[COMMAND]) {
|
||||
case CMD_ENABLE_CONF:
|
||||
ESP_LOGV(TAG, "Enable conf");
|
||||
break;
|
||||
|
||||
case CMD_DISABLE_CONF:
|
||||
ESP_LOGV(TAG, "Disabled conf");
|
||||
break;
|
||||
|
||||
case CMD_SET_BAUD_RATE:
|
||||
ESP_LOGV(TAG, "Baud rate change");
|
||||
#ifdef USE_SELECT
|
||||
if (this->baud_rate_select_ != nullptr) {
|
||||
ESP_LOGW(TAG, "Change baud rate to %s and reinstall", this->baud_rate_select_->state.c_str());
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
|
||||
case CMD_QUERY_VERSION: {
|
||||
std::memcpy(this->version_, &this->buffer_data_[12], sizeof(this->version_));
|
||||
std::string version = str_sprintf(VERSION_FMT, this->version_[1], this->version_[0], this->version_[5],
|
||||
this->version_[4], this->version_[3], this->version_[2]);
|
||||
ESP_LOGV(TAG, "Firmware version: %s", version.c_str());
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
if (this->version_text_sensor_ != nullptr) {
|
||||
this->version_text_sensor_->publish_state(version);
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
case CMD_QUERY_DISTANCE_RESOLUTION: {
|
||||
const auto *distance_resolution = find_str(DISTANCE_RESOLUTIONS_BY_UINT, this->buffer_data_[10]);
|
||||
ESP_LOGV(TAG, "Distance resolution: %s", distance_resolution);
|
||||
#ifdef USE_SELECT
|
||||
if (this->distance_resolution_select_ != nullptr) {
|
||||
this->distance_resolution_select_->publish_state(distance_resolution);
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
|
||||
case CMD_QUERY_LIGHT_CONTROL: {
|
||||
this->light_function_ = this->buffer_data_[10];
|
||||
this->light_threshold_ = this->buffer_data_[11];
|
||||
const auto *light_function_str = find_str(LIGHT_FUNCTIONS_BY_UINT, this->light_function_);
|
||||
ESP_LOGV(TAG,
|
||||
"Light function: %s\n"
|
||||
"Light threshold: %u",
|
||||
light_function_str, this->light_threshold_);
|
||||
#ifdef USE_SELECT
|
||||
if (this->light_function_select_ != nullptr) {
|
||||
this->light_function_select_->publish_state(light_function_str);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_NUMBER
|
||||
if (this->light_threshold_number_ != nullptr) {
|
||||
this->light_threshold_number_->publish_state(static_cast<float>(this->light_threshold_));
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
|
||||
case CMD_QUERY_MAC_ADDRESS: {
|
||||
if (this->buffer_pos_ < 20) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this->bluetooth_on_ = std::memcmp(&this->buffer_data_[10], NO_MAC, sizeof(NO_MAC)) != 0;
|
||||
if (this->bluetooth_on_) {
|
||||
std::memcpy(this->mac_address_, &this->buffer_data_[10], sizeof(this->mac_address_));
|
||||
}
|
||||
|
||||
std::string mac_str =
|
||||
mac_address_is_valid(this->mac_address_) ? format_mac_address_pretty(this->mac_address_) : UNKNOWN_MAC;
|
||||
ESP_LOGV(TAG, "MAC address: %s", mac_str.c_str());
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
if (this->mac_text_sensor_ != nullptr) {
|
||||
this->mac_text_sensor_->publish_state(mac_str);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_SWITCH
|
||||
if (this->bluetooth_switch_ != nullptr) {
|
||||
this->bluetooth_switch_->publish_state(this->bluetooth_on_);
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
|
||||
case CMD_SET_DISTANCE_RESOLUTION:
|
||||
ESP_LOGV(TAG, "Handled set distance resolution command");
|
||||
break;
|
||||
|
||||
case CMD_QUERY_DYNAMIC_BACKGROUND_CORRECTION: {
|
||||
ESP_LOGV(TAG, "Handled query dynamic background correction");
|
||||
bool dynamic_background_correction_active = (this->buffer_data_[10] != 0x00);
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
if (this->dynamic_background_correction_status_binary_sensor_ != nullptr) {
|
||||
this->dynamic_background_correction_status_binary_sensor_->publish_state(dynamic_background_correction_active);
|
||||
}
|
||||
#endif
|
||||
this->dynamic_background_correction_active_ = dynamic_background_correction_active;
|
||||
break;
|
||||
}
|
||||
|
||||
case CMD_BLUETOOTH:
|
||||
ESP_LOGV(TAG, "Handled bluetooth command");
|
||||
break;
|
||||
|
||||
case CMD_SET_LIGHT_CONTROL:
|
||||
ESP_LOGV(TAG, "Handled set light control command");
|
||||
break;
|
||||
|
||||
case CMD_QUERY_MOTION_GATE_SENS: {
|
||||
#ifdef USE_NUMBER
|
||||
std::vector<std::function<void(void)>> updates;
|
||||
updates.reserve(this->gate_still_threshold_numbers_.size());
|
||||
for (size_t i = 0; i < this->gate_still_threshold_numbers_.size(); i++) {
|
||||
updates.push_back(set_number_value(this->gate_move_threshold_numbers_[i], this->buffer_data_[10 + i]));
|
||||
}
|
||||
for (auto &update : updates) {
|
||||
update();
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
|
||||
case CMD_QUERY_STATIC_GATE_SENS: {
|
||||
#ifdef USE_NUMBER
|
||||
std::vector<std::function<void(void)>> updates;
|
||||
updates.reserve(this->gate_still_threshold_numbers_.size());
|
||||
for (size_t i = 0; i < this->gate_still_threshold_numbers_.size(); i++) {
|
||||
updates.push_back(set_number_value(this->gate_still_threshold_numbers_[i], this->buffer_data_[10 + i]));
|
||||
}
|
||||
for (auto &update : updates) {
|
||||
update();
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
|
||||
case CMD_QUERY_BASIC_CONF: // Query parameters response
|
||||
{
|
||||
#ifdef USE_NUMBER
|
||||
/*
|
||||
Moving distance range: 9th byte
|
||||
Still distance range: 10th byte
|
||||
*/
|
||||
std::vector<std::function<void(void)>> updates;
|
||||
updates.push_back(set_number_value(this->min_distance_gate_number_, this->buffer_data_[10]));
|
||||
updates.push_back(set_number_value(this->max_distance_gate_number_, this->buffer_data_[11] - 1));
|
||||
ESP_LOGV(TAG, "min_distance_gate_number_: %u, max_distance_gate_number_ %u", this->buffer_data_[10],
|
||||
this->buffer_data_[11]);
|
||||
/*
|
||||
None Duration: 11~12th bytes
|
||||
*/
|
||||
updates.push_back(set_number_value(this->timeout_number_,
|
||||
ld2412::two_byte_to_int(this->buffer_data_[12], this->buffer_data_[13])));
|
||||
ESP_LOGV(TAG, "timeout_number_: %u", ld2412::two_byte_to_int(this->buffer_data_[12], this->buffer_data_[13]));
|
||||
/*
|
||||
Output pin configuration: 13th bytes
|
||||
*/
|
||||
this->out_pin_level_ = this->buffer_data_[14];
|
||||
#ifdef USE_SELECT
|
||||
const auto *out_pin_level_str = find_str(OUT_PIN_LEVELS_BY_UINT, this->out_pin_level_);
|
||||
if (this->out_pin_level_select_ != nullptr) {
|
||||
this->out_pin_level_select_->publish_state(out_pin_level_str);
|
||||
}
|
||||
#endif
|
||||
for (auto &update : updates) {
|
||||
update();
|
||||
}
|
||||
#endif
|
||||
} break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void LD2412Component::readline_(int readch) {
|
||||
if (readch < 0) {
|
||||
return; // No data available
|
||||
}
|
||||
if (this->buffer_pos_ < HEADER_FOOTER_SIZE && readch != DATA_FRAME_HEADER[this->buffer_pos_] &&
|
||||
readch != CMD_FRAME_HEADER[this->buffer_pos_]) {
|
||||
this->buffer_pos_ = 0;
|
||||
return;
|
||||
}
|
||||
if (this->buffer_pos_ < MAX_LINE_LENGTH - 1) {
|
||||
this->buffer_data_[this->buffer_pos_++] = readch;
|
||||
this->buffer_data_[this->buffer_pos_] = 0;
|
||||
} else {
|
||||
// We should never get here, but just in case...
|
||||
ESP_LOGW(TAG, "Max command length exceeded; ignoring");
|
||||
this->buffer_pos_ = 0;
|
||||
}
|
||||
if (this->buffer_pos_ < 4) {
|
||||
return; // Not enough data to process yet
|
||||
}
|
||||
if (ld2412::validate_header_footer(DATA_FRAME_FOOTER, &this->buffer_data_[this->buffer_pos_ - 4])) {
|
||||
ESP_LOGV(TAG, "Handling Periodic Data: %s", format_hex_pretty(this->buffer_data_, this->buffer_pos_).c_str());
|
||||
this->handle_periodic_data_();
|
||||
this->buffer_pos_ = 0; // Reset position index for next message
|
||||
} else if (ld2412::validate_header_footer(CMD_FRAME_FOOTER, &this->buffer_data_[this->buffer_pos_ - 4])) {
|
||||
ESP_LOGV(TAG, "Handling Ack Data: %s", format_hex_pretty(this->buffer_data_, this->buffer_pos_).c_str());
|
||||
if (this->handle_ack_data_()) {
|
||||
this->buffer_pos_ = 0; // Reset position index for next message
|
||||
} else {
|
||||
ESP_LOGV(TAG, "Ack Data incomplete");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LD2412Component::set_config_mode_(bool enable) {
|
||||
const uint8_t cmd = enable ? CMD_ENABLE_CONF : CMD_DISABLE_CONF;
|
||||
const uint8_t cmd_value[2] = {0x01, 0x00};
|
||||
this->send_command_(cmd, enable ? cmd_value : nullptr, sizeof(cmd_value));
|
||||
}
|
||||
|
||||
void LD2412Component::set_bluetooth(bool enable) {
|
||||
this->set_config_mode_(true);
|
||||
const uint8_t cmd_value[2] = {enable ? (uint8_t) 0x01 : (uint8_t) 0x00, 0x00};
|
||||
this->send_command_(CMD_BLUETOOTH, cmd_value, sizeof(cmd_value));
|
||||
this->set_timeout(200, [this]() { this->restart_and_read_all_info(); });
|
||||
}
|
||||
|
||||
void LD2412Component::set_distance_resolution(const std::string &state) {
|
||||
this->set_config_mode_(true);
|
||||
const uint8_t cmd_value[6] = {find_uint8(DISTANCE_RESOLUTIONS_BY_STR, state), 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
this->send_command_(CMD_SET_DISTANCE_RESOLUTION, cmd_value, sizeof(cmd_value));
|
||||
this->set_timeout(200, [this]() { this->restart_and_read_all_info(); });
|
||||
}
|
||||
|
||||
void LD2412Component::set_baud_rate(const std::string &state) {
|
||||
this->set_config_mode_(true);
|
||||
const uint8_t cmd_value[2] = {find_uint8(BAUD_RATES_BY_STR, state), 0x00};
|
||||
this->send_command_(CMD_SET_BAUD_RATE, cmd_value, sizeof(cmd_value));
|
||||
this->set_timeout(200, [this]() { this->restart_(); });
|
||||
}
|
||||
|
||||
void LD2412Component::query_dynamic_background_correction_() {
|
||||
this->send_command_(CMD_QUERY_DYNAMIC_BACKGROUND_CORRECTION, nullptr, 0);
|
||||
}
|
||||
|
||||
void LD2412Component::start_dynamic_background_correction() {
|
||||
if (this->dynamic_background_correction_active_) {
|
||||
return; // Already in progress
|
||||
}
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
if (this->dynamic_background_correction_status_binary_sensor_ != nullptr) {
|
||||
this->dynamic_background_correction_status_binary_sensor_->publish_state(true);
|
||||
}
|
||||
#endif
|
||||
this->dynamic_background_correction_active_ = true;
|
||||
this->set_config_mode_(true);
|
||||
this->send_command_(CMD_DYNAMIC_BACKGROUND_CORRECTION, nullptr, 0);
|
||||
this->set_config_mode_(false);
|
||||
}
|
||||
|
||||
void LD2412Component::set_engineering_mode(bool enable) {
|
||||
const uint8_t cmd = enable ? CMD_ENABLE_ENG : CMD_DISABLE_ENG;
|
||||
this->set_config_mode_(true);
|
||||
this->send_command_(cmd, nullptr, 0);
|
||||
this->set_config_mode_(false);
|
||||
}
|
||||
|
||||
void LD2412Component::factory_reset() {
|
||||
this->set_config_mode_(true);
|
||||
this->send_command_(CMD_FACTORY_RESET, nullptr, 0);
|
||||
this->set_timeout(2000, [this]() { this->restart_and_read_all_info(); });
|
||||
}
|
||||
|
||||
void LD2412Component::restart_() { this->send_command_(CMD_RESTART, nullptr, 0); }
|
||||
|
||||
void LD2412Component::query_parameters_() { this->send_command_(CMD_QUERY_BASIC_CONF, nullptr, 0); }
|
||||
|
||||
void LD2412Component::get_version_() { this->send_command_(CMD_QUERY_VERSION, nullptr, 0); }
|
||||
|
||||
void LD2412Component::get_mac_() {
|
||||
const uint8_t cmd_value[2] = {0x01, 0x00};
|
||||
this->send_command_(CMD_QUERY_MAC_ADDRESS, cmd_value, sizeof(cmd_value));
|
||||
}
|
||||
|
||||
void LD2412Component::get_distance_resolution_() { this->send_command_(CMD_QUERY_DISTANCE_RESOLUTION, nullptr, 0); }
|
||||
|
||||
void LD2412Component::query_light_control_() { this->send_command_(CMD_QUERY_LIGHT_CONTROL, nullptr, 0); }
|
||||
|
||||
void LD2412Component::set_basic_config() {
|
||||
#ifdef USE_NUMBER
|
||||
if (!this->min_distance_gate_number_->has_state() || !this->max_distance_gate_number_->has_state() ||
|
||||
!this->timeout_number_->has_state()) {
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_SELECT
|
||||
if (!this->out_pin_level_select_->has_state()) {
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
uint8_t value[5] = {
|
||||
#ifdef USE_NUMBER
|
||||
lowbyte(static_cast<int>(this->min_distance_gate_number_->state)),
|
||||
lowbyte(static_cast<int>(this->max_distance_gate_number_->state) + 1),
|
||||
lowbyte(static_cast<int>(this->timeout_number_->state)),
|
||||
highbyte(static_cast<int>(this->timeout_number_->state)),
|
||||
#else
|
||||
1, TOTAL_GATES, DEFAULT_PRESENCE_TIMEOUT, 0,
|
||||
#endif
|
||||
#ifdef USE_SELECT
|
||||
find_uint8(OUT_PIN_LEVELS_BY_STR, this->out_pin_level_select_->state),
|
||||
#else
|
||||
0x01, // Default value if not using select
|
||||
#endif
|
||||
};
|
||||
this->set_config_mode_(true);
|
||||
this->send_command_(CMD_BASIC_CONF, value, sizeof(value));
|
||||
this->set_config_mode_(false);
|
||||
}
|
||||
|
||||
#ifdef USE_NUMBER
|
||||
void LD2412Component::set_gate_threshold() {
|
||||
if (this->gate_move_threshold_numbers_.empty() && this->gate_still_threshold_numbers_.empty()) {
|
||||
return; // No gate threshold numbers set; nothing to do here
|
||||
}
|
||||
uint8_t value[TOTAL_GATES] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
|
||||
this->set_config_mode_(true);
|
||||
if (!this->gate_move_threshold_numbers_.empty()) {
|
||||
for (size_t i = 0; i < this->gate_move_threshold_numbers_.size(); i++) {
|
||||
value[i] = lowbyte(static_cast<int>(this->gate_move_threshold_numbers_[i]->state));
|
||||
}
|
||||
this->send_command_(CMD_MOTION_GATE_SENS, value, sizeof(value));
|
||||
}
|
||||
if (!this->gate_still_threshold_numbers_.empty()) {
|
||||
for (size_t i = 0; i < this->gate_still_threshold_numbers_.size(); i++) {
|
||||
value[i] = lowbyte(static_cast<int>(this->gate_still_threshold_numbers_[i]->state));
|
||||
}
|
||||
this->send_command_(CMD_STATIC_GATE_SENS, value, sizeof(value));
|
||||
}
|
||||
this->set_config_mode_(false);
|
||||
}
|
||||
|
||||
void LD2412Component::get_gate_threshold() {
|
||||
this->send_command_(CMD_QUERY_MOTION_GATE_SENS, nullptr, 0);
|
||||
this->send_command_(CMD_QUERY_STATIC_GATE_SENS, nullptr, 0);
|
||||
}
|
||||
|
||||
void LD2412Component::set_gate_still_threshold_number(uint8_t gate, number::Number *n) {
|
||||
this->gate_still_threshold_numbers_[gate] = n;
|
||||
}
|
||||
|
||||
void LD2412Component::set_gate_move_threshold_number(uint8_t gate, number::Number *n) {
|
||||
this->gate_move_threshold_numbers_[gate] = n;
|
||||
}
|
||||
#endif
|
||||
|
||||
void LD2412Component::set_light_out_control() {
|
||||
#ifdef USE_NUMBER
|
||||
if (this->light_threshold_number_ != nullptr && this->light_threshold_number_->has_state()) {
|
||||
this->light_threshold_ = static_cast<uint8_t>(this->light_threshold_number_->state);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_SELECT
|
||||
if (this->light_function_select_ != nullptr && this->light_function_select_->has_state()) {
|
||||
this->light_function_ = find_uint8(LIGHT_FUNCTIONS_BY_STR, this->light_function_select_->state);
|
||||
}
|
||||
#endif
|
||||
uint8_t value[2] = {this->light_function_, this->light_threshold_};
|
||||
this->set_config_mode_(true);
|
||||
this->send_command_(CMD_SET_LIGHT_CONTROL, value, sizeof(value));
|
||||
this->query_light_control_();
|
||||
this->set_timeout(200, [this]() { this->restart_and_read_all_info(); });
|
||||
}
|
||||
|
||||
#ifdef USE_SENSOR
|
||||
// These could leak memory, but they are only set once prior to 'setup()' and should never be used again.
|
||||
void LD2412Component::set_gate_move_sensor(uint8_t gate, sensor::Sensor *s) {
|
||||
this->gate_move_sensors_[gate] = new SensorWithDedup<uint8_t>(s);
|
||||
}
|
||||
void LD2412Component::set_gate_still_sensor(uint8_t gate, sensor::Sensor *s) {
|
||||
this->gate_still_sensors_[gate] = new SensorWithDedup<uint8_t>(s);
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace ld2412
|
||||
} // namespace esphome
|
141
esphome/components/ld2412/ld2412.h
Normal file
141
esphome/components/ld2412/ld2412.h
Normal file
@@ -0,0 +1,141 @@
|
||||
#pragma once
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/component.h"
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
#include "esphome/components/binary_sensor/binary_sensor.h"
|
||||
#endif
|
||||
#ifdef USE_SENSOR
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#endif
|
||||
#ifdef USE_NUMBER
|
||||
#include "esphome/components/number/number.h"
|
||||
#endif
|
||||
#ifdef USE_SWITCH
|
||||
#include "esphome/components/switch/switch.h"
|
||||
#endif
|
||||
#ifdef USE_BUTTON
|
||||
#include "esphome/components/button/button.h"
|
||||
#endif
|
||||
#ifdef USE_SELECT
|
||||
#include "esphome/components/select/select.h"
|
||||
#endif
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
#include "esphome/components/text_sensor/text_sensor.h"
|
||||
#endif
|
||||
#include "esphome/components/ld24xx/ld24xx.h"
|
||||
#include "esphome/components/uart/uart.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
#include <array>
|
||||
|
||||
namespace esphome {
|
||||
namespace ld2412 {
|
||||
|
||||
using namespace ld24xx;
|
||||
|
||||
static constexpr uint8_t MAX_LINE_LENGTH = 54; // Max characters for serial buffer
|
||||
static constexpr uint8_t TOTAL_GATES = 14; // Total number of gates supported by the LD2412
|
||||
|
||||
class LD2412Component : public Component, public uart::UARTDevice {
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
SUB_BINARY_SENSOR(dynamic_background_correction_status)
|
||||
SUB_BINARY_SENSOR(moving_target)
|
||||
SUB_BINARY_SENSOR(still_target)
|
||||
SUB_BINARY_SENSOR(target)
|
||||
#endif
|
||||
#ifdef USE_SENSOR
|
||||
SUB_SENSOR_WITH_DEDUP(light, uint8_t)
|
||||
SUB_SENSOR_WITH_DEDUP(detection_distance, int)
|
||||
SUB_SENSOR_WITH_DEDUP(moving_target_distance, int)
|
||||
SUB_SENSOR_WITH_DEDUP(moving_target_energy, uint8_t)
|
||||
SUB_SENSOR_WITH_DEDUP(still_target_distance, int)
|
||||
SUB_SENSOR_WITH_DEDUP(still_target_energy, uint8_t)
|
||||
#endif
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
SUB_TEXT_SENSOR(mac)
|
||||
SUB_TEXT_SENSOR(version)
|
||||
#endif
|
||||
#ifdef USE_NUMBER
|
||||
SUB_NUMBER(light_threshold)
|
||||
SUB_NUMBER(max_distance_gate)
|
||||
SUB_NUMBER(min_distance_gate)
|
||||
SUB_NUMBER(timeout)
|
||||
#endif
|
||||
#ifdef USE_SELECT
|
||||
SUB_SELECT(baud_rate)
|
||||
SUB_SELECT(distance_resolution)
|
||||
SUB_SELECT(light_function)
|
||||
SUB_SELECT(out_pin_level)
|
||||
#endif
|
||||
#ifdef USE_SWITCH
|
||||
SUB_SWITCH(bluetooth)
|
||||
SUB_SWITCH(engineering_mode)
|
||||
#endif
|
||||
#ifdef USE_BUTTON
|
||||
SUB_BUTTON(factory_reset)
|
||||
SUB_BUTTON(query)
|
||||
SUB_BUTTON(restart)
|
||||
SUB_BUTTON(start_dynamic_background_correction)
|
||||
#endif
|
||||
|
||||
public:
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
void loop() override;
|
||||
void set_light_out_control();
|
||||
void set_basic_config();
|
||||
#ifdef USE_NUMBER
|
||||
void set_gate_move_threshold_number(uint8_t gate, number::Number *n);
|
||||
void set_gate_still_threshold_number(uint8_t gate, number::Number *n);
|
||||
void set_gate_threshold();
|
||||
void get_gate_threshold();
|
||||
#endif
|
||||
#ifdef USE_SENSOR
|
||||
void set_gate_move_sensor(uint8_t gate, sensor::Sensor *s);
|
||||
void set_gate_still_sensor(uint8_t gate, sensor::Sensor *s);
|
||||
#endif
|
||||
void set_engineering_mode(bool enable);
|
||||
void read_all_info();
|
||||
void restart_and_read_all_info();
|
||||
void set_bluetooth(bool enable);
|
||||
void set_distance_resolution(const std::string &state);
|
||||
void set_baud_rate(const std::string &state);
|
||||
void factory_reset();
|
||||
void start_dynamic_background_correction();
|
||||
|
||||
protected:
|
||||
void send_command_(uint8_t command_str, const uint8_t *command_value, uint8_t command_value_len);
|
||||
void set_config_mode_(bool enable);
|
||||
void handle_periodic_data_();
|
||||
bool handle_ack_data_();
|
||||
void readline_(int readch);
|
||||
void query_parameters_();
|
||||
void get_version_();
|
||||
void get_mac_();
|
||||
void get_distance_resolution_();
|
||||
void query_light_control_();
|
||||
void restart_();
|
||||
void query_dynamic_background_correction_();
|
||||
|
||||
uint8_t light_function_ = 0;
|
||||
uint8_t light_threshold_ = 0;
|
||||
uint8_t out_pin_level_ = 0;
|
||||
uint8_t buffer_pos_ = 0; // where to resume processing/populating buffer
|
||||
uint8_t buffer_data_[MAX_LINE_LENGTH];
|
||||
uint8_t mac_address_[6] = {0, 0, 0, 0, 0, 0};
|
||||
uint8_t version_[6] = {0, 0, 0, 0, 0, 0};
|
||||
bool bluetooth_on_{false};
|
||||
bool dynamic_background_correction_active_{false};
|
||||
#ifdef USE_NUMBER
|
||||
std::array<number::Number *, TOTAL_GATES> gate_move_threshold_numbers_{};
|
||||
std::array<number::Number *, TOTAL_GATES> gate_still_threshold_numbers_{};
|
||||
#endif
|
||||
#ifdef USE_SENSOR
|
||||
std::array<SensorWithDedup<uint8_t> *, TOTAL_GATES> gate_move_sensors_{};
|
||||
std::array<SensorWithDedup<uint8_t> *, TOTAL_GATES> gate_still_sensors_{};
|
||||
#endif
|
||||
};
|
||||
|
||||
} // namespace ld2412
|
||||
} // namespace esphome
|
126
esphome/components/ld2412/number/__init__.py
Normal file
126
esphome/components/ld2412/number/__init__.py
Normal file
@@ -0,0 +1,126 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import number
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_MOVE_THRESHOLD,
|
||||
CONF_STILL_THRESHOLD,
|
||||
CONF_TIMEOUT,
|
||||
DEVICE_CLASS_DISTANCE,
|
||||
DEVICE_CLASS_ILLUMINANCE,
|
||||
DEVICE_CLASS_SIGNAL_STRENGTH,
|
||||
ENTITY_CATEGORY_CONFIG,
|
||||
ICON_LIGHTBULB,
|
||||
ICON_MOTION_SENSOR,
|
||||
ICON_TIMELAPSE,
|
||||
UNIT_PERCENT,
|
||||
UNIT_SECOND,
|
||||
)
|
||||
|
||||
from .. import CONF_LD2412_ID, LD2412_ns, LD2412Component
|
||||
|
||||
GateThresholdNumber = LD2412_ns.class_("GateThresholdNumber", number.Number)
|
||||
LightThresholdNumber = LD2412_ns.class_("LightThresholdNumber", number.Number)
|
||||
MaxDistanceTimeoutNumber = LD2412_ns.class_("MaxDistanceTimeoutNumber", number.Number)
|
||||
|
||||
CONF_LIGHT_THRESHOLD = "light_threshold"
|
||||
CONF_MAX_DISTANCE_GATE = "max_distance_gate"
|
||||
CONF_MIN_DISTANCE_GATE = "min_distance_gate"
|
||||
|
||||
TIMEOUT_GROUP = "timeout"
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(CONF_LD2412_ID): cv.use_id(LD2412Component),
|
||||
cv.Optional(CONF_LIGHT_THRESHOLD): number.number_schema(
|
||||
LightThresholdNumber,
|
||||
device_class=DEVICE_CLASS_ILLUMINANCE,
|
||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||
icon=ICON_LIGHTBULB,
|
||||
),
|
||||
cv.Optional(CONF_MAX_DISTANCE_GATE): number.number_schema(
|
||||
MaxDistanceTimeoutNumber,
|
||||
device_class=DEVICE_CLASS_DISTANCE,
|
||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||
icon=ICON_MOTION_SENSOR,
|
||||
),
|
||||
cv.Optional(CONF_MIN_DISTANCE_GATE): number.number_schema(
|
||||
MaxDistanceTimeoutNumber,
|
||||
device_class=DEVICE_CLASS_DISTANCE,
|
||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||
icon=ICON_MOTION_SENSOR,
|
||||
),
|
||||
cv.Optional(CONF_TIMEOUT): number.number_schema(
|
||||
MaxDistanceTimeoutNumber,
|
||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||
icon=ICON_TIMELAPSE,
|
||||
unit_of_measurement=UNIT_SECOND,
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = CONFIG_SCHEMA.extend(
|
||||
{
|
||||
cv.Optional(f"gate_{x}"): (
|
||||
{
|
||||
cv.Required(CONF_MOVE_THRESHOLD): number.number_schema(
|
||||
GateThresholdNumber,
|
||||
device_class=DEVICE_CLASS_SIGNAL_STRENGTH,
|
||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||
icon=ICON_MOTION_SENSOR,
|
||||
unit_of_measurement=UNIT_PERCENT,
|
||||
),
|
||||
cv.Required(CONF_STILL_THRESHOLD): number.number_schema(
|
||||
GateThresholdNumber,
|
||||
device_class=DEVICE_CLASS_SIGNAL_STRENGTH,
|
||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||
icon=ICON_MOTION_SENSOR,
|
||||
unit_of_measurement=UNIT_PERCENT,
|
||||
),
|
||||
}
|
||||
)
|
||||
for x in range(14)
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
LD2412_component = await cg.get_variable(config[CONF_LD2412_ID])
|
||||
if light_threshold_config := config.get(CONF_LIGHT_THRESHOLD):
|
||||
n = await number.new_number(
|
||||
light_threshold_config, min_value=0, max_value=255, step=1
|
||||
)
|
||||
await cg.register_parented(n, config[CONF_LD2412_ID])
|
||||
cg.add(LD2412_component.set_light_threshold_number(n))
|
||||
if max_distance_gate_config := config.get(CONF_MAX_DISTANCE_GATE):
|
||||
n = await number.new_number(
|
||||
max_distance_gate_config, min_value=2, max_value=13, step=1
|
||||
)
|
||||
await cg.register_parented(n, config[CONF_LD2412_ID])
|
||||
cg.add(LD2412_component.set_max_distance_gate_number(n))
|
||||
if min_distance_gate_config := config.get(CONF_MIN_DISTANCE_GATE):
|
||||
n = await number.new_number(
|
||||
min_distance_gate_config, min_value=1, max_value=12, step=1
|
||||
)
|
||||
await cg.register_parented(n, config[CONF_LD2412_ID])
|
||||
cg.add(LD2412_component.set_min_distance_gate_number(n))
|
||||
for x in range(14):
|
||||
if gate_conf := config.get(f"gate_{x}"):
|
||||
move_config = gate_conf[CONF_MOVE_THRESHOLD]
|
||||
n = cg.new_Pvariable(move_config[CONF_ID], x)
|
||||
await number.register_number(
|
||||
n, move_config, min_value=0, max_value=100, step=1
|
||||
)
|
||||
await cg.register_parented(n, config[CONF_LD2412_ID])
|
||||
cg.add(LD2412_component.set_gate_move_threshold_number(x, n))
|
||||
still_config = gate_conf[CONF_STILL_THRESHOLD]
|
||||
n = cg.new_Pvariable(still_config[CONF_ID], x)
|
||||
await number.register_number(
|
||||
n, still_config, min_value=0, max_value=100, step=1
|
||||
)
|
||||
await cg.register_parented(n, config[CONF_LD2412_ID])
|
||||
cg.add(LD2412_component.set_gate_still_threshold_number(x, n))
|
||||
if timeout_config := config.get(CONF_TIMEOUT):
|
||||
n = await number.new_number(timeout_config, min_value=0, max_value=900, step=1)
|
||||
await cg.register_parented(n, config[CONF_LD2412_ID])
|
||||
cg.add(LD2412_component.set_timeout_number(n))
|
14
esphome/components/ld2412/number/gate_threshold_number.cpp
Normal file
14
esphome/components/ld2412/number/gate_threshold_number.cpp
Normal file
@@ -0,0 +1,14 @@
|
||||
#include "gate_threshold_number.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ld2412 {
|
||||
|
||||
GateThresholdNumber::GateThresholdNumber(uint8_t gate) : gate_(gate) {}
|
||||
|
||||
void GateThresholdNumber::control(float value) {
|
||||
this->publish_state(value);
|
||||
this->parent_->set_gate_threshold();
|
||||
}
|
||||
|
||||
} // namespace ld2412
|
||||
} // namespace esphome
|
19
esphome/components/ld2412/number/gate_threshold_number.h
Normal file
19
esphome/components/ld2412/number/gate_threshold_number.h
Normal file
@@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/number/number.h"
|
||||
#include "../ld2412.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ld2412 {
|
||||
|
||||
class GateThresholdNumber : public number::Number, public Parented<LD2412Component> {
|
||||
public:
|
||||
GateThresholdNumber(uint8_t gate);
|
||||
|
||||
protected:
|
||||
uint8_t gate_;
|
||||
void control(float value) override;
|
||||
};
|
||||
|
||||
} // namespace ld2412
|
||||
} // namespace esphome
|
12
esphome/components/ld2412/number/light_threshold_number.cpp
Normal file
12
esphome/components/ld2412/number/light_threshold_number.cpp
Normal file
@@ -0,0 +1,12 @@
|
||||
#include "light_threshold_number.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ld2412 {
|
||||
|
||||
void LightThresholdNumber::control(float value) {
|
||||
this->publish_state(value);
|
||||
this->parent_->set_light_out_control();
|
||||
}
|
||||
|
||||
} // namespace ld2412
|
||||
} // namespace esphome
|
18
esphome/components/ld2412/number/light_threshold_number.h
Normal file
18
esphome/components/ld2412/number/light_threshold_number.h
Normal file
@@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/number/number.h"
|
||||
#include "../ld2412.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ld2412 {
|
||||
|
||||
class LightThresholdNumber : public number::Number, public Parented<LD2412Component> {
|
||||
public:
|
||||
LightThresholdNumber() = default;
|
||||
|
||||
protected:
|
||||
void control(float value) override;
|
||||
};
|
||||
|
||||
} // namespace ld2412
|
||||
} // namespace esphome
|
@@ -0,0 +1,12 @@
|
||||
#include "max_distance_timeout_number.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ld2412 {
|
||||
|
||||
void MaxDistanceTimeoutNumber::control(float value) {
|
||||
this->publish_state(value);
|
||||
this->parent_->set_basic_config();
|
||||
}
|
||||
|
||||
} // namespace ld2412
|
||||
} // namespace esphome
|
@@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/number/number.h"
|
||||
#include "../ld2412.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ld2412 {
|
||||
|
||||
class MaxDistanceTimeoutNumber : public number::Number, public Parented<LD2412Component> {
|
||||
public:
|
||||
MaxDistanceTimeoutNumber() = default;
|
||||
|
||||
protected:
|
||||
void control(float value) override;
|
||||
};
|
||||
|
||||
} // namespace ld2412
|
||||
} // namespace esphome
|
82
esphome/components/ld2412/select/__init__.py
Normal file
82
esphome/components/ld2412/select/__init__.py
Normal file
@@ -0,0 +1,82 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import select
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_BAUD_RATE,
|
||||
ENTITY_CATEGORY_CONFIG,
|
||||
ICON_LIGHTBULB,
|
||||
ICON_RULER,
|
||||
ICON_SCALE,
|
||||
ICON_THERMOMETER,
|
||||
)
|
||||
|
||||
from .. import CONF_LD2412_ID, LD2412_ns, LD2412Component
|
||||
|
||||
BaudRateSelect = LD2412_ns.class_("BaudRateSelect", select.Select)
|
||||
DistanceResolutionSelect = LD2412_ns.class_("DistanceResolutionSelect", select.Select)
|
||||
LightOutControlSelect = LD2412_ns.class_("LightOutControlSelect", select.Select)
|
||||
|
||||
CONF_DISTANCE_RESOLUTION = "distance_resolution"
|
||||
CONF_LIGHT_FUNCTION = "light_function"
|
||||
CONF_OUT_PIN_LEVEL = "out_pin_level"
|
||||
|
||||
|
||||
CONFIG_SCHEMA = {
|
||||
cv.GenerateID(CONF_LD2412_ID): cv.use_id(LD2412Component),
|
||||
cv.Optional(CONF_BAUD_RATE): select.select_schema(
|
||||
BaudRateSelect,
|
||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||
icon=ICON_THERMOMETER,
|
||||
),
|
||||
cv.Optional(CONF_DISTANCE_RESOLUTION): select.select_schema(
|
||||
DistanceResolutionSelect,
|
||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||
icon=ICON_RULER,
|
||||
),
|
||||
cv.Optional(CONF_LIGHT_FUNCTION): select.select_schema(
|
||||
LightOutControlSelect,
|
||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||
icon=ICON_LIGHTBULB,
|
||||
),
|
||||
cv.Optional(CONF_OUT_PIN_LEVEL): select.select_schema(
|
||||
LightOutControlSelect,
|
||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||
icon=ICON_SCALE,
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
LD2412_component = await cg.get_variable(config[CONF_LD2412_ID])
|
||||
if baud_rate_config := config.get(CONF_BAUD_RATE):
|
||||
s = await select.new_select(
|
||||
baud_rate_config,
|
||||
options=[
|
||||
"9600",
|
||||
"19200",
|
||||
"38400",
|
||||
"57600",
|
||||
"115200",
|
||||
"230400",
|
||||
"256000",
|
||||
"460800",
|
||||
],
|
||||
)
|
||||
await cg.register_parented(s, config[CONF_LD2412_ID])
|
||||
cg.add(LD2412_component.set_baud_rate_select(s))
|
||||
if distance_resolution_config := config.get(CONF_DISTANCE_RESOLUTION):
|
||||
s = await select.new_select(
|
||||
distance_resolution_config, options=["0.2m", "0.5m", "0.75m"]
|
||||
)
|
||||
await cg.register_parented(s, config[CONF_LD2412_ID])
|
||||
cg.add(LD2412_component.set_distance_resolution_select(s))
|
||||
if light_function_config := config.get(CONF_LIGHT_FUNCTION):
|
||||
s = await select.new_select(
|
||||
light_function_config, options=["off", "below", "above"]
|
||||
)
|
||||
await cg.register_parented(s, config[CONF_LD2412_ID])
|
||||
cg.add(LD2412_component.set_light_function_select(s))
|
||||
if out_pin_level_config := config.get(CONF_OUT_PIN_LEVEL):
|
||||
s = await select.new_select(out_pin_level_config, options=["low", "high"])
|
||||
await cg.register_parented(s, config[CONF_LD2412_ID])
|
||||
cg.add(LD2412_component.set_out_pin_level_select(s))
|
12
esphome/components/ld2412/select/baud_rate_select.cpp
Normal file
12
esphome/components/ld2412/select/baud_rate_select.cpp
Normal file
@@ -0,0 +1,12 @@
|
||||
#include "baud_rate_select.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ld2412 {
|
||||
|
||||
void BaudRateSelect::control(const std::string &value) {
|
||||
this->publish_state(value);
|
||||
this->parent_->set_baud_rate(state);
|
||||
}
|
||||
|
||||
} // namespace ld2412
|
||||
} // namespace esphome
|
18
esphome/components/ld2412/select/baud_rate_select.h
Normal file
18
esphome/components/ld2412/select/baud_rate_select.h
Normal file
@@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/select/select.h"
|
||||
#include "../ld2412.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ld2412 {
|
||||
|
||||
class BaudRateSelect : public select::Select, public Parented<LD2412Component> {
|
||||
public:
|
||||
BaudRateSelect() = default;
|
||||
|
||||
protected:
|
||||
void control(const std::string &value) override;
|
||||
};
|
||||
|
||||
} // namespace ld2412
|
||||
} // namespace esphome
|
@@ -0,0 +1,12 @@
|
||||
#include "distance_resolution_select.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ld2412 {
|
||||
|
||||
void DistanceResolutionSelect::control(const std::string &value) {
|
||||
this->publish_state(value);
|
||||
this->parent_->set_distance_resolution(state);
|
||||
}
|
||||
|
||||
} // namespace ld2412
|
||||
} // namespace esphome
|
@@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/select/select.h"
|
||||
#include "../ld2412.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ld2412 {
|
||||
|
||||
class DistanceResolutionSelect : public select::Select, public Parented<LD2412Component> {
|
||||
public:
|
||||
DistanceResolutionSelect() = default;
|
||||
|
||||
protected:
|
||||
void control(const std::string &value) override;
|
||||
};
|
||||
|
||||
} // namespace ld2412
|
||||
} // namespace esphome
|
@@ -0,0 +1,12 @@
|
||||
#include "light_out_control_select.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ld2412 {
|
||||
|
||||
void LightOutControlSelect::control(const std::string &value) {
|
||||
this->publish_state(value);
|
||||
this->parent_->set_light_out_control();
|
||||
}
|
||||
|
||||
} // namespace ld2412
|
||||
} // namespace esphome
|
18
esphome/components/ld2412/select/light_out_control_select.h
Normal file
18
esphome/components/ld2412/select/light_out_control_select.h
Normal file
@@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/select/select.h"
|
||||
#include "../ld2412.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ld2412 {
|
||||
|
||||
class LightOutControlSelect : public select::Select, public Parented<LD2412Component> {
|
||||
public:
|
||||
LightOutControlSelect() = default;
|
||||
|
||||
protected:
|
||||
void control(const std::string &value) override;
|
||||
};
|
||||
|
||||
} // namespace ld2412
|
||||
} // namespace esphome
|
124
esphome/components/ld2412/sensor.py
Normal file
124
esphome/components/ld2412/sensor.py
Normal file
@@ -0,0 +1,124 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import sensor
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_LIGHT,
|
||||
CONF_MOVING_DISTANCE,
|
||||
DEVICE_CLASS_DISTANCE,
|
||||
DEVICE_CLASS_ILLUMINANCE,
|
||||
ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
ICON_FLASH,
|
||||
ICON_LIGHTBULB,
|
||||
ICON_MOTION_SENSOR,
|
||||
ICON_SIGNAL,
|
||||
UNIT_CENTIMETER,
|
||||
UNIT_EMPTY,
|
||||
UNIT_PERCENT,
|
||||
)
|
||||
|
||||
from . import CONF_LD2412_ID, LD2412Component
|
||||
|
||||
DEPENDENCIES = ["ld2412"]
|
||||
|
||||
CONF_DETECTION_DISTANCE = "detection_distance"
|
||||
CONF_MOVE_ENERGY = "move_energy"
|
||||
CONF_MOVING_ENERGY = "moving_energy"
|
||||
CONF_STILL_DISTANCE = "still_distance"
|
||||
CONF_STILL_ENERGY = "still_energy"
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(CONF_LD2412_ID): cv.use_id(LD2412Component),
|
||||
cv.Optional(CONF_DETECTION_DISTANCE): sensor.sensor_schema(
|
||||
device_class=DEVICE_CLASS_DISTANCE,
|
||||
filters=[{"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}],
|
||||
icon=ICON_SIGNAL,
|
||||
unit_of_measurement=UNIT_CENTIMETER,
|
||||
),
|
||||
cv.Optional(CONF_LIGHT): sensor.sensor_schema(
|
||||
device_class=DEVICE_CLASS_ILLUMINANCE,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
filters=[{"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}],
|
||||
icon=ICON_LIGHTBULB,
|
||||
unit_of_measurement=UNIT_EMPTY, # No standard unit for this light sensor
|
||||
),
|
||||
cv.Optional(CONF_MOVING_DISTANCE): sensor.sensor_schema(
|
||||
device_class=DEVICE_CLASS_DISTANCE,
|
||||
filters=[{"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}],
|
||||
icon=ICON_SIGNAL,
|
||||
unit_of_measurement=UNIT_CENTIMETER,
|
||||
),
|
||||
cv.Optional(CONF_MOVING_ENERGY): sensor.sensor_schema(
|
||||
filters=[{"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}],
|
||||
icon=ICON_MOTION_SENSOR,
|
||||
unit_of_measurement=UNIT_PERCENT,
|
||||
),
|
||||
cv.Optional(CONF_STILL_DISTANCE): sensor.sensor_schema(
|
||||
device_class=DEVICE_CLASS_DISTANCE,
|
||||
filters=[{"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}],
|
||||
icon=ICON_SIGNAL,
|
||||
unit_of_measurement=UNIT_CENTIMETER,
|
||||
),
|
||||
cv.Optional(CONF_STILL_ENERGY): sensor.sensor_schema(
|
||||
filters=[{"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}],
|
||||
icon=ICON_FLASH,
|
||||
unit_of_measurement=UNIT_PERCENT,
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = CONFIG_SCHEMA.extend(
|
||||
{
|
||||
cv.Optional(f"gate_{x}"): (
|
||||
{
|
||||
cv.Optional(CONF_MOVE_ENERGY): sensor.sensor_schema(
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
filters=[
|
||||
{"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}
|
||||
],
|
||||
icon=ICON_MOTION_SENSOR,
|
||||
unit_of_measurement=UNIT_PERCENT,
|
||||
),
|
||||
cv.Optional(CONF_STILL_ENERGY): sensor.sensor_schema(
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
filters=[
|
||||
{"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}
|
||||
],
|
||||
icon=ICON_FLASH,
|
||||
unit_of_measurement=UNIT_PERCENT,
|
||||
),
|
||||
}
|
||||
)
|
||||
for x in range(14)
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
LD2412_component = await cg.get_variable(config[CONF_LD2412_ID])
|
||||
if detection_distance_config := config.get(CONF_DETECTION_DISTANCE):
|
||||
sens = await sensor.new_sensor(detection_distance_config)
|
||||
cg.add(LD2412_component.set_detection_distance_sensor(sens))
|
||||
if light_config := config.get(CONF_LIGHT):
|
||||
sens = await sensor.new_sensor(light_config)
|
||||
cg.add(LD2412_component.set_light_sensor(sens))
|
||||
if moving_distance_config := config.get(CONF_MOVING_DISTANCE):
|
||||
sens = await sensor.new_sensor(moving_distance_config)
|
||||
cg.add(LD2412_component.set_moving_target_distance_sensor(sens))
|
||||
if moving_energy_config := config.get(CONF_MOVING_ENERGY):
|
||||
sens = await sensor.new_sensor(moving_energy_config)
|
||||
cg.add(LD2412_component.set_moving_target_energy_sensor(sens))
|
||||
if still_distance_config := config.get(CONF_STILL_DISTANCE):
|
||||
sens = await sensor.new_sensor(still_distance_config)
|
||||
cg.add(LD2412_component.set_still_target_distance_sensor(sens))
|
||||
if still_energy_config := config.get(CONF_STILL_ENERGY):
|
||||
sens = await sensor.new_sensor(still_energy_config)
|
||||
cg.add(LD2412_component.set_still_target_energy_sensor(sens))
|
||||
for x in range(14):
|
||||
if gate_conf := config.get(f"gate_{x}"):
|
||||
if move_config := gate_conf.get(CONF_MOVE_ENERGY):
|
||||
sens = await sensor.new_sensor(move_config)
|
||||
cg.add(LD2412_component.set_gate_move_sensor(x, sens))
|
||||
if still_config := gate_conf.get(CONF_STILL_ENERGY):
|
||||
sens = await sensor.new_sensor(still_config)
|
||||
cg.add(LD2412_component.set_gate_still_sensor(x, sens))
|
45
esphome/components/ld2412/switch/__init__.py
Normal file
45
esphome/components/ld2412/switch/__init__.py
Normal file
@@ -0,0 +1,45 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import switch
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_BLUETOOTH,
|
||||
DEVICE_CLASS_SWITCH,
|
||||
ENTITY_CATEGORY_CONFIG,
|
||||
ICON_BLUETOOTH,
|
||||
ICON_PULSE,
|
||||
)
|
||||
|
||||
from .. import CONF_LD2412_ID, LD2412_ns, LD2412Component
|
||||
|
||||
BluetoothSwitch = LD2412_ns.class_("BluetoothSwitch", switch.Switch)
|
||||
EngineeringModeSwitch = LD2412_ns.class_("EngineeringModeSwitch", switch.Switch)
|
||||
|
||||
CONF_ENGINEERING_MODE = "engineering_mode"
|
||||
|
||||
CONFIG_SCHEMA = {
|
||||
cv.GenerateID(CONF_LD2412_ID): cv.use_id(LD2412Component),
|
||||
cv.Optional(CONF_BLUETOOTH): switch.switch_schema(
|
||||
BluetoothSwitch,
|
||||
device_class=DEVICE_CLASS_SWITCH,
|
||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||
icon=ICON_BLUETOOTH,
|
||||
),
|
||||
cv.Optional(CONF_ENGINEERING_MODE): switch.switch_schema(
|
||||
EngineeringModeSwitch,
|
||||
device_class=DEVICE_CLASS_SWITCH,
|
||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||
icon=ICON_PULSE,
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
LD2412_component = await cg.get_variable(config[CONF_LD2412_ID])
|
||||
if bluetooth_config := config.get(CONF_BLUETOOTH):
|
||||
s = await switch.new_switch(bluetooth_config)
|
||||
await cg.register_parented(s, config[CONF_LD2412_ID])
|
||||
cg.add(LD2412_component.set_bluetooth_switch(s))
|
||||
if engineering_mode_config := config.get(CONF_ENGINEERING_MODE):
|
||||
s = await switch.new_switch(engineering_mode_config)
|
||||
await cg.register_parented(s, config[CONF_LD2412_ID])
|
||||
cg.add(LD2412_component.set_engineering_mode_switch(s))
|
12
esphome/components/ld2412/switch/bluetooth_switch.cpp
Normal file
12
esphome/components/ld2412/switch/bluetooth_switch.cpp
Normal file
@@ -0,0 +1,12 @@
|
||||
#include "bluetooth_switch.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ld2412 {
|
||||
|
||||
void BluetoothSwitch::write_state(bool state) {
|
||||
this->publish_state(state);
|
||||
this->parent_->set_bluetooth(state);
|
||||
}
|
||||
|
||||
} // namespace ld2412
|
||||
} // namespace esphome
|
18
esphome/components/ld2412/switch/bluetooth_switch.h
Normal file
18
esphome/components/ld2412/switch/bluetooth_switch.h
Normal file
@@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/switch/switch.h"
|
||||
#include "../ld2412.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ld2412 {
|
||||
|
||||
class BluetoothSwitch : public switch_::Switch, public Parented<LD2412Component> {
|
||||
public:
|
||||
BluetoothSwitch() = default;
|
||||
|
||||
protected:
|
||||
void write_state(bool state) override;
|
||||
};
|
||||
|
||||
} // namespace ld2412
|
||||
} // namespace esphome
|
12
esphome/components/ld2412/switch/engineering_mode_switch.cpp
Normal file
12
esphome/components/ld2412/switch/engineering_mode_switch.cpp
Normal file
@@ -0,0 +1,12 @@
|
||||
#include "engineering_mode_switch.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ld2412 {
|
||||
|
||||
void EngineeringModeSwitch::write_state(bool state) {
|
||||
this->publish_state(state);
|
||||
this->parent_->set_engineering_mode(state);
|
||||
}
|
||||
|
||||
} // namespace ld2412
|
||||
} // namespace esphome
|
18
esphome/components/ld2412/switch/engineering_mode_switch.h
Normal file
18
esphome/components/ld2412/switch/engineering_mode_switch.h
Normal file
@@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/switch/switch.h"
|
||||
#include "../ld2412.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ld2412 {
|
||||
|
||||
class EngineeringModeSwitch : public switch_::Switch, public Parented<LD2412Component> {
|
||||
public:
|
||||
EngineeringModeSwitch() = default;
|
||||
|
||||
protected:
|
||||
void write_state(bool state) override;
|
||||
};
|
||||
|
||||
} // namespace ld2412
|
||||
} // namespace esphome
|
34
esphome/components/ld2412/text_sensor.py
Normal file
34
esphome/components/ld2412/text_sensor.py
Normal file
@@ -0,0 +1,34 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import text_sensor
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_MAC_ADDRESS,
|
||||
CONF_VERSION,
|
||||
ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
ICON_BLUETOOTH,
|
||||
ICON_CHIP,
|
||||
)
|
||||
|
||||
from . import CONF_LD2412_ID, LD2412Component
|
||||
|
||||
DEPENDENCIES = ["ld2412"]
|
||||
|
||||
CONFIG_SCHEMA = {
|
||||
cv.GenerateID(CONF_LD2412_ID): cv.use_id(LD2412Component),
|
||||
cv.Optional(CONF_VERSION): text_sensor.text_sensor_schema(
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC, icon=ICON_CHIP
|
||||
),
|
||||
cv.Optional(CONF_MAC_ADDRESS): text_sensor.text_sensor_schema(
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC, icon=ICON_BLUETOOTH
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
LD2412_component = await cg.get_variable(config[CONF_LD2412_ID])
|
||||
if version_config := config.get(CONF_VERSION):
|
||||
sens = await text_sensor.new_text_sensor(version_config)
|
||||
cg.add(LD2412_component.set_version_text_sensor(sens))
|
||||
if mac_address_config := config.get(CONF_MAC_ADDRESS):
|
||||
sens = await text_sensor.new_text_sensor(mac_address_config)
|
||||
cg.add(LD2412_component.set_mac_text_sensor(sens))
|
@@ -194,45 +194,7 @@ def final_validate(config):
|
||||
)
|
||||
|
||||
|
||||
def final_validate_power_esp32_ble(value):
|
||||
if not CORE.is_esp32:
|
||||
return
|
||||
if value != "NONE":
|
||||
# WiFi should be in modem sleep (!=NONE) with BLE coexistence
|
||||
# https://docs.espressif.com/projects/esp-idf/en/v3.3.5/api-guides/wifi.html#station-sleep
|
||||
return
|
||||
for conflicting in [
|
||||
"esp32_ble",
|
||||
"esp32_ble_beacon",
|
||||
"esp32_ble_server",
|
||||
"esp32_ble_tracker",
|
||||
]:
|
||||
if conflicting not in fv.full_config.get():
|
||||
continue
|
||||
|
||||
try:
|
||||
# Only arduino 1.0.5+ and esp-idf impacted
|
||||
cv.require_framework_version(
|
||||
esp32_arduino=cv.Version(1, 0, 5),
|
||||
esp_idf=cv.Version(4, 0, 0),
|
||||
)(None)
|
||||
except cv.Invalid:
|
||||
pass
|
||||
else:
|
||||
raise cv.Invalid(
|
||||
f"power_save_mode NONE is incompatible with {conflicting}. "
|
||||
f"Please remove the power save mode. See also "
|
||||
f"https://github.com/esphome/issues/issues/2141#issuecomment-865688582"
|
||||
)
|
||||
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_POWER_SAVE_MODE): final_validate_power_esp32_ble,
|
||||
},
|
||||
extra=cv.ALLOW_EXTRA,
|
||||
),
|
||||
final_validate,
|
||||
validate_variant,
|
||||
)
|
||||
|
@@ -942,6 +942,9 @@ def validate_config(
|
||||
# do not try to validate further as we don't know what the target is
|
||||
return result
|
||||
|
||||
# Reset the pin registry so that any target platforms with pin validations do not get the duplicate pin warning.
|
||||
pins.PIN_SCHEMA_REGISTRY.reset()
|
||||
|
||||
for domain, conf in config.items():
|
||||
result.add_validation_step(LoadValidationStep(domain, conf))
|
||||
result.add_validation_step(IDPassValidationStep())
|
||||
|
@@ -761,6 +761,7 @@ CONF_POSITION_COMMAND_TOPIC = "position_command_topic"
|
||||
CONF_POSITION_STATE_TOPIC = "position_state_topic"
|
||||
CONF_POWER = "power"
|
||||
CONF_POWER_FACTOR = "power_factor"
|
||||
CONF_POWER_MODE = "power_mode"
|
||||
CONF_POWER_ON_VALUE = "power_on_value"
|
||||
CONF_POWER_SAVE_MODE = "power_save_mode"
|
||||
CONF_POWER_SUPPLY = "power_supply"
|
||||
|
@@ -12,7 +12,7 @@ platformio==6.1.18 # When updating platformio, also update /docker/Dockerfile
|
||||
esptool==5.0.2
|
||||
click==8.1.7
|
||||
esphome-dashboard==20250514.0
|
||||
aioesphomeapi==38.1.0
|
||||
aioesphomeapi==38.2.1
|
||||
zeroconf==0.147.0
|
||||
puremagic==1.30
|
||||
ruamel.yaml==0.18.14 # dashboard_import
|
||||
|
233
tests/components/ld2412/common.yaml
Normal file
233
tests/components/ld2412/common.yaml
Normal file
@@ -0,0 +1,233 @@
|
||||
uart:
|
||||
- id: uart_ld2412
|
||||
tx_pin: ${tx_pin}
|
||||
rx_pin: ${rx_pin}
|
||||
baud_rate: 9600
|
||||
|
||||
ld2412:
|
||||
id: my_ld2412
|
||||
|
||||
binary_sensor:
|
||||
- platform: ld2412
|
||||
dynamic_background_correction_status:
|
||||
name: Dynamic Background Correction Status
|
||||
has_target:
|
||||
name: Presence
|
||||
has_moving_target:
|
||||
name: Moving Target
|
||||
has_still_target:
|
||||
name: Still Target
|
||||
|
||||
button:
|
||||
- platform: ld2412
|
||||
factory_reset:
|
||||
name: Factory reset
|
||||
restart:
|
||||
name: Restart
|
||||
query_params:
|
||||
name: Query params
|
||||
start_dynamic_background_correction:
|
||||
name: Start Dynamic Background Correction
|
||||
|
||||
number:
|
||||
- platform: ld2412
|
||||
light_threshold:
|
||||
name: Light Threshold
|
||||
timeout:
|
||||
name: Presence timeout
|
||||
min_distance_gate:
|
||||
name: Minimum distance gate
|
||||
max_distance_gate:
|
||||
name: Maximum distance gate
|
||||
gate_0:
|
||||
move_threshold:
|
||||
name: Gate 0 Move Threshold
|
||||
still_threshold:
|
||||
name: Gate 0 Still Threshold
|
||||
gate_1:
|
||||
move_threshold:
|
||||
name: Gate 1 Move Threshold
|
||||
still_threshold:
|
||||
name: Gate 1 Still Threshold
|
||||
gate_2:
|
||||
move_threshold:
|
||||
name: Gate 2 Move Threshold
|
||||
still_threshold:
|
||||
name: Gate 2 Still Threshold
|
||||
gate_3:
|
||||
move_threshold:
|
||||
name: Gate 3 Move Threshold
|
||||
still_threshold:
|
||||
name: Gate 3 Still Threshold
|
||||
gate_4:
|
||||
move_threshold:
|
||||
name: Gate 4 Move Threshold
|
||||
still_threshold:
|
||||
name: Gate 4 Still Threshold
|
||||
gate_5:
|
||||
move_threshold:
|
||||
name: Gate 5 Move Threshold
|
||||
still_threshold:
|
||||
name: Gate 5 Still Threshold
|
||||
gate_6:
|
||||
move_threshold:
|
||||
name: Gate 6 Move Threshold
|
||||
still_threshold:
|
||||
name: Gate 6 Still Threshold
|
||||
gate_7:
|
||||
move_threshold:
|
||||
name: Gate 7 Move Threshold
|
||||
still_threshold:
|
||||
name: Gate 7 Still Threshold
|
||||
gate_8:
|
||||
move_threshold:
|
||||
name: Gate 8 Move Threshold
|
||||
still_threshold:
|
||||
name: Gate 8 Still Threshold
|
||||
gate_9:
|
||||
move_threshold:
|
||||
name: Gate 9 Move Threshold
|
||||
still_threshold:
|
||||
name: Gate 9 Still Threshold
|
||||
gate_10:
|
||||
move_threshold:
|
||||
name: Gate 10 Move Threshold
|
||||
still_threshold:
|
||||
name: Gate 10 Still Threshold
|
||||
gate_11:
|
||||
move_threshold:
|
||||
name: Gate 11 Move Threshold
|
||||
still_threshold:
|
||||
name: Gate 11 Still Threshold
|
||||
gate_12:
|
||||
move_threshold:
|
||||
name: Gate 12 Move Threshold
|
||||
still_threshold:
|
||||
name: Gate 12 Still Threshold
|
||||
gate_13:
|
||||
move_threshold:
|
||||
name: Gate 13 Move Threshold
|
||||
still_threshold:
|
||||
name: Gate 13 Still Threshold
|
||||
|
||||
select:
|
||||
- platform: ld2412
|
||||
light_function:
|
||||
name: Light Function
|
||||
out_pin_level:
|
||||
name: Hardware output pin level
|
||||
distance_resolution:
|
||||
name: Distance resolution
|
||||
baud_rate:
|
||||
name: Baud rate
|
||||
on_value:
|
||||
- delay: 3s
|
||||
- lambda: |-
|
||||
id(uart_ld2412).flush();
|
||||
uint32_t new_baud_rate = stoi(x);
|
||||
ESP_LOGD("change_baud_rate", "Changing baud rate from %i to %i",id(uart_ld2412).get_baud_rate(), new_baud_rate);
|
||||
if (id(uart_ld2412).get_baud_rate() != new_baud_rate) {
|
||||
id(uart_ld2412).set_baud_rate(new_baud_rate);
|
||||
#if defined(USE_ESP8266) || defined(USE_ESP32)
|
||||
id(uart_ld2412).load_settings();
|
||||
#endif
|
||||
}
|
||||
|
||||
sensor:
|
||||
- platform: ld2412
|
||||
light:
|
||||
name: Light
|
||||
moving_distance:
|
||||
name: Moving Distance
|
||||
still_distance:
|
||||
name: Still Distance
|
||||
moving_energy:
|
||||
name: Move Energy
|
||||
still_energy:
|
||||
name: Still Energy
|
||||
detection_distance:
|
||||
name: Detection Distance
|
||||
gate_0:
|
||||
move_energy:
|
||||
name: Gate 0 Move Energy
|
||||
still_energy:
|
||||
name: Gate 0 Still Energy
|
||||
gate_1:
|
||||
move_energy:
|
||||
name: Gate 1 Move Energy
|
||||
still_energy:
|
||||
name: Gate 1 Still Energy
|
||||
gate_2:
|
||||
move_energy:
|
||||
name: Gate 2 Move Energy
|
||||
still_energy:
|
||||
name: Gate 2 Still Energy
|
||||
gate_3:
|
||||
move_energy:
|
||||
name: Gate 3 Move Energy
|
||||
still_energy:
|
||||
name: Gate 3 Still Energy
|
||||
gate_4:
|
||||
move_energy:
|
||||
name: Gate 4 Move Energy
|
||||
still_energy:
|
||||
name: Gate 4 Still Energy
|
||||
gate_5:
|
||||
move_energy:
|
||||
name: Gate 5 Move Energy
|
||||
still_energy:
|
||||
name: Gate 5 Still Energy
|
||||
gate_6:
|
||||
move_energy:
|
||||
name: Gate 6 Move Energy
|
||||
still_energy:
|
||||
name: Gate 6 Still Energy
|
||||
gate_7:
|
||||
move_energy:
|
||||
name: Gate 7 Move Energy
|
||||
still_energy:
|
||||
name: Gate 7 Still Energy
|
||||
gate_8:
|
||||
move_energy:
|
||||
name: Gate 8 Move Energy
|
||||
still_energy:
|
||||
name: Gate 8 Still Energy
|
||||
gate_9:
|
||||
move_energy:
|
||||
name: Gate 9 Move Energy
|
||||
still_energy:
|
||||
name: Gate 9 Still Energy
|
||||
gate_10:
|
||||
move_energy:
|
||||
name: Gate 10 Move Energy
|
||||
still_energy:
|
||||
name: Gate 10 Still Energy
|
||||
gate_11:
|
||||
move_energy:
|
||||
name: Gate 11 Move Energy
|
||||
still_energy:
|
||||
name: Gate 11 Still Energy
|
||||
gate_12:
|
||||
move_energy:
|
||||
name: Gate 12 Move Energy
|
||||
still_energy:
|
||||
name: Gate 12 Still Energy
|
||||
gate_13:
|
||||
move_energy:
|
||||
name: Gate 13 Move Energy
|
||||
still_energy:
|
||||
name: Gate 13 Still Energy
|
||||
|
||||
switch:
|
||||
- platform: ld2412
|
||||
bluetooth:
|
||||
name: Bluetooth
|
||||
engineering_mode:
|
||||
name: Engineering Mode
|
||||
|
||||
text_sensor:
|
||||
- platform: ld2412
|
||||
version:
|
||||
name: Firmware version
|
||||
mac_address:
|
||||
name: MAC address
|
5
tests/components/ld2412/test.esp32-ard.yaml
Normal file
5
tests/components/ld2412/test.esp32-ard.yaml
Normal file
@@ -0,0 +1,5 @@
|
||||
substitutions:
|
||||
tx_pin: GPIO17
|
||||
rx_pin: GPIO16
|
||||
|
||||
<<: !include common.yaml
|
5
tests/components/ld2412/test.esp32-c3-ard.yaml
Normal file
5
tests/components/ld2412/test.esp32-c3-ard.yaml
Normal file
@@ -0,0 +1,5 @@
|
||||
substitutions:
|
||||
tx_pin: GPIO4
|
||||
rx_pin: GPIO5
|
||||
|
||||
<<: !include common.yaml
|
5
tests/components/ld2412/test.esp32-c3-idf.yaml
Normal file
5
tests/components/ld2412/test.esp32-c3-idf.yaml
Normal file
@@ -0,0 +1,5 @@
|
||||
substitutions:
|
||||
tx_pin: GPIO4
|
||||
rx_pin: GPIO5
|
||||
|
||||
<<: !include common.yaml
|
5
tests/components/ld2412/test.esp32-idf.yaml
Normal file
5
tests/components/ld2412/test.esp32-idf.yaml
Normal file
@@ -0,0 +1,5 @@
|
||||
substitutions:
|
||||
tx_pin: GPIO17
|
||||
rx_pin: GPIO16
|
||||
|
||||
<<: !include common.yaml
|
5
tests/components/ld2412/test.esp8266-ard.yaml
Normal file
5
tests/components/ld2412/test.esp8266-ard.yaml
Normal file
@@ -0,0 +1,5 @@
|
||||
substitutions:
|
||||
tx_pin: GPIO4
|
||||
rx_pin: GPIO5
|
||||
|
||||
<<: !include common.yaml
|
5
tests/components/ld2412/test.rp2040-ard.yaml
Normal file
5
tests/components/ld2412/test.rp2040-ard.yaml
Normal file
@@ -0,0 +1,5 @@
|
||||
substitutions:
|
||||
tx_pin: GPIO4
|
||||
rx_pin: GPIO5
|
||||
|
||||
<<: !include common.yaml
|
Reference in New Issue
Block a user