mirror of
https://github.com/esphome/esphome.git
synced 2024-11-27 17:27:59 +01:00
Merge branch 'esphome:dev' into tilt-cover
This commit is contained in:
commit
8559fdfd4c
188 changed files with 4760 additions and 969 deletions
13
.github/actions/restore-python/action.yml
vendored
13
.github/actions/restore-python/action.yml
vendored
|
@ -28,11 +28,20 @@ runs:
|
||||||
# yamllint disable-line rule:line-length
|
# yamllint disable-line rule:line-length
|
||||||
key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-venv-${{ inputs.cache-key }}
|
key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-venv-${{ inputs.cache-key }}
|
||||||
- name: Create Python virtual environment
|
- name: Create Python virtual environment
|
||||||
if: steps.cache-venv.outputs.cache-hit != 'true'
|
if: steps.cache-venv.outputs.cache-hit != 'true' && runner.os != 'Windows'
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
python -m venv venv
|
python -m venv venv
|
||||||
. venv/bin/activate
|
source venv/bin/activate
|
||||||
|
python --version
|
||||||
|
pip install -r requirements.txt -r requirements_optional.txt -r requirements_test.txt
|
||||||
|
pip install -e .
|
||||||
|
- name: Create Python virtual environment
|
||||||
|
if: steps.cache-venv.outputs.cache-hit != 'true' && runner.os == 'Windows'
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
python -m venv venv
|
||||||
|
./venv/Scripts/activate
|
||||||
python --version
|
python --version
|
||||||
pip install -r requirements.txt -r requirements_optional.txt -r requirements_test.txt
|
pip install -r requirements.txt -r requirements_optional.txt -r requirements_test.txt
|
||||||
pip install -e .
|
pip install -e .
|
||||||
|
|
105
.github/workflows/ci.yml
vendored
105
.github/workflows/ci.yml
vendored
|
@ -45,7 +45,7 @@ jobs:
|
||||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||||
- name: Restore Python virtual environment
|
- name: Restore Python virtual environment
|
||||||
id: cache-venv
|
id: cache-venv
|
||||||
uses: actions/cache@v3.3.2
|
uses: actions/cache@v4.0.0
|
||||||
with:
|
with:
|
||||||
path: venv
|
path: venv
|
||||||
# yamllint disable-line rule:line-length
|
# yamllint disable-line rule:line-length
|
||||||
|
@ -166,7 +166,35 @@ jobs:
|
||||||
|
|
||||||
pytest:
|
pytest:
|
||||||
name: Run pytest
|
name: Run pytest
|
||||||
runs-on: ubuntu-latest
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
python-version:
|
||||||
|
- "3.9"
|
||||||
|
- "3.10"
|
||||||
|
- "3.11"
|
||||||
|
- "3.12"
|
||||||
|
os:
|
||||||
|
- ubuntu-latest
|
||||||
|
- macOS-latest
|
||||||
|
- windows-latest
|
||||||
|
exclude:
|
||||||
|
# Minimize CI resource usage
|
||||||
|
# by only running the Python version
|
||||||
|
# version used for docker images on Windows and macOS
|
||||||
|
- python-version: "3.12"
|
||||||
|
os: windows-latest
|
||||||
|
- python-version: "3.10"
|
||||||
|
os: windows-latest
|
||||||
|
- python-version: "3.9"
|
||||||
|
os: windows-latest
|
||||||
|
- python-version: "3.12"
|
||||||
|
os: macOS-latest
|
||||||
|
- python-version: "3.10"
|
||||||
|
os: macOS-latest
|
||||||
|
- python-version: "3.9"
|
||||||
|
os: macOS-latest
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
needs:
|
needs:
|
||||||
- common
|
- common
|
||||||
steps:
|
steps:
|
||||||
|
@ -175,14 +203,24 @@ jobs:
|
||||||
- name: Restore Python
|
- name: Restore Python
|
||||||
uses: ./.github/actions/restore-python
|
uses: ./.github/actions/restore-python
|
||||||
with:
|
with:
|
||||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
python-version: ${{ matrix.python-version }}
|
||||||
cache-key: ${{ needs.common.outputs.cache-key }}
|
cache-key: ${{ needs.common.outputs.cache-key }}
|
||||||
- name: Register matcher
|
- name: Register matcher
|
||||||
run: echo "::add-matcher::.github/workflows/matchers/pytest.json"
|
run: echo "::add-matcher::.github/workflows/matchers/pytest.json"
|
||||||
- name: Run pytest
|
- name: Run pytest
|
||||||
|
if: matrix.os == 'windows-latest'
|
||||||
|
run: |
|
||||||
|
./venv/Scripts/activate
|
||||||
|
pytest -vv --cov-report=xml --tb=native tests
|
||||||
|
- name: Run pytest
|
||||||
|
if: matrix.os == 'ubuntu-latest' || matrix.os == 'macOS-latest'
|
||||||
run: |
|
run: |
|
||||||
. venv/bin/activate
|
. venv/bin/activate
|
||||||
pytest -vv --tb=native tests
|
pytest -vv --cov-report=xml --tb=native tests
|
||||||
|
- name: Upload coverage to Codecov
|
||||||
|
uses: codecov/codecov-action@v3
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.CODECOV_TOKEN }}
|
||||||
|
|
||||||
clang-format:
|
clang-format:
|
||||||
name: Check clang-format
|
name: Check clang-format
|
||||||
|
@ -327,7 +365,7 @@ jobs:
|
||||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||||
cache-key: ${{ needs.common.outputs.cache-key }}
|
cache-key: ${{ needs.common.outputs.cache-key }}
|
||||||
- name: Cache platformio
|
- name: Cache platformio
|
||||||
uses: actions/cache@v3.3.2
|
uses: actions/cache@v4.0.0
|
||||||
with:
|
with:
|
||||||
path: ~/.platformio
|
path: ~/.platformio
|
||||||
# yamllint disable-line rule:line-length
|
# yamllint disable-line rule:line-length
|
||||||
|
@ -354,6 +392,62 @@ jobs:
|
||||||
# yamllint disable-line rule:line-length
|
# yamllint disable-line rule:line-length
|
||||||
if: always()
|
if: always()
|
||||||
|
|
||||||
|
list-components:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs:
|
||||||
|
- common
|
||||||
|
outputs:
|
||||||
|
matrix: ${{ steps.set-matrix.outputs.matrix }}
|
||||||
|
steps:
|
||||||
|
- name: Check out code from GitHub
|
||||||
|
uses: actions/checkout@v4.1.1
|
||||||
|
with:
|
||||||
|
# Fetch enough history so `git merge-base refs/remotes/origin/dev HEAD` works.
|
||||||
|
fetch-depth: 500
|
||||||
|
- name: Fetch dev branch
|
||||||
|
run: |
|
||||||
|
git -c protocol.version=2 fetch --no-tags --prune --no-recurse-submodules --depth=1 origin +refs/heads/dev*:refs/remotes/origin/dev* +refs/tags/dev*:refs/tags/dev*
|
||||||
|
git merge-base refs/remotes/origin/dev HEAD
|
||||||
|
- name: Restore Python
|
||||||
|
uses: ./.github/actions/restore-python
|
||||||
|
with:
|
||||||
|
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||||
|
cache-key: ${{ needs.common.outputs.cache-key }}
|
||||||
|
- name: Find changed components
|
||||||
|
id: set-matrix
|
||||||
|
run: |
|
||||||
|
. venv/bin/activate
|
||||||
|
echo "matrix=$(script/list-components.py --changed | jq -R -s -c 'split("\n")[:-1]')" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
test-build-components:
|
||||||
|
name: Component test ${{ matrix.file }}
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs:
|
||||||
|
- common
|
||||||
|
- list-components
|
||||||
|
if: ${{ needs.list-components.outputs.matrix != '[]' && needs.list-components.outputs.matrix != '' }}
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
max-parallel: 2
|
||||||
|
matrix:
|
||||||
|
file: ${{ fromJson(needs.list-components.outputs.matrix) }}
|
||||||
|
steps:
|
||||||
|
- name: Check out code from GitHub
|
||||||
|
uses: actions/checkout@v4.1.1
|
||||||
|
- name: Restore Python
|
||||||
|
uses: ./.github/actions/restore-python
|
||||||
|
with:
|
||||||
|
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||||
|
cache-key: ${{ needs.common.outputs.cache-key }}
|
||||||
|
- name: test_build_components -e config -c ${{ matrix.file }}
|
||||||
|
run: |
|
||||||
|
. venv/bin/activate
|
||||||
|
./script/test_build_components -e config -c ${{ matrix.file }}
|
||||||
|
- name: test_build_components -e compile -c ${{ matrix.file }}
|
||||||
|
run: |
|
||||||
|
. venv/bin/activate
|
||||||
|
./script/test_build_components -e compile -c ${{ matrix.file }}
|
||||||
|
|
||||||
ci-status:
|
ci-status:
|
||||||
name: CI Status
|
name: CI Status
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
@ -368,6 +462,7 @@ jobs:
|
||||||
- pyupgrade
|
- pyupgrade
|
||||||
- compile-tests
|
- compile-tests
|
||||||
- clang-tidy
|
- clang-tidy
|
||||||
|
- test-build-components
|
||||||
if: always()
|
if: always()
|
||||||
steps:
|
steps:
|
||||||
- name: Success
|
- name: Success
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
# See https://pre-commit.com/hooks.html for more hooks
|
# See https://pre-commit.com/hooks.html for more hooks
|
||||||
repos:
|
repos:
|
||||||
- repo: https://github.com/psf/black-pre-commit-mirror
|
- repo: https://github.com/psf/black-pre-commit-mirror
|
||||||
rev: 23.12.0
|
rev: 23.12.1
|
||||||
hooks:
|
hooks:
|
||||||
- id: black
|
- id: black
|
||||||
args:
|
args:
|
||||||
|
|
|
@ -54,6 +54,8 @@ esphome/components/bl0940/* @tobias-
|
||||||
esphome/components/bl0942/* @dbuezas
|
esphome/components/bl0942/* @dbuezas
|
||||||
esphome/components/ble_client/* @buxtronix @clydebarrow
|
esphome/components/ble_client/* @buxtronix @clydebarrow
|
||||||
esphome/components/bluetooth_proxy/* @jesserockz
|
esphome/components/bluetooth_proxy/* @jesserockz
|
||||||
|
esphome/components/bme280_base/* @esphome/core
|
||||||
|
esphome/components/bme280_spi/* @apbodrov
|
||||||
esphome/components/bme680_bsec/* @trvrnrth
|
esphome/components/bme680_bsec/* @trvrnrth
|
||||||
esphome/components/bmi160/* @flaviut
|
esphome/components/bmi160/* @flaviut
|
||||||
esphome/components/bmp3xx/* @martgras
|
esphome/components/bmp3xx/* @martgras
|
||||||
|
@ -69,6 +71,7 @@ esphome/components/cd74hc4067/* @asoehlke
|
||||||
esphome/components/climate/* @esphome/core
|
esphome/components/climate/* @esphome/core
|
||||||
esphome/components/climate_ir/* @glmnet
|
esphome/components/climate_ir/* @glmnet
|
||||||
esphome/components/color_temperature/* @jesserockz
|
esphome/components/color_temperature/* @jesserockz
|
||||||
|
esphome/components/combination/* @Cat-Ion @kahrendt
|
||||||
esphome/components/coolix/* @glmnet
|
esphome/components/coolix/* @glmnet
|
||||||
esphome/components/copy/* @OttoWinter
|
esphome/components/copy/* @OttoWinter
|
||||||
esphome/components/cover/* @esphome/core
|
esphome/components/cover/* @esphome/core
|
||||||
|
@ -135,6 +138,7 @@ esphome/components/heatpumpir/* @rob-deutsch
|
||||||
esphome/components/hitachi_ac424/* @sourabhjaiswal
|
esphome/components/hitachi_ac424/* @sourabhjaiswal
|
||||||
esphome/components/hm3301/* @freekode
|
esphome/components/hm3301/* @freekode
|
||||||
esphome/components/homeassistant/* @OttoWinter
|
esphome/components/homeassistant/* @OttoWinter
|
||||||
|
esphome/components/honeywell_hih_i2c/* @Benichou34
|
||||||
esphome/components/honeywellabp/* @RubyBailey
|
esphome/components/honeywellabp/* @RubyBailey
|
||||||
esphome/components/honeywellabp2_i2c/* @jpfaff
|
esphome/components/honeywellabp2_i2c/* @jpfaff
|
||||||
esphome/components/host/* @esphome/core
|
esphome/components/host/* @esphome/core
|
||||||
|
@ -158,7 +162,6 @@ esphome/components/integration/* @OttoWinter
|
||||||
esphome/components/internal_temperature/* @Mat931
|
esphome/components/internal_temperature/* @Mat931
|
||||||
esphome/components/interval/* @esphome/core
|
esphome/components/interval/* @esphome/core
|
||||||
esphome/components/json/* @OttoWinter
|
esphome/components/json/* @OttoWinter
|
||||||
esphome/components/kalman_combinator/* @Cat-Ion
|
|
||||||
esphome/components/key_collector/* @ssieb
|
esphome/components/key_collector/* @ssieb
|
||||||
esphome/components/key_provider/* @ssieb
|
esphome/components/key_provider/* @ssieb
|
||||||
esphome/components/kuntze/* @ssieb
|
esphome/components/kuntze/* @ssieb
|
||||||
|
@ -225,7 +228,7 @@ esphome/components/nextion/binary_sensor/* @senexcrenshaw
|
||||||
esphome/components/nextion/sensor/* @senexcrenshaw
|
esphome/components/nextion/sensor/* @senexcrenshaw
|
||||||
esphome/components/nextion/switch/* @senexcrenshaw
|
esphome/components/nextion/switch/* @senexcrenshaw
|
||||||
esphome/components/nextion/text_sensor/* @senexcrenshaw
|
esphome/components/nextion/text_sensor/* @senexcrenshaw
|
||||||
esphome/components/nfc/* @jesserockz
|
esphome/components/nfc/* @jesserockz @kbx81
|
||||||
esphome/components/noblex/* @AGalfra
|
esphome/components/noblex/* @AGalfra
|
||||||
esphome/components/number/* @esphome/core
|
esphome/components/number/* @esphome/core
|
||||||
esphome/components/ota/* @esphome/core
|
esphome/components/ota/* @esphome/core
|
||||||
|
@ -360,9 +363,11 @@ esphome/components/ufire_ec/* @pvizeli
|
||||||
esphome/components/ufire_ise/* @pvizeli
|
esphome/components/ufire_ise/* @pvizeli
|
||||||
esphome/components/ultrasonic/* @OttoWinter
|
esphome/components/ultrasonic/* @OttoWinter
|
||||||
esphome/components/vbus/* @ssieb
|
esphome/components/vbus/* @ssieb
|
||||||
|
esphome/components/veml3235/* @kbx81
|
||||||
esphome/components/version/* @esphome/core
|
esphome/components/version/* @esphome/core
|
||||||
esphome/components/voice_assistant/* @jesserockz
|
esphome/components/voice_assistant/* @jesserockz
|
||||||
esphome/components/wake_on_lan/* @willwill2will54
|
esphome/components/wake_on_lan/* @willwill2will54
|
||||||
|
esphome/components/waveshare_epaper/* @clydebarrow
|
||||||
esphome/components/web_server_base/* @OttoWinter
|
esphome/components/web_server_base/* @OttoWinter
|
||||||
esphome/components/web_server_idf/* @dentra
|
esphome/components/web_server_idf/* @dentra
|
||||||
esphome/components/whirlpool/* @glmnet
|
esphome/components/whirlpool/* @glmnet
|
||||||
|
|
|
@ -81,7 +81,7 @@ RUN \
|
||||||
fi; \
|
fi; \
|
||||||
pip3 install \
|
pip3 install \
|
||||||
--break-system-packages --no-cache-dir \
|
--break-system-packages --no-cache-dir \
|
||||||
platformio==6.1.11 \
|
platformio==6.1.13 \
|
||||||
# Change some platformio settings
|
# Change some platformio settings
|
||||||
&& platformio settings set enable_telemetry No \
|
&& platformio settings set enable_telemetry No \
|
||||||
&& platformio settings set check_platformio_interval 1000000 \
|
&& platformio settings set check_platformio_interval 1000000 \
|
||||||
|
|
|
@ -139,6 +139,9 @@ ESP32_VARIANT_ADC2_PIN_TO_CHANNEL = {
|
||||||
VARIANT_ESP32C3: {
|
VARIANT_ESP32C3: {
|
||||||
5: adc2_channel_t.ADC2_CHANNEL_0,
|
5: adc2_channel_t.ADC2_CHANNEL_0,
|
||||||
},
|
},
|
||||||
|
VARIANT_ESP32C2: {},
|
||||||
|
VARIANT_ESP32C6: {},
|
||||||
|
VARIANT_ESP32H2: {},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -242,7 +242,7 @@ void BedJetHub::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga
|
||||||
this->set_notify_(true);
|
this->set_notify_(true);
|
||||||
|
|
||||||
#ifdef USE_TIME
|
#ifdef USE_TIME
|
||||||
if (this->time_id_.has_value()) {
|
if (this->time_id_ != nullptr) {
|
||||||
this->send_local_time();
|
this->send_local_time();
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
@ -441,9 +441,8 @@ uint8_t BedJetHub::write_notify_config_descriptor_(bool enable) {
|
||||||
|
|
||||||
#ifdef USE_TIME
|
#ifdef USE_TIME
|
||||||
void BedJetHub::send_local_time() {
|
void BedJetHub::send_local_time() {
|
||||||
if (this->time_id_.has_value()) {
|
if (this->time_id_ != nullptr) {
|
||||||
auto *time_id = *this->time_id_;
|
ESPTime now = this->time_id_->now();
|
||||||
ESPTime now = time_id->now();
|
|
||||||
if (now.is_valid()) {
|
if (now.is_valid()) {
|
||||||
this->set_clock(now.hour, now.minute);
|
this->set_clock(now.hour, now.minute);
|
||||||
ESP_LOGD(TAG, "Using time component to set BedJet clock: %d:%02d", now.hour, now.minute);
|
ESP_LOGD(TAG, "Using time component to set BedJet clock: %d:%02d", now.hour, now.minute);
|
||||||
|
@ -454,10 +453,9 @@ void BedJetHub::send_local_time() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void BedJetHub::setup_time_() {
|
void BedJetHub::setup_time_() {
|
||||||
if (this->time_id_.has_value()) {
|
if (this->time_id_ != nullptr) {
|
||||||
this->send_local_time();
|
this->send_local_time();
|
||||||
auto *time_id = *this->time_id_;
|
this->time_id_->add_on_time_sync_callback([this] { this->send_local_time(); });
|
||||||
time_id->add_on_time_sync_callback([this] { this->send_local_time(); });
|
|
||||||
} else {
|
} else {
|
||||||
ESP_LOGI(TAG, "`time_id` is not configured: will not sync BedJet clock.");
|
ESP_LOGI(TAG, "`time_id` is not configured: will not sync BedJet clock.");
|
||||||
}
|
}
|
||||||
|
|
|
@ -141,7 +141,7 @@ class BedJetHub : public esphome::ble_client::BLEClientNode, public PollingCompo
|
||||||
#ifdef USE_TIME
|
#ifdef USE_TIME
|
||||||
/** Initializes time sync callbacks to support syncing current time to the BedJet. */
|
/** Initializes time sync callbacks to support syncing current time to the BedJet. */
|
||||||
void setup_time_();
|
void setup_time_();
|
||||||
optional<time::RealTimeClock *> time_id_{};
|
time::RealTimeClock *time_id_{nullptr};
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
uint32_t timeout_{DEFAULT_STATUS_TIMEOUT};
|
uint32_t timeout_{DEFAULT_STATUS_TIMEOUT};
|
||||||
|
|
|
@ -18,6 +18,7 @@ from esphome.const import (
|
||||||
UNIT_KILOWATT_HOURS,
|
UNIT_KILOWATT_HOURS,
|
||||||
UNIT_VOLT,
|
UNIT_VOLT,
|
||||||
UNIT_WATT,
|
UNIT_WATT,
|
||||||
|
STATE_CLASS_TOTAL_INCREASING,
|
||||||
)
|
)
|
||||||
|
|
||||||
DEPENDENCIES = ["uart"]
|
DEPENDENCIES = ["uart"]
|
||||||
|
@ -54,6 +55,7 @@ CONFIG_SCHEMA = (
|
||||||
unit_of_measurement=UNIT_KILOWATT_HOURS,
|
unit_of_measurement=UNIT_KILOWATT_HOURS,
|
||||||
accuracy_decimals=0,
|
accuracy_decimals=0,
|
||||||
device_class=DEVICE_CLASS_ENERGY,
|
device_class=DEVICE_CLASS_ENERGY,
|
||||||
|
state_class=STATE_CLASS_TOTAL_INCREASING,
|
||||||
),
|
),
|
||||||
cv.Optional(CONF_INTERNAL_TEMPERATURE): sensor.sensor_schema(
|
cv.Optional(CONF_INTERNAL_TEMPERATURE): sensor.sensor_schema(
|
||||||
unit_of_measurement=UNIT_CELSIUS,
|
unit_of_measurement=UNIT_CELSIUS,
|
||||||
|
|
|
@ -19,6 +19,7 @@ from esphome.const import (
|
||||||
UNIT_VOLT,
|
UNIT_VOLT,
|
||||||
UNIT_WATT,
|
UNIT_WATT,
|
||||||
UNIT_HERTZ,
|
UNIT_HERTZ,
|
||||||
|
STATE_CLASS_TOTAL_INCREASING,
|
||||||
)
|
)
|
||||||
|
|
||||||
DEPENDENCIES = ["uart"]
|
DEPENDENCIES = ["uart"]
|
||||||
|
@ -52,6 +53,7 @@ CONFIG_SCHEMA = (
|
||||||
unit_of_measurement=UNIT_KILOWATT_HOURS,
|
unit_of_measurement=UNIT_KILOWATT_HOURS,
|
||||||
accuracy_decimals=0,
|
accuracy_decimals=0,
|
||||||
device_class=DEVICE_CLASS_ENERGY,
|
device_class=DEVICE_CLASS_ENERGY,
|
||||||
|
state_class=STATE_CLASS_TOTAL_INCREASING,
|
||||||
),
|
),
|
||||||
cv.Optional(CONF_FREQUENCY): sensor.sensor_schema(
|
cv.Optional(CONF_FREQUENCY): sensor.sensor_schema(
|
||||||
unit_of_measurement=UNIT_HERTZ,
|
unit_of_measurement=UNIT_HERTZ,
|
||||||
|
|
|
@ -1,116 +0,0 @@
|
||||||
import esphome.codegen as cg
|
|
||||||
import esphome.config_validation as cv
|
|
||||||
from esphome.components import i2c, sensor
|
|
||||||
from esphome.const import (
|
|
||||||
CONF_HUMIDITY,
|
|
||||||
CONF_ID,
|
|
||||||
CONF_IIR_FILTER,
|
|
||||||
CONF_OVERSAMPLING,
|
|
||||||
CONF_PRESSURE,
|
|
||||||
CONF_TEMPERATURE,
|
|
||||||
DEVICE_CLASS_HUMIDITY,
|
|
||||||
DEVICE_CLASS_PRESSURE,
|
|
||||||
DEVICE_CLASS_TEMPERATURE,
|
|
||||||
STATE_CLASS_MEASUREMENT,
|
|
||||||
UNIT_CELSIUS,
|
|
||||||
UNIT_HECTOPASCAL,
|
|
||||||
UNIT_PERCENT,
|
|
||||||
)
|
|
||||||
|
|
||||||
DEPENDENCIES = ["i2c"]
|
|
||||||
|
|
||||||
bme280_ns = cg.esphome_ns.namespace("bme280")
|
|
||||||
BME280Oversampling = bme280_ns.enum("BME280Oversampling")
|
|
||||||
OVERSAMPLING_OPTIONS = {
|
|
||||||
"NONE": BME280Oversampling.BME280_OVERSAMPLING_NONE,
|
|
||||||
"1X": BME280Oversampling.BME280_OVERSAMPLING_1X,
|
|
||||||
"2X": BME280Oversampling.BME280_OVERSAMPLING_2X,
|
|
||||||
"4X": BME280Oversampling.BME280_OVERSAMPLING_4X,
|
|
||||||
"8X": BME280Oversampling.BME280_OVERSAMPLING_8X,
|
|
||||||
"16X": BME280Oversampling.BME280_OVERSAMPLING_16X,
|
|
||||||
}
|
|
||||||
|
|
||||||
BME280IIRFilter = bme280_ns.enum("BME280IIRFilter")
|
|
||||||
IIR_FILTER_OPTIONS = {
|
|
||||||
"OFF": BME280IIRFilter.BME280_IIR_FILTER_OFF,
|
|
||||||
"2X": BME280IIRFilter.BME280_IIR_FILTER_2X,
|
|
||||||
"4X": BME280IIRFilter.BME280_IIR_FILTER_4X,
|
|
||||||
"8X": BME280IIRFilter.BME280_IIR_FILTER_8X,
|
|
||||||
"16X": BME280IIRFilter.BME280_IIR_FILTER_16X,
|
|
||||||
}
|
|
||||||
|
|
||||||
BME280Component = bme280_ns.class_(
|
|
||||||
"BME280Component", cg.PollingComponent, i2c.I2CDevice
|
|
||||||
)
|
|
||||||
|
|
||||||
CONFIG_SCHEMA = (
|
|
||||||
cv.Schema(
|
|
||||||
{
|
|
||||||
cv.GenerateID(): cv.declare_id(BME280Component),
|
|
||||||
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
|
|
||||||
unit_of_measurement=UNIT_CELSIUS,
|
|
||||||
accuracy_decimals=1,
|
|
||||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
|
||||||
state_class=STATE_CLASS_MEASUREMENT,
|
|
||||||
).extend(
|
|
||||||
{
|
|
||||||
cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum(
|
|
||||||
OVERSAMPLING_OPTIONS, upper=True
|
|
||||||
),
|
|
||||||
}
|
|
||||||
),
|
|
||||||
cv.Optional(CONF_PRESSURE): sensor.sensor_schema(
|
|
||||||
unit_of_measurement=UNIT_HECTOPASCAL,
|
|
||||||
accuracy_decimals=1,
|
|
||||||
device_class=DEVICE_CLASS_PRESSURE,
|
|
||||||
state_class=STATE_CLASS_MEASUREMENT,
|
|
||||||
).extend(
|
|
||||||
{
|
|
||||||
cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum(
|
|
||||||
OVERSAMPLING_OPTIONS, upper=True
|
|
||||||
),
|
|
||||||
}
|
|
||||||
),
|
|
||||||
cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
|
|
||||||
unit_of_measurement=UNIT_PERCENT,
|
|
||||||
accuracy_decimals=1,
|
|
||||||
device_class=DEVICE_CLASS_HUMIDITY,
|
|
||||||
state_class=STATE_CLASS_MEASUREMENT,
|
|
||||||
).extend(
|
|
||||||
{
|
|
||||||
cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum(
|
|
||||||
OVERSAMPLING_OPTIONS, upper=True
|
|
||||||
),
|
|
||||||
}
|
|
||||||
),
|
|
||||||
cv.Optional(CONF_IIR_FILTER, default="OFF"): cv.enum(
|
|
||||||
IIR_FILTER_OPTIONS, upper=True
|
|
||||||
),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.extend(cv.polling_component_schema("60s"))
|
|
||||||
.extend(i2c.i2c_device_schema(0x77))
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
async def to_code(config):
|
|
||||||
var = cg.new_Pvariable(config[CONF_ID])
|
|
||||||
await cg.register_component(var, config)
|
|
||||||
await i2c.register_i2c_device(var, config)
|
|
||||||
|
|
||||||
if temperature_config := config.get(CONF_TEMPERATURE):
|
|
||||||
sens = await sensor.new_sensor(temperature_config)
|
|
||||||
cg.add(var.set_temperature_sensor(sens))
|
|
||||||
cg.add(var.set_temperature_oversampling(temperature_config[CONF_OVERSAMPLING]))
|
|
||||||
|
|
||||||
if pressure_config := config.get(CONF_PRESSURE):
|
|
||||||
sens = await sensor.new_sensor(pressure_config)
|
|
||||||
cg.add(var.set_pressure_sensor(sens))
|
|
||||||
cg.add(var.set_pressure_oversampling(pressure_config[CONF_OVERSAMPLING]))
|
|
||||||
|
|
||||||
if humidity_config := config.get(CONF_HUMIDITY):
|
|
||||||
sens = await sensor.new_sensor(humidity_config)
|
|
||||||
cg.add(var.set_humidity_sensor(sens))
|
|
||||||
cg.add(var.set_humidity_oversampling(humidity_config[CONF_OVERSAMPLING]))
|
|
||||||
|
|
||||||
cg.add(var.set_iir_filter(config[CONF_IIR_FILTER]))
|
|
1
esphome/components/bme280_base/__init__.py
Normal file
1
esphome/components/bme280_base/__init__.py
Normal file
|
@ -0,0 +1 @@
|
||||||
|
CODEOWNERS = ["@esphome/core"]
|
|
@ -1,9 +1,14 @@
|
||||||
#include "bme280.h"
|
#include <cmath>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
#include "bme280_base.h"
|
||||||
#include "esphome/core/hal.h"
|
#include "esphome/core/hal.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
#include <esphome/components/sensor/sensor.h>
|
||||||
|
#include <esphome/core/component.h>
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace bme280 {
|
namespace bme280_base {
|
||||||
|
|
||||||
static const char *const TAG = "bme280.sensor";
|
static const char *const TAG = "bme280.sensor";
|
||||||
|
|
||||||
|
@ -46,7 +51,24 @@ static const uint8_t BME280_STATUS_IM_UPDATE = 0b01;
|
||||||
|
|
||||||
inline uint16_t combine_bytes(uint8_t msb, uint8_t lsb) { return ((msb & 0xFF) << 8) | (lsb & 0xFF); }
|
inline uint16_t combine_bytes(uint8_t msb, uint8_t lsb) { return ((msb & 0xFF) << 8) | (lsb & 0xFF); }
|
||||||
|
|
||||||
static const char *oversampling_to_str(BME280Oversampling oversampling) {
|
const char *iir_filter_to_str(BME280IIRFilter filter) { // NOLINT
|
||||||
|
switch (filter) {
|
||||||
|
case BME280_IIR_FILTER_OFF:
|
||||||
|
return "OFF";
|
||||||
|
case BME280_IIR_FILTER_2X:
|
||||||
|
return "2x";
|
||||||
|
case BME280_IIR_FILTER_4X:
|
||||||
|
return "4x";
|
||||||
|
case BME280_IIR_FILTER_8X:
|
||||||
|
return "8x";
|
||||||
|
case BME280_IIR_FILTER_16X:
|
||||||
|
return "16x";
|
||||||
|
default:
|
||||||
|
return "UNKNOWN";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *oversampling_to_str(BME280Oversampling oversampling) { // NOLINT
|
||||||
switch (oversampling) {
|
switch (oversampling) {
|
||||||
case BME280_OVERSAMPLING_NONE:
|
case BME280_OVERSAMPLING_NONE:
|
||||||
return "None";
|
return "None";
|
||||||
|
@ -65,23 +87,6 @@ static const char *oversampling_to_str(BME280Oversampling oversampling) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static const char *iir_filter_to_str(BME280IIRFilter filter) {
|
|
||||||
switch (filter) {
|
|
||||||
case BME280_IIR_FILTER_OFF:
|
|
||||||
return "OFF";
|
|
||||||
case BME280_IIR_FILTER_2X:
|
|
||||||
return "2x";
|
|
||||||
case BME280_IIR_FILTER_4X:
|
|
||||||
return "4x";
|
|
||||||
case BME280_IIR_FILTER_8X:
|
|
||||||
return "8x";
|
|
||||||
case BME280_IIR_FILTER_16X:
|
|
||||||
return "16x";
|
|
||||||
default:
|
|
||||||
return "UNKNOWN";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void BME280Component::setup() {
|
void BME280Component::setup() {
|
||||||
ESP_LOGCONFIG(TAG, "Setting up BME280...");
|
ESP_LOGCONFIG(TAG, "Setting up BME280...");
|
||||||
uint8_t chip_id = 0;
|
uint8_t chip_id = 0;
|
||||||
|
@ -112,7 +117,7 @@ void BME280Component::setup() {
|
||||||
// Wait until the NVM data has finished loading.
|
// Wait until the NVM data has finished loading.
|
||||||
uint8_t status;
|
uint8_t status;
|
||||||
uint8_t retry = 5;
|
uint8_t retry = 5;
|
||||||
do {
|
do { // NOLINT
|
||||||
delay(2);
|
delay(2);
|
||||||
if (!this->read_byte(BME280_REGISTER_STATUS, &status)) {
|
if (!this->read_byte(BME280_REGISTER_STATUS, &status)) {
|
||||||
ESP_LOGW(TAG, "Error reading status register.");
|
ESP_LOGW(TAG, "Error reading status register.");
|
||||||
|
@ -175,7 +180,6 @@ void BME280Component::setup() {
|
||||||
}
|
}
|
||||||
void BME280Component::dump_config() {
|
void BME280Component::dump_config() {
|
||||||
ESP_LOGCONFIG(TAG, "BME280:");
|
ESP_LOGCONFIG(TAG, "BME280:");
|
||||||
LOG_I2C_DEVICE(this);
|
|
||||||
switch (this->error_code_) {
|
switch (this->error_code_) {
|
||||||
case COMMUNICATION_FAILED:
|
case COMMUNICATION_FAILED:
|
||||||
ESP_LOGE(TAG, "Communication with BME280 failed!");
|
ESP_LOGE(TAG, "Communication with BME280 failed!");
|
||||||
|
@ -226,14 +230,14 @@ void BME280Component::update() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
int32_t t_fine = 0;
|
int32_t t_fine = 0;
|
||||||
float temperature = this->read_temperature_(data, &t_fine);
|
float const temperature = this->read_temperature_(data, &t_fine);
|
||||||
if (std::isnan(temperature)) {
|
if (std::isnan(temperature)) {
|
||||||
ESP_LOGW(TAG, "Invalid temperature, cannot read pressure & humidity values.");
|
ESP_LOGW(TAG, "Invalid temperature, cannot read pressure & humidity values.");
|
||||||
this->status_set_warning();
|
this->status_set_warning();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
float pressure = this->read_pressure_(data, t_fine);
|
float const pressure = this->read_pressure_(data, t_fine);
|
||||||
float humidity = this->read_humidity_(data, t_fine);
|
float const humidity = this->read_humidity_(data, t_fine);
|
||||||
|
|
||||||
ESP_LOGV(TAG, "Got temperature=%.1f°C pressure=%.1fhPa humidity=%.1f%%", temperature, pressure, humidity);
|
ESP_LOGV(TAG, "Got temperature=%.1f°C pressure=%.1fhPa humidity=%.1f%%", temperature, pressure, humidity);
|
||||||
if (this->temperature_sensor_ != nullptr)
|
if (this->temperature_sensor_ != nullptr)
|
||||||
|
@ -257,12 +261,12 @@ float BME280Component::read_temperature_(const uint8_t *data, int32_t *t_fine) {
|
||||||
const int32_t t2 = this->calibration_.t2;
|
const int32_t t2 = this->calibration_.t2;
|
||||||
const int32_t t3 = this->calibration_.t3;
|
const int32_t t3 = this->calibration_.t3;
|
||||||
|
|
||||||
int32_t var1 = (((adc >> 3) - (t1 << 1)) * t2) >> 11;
|
int32_t const var1 = (((adc >> 3) - (t1 << 1)) * t2) >> 11;
|
||||||
int32_t var2 = (((((adc >> 4) - t1) * ((adc >> 4) - t1)) >> 12) * t3) >> 14;
|
int32_t const var2 = (((((adc >> 4) - t1) * ((adc >> 4) - t1)) >> 12) * t3) >> 14;
|
||||||
*t_fine = var1 + var2;
|
*t_fine = var1 + var2;
|
||||||
|
|
||||||
float temperature = (*t_fine * 5 + 128) >> 8;
|
float const temperature = (*t_fine * 5 + 128);
|
||||||
return temperature / 100.0f;
|
return temperature / 25600.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
float BME280Component::read_pressure_(const uint8_t *data, int32_t t_fine) {
|
float BME280Component::read_pressure_(const uint8_t *data, int32_t t_fine) {
|
||||||
|
@ -303,11 +307,11 @@ float BME280Component::read_pressure_(const uint8_t *data, int32_t t_fine) {
|
||||||
}
|
}
|
||||||
|
|
||||||
float BME280Component::read_humidity_(const uint8_t *data, int32_t t_fine) {
|
float BME280Component::read_humidity_(const uint8_t *data, int32_t t_fine) {
|
||||||
uint16_t raw_adc = ((data[6] & 0xFF) << 8) | (data[7] & 0xFF);
|
uint16_t const raw_adc = ((data[6] & 0xFF) << 8) | (data[7] & 0xFF);
|
||||||
if (raw_adc == 0x8000)
|
if (raw_adc == 0x8000)
|
||||||
return NAN;
|
return NAN;
|
||||||
|
|
||||||
int32_t adc = raw_adc;
|
int32_t const adc = raw_adc;
|
||||||
|
|
||||||
const int32_t h1 = this->calibration_.h1;
|
const int32_t h1 = this->calibration_.h1;
|
||||||
const int32_t h2 = this->calibration_.h2;
|
const int32_t h2 = this->calibration_.h2;
|
||||||
|
@ -325,7 +329,7 @@ float BME280Component::read_humidity_(const uint8_t *data, int32_t t_fine) {
|
||||||
|
|
||||||
v_x1_u32r = v_x1_u32r < 0 ? 0 : v_x1_u32r;
|
v_x1_u32r = v_x1_u32r < 0 ? 0 : v_x1_u32r;
|
||||||
v_x1_u32r = v_x1_u32r > 419430400 ? 419430400 : v_x1_u32r;
|
v_x1_u32r = v_x1_u32r > 419430400 ? 419430400 : v_x1_u32r;
|
||||||
float h = v_x1_u32r >> 12;
|
float const h = v_x1_u32r >> 12;
|
||||||
|
|
||||||
return h / 1024.0f;
|
return h / 1024.0f;
|
||||||
}
|
}
|
||||||
|
@ -351,5 +355,5 @@ uint16_t BME280Component::read_u16_le_(uint8_t a_register) {
|
||||||
}
|
}
|
||||||
int16_t BME280Component::read_s16_le_(uint8_t a_register) { return this->read_u16_le_(a_register); }
|
int16_t BME280Component::read_s16_le_(uint8_t a_register) { return this->read_u16_le_(a_register); }
|
||||||
|
|
||||||
} // namespace bme280
|
} // namespace bme280_base
|
||||||
} // namespace esphome
|
} // namespace esphome
|
|
@ -2,10 +2,9 @@
|
||||||
|
|
||||||
#include "esphome/core/component.h"
|
#include "esphome/core/component.h"
|
||||||
#include "esphome/components/sensor/sensor.h"
|
#include "esphome/components/sensor/sensor.h"
|
||||||
#include "esphome/components/i2c/i2c.h"
|
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace bme280 {
|
namespace bme280_base {
|
||||||
|
|
||||||
/// Internal struct storing the calibration values of an BME280.
|
/// Internal struct storing the calibration values of an BME280.
|
||||||
struct BME280CalibrationData {
|
struct BME280CalibrationData {
|
||||||
|
@ -57,8 +56,8 @@ enum BME280IIRFilter {
|
||||||
BME280_IIR_FILTER_16X = 0b100,
|
BME280_IIR_FILTER_16X = 0b100,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// This class implements support for the BME280 Temperature+Pressure+Humidity i2c sensor.
|
/// This class implements support for the BME280 Temperature+Pressure+Humidity sensor.
|
||||||
class BME280Component : public PollingComponent, public i2c::I2CDevice {
|
class BME280Component : public PollingComponent {
|
||||||
public:
|
public:
|
||||||
void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; }
|
void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; }
|
||||||
void set_pressure_sensor(sensor::Sensor *pressure_sensor) { pressure_sensor_ = pressure_sensor; }
|
void set_pressure_sensor(sensor::Sensor *pressure_sensor) { pressure_sensor_ = pressure_sensor; }
|
||||||
|
@ -91,6 +90,11 @@ class BME280Component : public PollingComponent, public i2c::I2CDevice {
|
||||||
uint16_t read_u16_le_(uint8_t a_register);
|
uint16_t read_u16_le_(uint8_t a_register);
|
||||||
int16_t read_s16_le_(uint8_t a_register);
|
int16_t read_s16_le_(uint8_t a_register);
|
||||||
|
|
||||||
|
virtual bool read_byte(uint8_t a_register, uint8_t *data) = 0;
|
||||||
|
virtual bool write_byte(uint8_t a_register, uint8_t data) = 0;
|
||||||
|
virtual bool read_bytes(uint8_t a_register, uint8_t *data, size_t len) = 0;
|
||||||
|
virtual bool read_byte_16(uint8_t a_register, uint16_t *data) = 0;
|
||||||
|
|
||||||
BME280CalibrationData calibration_;
|
BME280CalibrationData calibration_;
|
||||||
BME280Oversampling temperature_oversampling_{BME280_OVERSAMPLING_16X};
|
BME280Oversampling temperature_oversampling_{BME280_OVERSAMPLING_16X};
|
||||||
BME280Oversampling pressure_oversampling_{BME280_OVERSAMPLING_16X};
|
BME280Oversampling pressure_oversampling_{BME280_OVERSAMPLING_16X};
|
||||||
|
@ -106,5 +110,5 @@ class BME280Component : public PollingComponent, public i2c::I2CDevice {
|
||||||
} error_code_{NONE};
|
} error_code_{NONE};
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace bme280
|
} // namespace bme280_base
|
||||||
} // namespace esphome
|
} // namespace esphome
|
106
esphome/components/bme280_base/sensor.py
Normal file
106
esphome/components/bme280_base/sensor.py
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
import esphome.codegen as cg
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
from esphome.components import sensor
|
||||||
|
from esphome.const import (
|
||||||
|
CONF_HUMIDITY,
|
||||||
|
CONF_ID,
|
||||||
|
CONF_IIR_FILTER,
|
||||||
|
CONF_OVERSAMPLING,
|
||||||
|
CONF_PRESSURE,
|
||||||
|
CONF_TEMPERATURE,
|
||||||
|
DEVICE_CLASS_HUMIDITY,
|
||||||
|
DEVICE_CLASS_PRESSURE,
|
||||||
|
DEVICE_CLASS_TEMPERATURE,
|
||||||
|
STATE_CLASS_MEASUREMENT,
|
||||||
|
UNIT_CELSIUS,
|
||||||
|
UNIT_HECTOPASCAL,
|
||||||
|
UNIT_PERCENT,
|
||||||
|
)
|
||||||
|
|
||||||
|
bme280_ns = cg.esphome_ns.namespace("bme280_base")
|
||||||
|
BME280Oversampling = bme280_ns.enum("BME280Oversampling")
|
||||||
|
OVERSAMPLING_OPTIONS = {
|
||||||
|
"NONE": BME280Oversampling.BME280_OVERSAMPLING_NONE,
|
||||||
|
"1X": BME280Oversampling.BME280_OVERSAMPLING_1X,
|
||||||
|
"2X": BME280Oversampling.BME280_OVERSAMPLING_2X,
|
||||||
|
"4X": BME280Oversampling.BME280_OVERSAMPLING_4X,
|
||||||
|
"8X": BME280Oversampling.BME280_OVERSAMPLING_8X,
|
||||||
|
"16X": BME280Oversampling.BME280_OVERSAMPLING_16X,
|
||||||
|
}
|
||||||
|
|
||||||
|
BME280IIRFilter = bme280_ns.enum("BME280IIRFilter")
|
||||||
|
IIR_FILTER_OPTIONS = {
|
||||||
|
"OFF": BME280IIRFilter.BME280_IIR_FILTER_OFF,
|
||||||
|
"2X": BME280IIRFilter.BME280_IIR_FILTER_2X,
|
||||||
|
"4X": BME280IIRFilter.BME280_IIR_FILTER_4X,
|
||||||
|
"8X": BME280IIRFilter.BME280_IIR_FILTER_8X,
|
||||||
|
"16X": BME280IIRFilter.BME280_IIR_FILTER_16X,
|
||||||
|
}
|
||||||
|
|
||||||
|
CONFIG_SCHEMA_BASE = cv.Schema(
|
||||||
|
{
|
||||||
|
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
|
||||||
|
unit_of_measurement=UNIT_CELSIUS,
|
||||||
|
accuracy_decimals=1,
|
||||||
|
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||||
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
|
).extend(
|
||||||
|
{
|
||||||
|
cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum(
|
||||||
|
OVERSAMPLING_OPTIONS, upper=True
|
||||||
|
),
|
||||||
|
}
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_PRESSURE): sensor.sensor_schema(
|
||||||
|
unit_of_measurement=UNIT_HECTOPASCAL,
|
||||||
|
accuracy_decimals=1,
|
||||||
|
device_class=DEVICE_CLASS_PRESSURE,
|
||||||
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
|
).extend(
|
||||||
|
{
|
||||||
|
cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum(
|
||||||
|
OVERSAMPLING_OPTIONS, upper=True
|
||||||
|
),
|
||||||
|
}
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
|
||||||
|
unit_of_measurement=UNIT_PERCENT,
|
||||||
|
accuracy_decimals=1,
|
||||||
|
device_class=DEVICE_CLASS_HUMIDITY,
|
||||||
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
|
).extend(
|
||||||
|
{
|
||||||
|
cv.Optional(CONF_OVERSAMPLING, default="16X"): cv.enum(
|
||||||
|
OVERSAMPLING_OPTIONS, upper=True
|
||||||
|
),
|
||||||
|
}
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_IIR_FILTER, default="OFF"): cv.enum(
|
||||||
|
IIR_FILTER_OPTIONS, upper=True
|
||||||
|
),
|
||||||
|
}
|
||||||
|
).extend(cv.polling_component_schema("60s"))
|
||||||
|
|
||||||
|
|
||||||
|
async def to_code(config, func=None):
|
||||||
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
|
await cg.register_component(var, config)
|
||||||
|
if func is not None:
|
||||||
|
await func(var, config)
|
||||||
|
|
||||||
|
if temperature_config := config.get(CONF_TEMPERATURE):
|
||||||
|
sens = await sensor.new_sensor(temperature_config)
|
||||||
|
cg.add(var.set_temperature_sensor(sens))
|
||||||
|
cg.add(var.set_temperature_oversampling(temperature_config[CONF_OVERSAMPLING]))
|
||||||
|
|
||||||
|
if pressure_config := config.get(CONF_PRESSURE):
|
||||||
|
sens = await sensor.new_sensor(pressure_config)
|
||||||
|
cg.add(var.set_pressure_sensor(sens))
|
||||||
|
cg.add(var.set_pressure_oversampling(pressure_config[CONF_OVERSAMPLING]))
|
||||||
|
|
||||||
|
if humidity_config := config.get(CONF_HUMIDITY):
|
||||||
|
sens = await sensor.new_sensor(humidity_config)
|
||||||
|
cg.add(var.set_humidity_sensor(sens))
|
||||||
|
cg.add(var.set_humidity_oversampling(humidity_config[CONF_OVERSAMPLING]))
|
||||||
|
|
||||||
|
cg.add(var.set_iir_filter(config[CONF_IIR_FILTER]))
|
30
esphome/components/bme280_i2c/bme280_i2c.cpp
Normal file
30
esphome/components/bme280_i2c/bme280_i2c.cpp
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
#include "bme280_i2c.h"
|
||||||
|
#include "esphome/components/i2c/i2c.h"
|
||||||
|
#include "../bme280_base/bme280_base.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace bme280_i2c {
|
||||||
|
|
||||||
|
bool BME280I2CComponent::read_byte(uint8_t a_register, uint8_t *data) {
|
||||||
|
return I2CDevice::read_byte(a_register, data);
|
||||||
|
};
|
||||||
|
bool BME280I2CComponent::write_byte(uint8_t a_register, uint8_t data) {
|
||||||
|
return I2CDevice::write_byte(a_register, data);
|
||||||
|
};
|
||||||
|
bool BME280I2CComponent::read_bytes(uint8_t a_register, uint8_t *data, size_t len) {
|
||||||
|
return I2CDevice::read_bytes(a_register, data, len);
|
||||||
|
};
|
||||||
|
bool BME280I2CComponent::read_byte_16(uint8_t a_register, uint16_t *data) {
|
||||||
|
return I2CDevice::read_byte_16(a_register, data);
|
||||||
|
};
|
||||||
|
|
||||||
|
void BME280I2CComponent::dump_config() {
|
||||||
|
LOG_I2C_DEVICE(this);
|
||||||
|
BME280Component::dump_config();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace bme280_i2c
|
||||||
|
} // namespace esphome
|
20
esphome/components/bme280_i2c/bme280_i2c.h
Normal file
20
esphome/components/bme280_i2c/bme280_i2c.h
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/components/bme280_base/bme280_base.h"
|
||||||
|
#include "esphome/components/i2c/i2c.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace bme280_i2c {
|
||||||
|
|
||||||
|
static const char *const TAG = "bme280_i2c.sensor";
|
||||||
|
|
||||||
|
class BME280I2CComponent : public esphome::bme280_base::BME280Component, public i2c::I2CDevice {
|
||||||
|
bool read_byte(uint8_t a_register, uint8_t *data) override;
|
||||||
|
bool write_byte(uint8_t a_register, uint8_t data) override;
|
||||||
|
bool read_bytes(uint8_t a_register, uint8_t *data, size_t len) override;
|
||||||
|
bool read_byte_16(uint8_t a_register, uint16_t *data) override;
|
||||||
|
void dump_config() override;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace bme280_i2c
|
||||||
|
} // namespace esphome
|
19
esphome/components/bme280_i2c/sensor.py
Normal file
19
esphome/components/bme280_i2c/sensor.py
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import esphome.codegen as cg
|
||||||
|
from esphome.components import i2c
|
||||||
|
from ..bme280_base.sensor import to_code as to_code_base, cv, CONFIG_SCHEMA_BASE
|
||||||
|
|
||||||
|
DEPENDENCIES = ["i2c"]
|
||||||
|
AUTO_LOAD = ["bme280_base"]
|
||||||
|
|
||||||
|
bme280_ns = cg.esphome_ns.namespace("bme280_i2c")
|
||||||
|
BME280I2CComponent = bme280_ns.class_(
|
||||||
|
"BME280I2CComponent", cg.PollingComponent, i2c.I2CDevice
|
||||||
|
)
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = CONFIG_SCHEMA_BASE.extend(
|
||||||
|
i2c.i2c_device_schema(default_address=0x77)
|
||||||
|
).extend({cv.GenerateID(): cv.declare_id(BME280I2CComponent)})
|
||||||
|
|
||||||
|
|
||||||
|
async def to_code(config):
|
||||||
|
await to_code_base(config, func=i2c.register_i2c_device)
|
1
esphome/components/bme280_spi/__init__.py
Normal file
1
esphome/components/bme280_spi/__init__.py
Normal file
|
@ -0,0 +1 @@
|
||||||
|
CODEOWNERS = ["@apbodrov"]
|
66
esphome/components/bme280_spi/bme280_spi.cpp
Normal file
66
esphome/components/bme280_spi/bme280_spi.cpp
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstddef>
|
||||||
|
|
||||||
|
#include "bme280_spi.h"
|
||||||
|
#include <esphome/components/bme280_base/bme280_base.h>
|
||||||
|
|
||||||
|
int set_bit(uint8_t num, int position) {
|
||||||
|
int mask = 1 << position;
|
||||||
|
return num | mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
int clear_bit(uint8_t num, int position) {
|
||||||
|
int mask = 1 << position;
|
||||||
|
return num & ~mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace bme280_spi {
|
||||||
|
|
||||||
|
void BME280SPIComponent::setup() {
|
||||||
|
this->spi_setup();
|
||||||
|
BME280Component::setup();
|
||||||
|
};
|
||||||
|
|
||||||
|
// In SPI mode, only 7 bits of the register addresses are used; the MSB of register address is not used
|
||||||
|
// and replaced by a read/write bit (RW = ‘0’ for write and RW = ‘1’ for read).
|
||||||
|
// Example: address 0xF7 is accessed by using SPI register address 0x77. For write access, the byte
|
||||||
|
// 0x77 is transferred, for read access, the byte 0xF7 is transferred.
|
||||||
|
// https://www.bosch-sensortec.com/media/boschsensortec/downloads/datasheets/bst-bme280-ds002.pdf
|
||||||
|
|
||||||
|
bool BME280SPIComponent::read_byte(uint8_t a_register, uint8_t *data) {
|
||||||
|
this->enable();
|
||||||
|
// cause: *data = this->delegate_->transfer(tmp) doesnt work
|
||||||
|
this->delegate_->transfer(set_bit(a_register, 7));
|
||||||
|
*data = this->delegate_->transfer(0);
|
||||||
|
this->disable();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BME280SPIComponent::write_byte(uint8_t a_register, uint8_t data) {
|
||||||
|
this->enable();
|
||||||
|
this->delegate_->transfer(clear_bit(a_register, 7));
|
||||||
|
this->delegate_->transfer(data);
|
||||||
|
this->disable();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BME280SPIComponent::read_bytes(uint8_t a_register, uint8_t *data, size_t len) {
|
||||||
|
this->enable();
|
||||||
|
this->delegate_->transfer(set_bit(a_register, 7));
|
||||||
|
this->delegate_->read_array(data, len);
|
||||||
|
this->disable();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BME280SPIComponent::read_byte_16(uint8_t a_register, uint16_t *data) {
|
||||||
|
this->enable();
|
||||||
|
this->delegate_->transfer(set_bit(a_register, 7));
|
||||||
|
((uint8_t *) data)[1] = this->delegate_->transfer(0);
|
||||||
|
((uint8_t *) data)[0] = this->delegate_->transfer(0);
|
||||||
|
this->disable();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace bme280_spi
|
||||||
|
} // namespace esphome
|
20
esphome/components/bme280_spi/bme280_spi.h
Normal file
20
esphome/components/bme280_spi/bme280_spi.h
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/components/bme280_base/bme280_base.h"
|
||||||
|
#include "esphome/components/spi/spi.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace bme280_spi {
|
||||||
|
|
||||||
|
class BME280SPIComponent : public esphome::bme280_base::BME280Component,
|
||||||
|
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW,
|
||||||
|
spi::CLOCK_PHASE_LEADING, spi::DATA_RATE_200KHZ> {
|
||||||
|
void setup() override;
|
||||||
|
bool read_byte(uint8_t a_register, uint8_t *data) override;
|
||||||
|
bool write_byte(uint8_t a_register, uint8_t data) override;
|
||||||
|
bool read_bytes(uint8_t a_register, uint8_t *data, size_t len) override;
|
||||||
|
bool read_byte_16(uint8_t a_register, uint16_t *data) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace bme280_spi
|
||||||
|
} // namespace esphome
|
24
esphome/components/bme280_spi/sensor.py
Normal file
24
esphome/components/bme280_spi/sensor.py
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
import esphome.codegen as cg
|
||||||
|
from esphome.components import spi
|
||||||
|
from esphome.components.bme280_base.sensor import (
|
||||||
|
to_code as to_code_base,
|
||||||
|
cv,
|
||||||
|
CONFIG_SCHEMA_BASE,
|
||||||
|
)
|
||||||
|
|
||||||
|
DEPENDENCIES = ["spi"]
|
||||||
|
AUTO_LOAD = ["bme280_base"]
|
||||||
|
|
||||||
|
|
||||||
|
bme280_spi_ns = cg.esphome_ns.namespace("bme280_spi")
|
||||||
|
BME280SPIComponent = bme280_spi_ns.class_(
|
||||||
|
"BME280SPIComponent", cg.PollingComponent, spi.SPIDevice
|
||||||
|
)
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = CONFIG_SCHEMA_BASE.extend(spi.spi_device_schema()).extend(
|
||||||
|
{cv.GenerateID(): cv.declare_id(BME280SPIComponent)}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def to_code(config):
|
||||||
|
await to_code_base(config, func=spi.register_spi_device)
|
|
@ -200,8 +200,8 @@ float BMP280Component::read_temperature_(int32_t *t_fine) {
|
||||||
int32_t var2 = (((((adc >> 4) - t1) * ((adc >> 4) - t1)) >> 12) * t3) >> 14;
|
int32_t var2 = (((((adc >> 4) - t1) * ((adc >> 4) - t1)) >> 12) * t3) >> 14;
|
||||||
*t_fine = var1 + var2;
|
*t_fine = var1 + var2;
|
||||||
|
|
||||||
float temperature = (*t_fine * 5 + 128) >> 8;
|
float temperature = (*t_fine * 5 + 128);
|
||||||
return temperature / 100.0f;
|
return temperature / 25600.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
float BMP280Component::read_pressure_(int32_t t_fine) {
|
float BMP280Component::read_pressure_(int32_t t_fine) {
|
||||||
|
|
0
esphome/components/combination/__init__.py
Normal file
0
esphome/components/combination/__init__.py
Normal file
262
esphome/components/combination/combination.cpp
Normal file
262
esphome/components/combination/combination.cpp
Normal file
|
@ -0,0 +1,262 @@
|
||||||
|
#include "combination.h"
|
||||||
|
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
#include "esphome/core/hal.h"
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
#include <functional>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace combination {
|
||||||
|
|
||||||
|
static const char *const TAG = "combination";
|
||||||
|
|
||||||
|
void CombinationComponent::log_config_(const LogString *combo_type) {
|
||||||
|
LOG_SENSOR("", "Combination Sensor:", this);
|
||||||
|
ESP_LOGCONFIG(TAG, " Combination Type: %s", LOG_STR_ARG(combo_type));
|
||||||
|
this->log_source_sensors();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CombinationNoParameterComponent::add_source(Sensor *sensor) { this->sensors_.emplace_back(sensor); }
|
||||||
|
|
||||||
|
void CombinationOneParameterComponent::add_source(Sensor *sensor, std::function<float(float)> const &stddev) {
|
||||||
|
this->sensor_pairs_.emplace_back(sensor, stddev);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CombinationOneParameterComponent::add_source(Sensor *sensor, float stddev) {
|
||||||
|
this->add_source(sensor, std::function<float(float)>{[stddev](float x) -> float { return stddev; }});
|
||||||
|
}
|
||||||
|
|
||||||
|
void CombinationNoParameterComponent::log_source_sensors() {
|
||||||
|
ESP_LOGCONFIG(TAG, " Source Sensors:");
|
||||||
|
for (const auto &sensor : this->sensors_) {
|
||||||
|
ESP_LOGCONFIG(TAG, " - %s", sensor->get_name().c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CombinationOneParameterComponent::log_source_sensors() {
|
||||||
|
ESP_LOGCONFIG(TAG, " Source Sensors:");
|
||||||
|
for (const auto &sensor : this->sensor_pairs_) {
|
||||||
|
auto &entity = *sensor.first;
|
||||||
|
ESP_LOGCONFIG(TAG, " - %s", entity.get_name().c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CombinationNoParameterComponent::setup() {
|
||||||
|
for (const auto &sensor : this->sensors_) {
|
||||||
|
// All sensor updates are deferred until the next loop. This avoids publishing the combined sensor's result
|
||||||
|
// repeatedly in the same loop if multiple source senors update.
|
||||||
|
sensor->add_on_state_callback(
|
||||||
|
[this](float value) -> void { this->defer("update", [this, value]() { this->handle_new_value(value); }); });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void KalmanCombinationComponent::dump_config() {
|
||||||
|
this->log_config_(LOG_STR("kalman"));
|
||||||
|
ESP_LOGCONFIG(TAG, " Update variance: %f per ms", this->update_variance_value_);
|
||||||
|
|
||||||
|
if (this->std_dev_sensor_ != nullptr) {
|
||||||
|
LOG_SENSOR(" ", "Standard Deviation Sensor:", this->std_dev_sensor_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void KalmanCombinationComponent::setup() {
|
||||||
|
for (const auto &sensor : this->sensor_pairs_) {
|
||||||
|
const auto stddev = sensor.second;
|
||||||
|
sensor.first->add_on_state_callback([this, stddev](float x) -> void { this->correct_(x, stddev(x)); });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void KalmanCombinationComponent::update_variance_() {
|
||||||
|
uint32_t now = millis();
|
||||||
|
|
||||||
|
// Variance increases by update_variance_ each millisecond
|
||||||
|
auto dt = now - this->last_update_;
|
||||||
|
auto dv = this->update_variance_value_ * dt;
|
||||||
|
this->variance_ += dv;
|
||||||
|
this->last_update_ = now;
|
||||||
|
}
|
||||||
|
|
||||||
|
void KalmanCombinationComponent::correct_(float value, float stddev) {
|
||||||
|
if (std::isnan(value) || std::isinf(stddev)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (std::isnan(this->state_) || std::isinf(this->variance_)) {
|
||||||
|
this->state_ = value;
|
||||||
|
this->variance_ = stddev * stddev;
|
||||||
|
if (this->std_dev_sensor_ != nullptr) {
|
||||||
|
this->std_dev_sensor_->publish_state(stddev);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this->update_variance_();
|
||||||
|
|
||||||
|
// Combine two gaussian distributions mu1+-var1, mu2+-var2 to a new one around mu
|
||||||
|
// Use the value with the smaller variance as mu1 to prevent precision errors
|
||||||
|
const bool this_first = this->variance_ < (stddev * stddev);
|
||||||
|
const float mu1 = this_first ? this->state_ : value;
|
||||||
|
const float mu2 = this_first ? value : this->state_;
|
||||||
|
|
||||||
|
const float var1 = this_first ? this->variance_ : stddev * stddev;
|
||||||
|
const float var2 = this_first ? stddev * stddev : this->variance_;
|
||||||
|
|
||||||
|
const float mu = mu1 + var1 * (mu2 - mu1) / (var1 + var2);
|
||||||
|
const float var = var1 - (var1 * var1) / (var1 + var2);
|
||||||
|
|
||||||
|
// Update and publish state
|
||||||
|
this->state_ = mu;
|
||||||
|
this->variance_ = var;
|
||||||
|
|
||||||
|
this->publish_state(mu);
|
||||||
|
if (this->std_dev_sensor_ != nullptr) {
|
||||||
|
this->std_dev_sensor_->publish_state(std::sqrt(var));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void LinearCombinationComponent::setup() {
|
||||||
|
for (const auto &sensor : this->sensor_pairs_) {
|
||||||
|
// All sensor updates are deferred until the next loop. This avoids publishing the combined sensor's result
|
||||||
|
// repeatedly in the same loop if multiple source senors update.
|
||||||
|
sensor.first->add_on_state_callback(
|
||||||
|
[this](float value) -> void { this->defer("update", [this, value]() { this->handle_new_value(value); }); });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void LinearCombinationComponent::handle_new_value(float value) {
|
||||||
|
// Multiplies each sensor state by a configured coeffecient and then sums
|
||||||
|
|
||||||
|
if (!std::isfinite(value))
|
||||||
|
return;
|
||||||
|
|
||||||
|
float sum = 0.0;
|
||||||
|
|
||||||
|
for (const auto &sensor : this->sensor_pairs_) {
|
||||||
|
const float sensor_state = sensor.first->state;
|
||||||
|
if (std::isfinite(sensor_state)) {
|
||||||
|
sum += sensor_state * sensor.second(sensor_state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this->publish_state(sum);
|
||||||
|
};
|
||||||
|
|
||||||
|
void MaximumCombinationComponent::handle_new_value(float value) {
|
||||||
|
if (!std::isfinite(value))
|
||||||
|
return;
|
||||||
|
|
||||||
|
float max_value = (-1) * std::numeric_limits<float>::infinity(); // note x = max(x, -infinity)
|
||||||
|
|
||||||
|
for (const auto &sensor : this->sensors_) {
|
||||||
|
if (std::isfinite(sensor->state)) {
|
||||||
|
max_value = std::max(max_value, sensor->state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this->publish_state(max_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MeanCombinationComponent::handle_new_value(float value) {
|
||||||
|
if (!std::isfinite(value))
|
||||||
|
return;
|
||||||
|
|
||||||
|
float sum = 0.0;
|
||||||
|
size_t count = 0.0;
|
||||||
|
|
||||||
|
for (const auto &sensor : this->sensors_) {
|
||||||
|
if (std::isfinite(sensor->state)) {
|
||||||
|
++count;
|
||||||
|
sum += sensor->state;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
float mean = sum / count;
|
||||||
|
|
||||||
|
this->publish_state(mean);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MedianCombinationComponent::handle_new_value(float value) {
|
||||||
|
// Sorts sensor states in ascending order and determines the middle value
|
||||||
|
|
||||||
|
if (!std::isfinite(value))
|
||||||
|
return;
|
||||||
|
|
||||||
|
std::vector<float> sensor_states;
|
||||||
|
for (const auto &sensor : this->sensors_) {
|
||||||
|
if (std::isfinite(sensor->state)) {
|
||||||
|
sensor_states.push_back(sensor->state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sort(sensor_states.begin(), sensor_states.end());
|
||||||
|
size_t sensor_states_size = sensor_states.size();
|
||||||
|
|
||||||
|
float median = NAN;
|
||||||
|
|
||||||
|
if (sensor_states_size) {
|
||||||
|
if (sensor_states_size % 2) {
|
||||||
|
// Odd number of measurements, use middle measurement
|
||||||
|
median = sensor_states[sensor_states_size / 2];
|
||||||
|
} else {
|
||||||
|
// Even number of measurements, use the average of the two middle measurements
|
||||||
|
median = (sensor_states[sensor_states_size / 2] + sensor_states[sensor_states_size / 2 - 1]) / 2.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this->publish_state(median);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MinimumCombinationComponent::handle_new_value(float value) {
|
||||||
|
if (!std::isfinite(value))
|
||||||
|
return;
|
||||||
|
|
||||||
|
float min_value = std::numeric_limits<float>::infinity(); // note x = min(x, infinity)
|
||||||
|
|
||||||
|
for (const auto &sensor : this->sensors_) {
|
||||||
|
if (std::isfinite(sensor->state)) {
|
||||||
|
min_value = std::min(min_value, sensor->state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this->publish_state(min_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MostRecentCombinationComponent::handle_new_value(float value) { this->publish_state(value); }
|
||||||
|
|
||||||
|
void RangeCombinationComponent::handle_new_value(float value) {
|
||||||
|
// Sorts sensor states then takes difference between largest and smallest states
|
||||||
|
|
||||||
|
if (!std::isfinite(value))
|
||||||
|
return;
|
||||||
|
|
||||||
|
std::vector<float> sensor_states;
|
||||||
|
for (const auto &sensor : this->sensors_) {
|
||||||
|
if (std::isfinite(sensor->state)) {
|
||||||
|
sensor_states.push_back(sensor->state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sort(sensor_states.begin(), sensor_states.end());
|
||||||
|
|
||||||
|
float range = sensor_states.back() - sensor_states.front();
|
||||||
|
this->publish_state(range);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SumCombinationComponent::handle_new_value(float value) {
|
||||||
|
if (!std::isfinite(value))
|
||||||
|
return;
|
||||||
|
|
||||||
|
float sum = 0.0;
|
||||||
|
for (const auto &sensor : this->sensors_) {
|
||||||
|
if (std::isfinite(sensor->state)) {
|
||||||
|
sum += sensor->state;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this->publish_state(sum);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace combination
|
||||||
|
} // namespace esphome
|
141
esphome/components/combination/combination.h
Normal file
141
esphome/components/combination/combination.h
Normal file
|
@ -0,0 +1,141 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
#include "esphome/components/sensor/sensor.h"
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace combination {
|
||||||
|
|
||||||
|
class CombinationComponent : public Component, public sensor::Sensor {
|
||||||
|
public:
|
||||||
|
float get_setup_priority() const override { return esphome::setup_priority::DATA; }
|
||||||
|
|
||||||
|
/// @brief Logs all source sensor's names
|
||||||
|
virtual void log_source_sensors() = 0;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/// @brief Logs the sensor for use in dump_config
|
||||||
|
/// @param combo_type Name of the combination operation
|
||||||
|
void log_config_(const LogString *combo_type);
|
||||||
|
};
|
||||||
|
|
||||||
|
/// @brief Base class for operations that do not require an extra parameter to compute the combination
|
||||||
|
class CombinationNoParameterComponent : public CombinationComponent {
|
||||||
|
public:
|
||||||
|
/// @brief Adds a callback to each source sensor
|
||||||
|
void setup() override;
|
||||||
|
|
||||||
|
void add_source(Sensor *sensor);
|
||||||
|
|
||||||
|
/// @brief Computes the combination
|
||||||
|
/// @param value Newest sensor measurement
|
||||||
|
virtual void handle_new_value(float value) = 0;
|
||||||
|
|
||||||
|
/// @brief Logs all source sensor's names in sensors_
|
||||||
|
void log_source_sensors() override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
std::vector<Sensor *> sensors_;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Base class for opertions that require one parameter to compute the combination
|
||||||
|
class CombinationOneParameterComponent : public CombinationComponent {
|
||||||
|
public:
|
||||||
|
void add_source(Sensor *sensor, std::function<float(float)> const &stddev);
|
||||||
|
void add_source(Sensor *sensor, float stddev);
|
||||||
|
|
||||||
|
/// @brief Logs all source sensor's names in sensor_pairs_
|
||||||
|
void log_source_sensors() override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
std::vector<std::pair<Sensor *, std::function<float(float)>>> sensor_pairs_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class KalmanCombinationComponent : public CombinationOneParameterComponent {
|
||||||
|
public:
|
||||||
|
void dump_config() override;
|
||||||
|
void setup() override;
|
||||||
|
|
||||||
|
void set_process_std_dev(float process_std_dev) {
|
||||||
|
this->update_variance_value_ = process_std_dev * process_std_dev * 0.001f;
|
||||||
|
}
|
||||||
|
void set_std_dev_sensor(Sensor *sensor) { this->std_dev_sensor_ = sensor; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void update_variance_();
|
||||||
|
void correct_(float value, float stddev);
|
||||||
|
|
||||||
|
// Optional sensor for publishing the current error
|
||||||
|
sensor::Sensor *std_dev_sensor_{nullptr};
|
||||||
|
|
||||||
|
// Tick of the last update
|
||||||
|
uint32_t last_update_{0};
|
||||||
|
// Change of the variance, per ms
|
||||||
|
float update_variance_value_{0.f};
|
||||||
|
|
||||||
|
// Best guess for the state and its variance
|
||||||
|
float state_{NAN};
|
||||||
|
float variance_{INFINITY};
|
||||||
|
};
|
||||||
|
|
||||||
|
class LinearCombinationComponent : public CombinationOneParameterComponent {
|
||||||
|
public:
|
||||||
|
void dump_config() override { this->log_config_(LOG_STR("linear")); }
|
||||||
|
void setup() override;
|
||||||
|
|
||||||
|
void handle_new_value(float value);
|
||||||
|
};
|
||||||
|
|
||||||
|
class MaximumCombinationComponent : public CombinationNoParameterComponent {
|
||||||
|
public:
|
||||||
|
void dump_config() override { this->log_config_(LOG_STR("max")); }
|
||||||
|
|
||||||
|
void handle_new_value(float value) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
class MeanCombinationComponent : public CombinationNoParameterComponent {
|
||||||
|
public:
|
||||||
|
void dump_config() override { this->log_config_(LOG_STR("mean")); }
|
||||||
|
|
||||||
|
void handle_new_value(float value) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
class MedianCombinationComponent : public CombinationNoParameterComponent {
|
||||||
|
public:
|
||||||
|
void dump_config() override { this->log_config_(LOG_STR("median")); }
|
||||||
|
|
||||||
|
void handle_new_value(float value) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
class MinimumCombinationComponent : public CombinationNoParameterComponent {
|
||||||
|
public:
|
||||||
|
void dump_config() override { this->log_config_(LOG_STR("min")); }
|
||||||
|
|
||||||
|
void handle_new_value(float value) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
class MostRecentCombinationComponent : public CombinationNoParameterComponent {
|
||||||
|
public:
|
||||||
|
void dump_config() override { this->log_config_(LOG_STR("most_recently_updated")); }
|
||||||
|
|
||||||
|
void handle_new_value(float value) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
class RangeCombinationComponent : public CombinationNoParameterComponent {
|
||||||
|
public:
|
||||||
|
void dump_config() override { this->log_config_(LOG_STR("range")); }
|
||||||
|
|
||||||
|
void handle_new_value(float value) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
class SumCombinationComponent : public CombinationNoParameterComponent {
|
||||||
|
public:
|
||||||
|
void dump_config() override { this->log_config_(LOG_STR("sum")); }
|
||||||
|
|
||||||
|
void handle_new_value(float value) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace combination
|
||||||
|
} // namespace esphome
|
176
esphome/components/combination/sensor.py
Normal file
176
esphome/components/combination/sensor.py
Normal file
|
@ -0,0 +1,176 @@
|
||||||
|
import esphome.codegen as cg
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
from esphome.components import sensor
|
||||||
|
from esphome.const import (
|
||||||
|
CONF_ACCURACY_DECIMALS,
|
||||||
|
CONF_DEVICE_CLASS,
|
||||||
|
CONF_ENTITY_CATEGORY,
|
||||||
|
CONF_ICON,
|
||||||
|
CONF_ID,
|
||||||
|
CONF_RANGE,
|
||||||
|
CONF_SOURCE,
|
||||||
|
CONF_SUM,
|
||||||
|
CONF_TYPE,
|
||||||
|
CONF_UNIT_OF_MEASUREMENT,
|
||||||
|
)
|
||||||
|
from esphome.core.entity_helpers import inherit_property_from
|
||||||
|
|
||||||
|
CODEOWNERS = ["@Cat-Ion", "@kahrendt"]
|
||||||
|
|
||||||
|
combination_ns = cg.esphome_ns.namespace("combination")
|
||||||
|
|
||||||
|
KalmanCombinationComponent = combination_ns.class_(
|
||||||
|
"KalmanCombinationComponent", cg.Component, sensor.Sensor
|
||||||
|
)
|
||||||
|
LinearCombinationComponent = combination_ns.class_(
|
||||||
|
"LinearCombinationComponent", cg.Component, sensor.Sensor
|
||||||
|
)
|
||||||
|
MaximumCombinationComponent = combination_ns.class_(
|
||||||
|
"MaximumCombinationComponent", cg.Component, sensor.Sensor
|
||||||
|
)
|
||||||
|
MeanCombinationComponent = combination_ns.class_(
|
||||||
|
"MeanCombinationComponent", cg.Component, sensor.Sensor
|
||||||
|
)
|
||||||
|
MedianCombinationComponent = combination_ns.class_(
|
||||||
|
"MedianCombinationComponent", cg.Component, sensor.Sensor
|
||||||
|
)
|
||||||
|
MinimumCombinationComponent = combination_ns.class_(
|
||||||
|
"MinimumCombinationComponent", cg.Component, sensor.Sensor
|
||||||
|
)
|
||||||
|
MostRecentCombinationComponent = combination_ns.class_(
|
||||||
|
"MostRecentCombinationComponent", cg.Component, sensor.Sensor
|
||||||
|
)
|
||||||
|
RangeCombinationComponent = combination_ns.class_(
|
||||||
|
"RangeCombinationComponent", cg.Component, sensor.Sensor
|
||||||
|
)
|
||||||
|
SumCombinationComponent = combination_ns.class_(
|
||||||
|
"SumCombinationComponent", cg.Component, sensor.Sensor
|
||||||
|
)
|
||||||
|
|
||||||
|
CONF_COEFFECIENT = "coeffecient"
|
||||||
|
CONF_ERROR = "error"
|
||||||
|
CONF_KALMAN = "kalman"
|
||||||
|
CONF_LINEAR = "linear"
|
||||||
|
CONF_MAX = "max"
|
||||||
|
CONF_MEAN = "mean"
|
||||||
|
CONF_MEDIAN = "median"
|
||||||
|
CONF_MIN = "min"
|
||||||
|
CONF_MOST_RECENTLY_UPDATED = "most_recently_updated"
|
||||||
|
CONF_PROCESS_STD_DEV = "process_std_dev"
|
||||||
|
CONF_SOURCES = "sources"
|
||||||
|
CONF_STD_DEV = "std_dev"
|
||||||
|
|
||||||
|
|
||||||
|
KALMAN_SOURCE_SCHEMA = cv.Schema(
|
||||||
|
{
|
||||||
|
cv.Required(CONF_SOURCE): cv.use_id(sensor.Sensor),
|
||||||
|
cv.Required(CONF_ERROR): cv.templatable(cv.positive_float),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
LINEAR_SOURCE_SCHEMA = cv.Schema(
|
||||||
|
{
|
||||||
|
cv.Required(CONF_SOURCE): cv.use_id(sensor.Sensor),
|
||||||
|
cv.Required(CONF_COEFFECIENT): cv.templatable(cv.float_),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
SENSOR_ONLY_SOURCE_SCHEMA = cv.Schema(
|
||||||
|
{
|
||||||
|
cv.Required(CONF_SOURCE): cv.use_id(sensor.Sensor),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = cv.typed_schema(
|
||||||
|
{
|
||||||
|
CONF_KALMAN: sensor.sensor_schema(KalmanCombinationComponent)
|
||||||
|
.extend(cv.COMPONENT_SCHEMA)
|
||||||
|
.extend(
|
||||||
|
{
|
||||||
|
cv.Required(CONF_PROCESS_STD_DEV): cv.positive_float,
|
||||||
|
cv.Required(CONF_SOURCES): cv.ensure_list(KALMAN_SOURCE_SCHEMA),
|
||||||
|
cv.Optional(CONF_STD_DEV): sensor.sensor_schema(),
|
||||||
|
}
|
||||||
|
),
|
||||||
|
CONF_LINEAR: sensor.sensor_schema(LinearCombinationComponent)
|
||||||
|
.extend(cv.COMPONENT_SCHEMA)
|
||||||
|
.extend({cv.Required(CONF_SOURCES): cv.ensure_list(LINEAR_SOURCE_SCHEMA)}),
|
||||||
|
CONF_MAX: sensor.sensor_schema(MaximumCombinationComponent)
|
||||||
|
.extend(cv.COMPONENT_SCHEMA)
|
||||||
|
.extend({cv.Required(CONF_SOURCES): cv.ensure_list(SENSOR_ONLY_SOURCE_SCHEMA)}),
|
||||||
|
CONF_MEAN: sensor.sensor_schema(MeanCombinationComponent)
|
||||||
|
.extend(cv.COMPONENT_SCHEMA)
|
||||||
|
.extend({cv.Required(CONF_SOURCES): cv.ensure_list(SENSOR_ONLY_SOURCE_SCHEMA)}),
|
||||||
|
CONF_MEDIAN: sensor.sensor_schema(MedianCombinationComponent)
|
||||||
|
.extend(cv.COMPONENT_SCHEMA)
|
||||||
|
.extend({cv.Required(CONF_SOURCES): cv.ensure_list(SENSOR_ONLY_SOURCE_SCHEMA)}),
|
||||||
|
CONF_MIN: sensor.sensor_schema(MinimumCombinationComponent)
|
||||||
|
.extend(cv.COMPONENT_SCHEMA)
|
||||||
|
.extend({cv.Required(CONF_SOURCES): cv.ensure_list(SENSOR_ONLY_SOURCE_SCHEMA)}),
|
||||||
|
CONF_MOST_RECENTLY_UPDATED: sensor.sensor_schema(MostRecentCombinationComponent)
|
||||||
|
.extend(cv.COMPONENT_SCHEMA)
|
||||||
|
.extend({cv.Required(CONF_SOURCES): cv.ensure_list(SENSOR_ONLY_SOURCE_SCHEMA)}),
|
||||||
|
CONF_RANGE: sensor.sensor_schema(RangeCombinationComponent)
|
||||||
|
.extend(cv.COMPONENT_SCHEMA)
|
||||||
|
.extend({cv.Required(CONF_SOURCES): cv.ensure_list(SENSOR_ONLY_SOURCE_SCHEMA)}),
|
||||||
|
CONF_SUM: sensor.sensor_schema(SumCombinationComponent)
|
||||||
|
.extend(cv.COMPONENT_SCHEMA)
|
||||||
|
.extend({cv.Required(CONF_SOURCES): cv.ensure_list(SENSOR_ONLY_SOURCE_SCHEMA)}),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# Inherit some sensor values from the first source, for both the state and the error value
|
||||||
|
# CONF_STATE_CLASS could also be inherited, but might lead to unexpected behaviour with "total_increasing"
|
||||||
|
properties_to_inherit = [
|
||||||
|
CONF_ACCURACY_DECIMALS,
|
||||||
|
CONF_DEVICE_CLASS,
|
||||||
|
CONF_ENTITY_CATEGORY,
|
||||||
|
CONF_ICON,
|
||||||
|
CONF_UNIT_OF_MEASUREMENT,
|
||||||
|
]
|
||||||
|
inherit_schema_for_state = [
|
||||||
|
inherit_property_from(property, [CONF_SOURCES, 0, CONF_SOURCE])
|
||||||
|
for property in properties_to_inherit
|
||||||
|
]
|
||||||
|
inherit_schema_for_std_dev = [
|
||||||
|
inherit_property_from([CONF_STD_DEV, property], [CONF_SOURCES, 0, CONF_SOURCE])
|
||||||
|
for property in properties_to_inherit
|
||||||
|
]
|
||||||
|
|
||||||
|
FINAL_VALIDATE_SCHEMA = cv.All(
|
||||||
|
*inherit_schema_for_state,
|
||||||
|
*inherit_schema_for_std_dev,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def to_code(config):
|
||||||
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
|
await cg.register_component(var, config)
|
||||||
|
await sensor.register_sensor(var, config)
|
||||||
|
|
||||||
|
if proces_std_dev := config.get(CONF_PROCESS_STD_DEV):
|
||||||
|
cg.add(var.set_process_std_dev(proces_std_dev))
|
||||||
|
|
||||||
|
for source_conf in config[CONF_SOURCES]:
|
||||||
|
source = await cg.get_variable(source_conf[CONF_SOURCE])
|
||||||
|
if config[CONF_TYPE] == CONF_KALMAN:
|
||||||
|
error = await cg.templatable(
|
||||||
|
source_conf[CONF_ERROR],
|
||||||
|
[(float, "x")],
|
||||||
|
cg.float_,
|
||||||
|
)
|
||||||
|
cg.add(var.add_source(source, error))
|
||||||
|
elif config[CONF_TYPE] == CONF_LINEAR:
|
||||||
|
coeffecient = await cg.templatable(
|
||||||
|
source_conf[CONF_COEFFECIENT],
|
||||||
|
[(float, "x")],
|
||||||
|
cg.float_,
|
||||||
|
)
|
||||||
|
cg.add(var.add_source(source, coeffecient))
|
||||||
|
else:
|
||||||
|
cg.add(var.add_source(source))
|
||||||
|
|
||||||
|
if CONF_STD_DEV in config:
|
||||||
|
sens = await sensor.new_sensor(config[CONF_STD_DEV])
|
||||||
|
cg.add(var.set_std_dev_sensor(sens))
|
|
@ -113,8 +113,9 @@ void CSE7766Component::parse_data_() {
|
||||||
bool have_voltage = adj & 0x40;
|
bool have_voltage = adj & 0x40;
|
||||||
if (have_voltage) {
|
if (have_voltage) {
|
||||||
// voltage cycle of serial port outputted is a complete cycle;
|
// voltage cycle of serial port outputted is a complete cycle;
|
||||||
this->voltage_acc_ += voltage_calib / float(voltage_cycle);
|
float voltage = voltage_calib / float(voltage_cycle);
|
||||||
this->voltage_counts_ += 1;
|
if (this->voltage_sensor_ != nullptr)
|
||||||
|
this->voltage_sensor_->publish_state(voltage);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool have_power = adj & 0x10;
|
bool have_power = adj & 0x10;
|
||||||
|
@ -126,8 +127,8 @@ void CSE7766Component::parse_data_() {
|
||||||
if (!power_cycle_exceeds_range) {
|
if (!power_cycle_exceeds_range) {
|
||||||
power = power_calib / float(power_cycle);
|
power = power_calib / float(power_cycle);
|
||||||
}
|
}
|
||||||
this->power_acc_ += power;
|
if (this->power_sensor_ != nullptr)
|
||||||
this->power_counts_ += 1;
|
this->power_sensor_->publish_state(power);
|
||||||
|
|
||||||
uint32_t difference;
|
uint32_t difference;
|
||||||
if (this->cf_pulses_last_ == 0) {
|
if (this->cf_pulses_last_ == 0) {
|
||||||
|
@ -141,7 +142,10 @@ void CSE7766Component::parse_data_() {
|
||||||
}
|
}
|
||||||
this->cf_pulses_last_ = cf_pulses;
|
this->cf_pulses_last_ = cf_pulses;
|
||||||
this->energy_total_ += difference * float(power_calib) / 1000000.0f / 3600.0f;
|
this->energy_total_ += difference * float(power_calib) / 1000000.0f / 3600.0f;
|
||||||
this->energy_total_counts_ += 1;
|
if (this->energy_sensor_ != nullptr)
|
||||||
|
this->energy_sensor_->publish_state(this->energy_total_);
|
||||||
|
} else if ((this->energy_sensor_ != nullptr) && !this->energy_sensor_->has_state()) {
|
||||||
|
this->energy_sensor_->publish_state(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (adj & 0x20) {
|
if (adj & 0x20) {
|
||||||
|
@ -150,42 +154,13 @@ void CSE7766Component::parse_data_() {
|
||||||
if (have_voltage && !have_power) {
|
if (have_voltage && !have_power) {
|
||||||
// Testing has shown that when we have voltage and current but not power, that means the power is 0.
|
// Testing has shown that when we have voltage and current but not power, that means the power is 0.
|
||||||
// We report a power of 0, which in turn means we should report a current of 0.
|
// We report a power of 0, which in turn means we should report a current of 0.
|
||||||
this->power_counts_ += 1;
|
if (this->power_sensor_ != nullptr)
|
||||||
|
this->power_sensor_->publish_state(0);
|
||||||
} else if (power != 0.0f) {
|
} else if (power != 0.0f) {
|
||||||
current = current_calib / float(current_cycle);
|
current = current_calib / float(current_cycle);
|
||||||
}
|
}
|
||||||
this->current_acc_ += current;
|
if (this->current_sensor_ != nullptr)
|
||||||
this->current_counts_ += 1;
|
this->current_sensor_->publish_state(current);
|
||||||
}
|
|
||||||
}
|
|
||||||
void CSE7766Component::update() {
|
|
||||||
const auto publish_state = [](const char *name, sensor::Sensor *sensor, float &acc, uint32_t &counts) {
|
|
||||||
if (counts != 0) {
|
|
||||||
const auto avg = acc / counts;
|
|
||||||
|
|
||||||
ESP_LOGV(TAG, "Got %s_acc=%.2f %s_counts=%" PRIu32 " %s=%.1f", name, acc, name, counts, name, avg);
|
|
||||||
|
|
||||||
if (sensor != nullptr) {
|
|
||||||
sensor->publish_state(avg);
|
|
||||||
}
|
|
||||||
|
|
||||||
acc = 0.0f;
|
|
||||||
counts = 0;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
publish_state("voltage", this->voltage_sensor_, this->voltage_acc_, this->voltage_counts_);
|
|
||||||
publish_state("current", this->current_sensor_, this->current_acc_, this->current_counts_);
|
|
||||||
publish_state("power", this->power_sensor_, this->power_acc_, this->power_counts_);
|
|
||||||
|
|
||||||
if (this->energy_total_counts_ != 0) {
|
|
||||||
ESP_LOGV(TAG, "Got energy_total=%.2f energy_total_counts=%" PRIu32, this->energy_total_,
|
|
||||||
this->energy_total_counts_);
|
|
||||||
|
|
||||||
if (this->energy_sensor_ != nullptr) {
|
|
||||||
this->energy_sensor_->publish_state(this->energy_total_);
|
|
||||||
}
|
|
||||||
this->energy_total_counts_ = 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -196,7 +171,6 @@ uint32_t CSE7766Component::get_24_bit_uint_(uint8_t start_index) {
|
||||||
|
|
||||||
void CSE7766Component::dump_config() {
|
void CSE7766Component::dump_config() {
|
||||||
ESP_LOGCONFIG(TAG, "CSE7766:");
|
ESP_LOGCONFIG(TAG, "CSE7766:");
|
||||||
LOG_UPDATE_INTERVAL(this);
|
|
||||||
LOG_SENSOR(" ", "Voltage", this->voltage_sensor_);
|
LOG_SENSOR(" ", "Voltage", this->voltage_sensor_);
|
||||||
LOG_SENSOR(" ", "Current", this->current_sensor_);
|
LOG_SENSOR(" ", "Current", this->current_sensor_);
|
||||||
LOG_SENSOR(" ", "Power", this->power_sensor_);
|
LOG_SENSOR(" ", "Power", this->power_sensor_);
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace cse7766 {
|
namespace cse7766 {
|
||||||
|
|
||||||
class CSE7766Component : public PollingComponent, public uart::UARTDevice {
|
class CSE7766Component : public Component, public uart::UARTDevice {
|
||||||
public:
|
public:
|
||||||
void set_voltage_sensor(sensor::Sensor *voltage_sensor) { voltage_sensor_ = voltage_sensor; }
|
void set_voltage_sensor(sensor::Sensor *voltage_sensor) { voltage_sensor_ = voltage_sensor; }
|
||||||
void set_current_sensor(sensor::Sensor *current_sensor) { current_sensor_ = current_sensor; }
|
void set_current_sensor(sensor::Sensor *current_sensor) { current_sensor_ = current_sensor; }
|
||||||
|
@ -16,7 +16,6 @@ class CSE7766Component : public PollingComponent, public uart::UARTDevice {
|
||||||
|
|
||||||
void loop() override;
|
void loop() override;
|
||||||
float get_setup_priority() const override;
|
float get_setup_priority() const override;
|
||||||
void update() override;
|
|
||||||
void dump_config() override;
|
void dump_config() override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
@ -31,16 +30,8 @@ class CSE7766Component : public PollingComponent, public uart::UARTDevice {
|
||||||
sensor::Sensor *current_sensor_{nullptr};
|
sensor::Sensor *current_sensor_{nullptr};
|
||||||
sensor::Sensor *power_sensor_{nullptr};
|
sensor::Sensor *power_sensor_{nullptr};
|
||||||
sensor::Sensor *energy_sensor_{nullptr};
|
sensor::Sensor *energy_sensor_{nullptr};
|
||||||
float voltage_acc_{0.0f};
|
|
||||||
float current_acc_{0.0f};
|
|
||||||
float power_acc_{0.0f};
|
|
||||||
float energy_total_{0.0f};
|
float energy_total_{0.0f};
|
||||||
uint32_t cf_pulses_last_{0};
|
uint32_t cf_pulses_last_{0};
|
||||||
uint32_t voltage_counts_{0};
|
|
||||||
uint32_t current_counts_{0};
|
|
||||||
uint32_t power_counts_{0};
|
|
||||||
// Setting this to 1 means it will always publish 0 once at startup
|
|
||||||
uint32_t energy_total_counts_{1};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace cse7766
|
} // namespace cse7766
|
||||||
|
|
|
@ -22,12 +22,9 @@ from esphome.const import (
|
||||||
DEPENDENCIES = ["uart"]
|
DEPENDENCIES = ["uart"]
|
||||||
|
|
||||||
cse7766_ns = cg.esphome_ns.namespace("cse7766")
|
cse7766_ns = cg.esphome_ns.namespace("cse7766")
|
||||||
CSE7766Component = cse7766_ns.class_(
|
CSE7766Component = cse7766_ns.class_("CSE7766Component", cg.Component, uart.UARTDevice)
|
||||||
"CSE7766Component", cg.PollingComponent, uart.UARTDevice
|
|
||||||
)
|
|
||||||
|
|
||||||
CONFIG_SCHEMA = (
|
CONFIG_SCHEMA = cv.Schema(
|
||||||
cv.Schema(
|
|
||||||
{
|
{
|
||||||
cv.GenerateID(): cv.declare_id(CSE7766Component),
|
cv.GenerateID(): cv.declare_id(CSE7766Component),
|
||||||
cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(
|
cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(
|
||||||
|
@ -55,10 +52,7 @@ CONFIG_SCHEMA = (
|
||||||
state_class=STATE_CLASS_TOTAL_INCREASING,
|
state_class=STATE_CLASS_TOTAL_INCREASING,
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
)
|
).extend(uart.UART_DEVICE_SCHEMA)
|
||||||
.extend(cv.polling_component_schema("60s"))
|
|
||||||
.extend(uart.UART_DEVICE_SCHEMA)
|
|
||||||
)
|
|
||||||
FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema(
|
FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema(
|
||||||
"cse7766", baud_rate=4800, require_rx=True
|
"cse7766", baud_rate=4800, require_rx=True
|
||||||
)
|
)
|
||||||
|
|
|
@ -91,7 +91,7 @@ bool HOT IRAM_ATTR DHT::read_sensor_(float *temperature, float *humidity, bool r
|
||||||
delayMicroseconds(40);
|
delayMicroseconds(40);
|
||||||
} else if (this->model_ == DHT_MODEL_DHT22_TYPE2) {
|
} else if (this->model_ == DHT_MODEL_DHT22_TYPE2) {
|
||||||
delayMicroseconds(2000);
|
delayMicroseconds(2000);
|
||||||
} else if (this->model_ == DHT_MODEL_AM2302) {
|
} else if (this->model_ == DHT_MODEL_AM2120 || this->model_ == DHT_MODEL_AM2302) {
|
||||||
delayMicroseconds(1000);
|
delayMicroseconds(1000);
|
||||||
} else {
|
} else {
|
||||||
delayMicroseconds(800);
|
delayMicroseconds(800);
|
||||||
|
@ -217,8 +217,12 @@ bool HOT IRAM_ATTR DHT::read_sensor_(float *temperature, float *humidity, bool r
|
||||||
uint16_t raw_humidity = (uint16_t(data[0] & 0xFF) << 8) | (data[1] & 0xFF);
|
uint16_t raw_humidity = (uint16_t(data[0] & 0xFF) << 8) | (data[1] & 0xFF);
|
||||||
uint16_t raw_temperature = (uint16_t(data[2] & 0xFF) << 8) | (data[3] & 0xFF);
|
uint16_t raw_temperature = (uint16_t(data[2] & 0xFF) << 8) | (data[3] & 0xFF);
|
||||||
|
|
||||||
if (this->model_ != DHT_MODEL_DHT22_TYPE2 && (raw_temperature & 0x8000) != 0)
|
if (raw_temperature & 0x8000) {
|
||||||
|
if (!(raw_temperature & 0x4000))
|
||||||
raw_temperature = ~(raw_temperature & 0x7FFF);
|
raw_temperature = ~(raw_temperature & 0x7FFF);
|
||||||
|
} else if (raw_temperature & 0x800) {
|
||||||
|
raw_temperature |= 0xf000;
|
||||||
|
}
|
||||||
|
|
||||||
if (raw_temperature == 1 && raw_humidity == 10) {
|
if (raw_temperature == 1 && raw_humidity == 10) {
|
||||||
if (report_errors) {
|
if (report_errors) {
|
||||||
|
|
|
@ -11,6 +11,7 @@ enum DHTModel {
|
||||||
DHT_MODEL_AUTO_DETECT = 0,
|
DHT_MODEL_AUTO_DETECT = 0,
|
||||||
DHT_MODEL_DHT11,
|
DHT_MODEL_DHT11,
|
||||||
DHT_MODEL_DHT22,
|
DHT_MODEL_DHT22,
|
||||||
|
DHT_MODEL_AM2120,
|
||||||
DHT_MODEL_AM2302,
|
DHT_MODEL_AM2302,
|
||||||
DHT_MODEL_RHT03,
|
DHT_MODEL_RHT03,
|
||||||
DHT_MODEL_SI7021,
|
DHT_MODEL_SI7021,
|
||||||
|
@ -27,6 +28,7 @@ class DHT : public PollingComponent {
|
||||||
* - DHT_MODEL_AUTO_DETECT (default)
|
* - DHT_MODEL_AUTO_DETECT (default)
|
||||||
* - DHT_MODEL_DHT11
|
* - DHT_MODEL_DHT11
|
||||||
* - DHT_MODEL_DHT22
|
* - DHT_MODEL_DHT22
|
||||||
|
* - DHT_MODEL_AM2120
|
||||||
* - DHT_MODEL_AM2302
|
* - DHT_MODEL_AM2302
|
||||||
* - DHT_MODEL_RHT03
|
* - DHT_MODEL_RHT03
|
||||||
* - DHT_MODEL_SI7021
|
* - DHT_MODEL_SI7021
|
||||||
|
|
|
@ -23,6 +23,7 @@ DHT_MODELS = {
|
||||||
"AUTO_DETECT": DHTModel.DHT_MODEL_AUTO_DETECT,
|
"AUTO_DETECT": DHTModel.DHT_MODEL_AUTO_DETECT,
|
||||||
"DHT11": DHTModel.DHT_MODEL_DHT11,
|
"DHT11": DHTModel.DHT_MODEL_DHT11,
|
||||||
"DHT22": DHTModel.DHT_MODEL_DHT22,
|
"DHT22": DHTModel.DHT_MODEL_DHT22,
|
||||||
|
"AM2120": DHTModel.DHT_MODEL_AM2120,
|
||||||
"AM2302": DHTModel.DHT_MODEL_AM2302,
|
"AM2302": DHTModel.DHT_MODEL_AM2302,
|
||||||
"RHT03": DHTModel.DHT_MODEL_RHT03,
|
"RHT03": DHTModel.DHT_MODEL_RHT03,
|
||||||
"SI7021": DHTModel.DHT_MODEL_SI7021,
|
"SI7021": DHTModel.DHT_MODEL_SI7021,
|
||||||
|
|
|
@ -145,7 +145,7 @@ async def display_page_show_to_code(config, action_id, template_arg, args):
|
||||||
DisplayPageShowNextAction,
|
DisplayPageShowNextAction,
|
||||||
maybe_simple_id(
|
maybe_simple_id(
|
||||||
{
|
{
|
||||||
cv.Required(CONF_ID): cv.templatable(cv.use_id(DisplayBuffer)),
|
cv.Required(CONF_ID): cv.templatable(cv.use_id(Display)),
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
@ -159,7 +159,7 @@ async def display_page_show_next_to_code(config, action_id, template_arg, args):
|
||||||
DisplayPageShowPrevAction,
|
DisplayPageShowPrevAction,
|
||||||
maybe_simple_id(
|
maybe_simple_id(
|
||||||
{
|
{
|
||||||
cv.Required(CONF_ID): cv.templatable(cv.use_id(DisplayBuffer)),
|
cv.Required(CONF_ID): cv.templatable(cv.use_id(Display)),
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
@ -173,7 +173,7 @@ async def display_page_show_previous_to_code(config, action_id, template_arg, ar
|
||||||
DisplayIsDisplayingPageCondition,
|
DisplayIsDisplayingPageCondition,
|
||||||
cv.maybe_simple_value(
|
cv.maybe_simple_value(
|
||||||
{
|
{
|
||||||
cv.GenerateID(CONF_ID): cv.use_id(DisplayBuffer),
|
cv.GenerateID(CONF_ID): cv.use_id(Display),
|
||||||
cv.Required(CONF_PAGE_ID): cv.use_id(DisplayPage),
|
cv.Required(CONF_PAGE_ID): cv.use_id(DisplayPage),
|
||||||
},
|
},
|
||||||
key=CONF_PAGE_ID,
|
key=CONF_PAGE_ID,
|
||||||
|
|
|
@ -141,6 +141,122 @@ void Display::filled_circle(int center_x, int center_y, int radius, Color color)
|
||||||
}
|
}
|
||||||
} while (dx <= 0);
|
} while (dx <= 0);
|
||||||
}
|
}
|
||||||
|
void HOT Display::triangle(int x1, int y1, int x2, int y2, int x3, int y3, Color color) {
|
||||||
|
this->line(x1, y1, x2, y2, color);
|
||||||
|
this->line(x1, y1, x3, y3, color);
|
||||||
|
this->line(x2, y2, x3, y3, color);
|
||||||
|
}
|
||||||
|
void Display::sort_triangle_points_by_y_(int *x1, int *y1, int *x2, int *y2, int *x3, int *y3) {
|
||||||
|
if (*y1 > *y2) {
|
||||||
|
int x_temp = *x1, y_temp = *y1;
|
||||||
|
*x1 = *x2, *y1 = *y2;
|
||||||
|
*x2 = x_temp, *y2 = y_temp;
|
||||||
|
}
|
||||||
|
if (*y1 > *y3) {
|
||||||
|
int x_temp = *x1, y_temp = *y1;
|
||||||
|
*x1 = *x3, *y1 = *y3;
|
||||||
|
*x3 = x_temp, *y3 = y_temp;
|
||||||
|
}
|
||||||
|
if (*y2 > *y3) {
|
||||||
|
int x_temp = *x2, y_temp = *y2;
|
||||||
|
*x2 = *x3, *y2 = *y3;
|
||||||
|
*x3 = x_temp, *y3 = y_temp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void Display::filled_flat_side_triangle_(int x1, int y1, int x2, int y2, int x3, int y3, Color color) {
|
||||||
|
// y2 must be equal to y3 (same horizontal line)
|
||||||
|
|
||||||
|
// Initialize Bresenham's algorithm for side 1
|
||||||
|
int s1_current_x = x1;
|
||||||
|
int s1_current_y = y1;
|
||||||
|
bool s1_axis_swap = false;
|
||||||
|
int s1_dx = abs(x2 - x1);
|
||||||
|
int s1_dy = abs(y2 - y1);
|
||||||
|
int s1_sign_x = ((x2 - x1) >= 0) ? 1 : -1;
|
||||||
|
int s1_sign_y = ((y2 - y1) >= 0) ? 1 : -1;
|
||||||
|
if (s1_dy > s1_dx) { // swap values
|
||||||
|
int tmp = s1_dx;
|
||||||
|
s1_dx = s1_dy;
|
||||||
|
s1_dy = tmp;
|
||||||
|
s1_axis_swap = true;
|
||||||
|
}
|
||||||
|
int s1_error = 2 * s1_dy - s1_dx;
|
||||||
|
|
||||||
|
// Initialize Bresenham's algorithm for side 2
|
||||||
|
int s2_current_x = x1;
|
||||||
|
int s2_current_y = y1;
|
||||||
|
bool s2_axis_swap = false;
|
||||||
|
int s2_dx = abs(x3 - x1);
|
||||||
|
int s2_dy = abs(y3 - y1);
|
||||||
|
int s2_sign_x = ((x3 - x1) >= 0) ? 1 : -1;
|
||||||
|
int s2_sign_y = ((y3 - y1) >= 0) ? 1 : -1;
|
||||||
|
if (s2_dy > s2_dx) { // swap values
|
||||||
|
int tmp = s2_dx;
|
||||||
|
s2_dx = s2_dy;
|
||||||
|
s2_dy = tmp;
|
||||||
|
s2_axis_swap = true;
|
||||||
|
}
|
||||||
|
int s2_error = 2 * s2_dy - s2_dx;
|
||||||
|
|
||||||
|
// Iterate on side 1 and allow side 2 to be processed to match the advance of the y-axis.
|
||||||
|
for (int i = 0; i <= s1_dx; i++) {
|
||||||
|
if (s1_current_x <= s2_current_x) {
|
||||||
|
this->horizontal_line(s1_current_x, s1_current_y, s2_current_x - s1_current_x + 1, color);
|
||||||
|
} else {
|
||||||
|
this->horizontal_line(s2_current_x, s2_current_y, s1_current_x - s2_current_x + 1, color);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bresenham's #1
|
||||||
|
// Side 1 s1_current_x and s1_current_y calculation
|
||||||
|
while (s1_error >= 0) {
|
||||||
|
if (s1_axis_swap) {
|
||||||
|
s1_current_x += s1_sign_x;
|
||||||
|
} else {
|
||||||
|
s1_current_y += s1_sign_y;
|
||||||
|
}
|
||||||
|
s1_error = s1_error - 2 * s1_dx;
|
||||||
|
}
|
||||||
|
if (s1_axis_swap) {
|
||||||
|
s1_current_y += s1_sign_y;
|
||||||
|
} else {
|
||||||
|
s1_current_x += s1_sign_x;
|
||||||
|
}
|
||||||
|
s1_error = s1_error + 2 * s1_dy;
|
||||||
|
|
||||||
|
// Bresenham's #2
|
||||||
|
// Side 2 s2_current_x and s2_current_y calculation
|
||||||
|
while (s2_current_y != s1_current_y) {
|
||||||
|
while (s2_error >= 0) {
|
||||||
|
if (s2_axis_swap) {
|
||||||
|
s2_current_x += s2_sign_x;
|
||||||
|
} else {
|
||||||
|
s2_current_y += s2_sign_y;
|
||||||
|
}
|
||||||
|
s2_error = s2_error - 2 * s2_dx;
|
||||||
|
}
|
||||||
|
if (s2_axis_swap) {
|
||||||
|
s2_current_y += s2_sign_y;
|
||||||
|
} else {
|
||||||
|
s2_current_x += s2_sign_x;
|
||||||
|
}
|
||||||
|
s2_error = s2_error + 2 * s2_dy;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void Display::filled_triangle(int x1, int y1, int x2, int y2, int x3, int y3, Color color) {
|
||||||
|
// Sort the three points by y-coordinate ascending, so [x1,y1] is the topmost point
|
||||||
|
this->sort_triangle_points_by_y_(&x1, &y1, &x2, &y2, &x3, &y3);
|
||||||
|
|
||||||
|
if (y2 == y3) { // Check for special case of a bottom-flat triangle
|
||||||
|
this->filled_flat_side_triangle_(x1, y1, x2, y2, x3, y3, color);
|
||||||
|
} else if (y1 == y2) { // Check for special case of a top-flat triangle
|
||||||
|
this->filled_flat_side_triangle_(x3, y3, x1, y1, x2, y2, color);
|
||||||
|
} else { // General case: split the no-flat-side triangle in a top-flat triangle and bottom-flat triangle
|
||||||
|
int x_temp = (int) (x1 + ((float) (y2 - y1) / (float) (y3 - y1)) * (x3 - x1)), y_temp = y2;
|
||||||
|
this->filled_flat_side_triangle_(x1, y1, x2, y2, x_temp, y_temp, color);
|
||||||
|
this->filled_flat_side_triangle_(x3, y3, x2, y2, x_temp, y_temp, color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Display::print(int x, int y, BaseFont *font, Color color, TextAlign align, const char *text) {
|
void Display::print(int x, int y, BaseFont *font, Color color, TextAlign align, const char *text) {
|
||||||
int x_start, y_start;
|
int x_start, y_start;
|
||||||
|
|
|
@ -236,6 +236,12 @@ class Display : public PollingComponent {
|
||||||
/// Fill a circle centered around [center_x,center_y] with the radius radius with the given color.
|
/// Fill a circle centered around [center_x,center_y] with the radius radius with the given color.
|
||||||
void filled_circle(int center_x, int center_y, int radius, Color color = COLOR_ON);
|
void filled_circle(int center_x, int center_y, int radius, Color color = COLOR_ON);
|
||||||
|
|
||||||
|
/// Draw the outline of a triangle contained between the points [x1,y1], [x2,y2] and [x3,y3] with the given color.
|
||||||
|
void triangle(int x1, int y1, int x2, int y2, int x3, int y3, Color color = COLOR_ON);
|
||||||
|
|
||||||
|
/// Fill a triangle contained between the points [x1,y1], [x2,y2] and [x3,y3] with the given color.
|
||||||
|
void filled_triangle(int x1, int y1, int x2, int y2, int x3, int y3, Color color = COLOR_ON);
|
||||||
|
|
||||||
/** Print `text` with the anchor point at [x,y] with `font`.
|
/** Print `text` with the anchor point at [x,y] with `font`.
|
||||||
*
|
*
|
||||||
* @param x The x coordinate of the text alignment anchor point.
|
* @param x The x coordinate of the text alignment anchor point.
|
||||||
|
@ -532,6 +538,15 @@ class Display : public PollingComponent {
|
||||||
void do_update_();
|
void do_update_();
|
||||||
void clear_clipping_();
|
void clear_clipping_();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method fills a triangle using only integer variables by using a
|
||||||
|
* modified bresenham algorithm.
|
||||||
|
* It is mandatory that [x2,y2] and [x3,y3] lie on the same horizontal line,
|
||||||
|
* so y2 must be equal to y3.
|
||||||
|
*/
|
||||||
|
void filled_flat_side_triangle_(int x1, int y1, int x2, int y2, int x3, int y3, Color color);
|
||||||
|
void sort_triangle_points_by_y_(int *x1, int *y1, int *x2, int *y2, int *x3, int *y3);
|
||||||
|
|
||||||
DisplayRotation rotation_{DISPLAY_ROTATION_0_DEGREES};
|
DisplayRotation rotation_{DISPLAY_ROTATION_0_DEGREES};
|
||||||
optional<display_writer_t> writer_{};
|
optional<display_writer_t> writer_{};
|
||||||
DisplayPage *page_{nullptr};
|
DisplayPage *page_{nullptr};
|
||||||
|
|
|
@ -226,7 +226,7 @@ ARDUINO_PLATFORM_VERSION = cv.Version(5, 4, 0)
|
||||||
# The default/recommended esp-idf framework version
|
# The default/recommended esp-idf framework version
|
||||||
# - https://github.com/espressif/esp-idf/releases
|
# - https://github.com/espressif/esp-idf/releases
|
||||||
# - https://api.registry.platformio.org/v3/packages/platformio/tool/framework-espidf
|
# - https://api.registry.platformio.org/v3/packages/platformio/tool/framework-espidf
|
||||||
RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION = cv.Version(4, 4, 5)
|
RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION = cv.Version(4, 4, 6)
|
||||||
# The platformio/espressif32 version to use for esp-idf frameworks
|
# The platformio/espressif32 version to use for esp-idf frameworks
|
||||||
# - https://github.com/platformio/platform-espressif32/releases
|
# - https://github.com/platformio/platform-espressif32/releases
|
||||||
# - https://api.registry.platformio.org/v3/packages/platformio/platform/espressif32
|
# - https://api.registry.platformio.org/v3/packages/platformio/platform/espressif32
|
||||||
|
@ -271,8 +271,8 @@ def _arduino_check_versions(value):
|
||||||
def _esp_idf_check_versions(value):
|
def _esp_idf_check_versions(value):
|
||||||
value = value.copy()
|
value = value.copy()
|
||||||
lookups = {
|
lookups = {
|
||||||
"dev": (cv.Version(5, 1, 0), "https://github.com/espressif/esp-idf.git"),
|
"dev": (cv.Version(5, 1, 2), "https://github.com/espressif/esp-idf.git"),
|
||||||
"latest": (cv.Version(5, 1, 0), None),
|
"latest": (cv.Version(5, 1, 2), None),
|
||||||
"recommended": (RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION, None),
|
"recommended": (RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION, None),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,23 +1,26 @@
|
||||||
import re
|
import re
|
||||||
|
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome import automation
|
from esphome import automation
|
||||||
|
from esphome.components import esp32_ble
|
||||||
|
from esphome.components.esp32 import add_idf_sdkconfig_option
|
||||||
from esphome.const import (
|
from esphome.const import (
|
||||||
CONF_ACTIVE,
|
CONF_ACTIVE,
|
||||||
|
CONF_DURATION,
|
||||||
CONF_ID,
|
CONF_ID,
|
||||||
CONF_INTERVAL,
|
CONF_INTERVAL,
|
||||||
CONF_DURATION,
|
|
||||||
CONF_TRIGGER_ID,
|
|
||||||
CONF_MAC_ADDRESS,
|
CONF_MAC_ADDRESS,
|
||||||
CONF_SERVICE_UUID,
|
|
||||||
CONF_MANUFACTURER_ID,
|
CONF_MANUFACTURER_ID,
|
||||||
CONF_ON_BLE_ADVERTISE,
|
CONF_ON_BLE_ADVERTISE,
|
||||||
CONF_ON_BLE_SERVICE_DATA_ADVERTISE,
|
|
||||||
CONF_ON_BLE_MANUFACTURER_DATA_ADVERTISE,
|
CONF_ON_BLE_MANUFACTURER_DATA_ADVERTISE,
|
||||||
|
CONF_ON_BLE_SERVICE_DATA_ADVERTISE,
|
||||||
|
CONF_SERVICE_UUID,
|
||||||
|
CONF_TRIGGER_ID,
|
||||||
|
KEY_CORE,
|
||||||
|
KEY_FRAMEWORK_VERSION,
|
||||||
)
|
)
|
||||||
from esphome.components import esp32_ble
|
|
||||||
from esphome.core import CORE
|
from esphome.core import CORE
|
||||||
from esphome.components.esp32 import add_idf_sdkconfig_option
|
|
||||||
|
|
||||||
AUTO_LOAD = ["esp32_ble"]
|
AUTO_LOAD = ["esp32_ble"]
|
||||||
DEPENDENCIES = ["esp32"]
|
DEPENDENCIES = ["esp32"]
|
||||||
|
@ -263,6 +266,9 @@ async def to_code(config):
|
||||||
# https://github.com/espressif/esp-idf/issues/2503
|
# https://github.com/espressif/esp-idf/issues/2503
|
||||||
# Match arduino CONFIG_BTU_TASK_STACK_SIZE
|
# Match arduino CONFIG_BTU_TASK_STACK_SIZE
|
||||||
# https://github.com/espressif/arduino-esp32/blob/fd72cf46ad6fc1a6de99c1d83ba8eba17d80a4ee/tools/sdk/esp32/sdkconfig#L1866
|
# https://github.com/espressif/arduino-esp32/blob/fd72cf46ad6fc1a6de99c1d83ba8eba17d80a4ee/tools/sdk/esp32/sdkconfig#L1866
|
||||||
|
if CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] >= cv.Version(4, 4, 6):
|
||||||
|
add_idf_sdkconfig_option("CONFIG_BT_BTU_TASK_STACK_SIZE", 8192)
|
||||||
|
else:
|
||||||
add_idf_sdkconfig_option("CONFIG_BTU_TASK_STACK_SIZE", 8192)
|
add_idf_sdkconfig_option("CONFIG_BTU_TASK_STACK_SIZE", 8192)
|
||||||
add_idf_sdkconfig_option("CONFIG_BT_ACL_CONNECTIONS", 9)
|
add_idf_sdkconfig_option("CONFIG_BT_ACL_CONNECTIONS", 9)
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,8 @@ namespace esp32_rmt_led_strip {
|
||||||
|
|
||||||
static const char *const TAG = "esp32_rmt_led_strip";
|
static const char *const TAG = "esp32_rmt_led_strip";
|
||||||
|
|
||||||
|
static const uint32_t RMT_CLK_FREQ = 80000000;
|
||||||
|
|
||||||
static const uint8_t RMT_CLK_DIV = 2;
|
static const uint8_t RMT_CLK_DIV = 2;
|
||||||
|
|
||||||
void ESP32RMTLEDStripLightOutput::setup() {
|
void ESP32RMTLEDStripLightOutput::setup() {
|
||||||
|
@ -65,7 +67,7 @@ void ESP32RMTLEDStripLightOutput::setup() {
|
||||||
|
|
||||||
void ESP32RMTLEDStripLightOutput::set_led_params(uint32_t bit0_high, uint32_t bit0_low, uint32_t bit1_high,
|
void ESP32RMTLEDStripLightOutput::set_led_params(uint32_t bit0_high, uint32_t bit0_low, uint32_t bit1_high,
|
||||||
uint32_t bit1_low) {
|
uint32_t bit1_low) {
|
||||||
float ratio = (float) APB_CLK_FREQ / RMT_CLK_DIV / 1e09f;
|
float ratio = (float) RMT_CLK_FREQ / RMT_CLK_DIV / 1e09f;
|
||||||
|
|
||||||
// 0-bit
|
// 0-bit
|
||||||
this->bit0_.duration0 = (uint32_t) (ratio * bit0_high);
|
this->bit0_.duration0 = (uint32_t) (ratio * bit0_high);
|
||||||
|
|
|
@ -13,8 +13,10 @@ from esphome.const import (
|
||||||
CONF_ON_ENROLLMENT_DONE,
|
CONF_ON_ENROLLMENT_DONE,
|
||||||
CONF_ON_ENROLLMENT_FAILED,
|
CONF_ON_ENROLLMENT_FAILED,
|
||||||
CONF_ON_ENROLLMENT_SCAN,
|
CONF_ON_ENROLLMENT_SCAN,
|
||||||
|
CONF_ON_FINGER_SCAN_START,
|
||||||
CONF_ON_FINGER_SCAN_MATCHED,
|
CONF_ON_FINGER_SCAN_MATCHED,
|
||||||
CONF_ON_FINGER_SCAN_UNMATCHED,
|
CONF_ON_FINGER_SCAN_UNMATCHED,
|
||||||
|
CONF_ON_FINGER_SCAN_MISPLACED,
|
||||||
CONF_ON_FINGER_SCAN_INVALID,
|
CONF_ON_FINGER_SCAN_INVALID,
|
||||||
CONF_PASSWORD,
|
CONF_PASSWORD,
|
||||||
CONF_SENSING_PIN,
|
CONF_SENSING_PIN,
|
||||||
|
@ -35,6 +37,10 @@ FingerprintGrowComponent = fingerprint_grow_ns.class_(
|
||||||
"FingerprintGrowComponent", cg.PollingComponent, uart.UARTDevice
|
"FingerprintGrowComponent", cg.PollingComponent, uart.UARTDevice
|
||||||
)
|
)
|
||||||
|
|
||||||
|
FingerScanStartTrigger = fingerprint_grow_ns.class_(
|
||||||
|
"FingerScanStartTrigger", automation.Trigger.template()
|
||||||
|
)
|
||||||
|
|
||||||
FingerScanMatchedTrigger = fingerprint_grow_ns.class_(
|
FingerScanMatchedTrigger = fingerprint_grow_ns.class_(
|
||||||
"FingerScanMatchedTrigger", automation.Trigger.template(cg.uint16, cg.uint16)
|
"FingerScanMatchedTrigger", automation.Trigger.template(cg.uint16, cg.uint16)
|
||||||
)
|
)
|
||||||
|
@ -43,6 +49,10 @@ FingerScanUnmatchedTrigger = fingerprint_grow_ns.class_(
|
||||||
"FingerScanUnmatchedTrigger", automation.Trigger.template()
|
"FingerScanUnmatchedTrigger", automation.Trigger.template()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
FingerScanMisplacedTrigger = fingerprint_grow_ns.class_(
|
||||||
|
"FingerScanMisplacedTrigger", automation.Trigger.template()
|
||||||
|
)
|
||||||
|
|
||||||
FingerScanInvalidTrigger = fingerprint_grow_ns.class_(
|
FingerScanInvalidTrigger = fingerprint_grow_ns.class_(
|
||||||
"FingerScanInvalidTrigger", automation.Trigger.template()
|
"FingerScanInvalidTrigger", automation.Trigger.template()
|
||||||
)
|
)
|
||||||
|
@ -99,6 +109,13 @@ CONFIG_SCHEMA = (
|
||||||
cv.Optional(CONF_SENSING_PIN): pins.gpio_input_pin_schema,
|
cv.Optional(CONF_SENSING_PIN): pins.gpio_input_pin_schema,
|
||||||
cv.Optional(CONF_PASSWORD): cv.uint32_t,
|
cv.Optional(CONF_PASSWORD): cv.uint32_t,
|
||||||
cv.Optional(CONF_NEW_PASSWORD): cv.uint32_t,
|
cv.Optional(CONF_NEW_PASSWORD): cv.uint32_t,
|
||||||
|
cv.Optional(CONF_ON_FINGER_SCAN_START): automation.validate_automation(
|
||||||
|
{
|
||||||
|
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
||||||
|
FingerScanStartTrigger
|
||||||
|
),
|
||||||
|
}
|
||||||
|
),
|
||||||
cv.Optional(CONF_ON_FINGER_SCAN_MATCHED): automation.validate_automation(
|
cv.Optional(CONF_ON_FINGER_SCAN_MATCHED): automation.validate_automation(
|
||||||
{
|
{
|
||||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
||||||
|
@ -113,6 +130,13 @@ CONFIG_SCHEMA = (
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
|
cv.Optional(CONF_ON_FINGER_SCAN_MISPLACED): automation.validate_automation(
|
||||||
|
{
|
||||||
|
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
||||||
|
FingerScanMisplacedTrigger
|
||||||
|
),
|
||||||
|
}
|
||||||
|
),
|
||||||
cv.Optional(CONF_ON_FINGER_SCAN_INVALID): automation.validate_automation(
|
cv.Optional(CONF_ON_FINGER_SCAN_INVALID): automation.validate_automation(
|
||||||
{
|
{
|
||||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
||||||
|
@ -164,6 +188,10 @@ async def to_code(config):
|
||||||
sensing_pin = await cg.gpio_pin_expression(config[CONF_SENSING_PIN])
|
sensing_pin = await cg.gpio_pin_expression(config[CONF_SENSING_PIN])
|
||||||
cg.add(var.set_sensing_pin(sensing_pin))
|
cg.add(var.set_sensing_pin(sensing_pin))
|
||||||
|
|
||||||
|
for conf in config.get(CONF_ON_FINGER_SCAN_START, []):
|
||||||
|
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||||
|
await automation.build_automation(trigger, [], conf)
|
||||||
|
|
||||||
for conf in config.get(CONF_ON_FINGER_SCAN_MATCHED, []):
|
for conf in config.get(CONF_ON_FINGER_SCAN_MATCHED, []):
|
||||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||||
await automation.build_automation(
|
await automation.build_automation(
|
||||||
|
@ -174,6 +202,10 @@ async def to_code(config):
|
||||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||||
await automation.build_automation(trigger, [], conf)
|
await automation.build_automation(trigger, [], conf)
|
||||||
|
|
||||||
|
for conf in config.get(CONF_ON_FINGER_SCAN_MISPLACED, []):
|
||||||
|
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||||
|
await automation.build_automation(trigger, [], conf)
|
||||||
|
|
||||||
for conf in config.get(CONF_ON_FINGER_SCAN_INVALID, []):
|
for conf in config.get(CONF_ON_FINGER_SCAN_INVALID, []):
|
||||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||||
await automation.build_automation(trigger, [], conf)
|
await automation.build_automation(trigger, [], conf)
|
||||||
|
|
|
@ -15,16 +15,18 @@ void FingerprintGrowComponent::update() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this->sensing_pin_ != nullptr) {
|
if (this->has_sensing_pin_) {
|
||||||
if (this->sensing_pin_->digital_read()) {
|
if (this->sensing_pin_->digital_read()) {
|
||||||
ESP_LOGV(TAG, "No touch sensing");
|
ESP_LOGV(TAG, "No touch sensing");
|
||||||
this->waiting_removal_ = false;
|
this->waiting_removal_ = false;
|
||||||
return;
|
return;
|
||||||
|
} else if (!this->waiting_removal_) {
|
||||||
|
this->finger_scan_start_callback_.call();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this->waiting_removal_) {
|
if (this->waiting_removal_) {
|
||||||
if (this->scan_image_(1) == NO_FINGER) {
|
if ((!this->has_sensing_pin_) && (this->scan_image_(1) == NO_FINGER)) {
|
||||||
ESP_LOGD(TAG, "Finger removed");
|
ESP_LOGD(TAG, "Finger removed");
|
||||||
this->waiting_removal_ = false;
|
this->waiting_removal_ = false;
|
||||||
}
|
}
|
||||||
|
@ -51,6 +53,7 @@ void FingerprintGrowComponent::update() {
|
||||||
|
|
||||||
void FingerprintGrowComponent::setup() {
|
void FingerprintGrowComponent::setup() {
|
||||||
ESP_LOGCONFIG(TAG, "Setting up Grow Fingerprint Reader...");
|
ESP_LOGCONFIG(TAG, "Setting up Grow Fingerprint Reader...");
|
||||||
|
this->has_sensing_pin_ = (this->sensing_pin_ != nullptr);
|
||||||
if (this->check_password_()) {
|
if (this->check_password_()) {
|
||||||
if (this->new_password_ != -1) {
|
if (this->new_password_ != -1) {
|
||||||
if (this->set_password_())
|
if (this->set_password_())
|
||||||
|
@ -91,7 +94,7 @@ void FingerprintGrowComponent::finish_enrollment(uint8_t result) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void FingerprintGrowComponent::scan_and_match_() {
|
void FingerprintGrowComponent::scan_and_match_() {
|
||||||
if (this->sensing_pin_ != nullptr) {
|
if (this->has_sensing_pin_) {
|
||||||
ESP_LOGD(TAG, "Scan and match");
|
ESP_LOGD(TAG, "Scan and match");
|
||||||
} else {
|
} else {
|
||||||
ESP_LOGV(TAG, "Scan and match");
|
ESP_LOGV(TAG, "Scan and match");
|
||||||
|
@ -122,33 +125,38 @@ void FingerprintGrowComponent::scan_and_match_() {
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t FingerprintGrowComponent::scan_image_(uint8_t buffer) {
|
uint8_t FingerprintGrowComponent::scan_image_(uint8_t buffer) {
|
||||||
if (this->sensing_pin_ != nullptr) {
|
if (this->has_sensing_pin_) {
|
||||||
ESP_LOGD(TAG, "Getting image %d", buffer);
|
ESP_LOGD(TAG, "Getting image %d", buffer);
|
||||||
} else {
|
} else {
|
||||||
ESP_LOGV(TAG, "Getting image %d", buffer);
|
ESP_LOGV(TAG, "Getting image %d", buffer);
|
||||||
}
|
}
|
||||||
this->data_ = {GET_IMAGE};
|
this->data_ = {GET_IMAGE};
|
||||||
switch (this->send_command_()) {
|
uint8_t send_result = this->send_command_();
|
||||||
|
switch (send_result) {
|
||||||
case OK:
|
case OK:
|
||||||
break;
|
break;
|
||||||
case NO_FINGER:
|
case NO_FINGER:
|
||||||
if (this->sensing_pin_ != nullptr) {
|
if (this->has_sensing_pin_) {
|
||||||
ESP_LOGD(TAG, "No finger");
|
this->waiting_removal_ = true;
|
||||||
this->finger_scan_invalid_callback_.call();
|
ESP_LOGD(TAG, "Finger Misplaced");
|
||||||
|
this->finger_scan_misplaced_callback_.call();
|
||||||
} else {
|
} else {
|
||||||
ESP_LOGV(TAG, "No finger");
|
ESP_LOGV(TAG, "No finger");
|
||||||
}
|
}
|
||||||
return this->data_[0];
|
return send_result;
|
||||||
case IMAGE_FAIL:
|
case IMAGE_FAIL:
|
||||||
ESP_LOGE(TAG, "Imaging error");
|
ESP_LOGE(TAG, "Imaging error");
|
||||||
this->finger_scan_invalid_callback_.call();
|
this->finger_scan_invalid_callback_.call();
|
||||||
|
return send_result;
|
||||||
default:
|
default:
|
||||||
return this->data_[0];
|
ESP_LOGD(TAG, "Unknown Scan Error: %d", send_result);
|
||||||
|
return send_result;
|
||||||
}
|
}
|
||||||
|
|
||||||
ESP_LOGD(TAG, "Processing image %d", buffer);
|
ESP_LOGD(TAG, "Processing image %d", buffer);
|
||||||
this->data_ = {IMAGE_2_TZ, buffer};
|
this->data_ = {IMAGE_2_TZ, buffer};
|
||||||
switch (this->send_command_()) {
|
send_result = this->send_command_();
|
||||||
|
switch (send_result) {
|
||||||
case OK:
|
case OK:
|
||||||
ESP_LOGI(TAG, "Processed image %d", buffer);
|
ESP_LOGI(TAG, "Processed image %d", buffer);
|
||||||
break;
|
break;
|
||||||
|
@ -162,7 +170,7 @@ uint8_t FingerprintGrowComponent::scan_image_(uint8_t buffer) {
|
||||||
this->finger_scan_invalid_callback_.call();
|
this->finger_scan_invalid_callback_.call();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return this->data_[0];
|
return send_result;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t FingerprintGrowComponent::save_fingerprint_() {
|
uint8_t FingerprintGrowComponent::save_fingerprint_() {
|
||||||
|
@ -225,10 +233,11 @@ bool FingerprintGrowComponent::get_parameters_() {
|
||||||
ESP_LOGD(TAG, "Getting parameters");
|
ESP_LOGD(TAG, "Getting parameters");
|
||||||
this->data_ = {READ_SYS_PARAM};
|
this->data_ = {READ_SYS_PARAM};
|
||||||
if (this->send_command_() == OK) {
|
if (this->send_command_() == OK) {
|
||||||
ESP_LOGD(TAG, "Got parameters");
|
ESP_LOGD(TAG, "Got parameters"); // Bear in mind data_[0] is the transfer status,
|
||||||
if (this->status_sensor_ != nullptr) {
|
if (this->status_sensor_ != nullptr) { // the parameters table start at data_[1]
|
||||||
this->status_sensor_->publish_state(((uint16_t) this->data_[1] << 8) | this->data_[2]);
|
this->status_sensor_->publish_state(((uint16_t) this->data_[1] << 8) | this->data_[2]);
|
||||||
}
|
}
|
||||||
|
this->system_identifier_code_ = ((uint16_t) this->data_[3] << 8) | this->data_[4];
|
||||||
this->capacity_ = ((uint16_t) this->data_[5] << 8) | this->data_[6];
|
this->capacity_ = ((uint16_t) this->data_[5] << 8) | this->data_[6];
|
||||||
if (this->capacity_sensor_ != nullptr) {
|
if (this->capacity_sensor_ != nullptr) {
|
||||||
this->capacity_sensor_->publish_state(this->capacity_);
|
this->capacity_sensor_->publish_state(this->capacity_);
|
||||||
|
@ -430,13 +439,22 @@ uint8_t FingerprintGrowComponent::send_command_() {
|
||||||
|
|
||||||
void FingerprintGrowComponent::dump_config() {
|
void FingerprintGrowComponent::dump_config() {
|
||||||
ESP_LOGCONFIG(TAG, "GROW_FINGERPRINT_READER:");
|
ESP_LOGCONFIG(TAG, "GROW_FINGERPRINT_READER:");
|
||||||
|
ESP_LOGCONFIG(TAG, " System Identifier Code: 0x%.4X", this->system_identifier_code_);
|
||||||
|
ESP_LOGCONFIG(TAG, " Touch Sensing Pin: %s",
|
||||||
|
this->has_sensing_pin_ ? this->sensing_pin_->dump_summary().c_str() : "None");
|
||||||
LOG_UPDATE_INTERVAL(this);
|
LOG_UPDATE_INTERVAL(this);
|
||||||
LOG_SENSOR(" ", "Fingerprint Count", this->fingerprint_count_sensor_);
|
LOG_SENSOR(" ", "Fingerprint Count", this->fingerprint_count_sensor_);
|
||||||
|
ESP_LOGCONFIG(TAG, " Current Value: %d", (uint16_t) this->fingerprint_count_sensor_->get_state());
|
||||||
LOG_SENSOR(" ", "Status", this->status_sensor_);
|
LOG_SENSOR(" ", "Status", this->status_sensor_);
|
||||||
|
ESP_LOGCONFIG(TAG, " Current Value: %d", (uint8_t) this->status_sensor_->get_state());
|
||||||
LOG_SENSOR(" ", "Capacity", this->capacity_sensor_);
|
LOG_SENSOR(" ", "Capacity", this->capacity_sensor_);
|
||||||
|
ESP_LOGCONFIG(TAG, " Current Value: %d", (uint16_t) this->capacity_sensor_->get_state());
|
||||||
LOG_SENSOR(" ", "Security Level", this->security_level_sensor_);
|
LOG_SENSOR(" ", "Security Level", this->security_level_sensor_);
|
||||||
|
ESP_LOGCONFIG(TAG, " Current Value: %d", (uint8_t) this->security_level_sensor_->get_state());
|
||||||
LOG_SENSOR(" ", "Last Finger ID", this->last_finger_id_sensor_);
|
LOG_SENSOR(" ", "Last Finger ID", this->last_finger_id_sensor_);
|
||||||
|
ESP_LOGCONFIG(TAG, " Current Value: %d", (uint32_t) this->last_finger_id_sensor_->get_state());
|
||||||
LOG_SENSOR(" ", "Last Confidence", this->last_confidence_sensor_);
|
LOG_SENSOR(" ", "Last Confidence", this->last_confidence_sensor_);
|
||||||
|
ESP_LOGCONFIG(TAG, " Current Value: %d", (uint32_t) this->last_confidence_sensor_->get_state());
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace fingerprint_grow
|
} // namespace fingerprint_grow
|
||||||
|
|
|
@ -118,12 +118,18 @@ class FingerprintGrowComponent : public PollingComponent, public uart::UARTDevic
|
||||||
void set_enrolling_binary_sensor(binary_sensor::BinarySensor *enrolling_binary_sensor) {
|
void set_enrolling_binary_sensor(binary_sensor::BinarySensor *enrolling_binary_sensor) {
|
||||||
this->enrolling_binary_sensor_ = enrolling_binary_sensor;
|
this->enrolling_binary_sensor_ = enrolling_binary_sensor;
|
||||||
}
|
}
|
||||||
|
void add_on_finger_scan_start_callback(std::function<void()> callback) {
|
||||||
|
this->finger_scan_start_callback_.add(std::move(callback));
|
||||||
|
}
|
||||||
void add_on_finger_scan_matched_callback(std::function<void(uint16_t, uint16_t)> callback) {
|
void add_on_finger_scan_matched_callback(std::function<void(uint16_t, uint16_t)> callback) {
|
||||||
this->finger_scan_matched_callback_.add(std::move(callback));
|
this->finger_scan_matched_callback_.add(std::move(callback));
|
||||||
}
|
}
|
||||||
void add_on_finger_scan_unmatched_callback(std::function<void()> callback) {
|
void add_on_finger_scan_unmatched_callback(std::function<void()> callback) {
|
||||||
this->finger_scan_unmatched_callback_.add(std::move(callback));
|
this->finger_scan_unmatched_callback_.add(std::move(callback));
|
||||||
}
|
}
|
||||||
|
void add_on_finger_scan_misplaced_callback(std::function<void()> callback) {
|
||||||
|
this->finger_scan_misplaced_callback_.add(std::move(callback));
|
||||||
|
}
|
||||||
void add_on_finger_scan_invalid_callback(std::function<void()> callback) {
|
void add_on_finger_scan_invalid_callback(std::function<void()> callback) {
|
||||||
this->finger_scan_invalid_callback_.add(std::move(callback));
|
this->finger_scan_invalid_callback_.add(std::move(callback));
|
||||||
}
|
}
|
||||||
|
@ -166,8 +172,10 @@ class FingerprintGrowComponent : public PollingComponent, public uart::UARTDevic
|
||||||
uint16_t enrollment_slot_ = ENROLLMENT_SLOT_UNUSED;
|
uint16_t enrollment_slot_ = ENROLLMENT_SLOT_UNUSED;
|
||||||
uint8_t enrollment_buffers_ = 5;
|
uint8_t enrollment_buffers_ = 5;
|
||||||
bool waiting_removal_ = false;
|
bool waiting_removal_ = false;
|
||||||
|
bool has_sensing_pin_ = false;
|
||||||
uint32_t last_aura_led_control_ = 0;
|
uint32_t last_aura_led_control_ = 0;
|
||||||
uint16_t last_aura_led_duration_ = 0;
|
uint16_t last_aura_led_duration_ = 0;
|
||||||
|
uint16_t system_identifier_code_ = 0;
|
||||||
sensor::Sensor *fingerprint_count_sensor_{nullptr};
|
sensor::Sensor *fingerprint_count_sensor_{nullptr};
|
||||||
sensor::Sensor *status_sensor_{nullptr};
|
sensor::Sensor *status_sensor_{nullptr};
|
||||||
sensor::Sensor *capacity_sensor_{nullptr};
|
sensor::Sensor *capacity_sensor_{nullptr};
|
||||||
|
@ -176,13 +184,22 @@ class FingerprintGrowComponent : public PollingComponent, public uart::UARTDevic
|
||||||
sensor::Sensor *last_confidence_sensor_{nullptr};
|
sensor::Sensor *last_confidence_sensor_{nullptr};
|
||||||
binary_sensor::BinarySensor *enrolling_binary_sensor_{nullptr};
|
binary_sensor::BinarySensor *enrolling_binary_sensor_{nullptr};
|
||||||
CallbackManager<void()> finger_scan_invalid_callback_;
|
CallbackManager<void()> finger_scan_invalid_callback_;
|
||||||
|
CallbackManager<void()> finger_scan_start_callback_;
|
||||||
CallbackManager<void(uint16_t, uint16_t)> finger_scan_matched_callback_;
|
CallbackManager<void(uint16_t, uint16_t)> finger_scan_matched_callback_;
|
||||||
CallbackManager<void()> finger_scan_unmatched_callback_;
|
CallbackManager<void()> finger_scan_unmatched_callback_;
|
||||||
|
CallbackManager<void()> finger_scan_misplaced_callback_;
|
||||||
CallbackManager<void(uint8_t, uint16_t)> enrollment_scan_callback_;
|
CallbackManager<void(uint8_t, uint16_t)> enrollment_scan_callback_;
|
||||||
CallbackManager<void(uint16_t)> enrollment_done_callback_;
|
CallbackManager<void(uint16_t)> enrollment_done_callback_;
|
||||||
CallbackManager<void(uint16_t)> enrollment_failed_callback_;
|
CallbackManager<void(uint16_t)> enrollment_failed_callback_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class FingerScanStartTrigger : public Trigger<> {
|
||||||
|
public:
|
||||||
|
explicit FingerScanStartTrigger(FingerprintGrowComponent *parent) {
|
||||||
|
parent->add_on_finger_scan_start_callback([this]() { this->trigger(); });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
class FingerScanMatchedTrigger : public Trigger<uint16_t, uint16_t> {
|
class FingerScanMatchedTrigger : public Trigger<uint16_t, uint16_t> {
|
||||||
public:
|
public:
|
||||||
explicit FingerScanMatchedTrigger(FingerprintGrowComponent *parent) {
|
explicit FingerScanMatchedTrigger(FingerprintGrowComponent *parent) {
|
||||||
|
@ -198,6 +215,13 @@ class FingerScanUnmatchedTrigger : public Trigger<> {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class FingerScanMisplacedTrigger : public Trigger<> {
|
||||||
|
public:
|
||||||
|
explicit FingerScanMisplacedTrigger(FingerprintGrowComponent *parent) {
|
||||||
|
parent->add_on_finger_scan_misplaced_callback([this]() { this->trigger(); });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
class FingerScanInvalidTrigger : public Trigger<> {
|
class FingerScanInvalidTrigger : public Trigger<> {
|
||||||
public:
|
public:
|
||||||
explicit FingerScanInvalidTrigger(FingerprintGrowComponent *parent) {
|
explicit FingerScanInvalidTrigger(FingerprintGrowComponent *parent) {
|
||||||
|
|
|
@ -67,13 +67,13 @@ def validate_pillow_installed(value):
|
||||||
except ImportError as err:
|
except ImportError as err:
|
||||||
raise cv.Invalid(
|
raise cv.Invalid(
|
||||||
"Please install the pillow python package to use this feature. "
|
"Please install the pillow python package to use this feature. "
|
||||||
'(pip install "pillow==10.1.0")'
|
'(pip install "pillow==10.2.0")'
|
||||||
) from err
|
) from err
|
||||||
|
|
||||||
if version.parse(PIL.__version__) != version.parse("10.1.0"):
|
if version.parse(PIL.__version__) != version.parse("10.2.0"):
|
||||||
raise cv.Invalid(
|
raise cv.Invalid(
|
||||||
"Please update your pillow installation to 10.1.0. "
|
"Please update your pillow installation to 10.2.0. "
|
||||||
'(pip install "pillow==10.1.0")'
|
'(pip install "pillow==10.2.0")'
|
||||||
)
|
)
|
||||||
|
|
||||||
return value
|
return value
|
||||||
|
@ -235,7 +235,7 @@ FILE_SCHEMA = cv.Schema(_file_schema)
|
||||||
|
|
||||||
|
|
||||||
DEFAULT_GLYPHS = (
|
DEFAULT_GLYPHS = (
|
||||||
' !"%()+=,-.:/0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz°'
|
' !"%()+=,-.:/?0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz°'
|
||||||
)
|
)
|
||||||
CONF_RAW_GLYPH_ID = "raw_glyph_id"
|
CONF_RAW_GLYPH_ID = "raw_glyph_id"
|
||||||
|
|
||||||
|
|
|
@ -13,14 +13,14 @@
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace ft63x6 {
|
namespace ft63x6 {
|
||||||
|
|
||||||
static const uint8_t FT63X6_ADDR_TOUCH_COUNT = 0x02;
|
static const uint8_t FT63X6_ADDR_TOUCH1_STATE = 0x03;
|
||||||
|
|
||||||
static const uint8_t FT63X6_ADDR_TOUCH1_ID = 0x05;
|
|
||||||
static const uint8_t FT63X6_ADDR_TOUCH1_X = 0x03;
|
static const uint8_t FT63X6_ADDR_TOUCH1_X = 0x03;
|
||||||
|
static const uint8_t FT63X6_ADDR_TOUCH1_ID = 0x05;
|
||||||
static const uint8_t FT63X6_ADDR_TOUCH1_Y = 0x05;
|
static const uint8_t FT63X6_ADDR_TOUCH1_Y = 0x05;
|
||||||
|
|
||||||
static const uint8_t FT63X6_ADDR_TOUCH2_ID = 0x0B;
|
static const uint8_t FT63X6_ADDR_TOUCH2_STATE = 0x09;
|
||||||
static const uint8_t FT63X6_ADDR_TOUCH2_X = 0x09;
|
static const uint8_t FT63X6_ADDR_TOUCH2_X = 0x09;
|
||||||
|
static const uint8_t FT63X6_ADDR_TOUCH2_ID = 0x0B;
|
||||||
static const uint8_t FT63X6_ADDR_TOUCH2_Y = 0x0B;
|
static const uint8_t FT63X6_ADDR_TOUCH2_Y = 0x0B;
|
||||||
|
|
||||||
static const char *const TAG = "FT63X6Touchscreen";
|
static const char *const TAG = "FT63X6Touchscreen";
|
||||||
|
@ -40,27 +40,12 @@ void FT63X6Touchscreen::setup() {
|
||||||
this->hard_reset_();
|
this->hard_reset_();
|
||||||
|
|
||||||
// Get touch resolution
|
// Get touch resolution
|
||||||
|
if (this->x_raw_max_ == this->x_raw_min_) {
|
||||||
this->x_raw_max_ = 320;
|
this->x_raw_max_ = 320;
|
||||||
|
}
|
||||||
|
if (this->y_raw_max_ == this->y_raw_min_) {
|
||||||
this->y_raw_max_ = 480;
|
this->y_raw_max_ = 480;
|
||||||
}
|
}
|
||||||
|
|
||||||
void FT63X6Touchscreen::update_touches() {
|
|
||||||
int touch_count = this->read_touch_count_();
|
|
||||||
if (touch_count == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t touch_id = this->read_touch_id_(FT63X6_ADDR_TOUCH1_ID); // id1 = 0 or 1
|
|
||||||
int16_t x = this->read_touch_coordinate_(FT63X6_ADDR_TOUCH1_X);
|
|
||||||
int16_t y = this->read_touch_coordinate_(FT63X6_ADDR_TOUCH1_Y);
|
|
||||||
this->add_raw_touch_position_(touch_id, x, y);
|
|
||||||
|
|
||||||
if (touch_count >= 2) {
|
|
||||||
touch_id = this->read_touch_id_(FT63X6_ADDR_TOUCH2_ID); // id2 = 0 or 1(~id1 & 0x01)
|
|
||||||
x = this->read_touch_coordinate_(FT63X6_ADDR_TOUCH2_X);
|
|
||||||
y = this->read_touch_coordinate_(FT63X6_ADDR_TOUCH2_Y);
|
|
||||||
this->add_raw_touch_position_(touch_id, x, y);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void FT63X6Touchscreen::hard_reset_() {
|
void FT63X6Touchscreen::hard_reset_() {
|
||||||
|
@ -76,23 +61,31 @@ void FT63X6Touchscreen::dump_config() {
|
||||||
LOG_I2C_DEVICE(this);
|
LOG_I2C_DEVICE(this);
|
||||||
LOG_PIN(" Interrupt Pin: ", this->interrupt_pin_);
|
LOG_PIN(" Interrupt Pin: ", this->interrupt_pin_);
|
||||||
LOG_PIN(" Reset Pin: ", this->reset_pin_);
|
LOG_PIN(" Reset Pin: ", this->reset_pin_);
|
||||||
|
LOG_UPDATE_INTERVAL(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t FT63X6Touchscreen::read_touch_count_() { return this->read_byte_(FT63X6_ADDR_TOUCH_COUNT); }
|
void FT63X6Touchscreen::update_touches() {
|
||||||
|
uint8_t data[15];
|
||||||
|
uint16_t touch_id, x, y;
|
||||||
|
|
||||||
// Touch functions
|
if (!this->read_bytes(0x00, (uint8_t *) data, 15)) {
|
||||||
uint16_t FT63X6Touchscreen::read_touch_coordinate_(uint8_t coordinate) {
|
ESP_LOGE(TAG, "Failed to read touch data");
|
||||||
uint8_t read_buf[2];
|
this->skip_update_ = true;
|
||||||
read_buf[0] = this->read_byte_(coordinate);
|
return;
|
||||||
read_buf[1] = this->read_byte_(coordinate + 1);
|
|
||||||
return ((read_buf[0] & 0x0f) << 8) | read_buf[1];
|
|
||||||
}
|
}
|
||||||
uint8_t FT63X6Touchscreen::read_touch_id_(uint8_t id_address) { return this->read_byte_(id_address) >> 4; }
|
|
||||||
|
|
||||||
uint8_t FT63X6Touchscreen::read_byte_(uint8_t addr) {
|
if (((data[FT63X6_ADDR_TOUCH1_STATE] >> 6) & 0x01) == 0) {
|
||||||
uint8_t byte = 0;
|
touch_id = data[FT63X6_ADDR_TOUCH1_ID] >> 4; // id1 = 0 or 1
|
||||||
this->read_byte(addr, &byte);
|
x = encode_uint16(data[FT63X6_ADDR_TOUCH1_X] & 0x0F, data[FT63X6_ADDR_TOUCH1_X + 1]);
|
||||||
return byte;
|
y = encode_uint16(data[FT63X6_ADDR_TOUCH1_Y] & 0x0F, data[FT63X6_ADDR_TOUCH1_Y + 1]);
|
||||||
|
this->add_raw_touch_position_(touch_id, x, y);
|
||||||
|
}
|
||||||
|
if (((data[FT63X6_ADDR_TOUCH2_STATE] >> 6) & 0x01) == 0) {
|
||||||
|
touch_id = data[FT63X6_ADDR_TOUCH2_ID] >> 4; // id1 = 0 or 1
|
||||||
|
x = encode_uint16(data[FT63X6_ADDR_TOUCH2_X] & 0x0F, data[FT63X6_ADDR_TOUCH2_X + 1]);
|
||||||
|
y = encode_uint16(data[FT63X6_ADDR_TOUCH2_Y] & 0x0F, data[FT63X6_ADDR_TOUCH2_Y + 1]);
|
||||||
|
this->add_raw_touch_position_(touch_id, x, y);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace ft63x6
|
} // namespace ft63x6
|
||||||
|
|
|
@ -61,6 +61,7 @@ VALUE_POSITION_TYPE = {
|
||||||
"BELOW": ValuePositionType.VALUE_POSITION_TYPE_BELOW,
|
"BELOW": ValuePositionType.VALUE_POSITION_TYPE_BELOW,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CONF_CONTINUOUS = "continuous"
|
||||||
|
|
||||||
GRAPH_TRACE_SCHEMA = cv.Schema(
|
GRAPH_TRACE_SCHEMA = cv.Schema(
|
||||||
{
|
{
|
||||||
|
@ -70,6 +71,7 @@ GRAPH_TRACE_SCHEMA = cv.Schema(
|
||||||
cv.Optional(CONF_LINE_THICKNESS): cv.positive_int,
|
cv.Optional(CONF_LINE_THICKNESS): cv.positive_int,
|
||||||
cv.Optional(CONF_LINE_TYPE): cv.enum(LINE_TYPE, upper=True),
|
cv.Optional(CONF_LINE_TYPE): cv.enum(LINE_TYPE, upper=True),
|
||||||
cv.Optional(CONF_COLOR): cv.use_id(color.ColorStruct),
|
cv.Optional(CONF_COLOR): cv.use_id(color.ColorStruct),
|
||||||
|
cv.Optional(CONF_CONTINUOUS): cv.boolean,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -186,6 +188,8 @@ async def to_code(config):
|
||||||
if CONF_COLOR in trace:
|
if CONF_COLOR in trace:
|
||||||
c = await cg.get_variable(trace[CONF_COLOR])
|
c = await cg.get_variable(trace[CONF_COLOR])
|
||||||
cg.add(tr.set_line_color(c))
|
cg.add(tr.set_line_color(c))
|
||||||
|
if CONF_CONTINUOUS in trace:
|
||||||
|
cg.add(tr.set_continuous(trace[CONF_CONTINUOUS]))
|
||||||
cg.add(var.add_trace(tr))
|
cg.add(var.add_trace(tr))
|
||||||
# Add legend
|
# Add legend
|
||||||
if CONF_LEGEND in config:
|
if CONF_LEGEND in config:
|
||||||
|
|
|
@ -165,17 +165,42 @@ void Graph::draw(Display *buff, uint16_t x_offset, uint16_t y_offset, Color colo
|
||||||
for (auto *trace : traces_) {
|
for (auto *trace : traces_) {
|
||||||
Color c = trace->get_line_color();
|
Color c = trace->get_line_color();
|
||||||
uint16_t thick = trace->get_line_thickness();
|
uint16_t thick = trace->get_line_thickness();
|
||||||
|
bool continuous = trace->get_continuous();
|
||||||
|
bool has_prev = false;
|
||||||
|
bool prev_b = false;
|
||||||
|
int16_t prev_y = 0;
|
||||||
for (uint32_t i = 0; i < this->width_; i++) {
|
for (uint32_t i = 0; i < this->width_; i++) {
|
||||||
float v = (trace->get_tracedata()->get_value(i) - ymin) / yrange;
|
float v = (trace->get_tracedata()->get_value(i) - ymin) / yrange;
|
||||||
if (!std::isnan(v) && (thick > 0)) {
|
if (!std::isnan(v) && (thick > 0)) {
|
||||||
int16_t x = this->width_ - 1 - i;
|
int16_t x = this->width_ - 1 - i + x_offset;
|
||||||
uint8_t b = (i % (thick * LineType::PATTERN_LENGTH)) / thick;
|
uint8_t bit = 1 << ((i % (thick * LineType::PATTERN_LENGTH)) / thick);
|
||||||
if (((uint8_t) trace->get_line_type() & (1 << b)) == (1 << b)) {
|
bool b = (trace->get_line_type() & bit) == bit;
|
||||||
int16_t y = (int16_t) roundf((this->height_ - 1) * (1.0 - v)) - thick / 2;
|
if (b) {
|
||||||
|
int16_t y = (int16_t) roundf((this->height_ - 1) * (1.0 - v)) - thick / 2 + y_offset;
|
||||||
|
if (!continuous || !has_prev || !prev_b || (abs(y - prev_y) <= thick)) {
|
||||||
for (uint16_t t = 0; t < thick; t++) {
|
for (uint16_t t = 0; t < thick; t++) {
|
||||||
buff->draw_pixel_at(x_offset + x, y_offset + y + t, c);
|
buff->draw_pixel_at(x, y + t, c);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
int16_t mid_y = (y + prev_y + thick) / 2;
|
||||||
|
if (y > prev_y) {
|
||||||
|
for (uint16_t t = prev_y + thick; t <= mid_y; t++)
|
||||||
|
buff->draw_pixel_at(x + 1, t, c);
|
||||||
|
for (uint16_t t = mid_y + 1; t < y + thick; t++)
|
||||||
|
buff->draw_pixel_at(x, t, c);
|
||||||
|
} else {
|
||||||
|
for (uint16_t t = prev_y - 1; t >= mid_y; t--)
|
||||||
|
buff->draw_pixel_at(x + 1, t, c);
|
||||||
|
for (uint16_t t = mid_y - 1; t >= y; t--)
|
||||||
|
buff->draw_pixel_at(x, t, c);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
prev_y = y;
|
||||||
|
}
|
||||||
|
prev_b = b;
|
||||||
|
has_prev = true;
|
||||||
|
} else {
|
||||||
|
has_prev = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -116,6 +116,8 @@ class GraphTrace {
|
||||||
void set_line_type(enum LineType val) { this->line_type_ = val; }
|
void set_line_type(enum LineType val) { this->line_type_ = val; }
|
||||||
Color get_line_color() { return this->line_color_; }
|
Color get_line_color() { return this->line_color_; }
|
||||||
void set_line_color(Color val) { this->line_color_ = val; }
|
void set_line_color(Color val) { this->line_color_ = val; }
|
||||||
|
bool get_continuous() { return this->continuous_; }
|
||||||
|
void set_continuous(bool continuous) { this->continuous_ = continuous; }
|
||||||
std::string get_name() { return name_; }
|
std::string get_name() { return name_; }
|
||||||
const HistoryData *get_tracedata() { return &data_; }
|
const HistoryData *get_tracedata() { return &data_; }
|
||||||
|
|
||||||
|
@ -125,6 +127,7 @@ class GraphTrace {
|
||||||
uint8_t line_thickness_{3};
|
uint8_t line_thickness_{3};
|
||||||
enum LineType line_type_ { LINE_TYPE_SOLID };
|
enum LineType line_type_ { LINE_TYPE_SOLID };
|
||||||
Color line_color_{COLOR_ON};
|
Color line_color_{COLOR_ON};
|
||||||
|
bool continuous_{false};
|
||||||
HistoryData data_;
|
HistoryData data_;
|
||||||
|
|
||||||
friend Graph;
|
friend Graph;
|
||||||
|
|
2
esphome/components/honeywell_hih_i2c/__init__.py
Normal file
2
esphome/components/honeywell_hih_i2c/__init__.py
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
"""Support for Honeywell HumidIcon HIH"""
|
||||||
|
CODEOWNERS = ["@Benichou34"]
|
97
esphome/components/honeywell_hih_i2c/honeywell_hih.cpp
Normal file
97
esphome/components/honeywell_hih_i2c/honeywell_hih.cpp
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
// Honeywell HumidIcon I2C Sensors
|
||||||
|
// https://prod-edam.honeywell.com/content/dam/honeywell-edam/sps/siot/en-us/products/sensors/humidity-with-temperature-sensors/common/documents/sps-siot-i2c-comms-humidicon-tn-009061-2-en-ciid-142171.pdf
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "honeywell_hih.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace honeywell_hih_i2c {
|
||||||
|
|
||||||
|
static const char *const TAG = "honeywell_hih.i2c";
|
||||||
|
|
||||||
|
static const uint8_t REQUEST_CMD[1] = {0x00}; // Measurement Request Format
|
||||||
|
static const uint16_t MAX_COUNT = 0x3FFE; // 2^14 - 2
|
||||||
|
|
||||||
|
void HoneywellHIComponent::read_sensor_data_() {
|
||||||
|
uint8_t data[4];
|
||||||
|
|
||||||
|
if (this->read(data, sizeof(data)) != i2c::ERROR_OK) {
|
||||||
|
ESP_LOGE(TAG, "Communication with Honeywell HIH failed!");
|
||||||
|
this->mark_failed();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint16_t raw_humidity = (static_cast<uint16_t>(data[0] & 0x3F) << 8) | data[1];
|
||||||
|
float humidity = (static_cast<float>(raw_humidity) / MAX_COUNT) * 100;
|
||||||
|
|
||||||
|
const uint16_t raw_temperature = (static_cast<uint16_t>(data[2]) << 6) | (data[3] >> 2);
|
||||||
|
float temperature = (static_cast<float>(raw_temperature) / MAX_COUNT) * 165 - 40;
|
||||||
|
|
||||||
|
ESP_LOGD(TAG, "Got temperature=%.2f°C humidity=%.2f%%", temperature, humidity);
|
||||||
|
if (this->temperature_sensor_ != nullptr)
|
||||||
|
this->temperature_sensor_->publish_state(temperature);
|
||||||
|
if (this->humidity_sensor_ != nullptr)
|
||||||
|
this->humidity_sensor_->publish_state(humidity);
|
||||||
|
}
|
||||||
|
|
||||||
|
void HoneywellHIComponent::start_measurement_() {
|
||||||
|
if (this->write(REQUEST_CMD, sizeof(REQUEST_CMD)) != i2c::ERROR_OK) {
|
||||||
|
ESP_LOGE(TAG, "Communication with Honeywell HIH failed!");
|
||||||
|
this->mark_failed();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this->measurement_running_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HoneywellHIComponent::is_measurement_ready_() {
|
||||||
|
uint8_t data[1];
|
||||||
|
|
||||||
|
if (this->read(data, sizeof(data)) != i2c::ERROR_OK) {
|
||||||
|
ESP_LOGE(TAG, "Communication with Honeywell HIH failed!");
|
||||||
|
this->mark_failed();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check status bits
|
||||||
|
return ((data[0] & 0xC0) == 0x00);
|
||||||
|
}
|
||||||
|
|
||||||
|
void HoneywellHIComponent::measurement_timeout_() {
|
||||||
|
ESP_LOGE(TAG, "Honeywell HIH Timeout!");
|
||||||
|
this->measurement_running_ = false;
|
||||||
|
this->mark_failed();
|
||||||
|
}
|
||||||
|
|
||||||
|
void HoneywellHIComponent::update() {
|
||||||
|
ESP_LOGV(TAG, "Update Honeywell HIH Sensor");
|
||||||
|
|
||||||
|
this->start_measurement_();
|
||||||
|
// The measurement cycle duration is typically 36.65 ms for temperature and humidity readings.
|
||||||
|
this->set_timeout("meas_timeout", 100, [this] { this->measurement_timeout_(); });
|
||||||
|
}
|
||||||
|
|
||||||
|
void HoneywellHIComponent::loop() {
|
||||||
|
if (this->measurement_running_ && this->is_measurement_ready_()) {
|
||||||
|
this->measurement_running_ = false;
|
||||||
|
this->cancel_timeout("meas_timeout");
|
||||||
|
this->read_sensor_data_();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void HoneywellHIComponent::dump_config() {
|
||||||
|
ESP_LOGD(TAG, "Honeywell HIH:");
|
||||||
|
LOG_I2C_DEVICE(this);
|
||||||
|
if (this->is_failed()) {
|
||||||
|
ESP_LOGE(TAG, "Communication with Honeywell HIH failed!");
|
||||||
|
}
|
||||||
|
LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
|
||||||
|
LOG_SENSOR(" ", "Humidity", this->humidity_sensor_);
|
||||||
|
LOG_UPDATE_INTERVAL(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
float HoneywellHIComponent::get_setup_priority() const { return setup_priority::DATA; }
|
||||||
|
|
||||||
|
} // namespace honeywell_hih_i2c
|
||||||
|
} // namespace esphome
|
34
esphome/components/honeywell_hih_i2c/honeywell_hih.h
Normal file
34
esphome/components/honeywell_hih_i2c/honeywell_hih.h
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
// Honeywell HumidIcon I2C Sensors
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
#include "esphome/components/sensor/sensor.h"
|
||||||
|
#include "esphome/components/i2c/i2c.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace honeywell_hih_i2c {
|
||||||
|
|
||||||
|
class HoneywellHIComponent : public PollingComponent, public i2c::I2CDevice {
|
||||||
|
public:
|
||||||
|
void dump_config() override;
|
||||||
|
float get_setup_priority() const override;
|
||||||
|
void loop() override;
|
||||||
|
void update() override;
|
||||||
|
|
||||||
|
void set_temperature_sensor(sensor::Sensor *temperature_sensor) { this->temperature_sensor_ = temperature_sensor; }
|
||||||
|
void set_humidity_sensor(sensor::Sensor *humidity_sensor) { this->humidity_sensor_ = humidity_sensor; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
bool measurement_running_{false};
|
||||||
|
sensor::Sensor *temperature_sensor_{nullptr};
|
||||||
|
sensor::Sensor *humidity_sensor_{nullptr};
|
||||||
|
|
||||||
|
private:
|
||||||
|
void read_sensor_data_();
|
||||||
|
void start_measurement_();
|
||||||
|
bool is_measurement_ready_();
|
||||||
|
void measurement_timeout_();
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace honeywell_hih_i2c
|
||||||
|
} // namespace esphome
|
56
esphome/components/honeywell_hih_i2c/sensor.py
Normal file
56
esphome/components/honeywell_hih_i2c/sensor.py
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
import esphome.codegen as cg
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
from esphome.components import i2c, sensor
|
||||||
|
from esphome.const import (
|
||||||
|
CONF_HUMIDITY,
|
||||||
|
CONF_ID,
|
||||||
|
CONF_TEMPERATURE,
|
||||||
|
STATE_CLASS_MEASUREMENT,
|
||||||
|
UNIT_CELSIUS,
|
||||||
|
UNIT_PERCENT,
|
||||||
|
DEVICE_CLASS_TEMPERATURE,
|
||||||
|
DEVICE_CLASS_HUMIDITY,
|
||||||
|
)
|
||||||
|
|
||||||
|
DEPENDENCIES = ["i2c"]
|
||||||
|
|
||||||
|
honeywell_hih_ns = cg.esphome_ns.namespace("honeywell_hih_i2c")
|
||||||
|
HONEYWELLHIComponent = honeywell_hih_ns.class_(
|
||||||
|
"HoneywellHIComponent", cg.PollingComponent, i2c.I2CDevice
|
||||||
|
)
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = (
|
||||||
|
cv.Schema(
|
||||||
|
{
|
||||||
|
cv.GenerateID(): cv.declare_id(HONEYWELLHIComponent),
|
||||||
|
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
|
||||||
|
unit_of_measurement=UNIT_CELSIUS,
|
||||||
|
accuracy_decimals=1,
|
||||||
|
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||||
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
|
||||||
|
unit_of_measurement=UNIT_PERCENT,
|
||||||
|
accuracy_decimals=1,
|
||||||
|
device_class=DEVICE_CLASS_HUMIDITY,
|
||||||
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.extend(cv.polling_component_schema("60s"))
|
||||||
|
.extend(i2c.i2c_device_schema(0x27))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def to_code(config):
|
||||||
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
|
await cg.register_component(var, config)
|
||||||
|
await i2c.register_i2c_device(var, config)
|
||||||
|
|
||||||
|
if temperature_config := config.get(CONF_TEMPERATURE):
|
||||||
|
sens = await sensor.new_sensor(temperature_config)
|
||||||
|
cg.add(var.set_temperature_sensor(sens))
|
||||||
|
|
||||||
|
if humidity_config := config.get(CONF_HUMIDITY):
|
||||||
|
sens = await sensor.new_sensor(humidity_config)
|
||||||
|
cg.add(var.set_humidity_sensor(sens))
|
|
@ -6,6 +6,7 @@ from esphome.const import (
|
||||||
PLATFORM_HOST,
|
PLATFORM_HOST,
|
||||||
)
|
)
|
||||||
from esphome.core import CORE
|
from esphome.core import CORE
|
||||||
|
from esphome.helpers import IS_MACOS
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
|
|
||||||
|
@ -14,7 +15,6 @@ from .const import KEY_HOST
|
||||||
# force import gpio to register pin schema
|
# force import gpio to register pin schema
|
||||||
from .gpio import host_pin_to_code # noqa
|
from .gpio import host_pin_to_code # noqa
|
||||||
|
|
||||||
|
|
||||||
CODEOWNERS = ["@esphome/core"]
|
CODEOWNERS = ["@esphome/core"]
|
||||||
AUTO_LOAD = ["network"]
|
AUTO_LOAD = ["network"]
|
||||||
|
|
||||||
|
@ -35,5 +35,9 @@ CONFIG_SCHEMA = cv.All(
|
||||||
|
|
||||||
async def to_code(config):
|
async def to_code(config):
|
||||||
cg.add_build_flag("-DUSE_HOST")
|
cg.add_build_flag("-DUSE_HOST")
|
||||||
|
cg.add_build_flag("-std=c++17")
|
||||||
|
cg.add_build_flag("-lsodium")
|
||||||
|
if IS_MACOS:
|
||||||
|
cg.add_build_flag("-L/opt/homebrew/lib")
|
||||||
cg.add_define("ESPHOME_BOARD", "host")
|
cg.add_define("ESPHOME_BOARD", "host")
|
||||||
cg.add_platformio_option("platform", "platformio/native")
|
cg.add_platformio_option("platform", "platformio/native")
|
||||||
|
|
|
@ -17,6 +17,12 @@ void HydreonRGxxComponent::dump_config() {
|
||||||
if (this->is_failed()) {
|
if (this->is_failed()) {
|
||||||
ESP_LOGE(TAG, "Connection with hydreon_rgxx failed!");
|
ESP_LOGE(TAG, "Connection with hydreon_rgxx failed!");
|
||||||
}
|
}
|
||||||
|
if (model_ == RG9) {
|
||||||
|
ESP_LOGCONFIG(TAG, " Model: RG9");
|
||||||
|
ESP_LOGCONFIG(TAG, " Disable Led: %s", TRUEFALSE(this->disable_led_));
|
||||||
|
} else {
|
||||||
|
ESP_LOGCONFIG(TAG, " Model: RG15");
|
||||||
|
}
|
||||||
LOG_UPDATE_INTERVAL(this);
|
LOG_UPDATE_INTERVAL(this);
|
||||||
|
|
||||||
int i = 0;
|
int i = 0;
|
||||||
|
@ -25,10 +31,6 @@ void HydreonRGxxComponent::dump_config() {
|
||||||
LOG_SENSOR(" ", #s, this->sensors_[i - 1]); \
|
LOG_SENSOR(" ", #s, this->sensors_[i - 1]); \
|
||||||
}
|
}
|
||||||
HYDREON_RGXX_PROTOCOL_LIST(HYDREON_RGXX_LOG_SENSOR, );
|
HYDREON_RGXX_PROTOCOL_LIST(HYDREON_RGXX_LOG_SENSOR, );
|
||||||
|
|
||||||
if (this->model_ == RG9) {
|
|
||||||
ESP_LOGCONFIG(TAG, "disable_led: %s", TRUEFALSE(this->disable_led_));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void HydreonRGxxComponent::setup() {
|
void HydreonRGxxComponent::setup() {
|
||||||
|
|
|
@ -138,6 +138,7 @@ async def to_code(config):
|
||||||
sens = await sensor.new_sensor(config[conf])
|
sens = await sensor.new_sensor(config[conf])
|
||||||
cg.add(var.set_sensor(sens, i))
|
cg.add(var.set_sensor(sens, i))
|
||||||
|
|
||||||
|
cg.add(var.set_model(config[CONF_MODEL]))
|
||||||
cg.add(var.set_request_temperature(CONF_TEMPERATURE in config))
|
cg.add(var.set_request_temperature(CONF_TEMPERATURE in config))
|
||||||
|
|
||||||
if CONF_DISABLE_LED in config:
|
if CONF_DISABLE_LED in config:
|
||||||
|
|
|
@ -20,7 +20,9 @@ DEPENDENCIES = ["i2s_audio"]
|
||||||
CONF_ADC_PIN = "adc_pin"
|
CONF_ADC_PIN = "adc_pin"
|
||||||
CONF_ADC_TYPE = "adc_type"
|
CONF_ADC_TYPE = "adc_type"
|
||||||
CONF_PDM = "pdm"
|
CONF_PDM = "pdm"
|
||||||
|
CONF_SAMPLE_RATE = "sample_rate"
|
||||||
CONF_BITS_PER_SAMPLE = "bits_per_sample"
|
CONF_BITS_PER_SAMPLE = "bits_per_sample"
|
||||||
|
CONF_USE_APLL = "use_apll"
|
||||||
|
|
||||||
I2SAudioMicrophone = i2s_audio_ns.class_(
|
I2SAudioMicrophone = i2s_audio_ns.class_(
|
||||||
"I2SAudioMicrophone", I2SAudioIn, microphone.Microphone, cg.Component
|
"I2SAudioMicrophone", I2SAudioIn, microphone.Microphone, cg.Component
|
||||||
|
@ -62,9 +64,11 @@ BASE_SCHEMA = microphone.MICROPHONE_SCHEMA.extend(
|
||||||
cv.GenerateID(): cv.declare_id(I2SAudioMicrophone),
|
cv.GenerateID(): cv.declare_id(I2SAudioMicrophone),
|
||||||
cv.GenerateID(CONF_I2S_AUDIO_ID): cv.use_id(I2SAudioComponent),
|
cv.GenerateID(CONF_I2S_AUDIO_ID): cv.use_id(I2SAudioComponent),
|
||||||
cv.Optional(CONF_CHANNEL, default="right"): cv.enum(CHANNELS),
|
cv.Optional(CONF_CHANNEL, default="right"): cv.enum(CHANNELS),
|
||||||
|
cv.Optional(CONF_SAMPLE_RATE, default=16000): cv.int_range(min=1),
|
||||||
cv.Optional(CONF_BITS_PER_SAMPLE, default="32bit"): cv.All(
|
cv.Optional(CONF_BITS_PER_SAMPLE, default="32bit"): cv.All(
|
||||||
_validate_bits, cv.enum(BITS_PER_SAMPLE)
|
_validate_bits, cv.enum(BITS_PER_SAMPLE)
|
||||||
),
|
),
|
||||||
|
cv.Optional(CONF_USE_APLL, default=False): cv.boolean,
|
||||||
}
|
}
|
||||||
).extend(cv.COMPONENT_SCHEMA)
|
).extend(cv.COMPONENT_SCHEMA)
|
||||||
|
|
||||||
|
@ -105,6 +109,8 @@ async def to_code(config):
|
||||||
cg.add(var.set_pdm(config[CONF_PDM]))
|
cg.add(var.set_pdm(config[CONF_PDM]))
|
||||||
|
|
||||||
cg.add(var.set_channel(config[CONF_CHANNEL]))
|
cg.add(var.set_channel(config[CONF_CHANNEL]))
|
||||||
|
cg.add(var.set_sample_rate(config[CONF_SAMPLE_RATE]))
|
||||||
cg.add(var.set_bits_per_sample(config[CONF_BITS_PER_SAMPLE]))
|
cg.add(var.set_bits_per_sample(config[CONF_BITS_PER_SAMPLE]))
|
||||||
|
cg.add(var.set_use_apll(config[CONF_USE_APLL]))
|
||||||
|
|
||||||
await microphone.register_microphone(var, config)
|
await microphone.register_microphone(var, config)
|
||||||
|
|
|
@ -47,14 +47,14 @@ void I2SAudioMicrophone::start_() {
|
||||||
}
|
}
|
||||||
i2s_driver_config_t config = {
|
i2s_driver_config_t config = {
|
||||||
.mode = (i2s_mode_t) (I2S_MODE_MASTER | I2S_MODE_RX),
|
.mode = (i2s_mode_t) (I2S_MODE_MASTER | I2S_MODE_RX),
|
||||||
.sample_rate = 16000,
|
.sample_rate = this->sample_rate_,
|
||||||
.bits_per_sample = this->bits_per_sample_,
|
.bits_per_sample = this->bits_per_sample_,
|
||||||
.channel_format = this->channel_,
|
.channel_format = this->channel_,
|
||||||
.communication_format = I2S_COMM_FORMAT_STAND_I2S,
|
.communication_format = I2S_COMM_FORMAT_STAND_I2S,
|
||||||
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
|
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
|
||||||
.dma_buf_count = 4,
|
.dma_buf_count = 4,
|
||||||
.dma_buf_len = 256,
|
.dma_buf_len = 256,
|
||||||
.use_apll = false,
|
.use_apll = this->use_apll_,
|
||||||
.tx_desc_auto_clear = false,
|
.tx_desc_auto_clear = false,
|
||||||
.fixed_mclk = 0,
|
.fixed_mclk = 0,
|
||||||
.mclk_multiple = I2S_MCLK_MULTIPLE_DEFAULT,
|
.mclk_multiple = I2S_MCLK_MULTIPLE_DEFAULT,
|
||||||
|
|
|
@ -31,7 +31,9 @@ class I2SAudioMicrophone : public I2SAudioIn, public microphone::Microphone, pub
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
void set_channel(i2s_channel_fmt_t channel) { this->channel_ = channel; }
|
void set_channel(i2s_channel_fmt_t channel) { this->channel_ = channel; }
|
||||||
|
void set_sample_rate(uint32_t sample_rate) { this->sample_rate_ = sample_rate; }
|
||||||
void set_bits_per_sample(i2s_bits_per_sample_t bits_per_sample) { this->bits_per_sample_ = bits_per_sample; }
|
void set_bits_per_sample(i2s_bits_per_sample_t bits_per_sample) { this->bits_per_sample_ = bits_per_sample; }
|
||||||
|
void set_use_apll(uint32_t use_apll) { this->use_apll_ = use_apll; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void start_();
|
void start_();
|
||||||
|
@ -45,7 +47,9 @@ class I2SAudioMicrophone : public I2SAudioIn, public microphone::Microphone, pub
|
||||||
#endif
|
#endif
|
||||||
bool pdm_{false};
|
bool pdm_{false};
|
||||||
i2s_channel_fmt_t channel_;
|
i2s_channel_fmt_t channel_;
|
||||||
|
uint32_t sample_rate_;
|
||||||
i2s_bits_per_sample_t bits_per_sample_;
|
i2s_bits_per_sample_t bits_per_sample_;
|
||||||
|
bool use_apll_;
|
||||||
|
|
||||||
HighFrequencyLoopRequester high_freq_;
|
HighFrequencyLoopRequester high_freq_;
|
||||||
};
|
};
|
||||||
|
|
|
@ -66,6 +66,7 @@ MODELS = {
|
||||||
"ST7789V": ili9xxx_ns.class_("ILI9XXXST7789V", ILI9XXXDisplay),
|
"ST7789V": ili9xxx_ns.class_("ILI9XXXST7789V", ILI9XXXDisplay),
|
||||||
"S3BOX": ili9xxx_ns.class_("ILI9XXXS3Box", ILI9XXXDisplay),
|
"S3BOX": ili9xxx_ns.class_("ILI9XXXS3Box", ILI9XXXDisplay),
|
||||||
"S3BOX_LITE": ili9xxx_ns.class_("ILI9XXXS3BoxLite", ILI9XXXDisplay),
|
"S3BOX_LITE": ili9xxx_ns.class_("ILI9XXXS3BoxLite", ILI9XXXDisplay),
|
||||||
|
"WAVESHARE_RES_3_5": ili9xxx_ns.class_("WAVESHARERES35", ILI9XXXDisplay),
|
||||||
}
|
}
|
||||||
|
|
||||||
COLOR_ORDERS = {
|
COLOR_ORDERS = {
|
||||||
|
|
|
@ -7,7 +7,6 @@
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace ili9xxx {
|
namespace ili9xxx {
|
||||||
|
|
||||||
static const char *const TAG = "ili9xxx";
|
|
||||||
static const uint16_t SPI_SETUP_US = 100; // estimated fixed overhead in microseconds for an SPI write
|
static const uint16_t SPI_SETUP_US = 100; // estimated fixed overhead in microseconds for an SPI write
|
||||||
static const uint16_t SPI_MAX_BLOCK_SIZE = 4092; // Max size of continuous SPI transfer
|
static const uint16_t SPI_MAX_BLOCK_SIZE = 4092; // Max size of continuous SPI transfer
|
||||||
|
|
||||||
|
@ -17,13 +16,7 @@ static inline void put16_be(uint8_t *buf, uint16_t value) {
|
||||||
buf[1] = value;
|
buf[1] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ILI9XXXDisplay::setup() {
|
void ILI9XXXDisplay::set_madctl() {
|
||||||
ESP_LOGD(TAG, "Setting up ILI9xxx");
|
|
||||||
|
|
||||||
this->setup_pins_();
|
|
||||||
this->init_lcd_();
|
|
||||||
|
|
||||||
this->command(this->pre_invertcolors_ ? ILI9XXX_INVON : ILI9XXX_INVOFF);
|
|
||||||
// custom x/y transform and color order
|
// custom x/y transform and color order
|
||||||
uint8_t mad = this->color_order_ == display::COLOR_ORDER_BGR ? MADCTL_BGR : MADCTL_RGB;
|
uint8_t mad = this->color_order_ == display::COLOR_ORDER_BGR ? MADCTL_BGR : MADCTL_RGB;
|
||||||
if (this->swap_xy_)
|
if (this->swap_xy_)
|
||||||
|
@ -32,8 +25,19 @@ void ILI9XXXDisplay::setup() {
|
||||||
mad |= MADCTL_MX;
|
mad |= MADCTL_MX;
|
||||||
if (this->mirror_y_)
|
if (this->mirror_y_)
|
||||||
mad |= MADCTL_MY;
|
mad |= MADCTL_MY;
|
||||||
this->send_command(ILI9XXX_MADCTL, &mad, 1);
|
this->command(ILI9XXX_MADCTL);
|
||||||
|
this->data(mad);
|
||||||
|
esph_log_d(TAG, "Wrote MADCTL 0x%02X", mad);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ILI9XXXDisplay::setup() {
|
||||||
|
ESP_LOGD(TAG, "Setting up ILI9xxx");
|
||||||
|
|
||||||
|
this->setup_pins_();
|
||||||
|
this->init_lcd_();
|
||||||
|
|
||||||
|
this->set_madctl();
|
||||||
|
this->command(this->pre_invertcolors_ ? ILI9XXX_INVON : ILI9XXX_INVOFF);
|
||||||
this->x_low_ = this->width_;
|
this->x_low_ = this->width_;
|
||||||
this->y_low_ = this->height_;
|
this->y_low_ = this->height_;
|
||||||
this->x_high_ = 0;
|
this->x_high_ = 0;
|
||||||
|
@ -89,6 +93,7 @@ void ILI9XXXDisplay::dump_config() {
|
||||||
LOG_PIN(" CS Pin: ", this->cs_);
|
LOG_PIN(" CS Pin: ", this->cs_);
|
||||||
LOG_PIN(" DC Pin: ", this->dc_pin_);
|
LOG_PIN(" DC Pin: ", this->dc_pin_);
|
||||||
LOG_PIN(" Busy Pin: ", this->busy_pin_);
|
LOG_PIN(" Busy Pin: ", this->busy_pin_);
|
||||||
|
ESP_LOGCONFIG(TAG, " Color order: %s", this->color_order_ == display::COLOR_ORDER_BGR ? "BGR" : "RGB");
|
||||||
ESP_LOGCONFIG(TAG, " Swap_xy: %s", YESNO(this->swap_xy_));
|
ESP_LOGCONFIG(TAG, " Swap_xy: %s", YESNO(this->swap_xy_));
|
||||||
ESP_LOGCONFIG(TAG, " Mirror_x: %s", YESNO(this->mirror_x_));
|
ESP_LOGCONFIG(TAG, " Mirror_x: %s", YESNO(this->mirror_x_));
|
||||||
ESP_LOGCONFIG(TAG, " Mirror_y: %s", YESNO(this->mirror_y_));
|
ESP_LOGCONFIG(TAG, " Mirror_y: %s", YESNO(this->mirror_y_));
|
||||||
|
@ -196,7 +201,6 @@ void ILI9XXXDisplay::display_() {
|
||||||
uint8_t transfer_buffer[ILI9XXX_TRANSFER_BUFFER_SIZE];
|
uint8_t transfer_buffer[ILI9XXX_TRANSFER_BUFFER_SIZE];
|
||||||
// check if something was displayed
|
// check if something was displayed
|
||||||
if ((this->x_high_ < this->x_low_) || (this->y_high_ < this->y_low_)) {
|
if ((this->x_high_ < this->x_low_) || (this->y_high_ < this->y_low_)) {
|
||||||
ESP_LOGV(TAG, "Nothing to display");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -211,14 +215,13 @@ void ILI9XXXDisplay::display_() {
|
||||||
size_t mw_time = (w * h * 16) / mhz + w * h * 2 / ILI9XXX_TRANSFER_BUFFER_SIZE * SPI_SETUP_US;
|
size_t mw_time = (w * h * 16) / mhz + w * h * 2 / ILI9XXX_TRANSFER_BUFFER_SIZE * SPI_SETUP_US;
|
||||||
ESP_LOGV(TAG,
|
ESP_LOGV(TAG,
|
||||||
"Start display(xlow:%d, ylow:%d, xhigh:%d, yhigh:%d, width:%d, "
|
"Start display(xlow:%d, ylow:%d, xhigh:%d, yhigh:%d, width:%d, "
|
||||||
"height:%d, mode=%d, 18bit=%d, sw_time=%dus, mw_time=%dus)",
|
"height:%zu, mode=%d, 18bit=%d, sw_time=%zuus, mw_time=%zuus)",
|
||||||
this->x_low_, this->y_low_, this->x_high_, this->y_high_, w, h, this->buffer_color_mode_,
|
this->x_low_, this->y_low_, this->x_high_, this->y_high_, w, h, this->buffer_color_mode_,
|
||||||
this->is_18bitdisplay_, sw_time, mw_time);
|
this->is_18bitdisplay_, sw_time, mw_time);
|
||||||
auto now = millis();
|
auto now = millis();
|
||||||
this->enable();
|
|
||||||
if (this->buffer_color_mode_ == BITS_16 && !this->is_18bitdisplay_ && sw_time < mw_time) {
|
if (this->buffer_color_mode_ == BITS_16 && !this->is_18bitdisplay_ && sw_time < mw_time) {
|
||||||
// 16 bit mode maps directly to display format
|
// 16 bit mode maps directly to display format
|
||||||
ESP_LOGV(TAG, "Doing single write of %d bytes", this->width_ * h * 2);
|
ESP_LOGV(TAG, "Doing single write of %zu bytes", this->width_ * h * 2);
|
||||||
set_addr_window_(0, this->y_low_, this->width_ - 1, this->y_high_);
|
set_addr_window_(0, this->y_low_, this->width_ - 1, this->y_high_);
|
||||||
this->write_array(this->buffer_ + this->y_low_ * this->width_ * 2, h * this->width_ * 2);
|
this->write_array(this->buffer_ + this->y_low_ * this->width_ * 2, h * this->width_ * 2);
|
||||||
} else {
|
} else {
|
||||||
|
@ -267,7 +270,7 @@ void ILI9XXXDisplay::display_() {
|
||||||
this->write_array(transfer_buffer, idx);
|
this->write_array(transfer_buffer, idx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this->disable();
|
this->end_data_();
|
||||||
ESP_LOGV(TAG, "Data write took %dms", (unsigned) (millis() - now));
|
ESP_LOGV(TAG, "Data write took %dms", (unsigned) (millis() - now));
|
||||||
// invalidate watermarks
|
// invalidate watermarks
|
||||||
this->x_low_ = this->width_;
|
this->x_low_ = this->width_;
|
||||||
|
@ -290,7 +293,6 @@ void ILI9XXXDisplay::draw_pixels_at(int x_start, int y_start, int w, int h, cons
|
||||||
return display::Display::draw_pixels_at(x_start, y_start, w, h, ptr, order, bitness, big_endian, x_offset, y_offset,
|
return display::Display::draw_pixels_at(x_start, y_start, w, h, ptr, order, bitness, big_endian, x_offset, y_offset,
|
||||||
x_pad);
|
x_pad);
|
||||||
}
|
}
|
||||||
this->enable();
|
|
||||||
this->set_addr_window_(x_start, y_start, x_start + w - 1, y_start + h - 1);
|
this->set_addr_window_(x_start, y_start, x_start + w - 1, y_start + h - 1);
|
||||||
// x_ and y_offset are offsets into the source buffer, unrelated to our own offsets into the display.
|
// x_ and y_offset are offsets into the source buffer, unrelated to our own offsets into the display.
|
||||||
if (x_offset == 0 && x_pad == 0 && y_offset == 0) {
|
if (x_offset == 0 && x_pad == 0 && y_offset == 0) {
|
||||||
|
@ -302,7 +304,7 @@ void ILI9XXXDisplay::draw_pixels_at(int x_start, int y_start, int w, int h, cons
|
||||||
this->write_array(ptr + (y + y_offset) * stride + x_offset, w * 2);
|
this->write_array(ptr + (y + y_offset) * stride + x_offset, w * 2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this->disable();
|
this->end_data_();
|
||||||
}
|
}
|
||||||
|
|
||||||
// should return the total size: return this->get_width_internal() * this->get_height_internal() * 2 // 16bit color
|
// should return the total size: return this->get_width_internal() * this->get_height_internal() * 2 // 16bit color
|
||||||
|
@ -328,20 +330,6 @@ void ILI9XXXDisplay::send_command(uint8_t command_byte, const uint8_t *data_byte
|
||||||
this->end_data_();
|
this->end_data_();
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t ILI9XXXDisplay::read_command(uint8_t command_byte, uint8_t index) {
|
|
||||||
uint8_t data = 0x10 + index;
|
|
||||||
this->send_command(0xD9, &data, 1); // Set Index Register
|
|
||||||
uint8_t result;
|
|
||||||
this->start_command_();
|
|
||||||
this->write_byte(command_byte);
|
|
||||||
this->start_data_();
|
|
||||||
do {
|
|
||||||
result = this->read_byte();
|
|
||||||
} while (index--);
|
|
||||||
this->end_data_();
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ILI9XXXDisplay::start_command_() {
|
void ILI9XXXDisplay::start_command_() {
|
||||||
this->dc_pin_->digital_write(false);
|
this->dc_pin_->digital_write(false);
|
||||||
this->enable();
|
this->enable();
|
||||||
|
@ -357,9 +345,9 @@ void ILI9XXXDisplay::end_data_() { this->disable(); }
|
||||||
void ILI9XXXDisplay::reset_() {
|
void ILI9XXXDisplay::reset_() {
|
||||||
if (this->reset_pin_ != nullptr) {
|
if (this->reset_pin_ != nullptr) {
|
||||||
this->reset_pin_->digital_write(false);
|
this->reset_pin_->digital_write(false);
|
||||||
delay(10);
|
delay(20);
|
||||||
this->reset_pin_->digital_write(true);
|
this->reset_pin_->digital_write(true);
|
||||||
delay(10);
|
delay(20);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -369,7 +357,7 @@ void ILI9XXXDisplay::init_lcd_() {
|
||||||
while ((cmd = *addr++) > 0) {
|
while ((cmd = *addr++) > 0) {
|
||||||
x = *addr++;
|
x = *addr++;
|
||||||
num_args = x & 0x7F;
|
num_args = x & 0x7F;
|
||||||
send_command(cmd, addr, num_args);
|
this->send_command(cmd, addr, num_args);
|
||||||
addr += num_args;
|
addr += num_args;
|
||||||
if (x & 0x80)
|
if (x & 0x80)
|
||||||
delay(150); // NOLINT
|
delay(150); // NOLINT
|
||||||
|
@ -377,24 +365,19 @@ void ILI9XXXDisplay::init_lcd_() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tell the display controller where we want to draw pixels.
|
// Tell the display controller where we want to draw pixels.
|
||||||
// when called, the SPI should have already been enabled, only the D/C pin will be toggled here.
|
|
||||||
void ILI9XXXDisplay::set_addr_window_(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2) {
|
void ILI9XXXDisplay::set_addr_window_(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2) {
|
||||||
uint8_t buf[4];
|
this->command(ILI9XXX_CASET);
|
||||||
this->dc_pin_->digital_write(false);
|
this->data(x1 >> 8);
|
||||||
this->write_byte(ILI9XXX_CASET); // Column address set
|
this->data(x1 & 0xFF);
|
||||||
put16_be(buf, x1 + this->offset_x_);
|
this->data(x2 >> 8);
|
||||||
put16_be(buf + 2, x2 + this->offset_x_);
|
this->data(x2 & 0xFF);
|
||||||
this->dc_pin_->digital_write(true);
|
this->command(ILI9XXX_PASET); // Page address set
|
||||||
this->write_array(buf, sizeof buf);
|
this->data(y1 >> 8);
|
||||||
this->dc_pin_->digital_write(false);
|
this->data(y1 & 0xFF);
|
||||||
this->write_byte(ILI9XXX_PASET); // Row address set
|
this->data(y2 >> 8);
|
||||||
put16_be(buf, y1 + this->offset_y_);
|
this->data(y2 & 0xFF);
|
||||||
put16_be(buf + 2, y2 + this->offset_y_);
|
this->command(ILI9XXX_RAMWR); // Write to RAM
|
||||||
this->dc_pin_->digital_write(true);
|
this->start_data_();
|
||||||
this->write_array(buf, sizeof buf);
|
|
||||||
this->dc_pin_->digital_write(false);
|
|
||||||
this->write_byte(ILI9XXX_RAMWR); // Write to RAM
|
|
||||||
this->dc_pin_->digital_write(true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ILI9XXXDisplay::invert_colors(bool invert) {
|
void ILI9XXXDisplay::invert_colors(bool invert) {
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace ili9xxx {
|
namespace ili9xxx {
|
||||||
|
|
||||||
|
static const char *const TAG = "ili9xxx";
|
||||||
const size_t ILI9XXX_TRANSFER_BUFFER_SIZE = 126; // ensure this is divisible by 6
|
const size_t ILI9XXX_TRANSFER_BUFFER_SIZE = 126; // ensure this is divisible by 6
|
||||||
|
|
||||||
enum ILI9XXXColorMode {
|
enum ILI9XXXColorMode {
|
||||||
|
@ -32,6 +33,7 @@ class ILI9XXXDisplay : public display::DisplayBuffer,
|
||||||
while ((cmd = *addr++) != 0) {
|
while ((cmd = *addr++) != 0) {
|
||||||
num_args = *addr++ & 0x7F;
|
num_args = *addr++ & 0x7F;
|
||||||
bits = *addr;
|
bits = *addr;
|
||||||
|
esph_log_d(TAG, "Command %02X, length %d, bits %02X", cmd, num_args, bits);
|
||||||
switch (cmd) {
|
switch (cmd) {
|
||||||
case ILI9XXX_MADCTL: {
|
case ILI9XXX_MADCTL: {
|
||||||
this->swap_xy_ = (bits & MADCTL_MV) != 0;
|
this->swap_xy_ = (bits & MADCTL_MV) != 0;
|
||||||
|
@ -68,10 +70,9 @@ class ILI9XXXDisplay : public display::DisplayBuffer,
|
||||||
this->offset_y_ = offset_y;
|
this->offset_y_ = offset_y;
|
||||||
}
|
}
|
||||||
void invert_colors(bool invert);
|
void invert_colors(bool invert);
|
||||||
void command(uint8_t value);
|
virtual void command(uint8_t value);
|
||||||
void data(uint8_t value);
|
virtual void data(uint8_t value);
|
||||||
void send_command(uint8_t command_byte, const uint8_t *data_bytes, uint8_t num_data_bytes);
|
void send_command(uint8_t command_byte, const uint8_t *data_bytes, uint8_t num_data_bytes);
|
||||||
uint8_t read_command(uint8_t command_byte, uint8_t index);
|
|
||||||
void set_color_order(display::ColorOrder color_order) { this->color_order_ = color_order; }
|
void set_color_order(display::ColorOrder color_order) { this->color_order_ = color_order; }
|
||||||
void set_swap_xy(bool swap_xy) { this->swap_xy_ = swap_xy; }
|
void set_swap_xy(bool swap_xy) { this->swap_xy_ = swap_xy; }
|
||||||
void set_mirror_x(bool mirror_x) { this->mirror_x_ = mirror_x; }
|
void set_mirror_x(bool mirror_x) { this->mirror_x_ = mirror_x; }
|
||||||
|
@ -92,6 +93,7 @@ class ILI9XXXDisplay : public display::DisplayBuffer,
|
||||||
void draw_absolute_pixel_internal(int x, int y, Color color) override;
|
void draw_absolute_pixel_internal(int x, int y, Color color) override;
|
||||||
void setup_pins_();
|
void setup_pins_();
|
||||||
|
|
||||||
|
virtual void set_madctl();
|
||||||
void display_();
|
void display_();
|
||||||
void init_lcd_();
|
void init_lcd_();
|
||||||
void set_addr_window_(uint16_t x, uint16_t y, uint16_t x2, uint16_t y2);
|
void set_addr_window_(uint16_t x, uint16_t y, uint16_t x2, uint16_t y2);
|
||||||
|
@ -127,7 +129,7 @@ class ILI9XXXDisplay : public display::DisplayBuffer,
|
||||||
bool need_update_ = false;
|
bool need_update_ = false;
|
||||||
bool is_18bitdisplay_ = false;
|
bool is_18bitdisplay_ = false;
|
||||||
bool pre_invertcolors_ = false;
|
bool pre_invertcolors_ = false;
|
||||||
display::ColorOrder color_order_{};
|
display::ColorOrder color_order_{display::COLOR_ORDER_BGR};
|
||||||
bool swap_xy_{};
|
bool swap_xy_{};
|
||||||
bool mirror_x_{};
|
bool mirror_x_{};
|
||||||
bool mirror_y_{};
|
bool mirror_y_{};
|
||||||
|
@ -181,10 +183,48 @@ class ILI9XXXILI9486 : public ILI9XXXDisplay {
|
||||||
ILI9XXXILI9486() : ILI9XXXDisplay(INITCMD_ILI9486, 480, 320, false) {}
|
ILI9XXXILI9486() : ILI9XXXDisplay(INITCMD_ILI9486, 480, 320, false) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
//----------- ILI9XXX_35_TFT rotated display --------------
|
|
||||||
class ILI9XXXILI9488 : public ILI9XXXDisplay {
|
class ILI9XXXILI9488 : public ILI9XXXDisplay {
|
||||||
public:
|
public:
|
||||||
ILI9XXXILI9488() : ILI9XXXDisplay(INITCMD_ILI9488, 480, 320, true) {}
|
ILI9XXXILI9488(const uint8_t *seq = INITCMD_ILI9488) : ILI9XXXDisplay(seq, 480, 320, true) {}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void set_madctl() override {
|
||||||
|
uint8_t mad = this->color_order_ == display::COLOR_ORDER_BGR ? MADCTL_BGR : MADCTL_RGB;
|
||||||
|
uint8_t dfun = 0x22;
|
||||||
|
this->width_ = 320;
|
||||||
|
this->height_ = 480;
|
||||||
|
if (!(this->swap_xy_ || this->mirror_x_ || this->mirror_y_)) {
|
||||||
|
// no transforms
|
||||||
|
} else if (this->mirror_y_ && this->mirror_x_) {
|
||||||
|
// rotate 180
|
||||||
|
dfun = 0x42;
|
||||||
|
} else if (this->swap_xy_) {
|
||||||
|
this->width_ = 480;
|
||||||
|
this->height_ = 320;
|
||||||
|
mad |= 0x20;
|
||||||
|
if (this->mirror_x_) {
|
||||||
|
dfun = 0x02;
|
||||||
|
} else {
|
||||||
|
dfun = 0x62;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this->command(ILI9XXX_DFUNCTR);
|
||||||
|
this->data(0);
|
||||||
|
this->data(dfun);
|
||||||
|
this->command(ILI9XXX_MADCTL);
|
||||||
|
this->data(mad);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
//----------- Waveshare 3.5 Res Touch - ILI9488 interfaced via 16 bit shift register to parallel */
|
||||||
|
class WAVESHARERES35 : public ILI9XXXILI9488 {
|
||||||
|
public:
|
||||||
|
WAVESHARERES35() : ILI9XXXILI9488(INITCMD_WAVESHARE_RES_3_5) {}
|
||||||
|
void data(uint8_t value) override {
|
||||||
|
this->start_data_();
|
||||||
|
this->write_byte(0);
|
||||||
|
this->write_byte(value);
|
||||||
|
this->end_data_();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
//----------- ILI9XXX_35_TFT origin colors rotated display --------------
|
//----------- ILI9XXX_35_TFT origin colors rotated display --------------
|
||||||
|
|
|
@ -141,7 +141,8 @@ static const uint8_t PROGMEM INITCMD_ILI9486[] = {
|
||||||
0x00 // End of list
|
0x00 // End of list
|
||||||
};
|
};
|
||||||
|
|
||||||
static const uint8_t PROGMEM INITCMD_ILI9488[] = {
|
|
||||||
|
static const uint8_t INITCMD_ILI9488[] = {
|
||||||
ILI9XXX_GMCTRP1,15, 0x0f, 0x24, 0x1c, 0x0a, 0x0f, 0x08, 0x43, 0x88, 0x32, 0x0f, 0x10, 0x06, 0x0f, 0x07, 0x00,
|
ILI9XXX_GMCTRP1,15, 0x0f, 0x24, 0x1c, 0x0a, 0x0f, 0x08, 0x43, 0x88, 0x32, 0x0f, 0x10, 0x06, 0x0f, 0x07, 0x00,
|
||||||
ILI9XXX_GMCTRN1,15, 0x0F, 0x38, 0x30, 0x09, 0x0f, 0x0f, 0x4e, 0x77, 0x3c, 0x07, 0x10, 0x05, 0x23, 0x1b, 0x00,
|
ILI9XXX_GMCTRN1,15, 0x0F, 0x38, 0x30, 0x09, 0x0f, 0x0f, 0x4e, 0x77, 0x3c, 0x07, 0x10, 0x05, 0x23, 0x1b, 0x00,
|
||||||
|
|
||||||
|
@ -153,28 +154,27 @@ static const uint8_t PROGMEM INITCMD_ILI9488[] = {
|
||||||
ILI9XXX_FRMCTR1, 1, 0xA0, // Frame rate = 60Hz
|
ILI9XXX_FRMCTR1, 1, 0xA0, // Frame rate = 60Hz
|
||||||
ILI9XXX_INVCTR, 1, 0x02, // Display Inversion Control = 2dot
|
ILI9XXX_INVCTR, 1, 0x02, // Display Inversion Control = 2dot
|
||||||
|
|
||||||
ILI9XXX_DFUNCTR, 2, 0x02, 0x02, // Nomal scan
|
|
||||||
|
|
||||||
0xE9, 1, 0x00, // Set Image Functio. Disable 24 bit data
|
0xE9, 1, 0x00, // Set Image Functio. Disable 24 bit data
|
||||||
|
|
||||||
ILI9XXX_ADJCTL3, 4, 0xA9, 0x51, 0x2C, 0x82, // Adjust Control 3
|
ILI9XXX_ADJCTL3, 4, 0xA9, 0x51, 0x2C, 0x82, // Adjust Control 3
|
||||||
|
|
||||||
ILI9XXX_MADCTL, 1, 0x28,
|
|
||||||
//ILI9XXX_PIXFMT, 1, 0x55, // Interface Pixel Format = 16bit
|
|
||||||
ILI9XXX_PIXFMT, 1, 0x66, //ILI9488 only supports 18-bit pixel format in 4/3 wire SPI mode
|
ILI9XXX_PIXFMT, 1, 0x66, //ILI9488 only supports 18-bit pixel format in 4/3 wire SPI mode
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 5 frames
|
|
||||||
//ILI9XXX_ETMOD, 1, 0xC6, //
|
|
||||||
|
|
||||||
|
|
||||||
ILI9XXX_SLPOUT, 0x80, // Exit sleep mode
|
ILI9XXX_SLPOUT, 0x80, // Exit sleep mode
|
||||||
//ILI9XXX_INVON , 0,
|
|
||||||
ILI9XXX_DISPON, 0x80, // Set display on
|
ILI9XXX_DISPON, 0x80, // Set display on
|
||||||
0x00 // end
|
0x00 // end
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static const uint8_t INITCMD_WAVESHARE_RES_3_5[] = {
|
||||||
|
ILI9XXX_PWCTR3, 1, 0x33,
|
||||||
|
ILI9XXX_VMCTR1, 3, 0x00, 0x1e, 0x80,
|
||||||
|
ILI9XXX_FRMCTR1, 1, 0xA0,
|
||||||
|
ILI9XXX_GMCTRP1, 15, 0x0, 0x13, 0x18, 0x04, 0x0F, 0x06, 0x3a, 0x56, 0x4d, 0x03, 0x0a, 0x06, 0x30, 0x3e, 0x0f,
|
||||||
|
ILI9XXX_GMCTRN1, 15, 0x0, 0x13, 0x18, 0x01, 0x11, 0x06, 0x38, 0x34, 0x4d, 0x06, 0x0d, 0x0b, 0x31, 0x37, 0x0f,
|
||||||
|
ILI9XXX_PIXFMT, 1, 0x55,
|
||||||
|
ILI9XXX_SLPOUT, 0x80, // slpout, delay
|
||||||
|
ILI9XXX_DISPON, 0,
|
||||||
|
0x00 // End of list
|
||||||
|
};
|
||||||
|
|
||||||
static const uint8_t PROGMEM INITCMD_ILI9488_A[] = {
|
static const uint8_t PROGMEM INITCMD_ILI9488_A[] = {
|
||||||
ILI9XXX_GMCTRP1,15, 0x00, 0x03, 0x09, 0x08, 0x16, 0x0A, 0x3F, 0x78, 0x4C, 0x09, 0x0A, 0x08, 0x16, 0x1A, 0x0F,
|
ILI9XXX_GMCTRP1,15, 0x00, 0x03, 0x09, 0x08, 0x16, 0x0A, 0x3F, 0x78, 0x4C, 0x09, 0x0A, 0x08, 0x16, 0x1A, 0x0F,
|
||||||
ILI9XXX_GMCTRN1,15, 0x00, 0x16, 0x19, 0x03, 0x0F, 0x05, 0x32, 0x45, 0x46, 0x04, 0x0E, 0x0D, 0x35, 0x37, 0x0F,
|
ILI9XXX_GMCTRN1,15, 0x00, 0x16, 0x19, 0x03, 0x0F, 0x05, 0x32, 0x45, 0x46, 0x04, 0x0E, 0x0D, 0x35, 0x37, 0x0F,
|
||||||
|
|
|
@ -37,6 +37,7 @@ class Image : public display::BaseImage {
|
||||||
Color get_pixel(int x, int y, Color color_on = display::COLOR_ON, Color color_off = display::COLOR_OFF) const;
|
Color get_pixel(int x, int y, Color color_on = display::COLOR_ON, Color color_off = display::COLOR_OFF) const;
|
||||||
int get_width() const override;
|
int get_width() const override;
|
||||||
int get_height() const override;
|
int get_height() const override;
|
||||||
|
const uint8_t *get_data_start() { return this->data_start_; }
|
||||||
ImageType get_type() const;
|
ImageType get_type() const;
|
||||||
|
|
||||||
void draw(int x, int y, display::Display *display, Color color_on, Color color_off) override;
|
void draw(int x, int y, display::Display *display, Color color_on, Color color_off) override;
|
||||||
|
|
|
@ -52,7 +52,7 @@ optional<uint8_t> ImprovSerialComponent::read_byte_() {
|
||||||
size_t available;
|
size_t available;
|
||||||
uart_get_buffered_data_len(this->uart_num_, &available);
|
uart_get_buffered_data_len(this->uart_num_, &available);
|
||||||
if (available) {
|
if (available) {
|
||||||
uart_read_bytes(this->uart_num_, &data, 1, 20 / portTICK_PERIOD_MS);
|
uart_read_bytes(this->uart_num_, &data, 1, 0);
|
||||||
byte = data;
|
byte = data;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -71,7 +71,7 @@ optional<uint8_t> ImprovSerialComponent::read_byte_() {
|
||||||
#endif // USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3
|
#endif // USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3
|
||||||
#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32S3)
|
#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32S3)
|
||||||
case logger::UART_SELECTION_USB_SERIAL_JTAG: {
|
case logger::UART_SELECTION_USB_SERIAL_JTAG: {
|
||||||
if (usb_serial_jtag_read_bytes((char *) &data, 1, 20 / portTICK_PERIOD_MS)) {
|
if (usb_serial_jtag_read_bytes((char *) &data, 1, 0)) {
|
||||||
byte = data;
|
byte = data;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -55,6 +55,9 @@ void Inkplate6::setup() {
|
||||||
this->wakeup_pin_->digital_write(false);
|
this->wakeup_pin_->digital_write(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allocate buffers. May be called after setup to re-initialise if e.g. greyscale is changed.
|
||||||
|
*/
|
||||||
void Inkplate6::initialize_() {
|
void Inkplate6::initialize_() {
|
||||||
ExternalRAMAllocator<uint8_t> allocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE);
|
ExternalRAMAllocator<uint8_t> allocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE);
|
||||||
ExternalRAMAllocator<uint32_t> allocator32(ExternalRAMAllocator<uint32_t>::ALLOW_FAILURE);
|
ExternalRAMAllocator<uint32_t> allocator32(ExternalRAMAllocator<uint32_t>::ALLOW_FAILURE);
|
||||||
|
|
|
@ -68,8 +68,9 @@ class Inkplate6 : public display::DisplayBuffer, public i2c::I2CDevice {
|
||||||
|
|
||||||
void set_greyscale(bool greyscale) {
|
void set_greyscale(bool greyscale) {
|
||||||
this->greyscale_ = greyscale;
|
this->greyscale_ = greyscale;
|
||||||
this->initialize_();
|
|
||||||
this->block_partial_ = true;
|
this->block_partial_ = true;
|
||||||
|
if (this->is_ready())
|
||||||
|
this->initialize_();
|
||||||
}
|
}
|
||||||
void set_partial_updating(bool partial_updating) { this->partial_updating_ = partial_updating; }
|
void set_partial_updating(bool partial_updating) { this->partial_updating_ = partial_updating; }
|
||||||
void set_full_update_every(uint32_t full_update_every) { this->full_update_every_ = full_update_every; }
|
void set_full_update_every(uint32_t full_update_every) { this->full_update_every_ = full_update_every; }
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
CODEOWNERS = ["@Cat-Ion"]
|
|
|
@ -1,82 +0,0 @@
|
||||||
#include "kalman_combinator.h"
|
|
||||||
#include "esphome/core/hal.h"
|
|
||||||
#include <cmath>
|
|
||||||
#include <functional>
|
|
||||||
|
|
||||||
namespace esphome {
|
|
||||||
namespace kalman_combinator {
|
|
||||||
|
|
||||||
void KalmanCombinatorComponent::dump_config() {
|
|
||||||
ESP_LOGCONFIG("kalman_combinator", "Kalman Combinator:");
|
|
||||||
ESP_LOGCONFIG("kalman_combinator", " Update variance: %f per ms", this->update_variance_value_);
|
|
||||||
ESP_LOGCONFIG("kalman_combinator", " Sensors:");
|
|
||||||
for (const auto &sensor : this->sensors_) {
|
|
||||||
auto &entity = *sensor.first;
|
|
||||||
ESP_LOGCONFIG("kalman_combinator", " - %s", entity.get_name().c_str());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void KalmanCombinatorComponent::setup() {
|
|
||||||
for (const auto &sensor : this->sensors_) {
|
|
||||||
const auto stddev = sensor.second;
|
|
||||||
sensor.first->add_on_state_callback([this, stddev](float x) -> void { this->correct_(x, stddev(x)); });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void KalmanCombinatorComponent::add_source(Sensor *sensor, std::function<float(float)> const &stddev) {
|
|
||||||
this->sensors_.emplace_back(sensor, stddev);
|
|
||||||
}
|
|
||||||
|
|
||||||
void KalmanCombinatorComponent::add_source(Sensor *sensor, float stddev) {
|
|
||||||
this->add_source(sensor, std::function<float(float)>{[stddev](float x) -> float { return stddev; }});
|
|
||||||
}
|
|
||||||
|
|
||||||
void KalmanCombinatorComponent::update_variance_() {
|
|
||||||
uint32_t now = millis();
|
|
||||||
|
|
||||||
// Variance increases by update_variance_ each millisecond
|
|
||||||
auto dt = now - this->last_update_;
|
|
||||||
auto dv = this->update_variance_value_ * dt;
|
|
||||||
this->variance_ += dv;
|
|
||||||
this->last_update_ = now;
|
|
||||||
}
|
|
||||||
|
|
||||||
void KalmanCombinatorComponent::correct_(float value, float stddev) {
|
|
||||||
if (std::isnan(value) || std::isinf(stddev)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (std::isnan(this->state_) || std::isinf(this->variance_)) {
|
|
||||||
this->state_ = value;
|
|
||||||
this->variance_ = stddev * stddev;
|
|
||||||
if (this->std_dev_sensor_ != nullptr) {
|
|
||||||
this->std_dev_sensor_->publish_state(stddev);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this->update_variance_();
|
|
||||||
|
|
||||||
// Combine two gaussian distributions mu1+-var1, mu2+-var2 to a new one around mu
|
|
||||||
// Use the value with the smaller variance as mu1 to prevent precision errors
|
|
||||||
const bool this_first = this->variance_ < (stddev * stddev);
|
|
||||||
const float mu1 = this_first ? this->state_ : value;
|
|
||||||
const float mu2 = this_first ? value : this->state_;
|
|
||||||
|
|
||||||
const float var1 = this_first ? this->variance_ : stddev * stddev;
|
|
||||||
const float var2 = this_first ? stddev * stddev : this->variance_;
|
|
||||||
|
|
||||||
const float mu = mu1 + var1 * (mu2 - mu1) / (var1 + var2);
|
|
||||||
const float var = var1 - (var1 * var1) / (var1 + var2);
|
|
||||||
|
|
||||||
// Update and publish state
|
|
||||||
this->state_ = mu;
|
|
||||||
this->variance_ = var;
|
|
||||||
|
|
||||||
this->publish_state(mu);
|
|
||||||
if (this->std_dev_sensor_ != nullptr) {
|
|
||||||
this->std_dev_sensor_->publish_state(std::sqrt(var));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} // namespace kalman_combinator
|
|
||||||
} // namespace esphome
|
|
|
@ -1,46 +0,0 @@
|
||||||
#pragma once
|
|
||||||
#include "esphome/core/component.h"
|
|
||||||
#include "esphome/components/sensor/sensor.h"
|
|
||||||
#include <cmath>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace esphome {
|
|
||||||
namespace kalman_combinator {
|
|
||||||
|
|
||||||
class KalmanCombinatorComponent : public Component, public sensor::Sensor {
|
|
||||||
public:
|
|
||||||
KalmanCombinatorComponent() = default;
|
|
||||||
|
|
||||||
float get_setup_priority() const override { return esphome::setup_priority::DATA; }
|
|
||||||
|
|
||||||
void dump_config() override;
|
|
||||||
void setup() override;
|
|
||||||
|
|
||||||
void add_source(Sensor *sensor, std::function<float(float)> const &stddev);
|
|
||||||
void add_source(Sensor *sensor, float stddev);
|
|
||||||
void set_process_std_dev(float process_std_dev) {
|
|
||||||
this->update_variance_value_ = process_std_dev * process_std_dev * 0.001f;
|
|
||||||
}
|
|
||||||
void set_std_dev_sensor(Sensor *sensor) { this->std_dev_sensor_ = sensor; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
void update_variance_();
|
|
||||||
void correct_(float value, float stddev);
|
|
||||||
|
|
||||||
// Source sensors and their error functions
|
|
||||||
std::vector<std::pair<Sensor *, std::function<float(float)>>> sensors_;
|
|
||||||
|
|
||||||
// Optional sensor for publishing the current error
|
|
||||||
sensor::Sensor *std_dev_sensor_{nullptr};
|
|
||||||
|
|
||||||
// Tick of the last update
|
|
||||||
uint32_t last_update_{0};
|
|
||||||
// Change of the variance, per ms
|
|
||||||
float update_variance_value_{0.f};
|
|
||||||
|
|
||||||
// Best guess for the state and its variance
|
|
||||||
float state_{NAN};
|
|
||||||
float variance_{INFINITY};
|
|
||||||
};
|
|
||||||
} // namespace kalman_combinator
|
|
||||||
} // namespace esphome
|
|
|
@ -1,90 +1,6 @@
|
||||||
import esphome.codegen as cg
|
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.components import sensor
|
|
||||||
from esphome.const import (
|
CONFIG_SCHEMA = CONFIG_SCHEMA = cv.invalid(
|
||||||
CONF_ID,
|
"The kalman_combinator sensor has moved.\nPlease use the combination platform instead with type: kalman.\n"
|
||||||
CONF_SOURCE,
|
"See https://esphome.io/components/sensor/combination.html"
|
||||||
CONF_ACCURACY_DECIMALS,
|
|
||||||
CONF_DEVICE_CLASS,
|
|
||||||
CONF_ENTITY_CATEGORY,
|
|
||||||
CONF_ICON,
|
|
||||||
CONF_UNIT_OF_MEASUREMENT,
|
|
||||||
)
|
)
|
||||||
from esphome.core.entity_helpers import inherit_property_from
|
|
||||||
|
|
||||||
kalman_combinator_ns = cg.esphome_ns.namespace("kalman_combinator")
|
|
||||||
KalmanCombinatorComponent = kalman_combinator_ns.class_(
|
|
||||||
"KalmanCombinatorComponent", cg.Component, sensor.Sensor
|
|
||||||
)
|
|
||||||
|
|
||||||
CONF_ERROR = "error"
|
|
||||||
CONF_SOURCES = "sources"
|
|
||||||
CONF_PROCESS_STD_DEV = "process_std_dev"
|
|
||||||
CONF_STD_DEV = "std_dev"
|
|
||||||
|
|
||||||
|
|
||||||
CONFIG_SCHEMA = (
|
|
||||||
sensor.sensor_schema(KalmanCombinatorComponent)
|
|
||||||
.extend(cv.COMPONENT_SCHEMA)
|
|
||||||
.extend(
|
|
||||||
{
|
|
||||||
cv.Required(CONF_PROCESS_STD_DEV): cv.positive_float,
|
|
||||||
cv.Required(CONF_SOURCES): cv.ensure_list(
|
|
||||||
cv.Schema(
|
|
||||||
{
|
|
||||||
cv.Required(CONF_SOURCE): cv.use_id(sensor.Sensor),
|
|
||||||
cv.Required(CONF_ERROR): cv.templatable(cv.positive_float),
|
|
||||||
}
|
|
||||||
),
|
|
||||||
),
|
|
||||||
cv.Optional(CONF_STD_DEV): sensor.sensor_schema(),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
# Inherit some sensor values from the first source, for both the state and the error value
|
|
||||||
properties_to_inherit = [
|
|
||||||
CONF_ACCURACY_DECIMALS,
|
|
||||||
CONF_DEVICE_CLASS,
|
|
||||||
CONF_ENTITY_CATEGORY,
|
|
||||||
CONF_ICON,
|
|
||||||
CONF_UNIT_OF_MEASUREMENT,
|
|
||||||
# CONF_STATE_CLASS could also be inherited, but might lead to unexpected behaviour with "total_increasing"
|
|
||||||
]
|
|
||||||
inherit_schema_for_state = [
|
|
||||||
inherit_property_from(property, [CONF_SOURCES, 0, CONF_SOURCE])
|
|
||||||
for property in properties_to_inherit
|
|
||||||
]
|
|
||||||
inherit_schema_for_std_dev = [
|
|
||||||
inherit_property_from([CONF_STD_DEV, property], [CONF_SOURCES, 0, CONF_SOURCE])
|
|
||||||
for property in properties_to_inherit
|
|
||||||
]
|
|
||||||
|
|
||||||
FINAL_VALIDATE_SCHEMA = cv.All(
|
|
||||||
CONFIG_SCHEMA.extend(
|
|
||||||
{cv.Required(CONF_ID): cv.use_id(KalmanCombinatorComponent)},
|
|
||||||
extra=cv.ALLOW_EXTRA,
|
|
||||||
),
|
|
||||||
*inherit_schema_for_state,
|
|
||||||
*inherit_schema_for_std_dev,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
async def to_code(config):
|
|
||||||
var = cg.new_Pvariable(config[CONF_ID])
|
|
||||||
await cg.register_component(var, config)
|
|
||||||
await sensor.register_sensor(var, config)
|
|
||||||
|
|
||||||
cg.add(var.set_process_std_dev(config[CONF_PROCESS_STD_DEV]))
|
|
||||||
for source_conf in config[CONF_SOURCES]:
|
|
||||||
source = await cg.get_variable(source_conf[CONF_SOURCE])
|
|
||||||
error = await cg.templatable(
|
|
||||||
source_conf[CONF_ERROR],
|
|
||||||
[(float, "x")],
|
|
||||||
cg.float_,
|
|
||||||
)
|
|
||||||
cg.add(var.add_source(source, error))
|
|
||||||
|
|
||||||
if CONF_STD_DEV in config:
|
|
||||||
sens = await sensor.new_sensor(config[CONF_STD_DEV])
|
|
||||||
cg.add(var.set_std_dev_sensor(sens))
|
|
||||||
|
|
|
@ -309,7 +309,7 @@ async def component_to_code(config):
|
||||||
lt_options["LT_UART_SILENT_ENABLED"] = 0
|
lt_options["LT_UART_SILENT_ENABLED"] = 0
|
||||||
lt_options["LT_UART_SILENT_ALL"] = 0
|
lt_options["LT_UART_SILENT_ALL"] = 0
|
||||||
# set default UART port
|
# set default UART port
|
||||||
if uart_port := framework.get(CONF_UART_PORT, None) is not None:
|
if (uart_port := framework.get(CONF_UART_PORT, None)) is not None:
|
||||||
lt_options["LT_UART_DEFAULT_PORT"] = uart_port
|
lt_options["LT_UART_DEFAULT_PORT"] = uart_port
|
||||||
# add custom options
|
# add custom options
|
||||||
lt_options.update(framework[CONF_OPTIONS])
|
lt_options.update(framework[CONF_OPTIONS])
|
||||||
|
|
|
@ -84,7 +84,7 @@ UART_SELECTION_ESP32 = {
|
||||||
VARIANT_ESP32: [UART0, UART1, UART2],
|
VARIANT_ESP32: [UART0, UART1, UART2],
|
||||||
VARIANT_ESP32S2: [UART0, UART1, USB_CDC],
|
VARIANT_ESP32S2: [UART0, UART1, USB_CDC],
|
||||||
VARIANT_ESP32S3: [UART0, UART1, USB_CDC, USB_SERIAL_JTAG],
|
VARIANT_ESP32S3: [UART0, UART1, USB_CDC, USB_SERIAL_JTAG],
|
||||||
VARIANT_ESP32C3: [UART0, UART1, USB_SERIAL_JTAG],
|
VARIANT_ESP32C3: [UART0, UART1, USB_CDC, USB_SERIAL_JTAG],
|
||||||
VARIANT_ESP32C2: [UART0, UART1],
|
VARIANT_ESP32C2: [UART0, UART1],
|
||||||
VARIANT_ESP32C6: [UART0, UART1, USB_CDC, USB_SERIAL_JTAG],
|
VARIANT_ESP32C6: [UART0, UART1, USB_CDC, USB_SERIAL_JTAG],
|
||||||
VARIANT_ESP32H2: [UART0, UART1, USB_CDC, USB_SERIAL_JTAG],
|
VARIANT_ESP32H2: [UART0, UART1, USB_CDC, USB_SERIAL_JTAG],
|
||||||
|
@ -172,9 +172,10 @@ CONFIG_SCHEMA = cv.All(
|
||||||
esp8266=UART0,
|
esp8266=UART0,
|
||||||
esp32=UART0,
|
esp32=UART0,
|
||||||
esp32_s2=USB_CDC,
|
esp32_s2=USB_CDC,
|
||||||
esp32_s3_idf=USB_SERIAL_JTAG,
|
|
||||||
esp32_c3_idf=USB_SERIAL_JTAG,
|
|
||||||
esp32_s3_arduino=USB_CDC,
|
esp32_s3_arduino=USB_CDC,
|
||||||
|
esp32_s3_idf=USB_SERIAL_JTAG,
|
||||||
|
esp32_c3_arduino=USB_CDC,
|
||||||
|
esp32_c3_idf=USB_SERIAL_JTAG,
|
||||||
rp2040=USB_CDC,
|
rp2040=USB_CDC,
|
||||||
bk72xx=DEFAULT,
|
bk72xx=DEFAULT,
|
||||||
rtl87xx=DEFAULT,
|
rtl87xx=DEFAULT,
|
||||||
|
@ -265,6 +266,8 @@ async def to_code(config):
|
||||||
if CORE.using_arduino:
|
if CORE.using_arduino:
|
||||||
if config[CONF_HARDWARE_UART] == USB_CDC:
|
if config[CONF_HARDWARE_UART] == USB_CDC:
|
||||||
cg.add_build_flag("-DARDUINO_USB_CDC_ON_BOOT=1")
|
cg.add_build_flag("-DARDUINO_USB_CDC_ON_BOOT=1")
|
||||||
|
if CORE.is_esp32 and get_esp32_variant() == VARIANT_ESP32C3:
|
||||||
|
cg.add_build_flag("-DARDUINO_USB_MODE=1")
|
||||||
|
|
||||||
if CORE.using_esp_idf:
|
if CORE.using_esp_idf:
|
||||||
if config[CONF_HARDWARE_UART] == USB_CDC:
|
if config[CONF_HARDWARE_UART] == USB_CDC:
|
||||||
|
|
|
@ -272,17 +272,13 @@ void Logger::pre_setup() {
|
||||||
#endif
|
#endif
|
||||||
#if defined(USE_ESP32) && \
|
#if defined(USE_ESP32) && \
|
||||||
(defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32C3))
|
(defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32C3))
|
||||||
#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
|
#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32C3)
|
||||||
case UART_SELECTION_USB_CDC:
|
case UART_SELECTION_USB_CDC:
|
||||||
#endif // USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3
|
#endif // USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3 || USE_ESP32_VARIANT_ESP32C3
|
||||||
#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S3)
|
#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S3)
|
||||||
case UART_SELECTION_USB_SERIAL_JTAG:
|
case UART_SELECTION_USB_SERIAL_JTAG:
|
||||||
#endif // USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32S3
|
#endif // USE_ESP32_VARIANT_ESP32C3 || USE_ESP32_VARIANT_ESP32S3
|
||||||
#ifdef USE_ESP32_VARIANT_ESP32C3
|
#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32C3)
|
||||||
this->hw_serial_ = &Serial;
|
|
||||||
Serial.begin(this->baud_rate_);
|
|
||||||
#endif // USE_ESP32_VARIANT_ESP32C3
|
|
||||||
#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
|
|
||||||
#if ARDUINO_USB_CDC_ON_BOOT
|
#if ARDUINO_USB_CDC_ON_BOOT
|
||||||
this->hw_serial_ = &Serial;
|
this->hw_serial_ = &Serial;
|
||||||
Serial.setTxTimeoutMs(0); // workaround for 2.0.9 crash when there's no data connection
|
Serial.setTxTimeoutMs(0); // workaround for 2.0.9 crash when there's no data connection
|
||||||
|
@ -291,7 +287,7 @@ void Logger::pre_setup() {
|
||||||
this->hw_serial_ = &Serial;
|
this->hw_serial_ = &Serial;
|
||||||
Serial.begin(this->baud_rate_);
|
Serial.begin(this->baud_rate_);
|
||||||
#endif // ARDUINO_USB_CDC_ON_BOOT
|
#endif // ARDUINO_USB_CDC_ON_BOOT
|
||||||
#endif // USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3
|
#endif // USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3 || USE_ESP32_VARIANT_ESP32C3
|
||||||
break;
|
break;
|
||||||
#endif // USE_ESP32 && (USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3 || USE_ESP32_VARIANT_ESP32C3)
|
#endif // USE_ESP32 && (USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3 || USE_ESP32_VARIANT_ESP32C3)
|
||||||
#ifdef USE_RP2040
|
#ifdef USE_RP2040
|
||||||
|
|
|
@ -45,9 +45,10 @@ enum UARTSelection {
|
||||||
UART_SELECTION_UART2,
|
UART_SELECTION_UART2,
|
||||||
#endif // !USE_ESP32_VARIANT_ESP32C3 && !USE_ESP32_VARIANT_ESP32C6 && !USE_ESP32_VARIANT_ESP32S2 &&
|
#endif // !USE_ESP32_VARIANT_ESP32C3 && !USE_ESP32_VARIANT_ESP32C6 && !USE_ESP32_VARIANT_ESP32S2 &&
|
||||||
// !USE_ESP32_VARIANT_ESP32S3 && !USE_ESP32_VARIANT_ESP32H2
|
// !USE_ESP32_VARIANT_ESP32S3 && !USE_ESP32_VARIANT_ESP32H2
|
||||||
#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
|
#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) || \
|
||||||
|
(defined(USE_ESP32_VARIANT_ESP32C3) && defined(USE_ARDUINO))
|
||||||
UART_SELECTION_USB_CDC,
|
UART_SELECTION_USB_CDC,
|
||||||
#endif // USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3
|
#endif // USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3 || USE_ESP32_VARIANT_ESP32C3
|
||||||
#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32S3) || \
|
#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32S3) || \
|
||||||
defined(USE_ESP32_VARIANT_ESP32H2)
|
defined(USE_ESP32_VARIANT_ESP32H2)
|
||||||
UART_SELECTION_USB_SERIAL_JTAG,
|
UART_SELECTION_USB_SERIAL_JTAG,
|
||||||
|
|
|
@ -10,6 +10,8 @@ from esphome.const import (
|
||||||
CONF_BIRTH_MESSAGE,
|
CONF_BIRTH_MESSAGE,
|
||||||
CONF_BROKER,
|
CONF_BROKER,
|
||||||
CONF_CERTIFICATE_AUTHORITY,
|
CONF_CERTIFICATE_AUTHORITY,
|
||||||
|
CONF_CLIENT_CERTIFICATE,
|
||||||
|
CONF_CLIENT_CERTIFICATE_KEY,
|
||||||
CONF_CLIENT_ID,
|
CONF_CLIENT_ID,
|
||||||
CONF_COMMAND_TOPIC,
|
CONF_COMMAND_TOPIC,
|
||||||
CONF_COMMAND_RETAIN,
|
CONF_COMMAND_RETAIN,
|
||||||
|
@ -199,6 +201,12 @@ CONFIG_SCHEMA = cv.All(
|
||||||
cv.Optional(CONF_CERTIFICATE_AUTHORITY): cv.All(
|
cv.Optional(CONF_CERTIFICATE_AUTHORITY): cv.All(
|
||||||
cv.string, cv.only_with_esp_idf
|
cv.string, cv.only_with_esp_idf
|
||||||
),
|
),
|
||||||
|
cv.Inclusive(CONF_CLIENT_CERTIFICATE, "cert-key-pair"): cv.All(
|
||||||
|
cv.string, cv.only_on_esp32
|
||||||
|
),
|
||||||
|
cv.Inclusive(CONF_CLIENT_CERTIFICATE_KEY, "cert-key-pair"): cv.All(
|
||||||
|
cv.string, cv.only_on_esp32
|
||||||
|
),
|
||||||
cv.SplitDefault(CONF_SKIP_CERT_CN_CHECK, esp32_idf=False): cv.All(
|
cv.SplitDefault(CONF_SKIP_CERT_CN_CHECK, esp32_idf=False): cv.All(
|
||||||
cv.boolean, cv.only_with_esp_idf
|
cv.boolean, cv.only_with_esp_idf
|
||||||
),
|
),
|
||||||
|
@ -378,6 +386,9 @@ async def to_code(config):
|
||||||
if CONF_CERTIFICATE_AUTHORITY in config:
|
if CONF_CERTIFICATE_AUTHORITY in config:
|
||||||
cg.add(var.set_ca_certificate(config[CONF_CERTIFICATE_AUTHORITY]))
|
cg.add(var.set_ca_certificate(config[CONF_CERTIFICATE_AUTHORITY]))
|
||||||
cg.add(var.set_skip_cert_cn_check(config[CONF_SKIP_CERT_CN_CHECK]))
|
cg.add(var.set_skip_cert_cn_check(config[CONF_SKIP_CERT_CN_CHECK]))
|
||||||
|
if CONF_CLIENT_CERTIFICATE in config:
|
||||||
|
cg.add(var.set_cl_certificate(config[CONF_CLIENT_CERTIFICATE]))
|
||||||
|
cg.add(var.set_cl_key(config[CONF_CLIENT_CERTIFICATE_KEY]))
|
||||||
|
|
||||||
# prevent error -0x428e
|
# prevent error -0x428e
|
||||||
# See https://github.com/espressif/esp-idf/issues/139
|
# See https://github.com/espressif/esp-idf/issues/139
|
||||||
|
|
|
@ -45,6 +45,11 @@ bool MQTTBackendESP32::initialize_() {
|
||||||
mqtt_cfg_.cert_pem = ca_certificate_.value().c_str();
|
mqtt_cfg_.cert_pem = ca_certificate_.value().c_str();
|
||||||
mqtt_cfg_.skip_cert_common_name_check = skip_cert_cn_check_;
|
mqtt_cfg_.skip_cert_common_name_check = skip_cert_cn_check_;
|
||||||
mqtt_cfg_.transport = MQTT_TRANSPORT_OVER_SSL;
|
mqtt_cfg_.transport = MQTT_TRANSPORT_OVER_SSL;
|
||||||
|
|
||||||
|
if (this->cl_certificate_.has_value() && this->cl_key_.has_value()) {
|
||||||
|
mqtt_cfg_.client_cert_pem = this->cl_certificate_.value().c_str();
|
||||||
|
mqtt_cfg_.client_key_pem = this->cl_key_.value().c_str();
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
mqtt_cfg_.transport = MQTT_TRANSPORT_OVER_TCP;
|
mqtt_cfg_.transport = MQTT_TRANSPORT_OVER_TCP;
|
||||||
}
|
}
|
||||||
|
@ -79,6 +84,11 @@ bool MQTTBackendESP32::initialize_() {
|
||||||
mqtt_cfg_.broker.verification.certificate = ca_certificate_.value().c_str();
|
mqtt_cfg_.broker.verification.certificate = ca_certificate_.value().c_str();
|
||||||
mqtt_cfg_.broker.verification.skip_cert_common_name_check = skip_cert_cn_check_;
|
mqtt_cfg_.broker.verification.skip_cert_common_name_check = skip_cert_cn_check_;
|
||||||
mqtt_cfg_.broker.address.transport = MQTT_TRANSPORT_OVER_SSL;
|
mqtt_cfg_.broker.address.transport = MQTT_TRANSPORT_OVER_SSL;
|
||||||
|
|
||||||
|
if (this->cl_certificate_.has_value() && this->cl_key_.has_value()) {
|
||||||
|
mqtt_cfg_.credentials.authentication.certificate = this->cl_certificate_.value().c_str();
|
||||||
|
mqtt_cfg_.credentials.authentication.key = this->cl_key_.value().c_str();
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
mqtt_cfg_.broker.address.transport = MQTT_TRANSPORT_OVER_TCP;
|
mqtt_cfg_.broker.address.transport = MQTT_TRANSPORT_OVER_TCP;
|
||||||
}
|
}
|
||||||
|
|
|
@ -124,6 +124,8 @@ class MQTTBackendESP32 final : public MQTTBackend {
|
||||||
void loop() final;
|
void loop() final;
|
||||||
|
|
||||||
void set_ca_certificate(const std::string &cert) { ca_certificate_ = cert; }
|
void set_ca_certificate(const std::string &cert) { ca_certificate_ = cert; }
|
||||||
|
void set_cl_certificate(const std::string &cert) { cl_certificate_ = cert; }
|
||||||
|
void set_cl_key(const std::string &key) { cl_key_ = key; }
|
||||||
void set_skip_cert_cn_check(bool skip_check) { skip_cert_cn_check_ = skip_check; }
|
void set_skip_cert_cn_check(bool skip_check) { skip_cert_cn_check_ = skip_check; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
@ -154,6 +156,8 @@ class MQTTBackendESP32 final : public MQTTBackend {
|
||||||
uint16_t keep_alive_;
|
uint16_t keep_alive_;
|
||||||
bool clean_session_;
|
bool clean_session_;
|
||||||
optional<std::string> ca_certificate_;
|
optional<std::string> ca_certificate_;
|
||||||
|
optional<std::string> cl_certificate_;
|
||||||
|
optional<std::string> cl_key_;
|
||||||
bool skip_cert_cn_check_{false};
|
bool skip_cert_cn_check_{false};
|
||||||
|
|
||||||
// callbacks
|
// callbacks
|
||||||
|
|
|
@ -146,6 +146,8 @@ class MQTTClientComponent : public Component {
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_ESP32
|
#ifdef USE_ESP32
|
||||||
void set_ca_certificate(const char *cert) { this->mqtt_backend_.set_ca_certificate(cert); }
|
void set_ca_certificate(const char *cert) { this->mqtt_backend_.set_ca_certificate(cert); }
|
||||||
|
void set_cl_certificate(const char *cert) { this->mqtt_backend_.set_cl_certificate(cert); }
|
||||||
|
void set_cl_key(const char *key) { this->mqtt_backend_.set_cl_key(key); }
|
||||||
void set_skip_cert_cn_check(bool skip_check) { this->mqtt_backend_.set_skip_cert_cn_check(skip_check); }
|
void set_skip_cert_cn_check(bool skip_check) { this->mqtt_backend_.set_skip_cert_cn_check(skip_check); }
|
||||||
#endif
|
#endif
|
||||||
const Availability &get_availability();
|
const Availability &get_availability();
|
||||||
|
|
|
@ -14,6 +14,13 @@
|
||||||
#include <IPAddress.h>
|
#include <IPAddress.h>
|
||||||
#endif /* USE_ADRDUINO */
|
#endif /* USE_ADRDUINO */
|
||||||
|
|
||||||
|
#ifdef USE_HOST
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
using ip_addr_t = in_addr;
|
||||||
|
using ip4_addr_t = in_addr;
|
||||||
|
#define ipaddr_aton(x, y) inet_aton((x), (y))
|
||||||
|
#endif
|
||||||
|
|
||||||
#if USE_ESP32_FRAMEWORK_ARDUINO
|
#if USE_ESP32_FRAMEWORK_ARDUINO
|
||||||
#define arduino_ns Arduino_h
|
#define arduino_ns Arduino_h
|
||||||
#elif USE_LIBRETINY
|
#elif USE_LIBRETINY
|
||||||
|
@ -32,6 +39,14 @@ namespace network {
|
||||||
|
|
||||||
struct IPAddress {
|
struct IPAddress {
|
||||||
public:
|
public:
|
||||||
|
#ifdef USE_HOST
|
||||||
|
IPAddress() { ip_addr_.s_addr = 0; }
|
||||||
|
IPAddress(uint8_t first, uint8_t second, uint8_t third, uint8_t fourth) {
|
||||||
|
this->ip_addr_.s_addr = htonl((first << 24) | (second << 16) | (third << 8) | fourth);
|
||||||
|
}
|
||||||
|
IPAddress(const std::string &in_address) { inet_aton(in_address.c_str(), &ip_addr_); }
|
||||||
|
IPAddress(const ip_addr_t *other_ip) { ip_addr_ = *other_ip; }
|
||||||
|
#else
|
||||||
IPAddress() { ip_addr_set_zero(&ip_addr_); }
|
IPAddress() { ip_addr_set_zero(&ip_addr_); }
|
||||||
IPAddress(uint8_t first, uint8_t second, uint8_t third, uint8_t fourth) {
|
IPAddress(uint8_t first, uint8_t second, uint8_t third, uint8_t fourth) {
|
||||||
IP_ADDR4(&ip_addr_, first, second, third, fourth);
|
IP_ADDR4(&ip_addr_, first, second, third, fourth);
|
||||||
|
@ -107,6 +122,7 @@ struct IPAddress {
|
||||||
}
|
}
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
ip_addr_t ip_addr_;
|
ip_addr_t ip_addr_;
|
||||||
|
|
|
@ -2,6 +2,7 @@ import esphome.codegen as cg
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome import automation
|
from esphome import automation
|
||||||
from esphome.components import display, uart
|
from esphome.components import display, uart
|
||||||
|
from esphome.components import esp32
|
||||||
from esphome.const import (
|
from esphome.const import (
|
||||||
CONF_ID,
|
CONF_ID,
|
||||||
CONF_LAMBDA,
|
CONF_LAMBDA,
|
||||||
|
@ -96,6 +97,11 @@ async def to_code(config):
|
||||||
if CORE.is_esp32 and CORE.using_arduino:
|
if CORE.is_esp32 and CORE.using_arduino:
|
||||||
cg.add_library("WiFiClientSecure", None)
|
cg.add_library("WiFiClientSecure", None)
|
||||||
cg.add_library("HTTPClient", None)
|
cg.add_library("HTTPClient", None)
|
||||||
|
elif CORE.is_esp32 and CORE.using_esp_idf:
|
||||||
|
esp32.add_idf_sdkconfig_option("CONFIG_ESP_TLS_INSECURE", True)
|
||||||
|
esp32.add_idf_sdkconfig_option(
|
||||||
|
"CONFIG_ESP_TLS_SKIP_SERVER_CERT_VERIFY", True
|
||||||
|
)
|
||||||
elif CORE.is_esp8266 and CORE.using_arduino:
|
elif CORE.is_esp8266 and CORE.using_arduino:
|
||||||
cg.add_library("ESP8266HTTPClient", None)
|
cg.add_library("ESP8266HTTPClient", None)
|
||||||
|
|
||||||
|
|
|
@ -750,6 +750,50 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe
|
||||||
*/
|
*/
|
||||||
void filled_circle(int center_x, int center_y, int radius, Color color);
|
void filled_circle(int center_x, int center_y, int radius, Color color);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draws a QR code in the screen
|
||||||
|
* @param x1 The top left x coordinate to start the QR code.
|
||||||
|
* @param y1 The top left y coordinate to start the QR code.
|
||||||
|
* @param content The content of the QR code (as a plain text - Nextion will generate the QR code).
|
||||||
|
* @param size The size (in pixels) for the QR code. Defaults to 200px.
|
||||||
|
* @param background_color The background color to draw with (as rgb565 integer). Defaults to 65535 (white).
|
||||||
|
* @param foreground_color The foreground color to draw with (as rgb565 integer). Defaults to 0 (black).
|
||||||
|
* @param logo_pic The picture id for the logo in the center of the QR code. Defaults to -1 (no logo).
|
||||||
|
* @param border_width The border width (in pixels) for the QR code. Defaults to 8px.
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
* ```cpp
|
||||||
|
* it.qrcode(25, 25, "WIFI:S:MySSID;T:WPA;P:MyPassW0rd;;");
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* Draws a QR code with a Wi-Fi network credentials starting at the given coordinates (25,25).
|
||||||
|
*/
|
||||||
|
void qrcode(int x1, int y1, const char *content, int size = 200, uint16_t background_color = 65535,
|
||||||
|
uint16_t foreground_color = 0, int logo_pic = -1, uint8_t border_width = 8);
|
||||||
|
/**
|
||||||
|
* Draws a QR code in the screen
|
||||||
|
* @param x1 The top left x coordinate to start the QR code.
|
||||||
|
* @param y1 The top left y coordinate to start the QR code.
|
||||||
|
* @param content The content of the QR code (as a plain text - Nextion will generate the QR code).
|
||||||
|
* @param size The size (in pixels) for the QR code. Defaults to 200px.
|
||||||
|
* @param background_color The background color to draw with (as Color). Defaults to 65535 (white).
|
||||||
|
* @param foreground_color The foreground color to draw with (as Color). Defaults to 0 (black).
|
||||||
|
* @param logo_pic The picture id for the logo in the center of the QR code. Defaults to -1 (no logo).
|
||||||
|
* @param border_width The border width (in pixels) for the QR code. Defaults to 8px.
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
* ```cpp
|
||||||
|
* auto blue = Color(0, 0, 255);
|
||||||
|
* auto red = Color(255, 0, 0);
|
||||||
|
* it.qrcode(25, 25, "WIFI:S:MySSID;T:WPA;P:MyPassW0rd;;", 150, blue, red);
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* Draws a QR code with a Wi-Fi network credentials starting at the given coordinates (25,25) with size of 150px in
|
||||||
|
* red on a blue background.
|
||||||
|
*/
|
||||||
|
void qrcode(int x1, int y1, const char *content, int size, Color background_color = Color(255, 255, 255),
|
||||||
|
Color foreground_color = Color(0, 0, 0), int logo_pic = -1, uint8_t border_width = 8);
|
||||||
|
|
||||||
/** Set the brightness of the backlight.
|
/** Set the brightness of the backlight.
|
||||||
*
|
*
|
||||||
* @param brightness The brightness percentage from 0 to 1.0.
|
* @param brightness The brightness percentage from 0 to 1.0.
|
||||||
|
|
|
@ -294,6 +294,19 @@ void Nextion::filled_circle(int center_x, int center_y, int radius, Color color)
|
||||||
display::ColorUtil::color_to_565(color));
|
display::ColorUtil::color_to_565(color));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Nextion::qrcode(int x1, int y1, const char *content, int size, uint16_t background_color,
|
||||||
|
uint16_t foreground_color, int logo_pic, uint8_t border_width) {
|
||||||
|
this->add_no_result_to_queue_with_printf_("qrcode", "qrcode %d,%d,%d,%d,%d,%d,%d,\"%s\"", x1, y1, size,
|
||||||
|
background_color, foreground_color, logo_pic, border_width, content);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Nextion::qrcode(int x1, int y1, const char *content, int size, Color background_color, Color foreground_color,
|
||||||
|
int logo_pic, uint8_t border_width) {
|
||||||
|
this->add_no_result_to_queue_with_printf_(
|
||||||
|
"qrcode", "qrcode %d,%d,%d,%d,%d,%d,%d,\"%s\"", x1, y1, size, display::ColorUtil::color_to_565(background_color),
|
||||||
|
display::ColorUtil::color_to_565(foreground_color), logo_pic, border_width, content);
|
||||||
|
}
|
||||||
|
|
||||||
void Nextion::set_nextion_rtc_time(ESPTime time) {
|
void Nextion::set_nextion_rtc_time(ESPTime time) {
|
||||||
this->add_no_result_to_queue_with_printf_("rtc0", "rtc0=%u", time.year);
|
this->add_no_result_to_queue_with_printf_("rtc0", "rtc0=%u", time.year);
|
||||||
this->add_no_result_to_queue_with_printf_("rtc1", "rtc1=%u", time.month);
|
this->add_no_result_to_queue_with_printf_("rtc1", "rtc1=%u", time.month);
|
||||||
|
|
|
@ -24,7 +24,7 @@ int Nextion::upload_range(const std::string &url, int range_start) {
|
||||||
ESP_LOGVV(TAG, "url: %s", url.c_str());
|
ESP_LOGVV(TAG, "url: %s", url.c_str());
|
||||||
uint range_size = this->tft_size_ - range_start;
|
uint range_size = this->tft_size_ - range_start;
|
||||||
ESP_LOGVV(TAG, "tft_size_: %i", this->tft_size_);
|
ESP_LOGVV(TAG, "tft_size_: %i", this->tft_size_);
|
||||||
ESP_LOGV(TAG, "Available heap: %u", esp_get_free_heap_size());
|
ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
|
||||||
int range_end = (range_start == 0) ? std::min(this->tft_size_, 16383) : this->tft_size_;
|
int range_end = (range_start == 0) ? std::min(this->tft_size_, 16383) : this->tft_size_;
|
||||||
if (range_size <= 0 or range_end <= range_start) {
|
if (range_size <= 0 or range_end <= range_start) {
|
||||||
ESP_LOGE(TAG, "Invalid range");
|
ESP_LOGE(TAG, "Invalid range");
|
||||||
|
@ -37,6 +37,8 @@ int Nextion::upload_range(const std::string &url, int range_start) {
|
||||||
esp_http_client_config_t config = {
|
esp_http_client_config_t config = {
|
||||||
.url = url.c_str(),
|
.url = url.c_str(),
|
||||||
.cert_pem = nullptr,
|
.cert_pem = nullptr,
|
||||||
|
.disable_auto_redirect = false,
|
||||||
|
.max_redirection_count = 10,
|
||||||
};
|
};
|
||||||
esp_http_client_handle_t client = esp_http_client_init(&config);
|
esp_http_client_handle_t client = esp_http_client_init(&config);
|
||||||
|
|
||||||
|
@ -44,7 +46,7 @@ int Nextion::upload_range(const std::string &url, int range_start) {
|
||||||
sprintf(range_header, "bytes=%d-%d", range_start, range_end);
|
sprintf(range_header, "bytes=%d-%d", range_start, range_end);
|
||||||
ESP_LOGV(TAG, "Requesting range: %s", range_header);
|
ESP_LOGV(TAG, "Requesting range: %s", range_header);
|
||||||
esp_http_client_set_header(client, "Range", range_header);
|
esp_http_client_set_header(client, "Range", range_header);
|
||||||
ESP_LOGVV(TAG, "Available heap: %u", esp_get_free_heap_size());
|
ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
|
||||||
|
|
||||||
ESP_LOGV(TAG, "Opening http connetion");
|
ESP_LOGV(TAG, "Opening http connetion");
|
||||||
esp_err_t err;
|
esp_err_t err;
|
||||||
|
@ -65,18 +67,19 @@ int Nextion::upload_range(const std::string &url, int range_start) {
|
||||||
|
|
||||||
int total_read_len = 0, read_len;
|
int total_read_len = 0, read_len;
|
||||||
|
|
||||||
|
ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
|
||||||
ESP_LOGV(TAG, "Allocate buffer");
|
ESP_LOGV(TAG, "Allocate buffer");
|
||||||
uint8_t *buffer = new uint8_t[4096];
|
uint8_t *buffer = new uint8_t[4096];
|
||||||
std::string recv_string;
|
std::string recv_string;
|
||||||
if (buffer == nullptr) {
|
if (buffer == nullptr) {
|
||||||
ESP_LOGE(TAG, "Failed to allocate memory for buffer");
|
ESP_LOGE(TAG, "Failed to allocate memory for buffer");
|
||||||
ESP_LOGV(TAG, "Available heap: %u", esp_get_free_heap_size());
|
ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
|
||||||
} else {
|
} else {
|
||||||
ESP_LOGV(TAG, "Memory for buffer allocated successfully");
|
ESP_LOGV(TAG, "Memory for buffer allocated successfully");
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
App.feed_wdt();
|
App.feed_wdt();
|
||||||
ESP_LOGVV(TAG, "Available heap: %u", esp_get_free_heap_size());
|
ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
|
||||||
int read_len = esp_http_client_read(client, reinterpret_cast<char *>(buffer), 4096);
|
int read_len = esp_http_client_read(client, reinterpret_cast<char *>(buffer), 4096);
|
||||||
ESP_LOGVV(TAG, "Read %d bytes from HTTP client, writing to UART", read_len);
|
ESP_LOGVV(TAG, "Read %d bytes from HTTP client, writing to UART", read_len);
|
||||||
if (read_len > 0) {
|
if (read_len > 0) {
|
||||||
|
@ -84,15 +87,14 @@ int Nextion::upload_range(const std::string &url, int range_start) {
|
||||||
ESP_LOGVV(TAG, "Write to UART successful");
|
ESP_LOGVV(TAG, "Write to UART successful");
|
||||||
this->recv_ret_string_(recv_string, 5000, true);
|
this->recv_ret_string_(recv_string, 5000, true);
|
||||||
this->content_length_ -= read_len;
|
this->content_length_ -= read_len;
|
||||||
ESP_LOGD(TAG, "Uploaded %0.2f %%, remaining %d bytes",
|
ESP_LOGD(TAG, "Uploaded %0.2f %%, remaining %d bytes, heap is %" PRIu32 " bytes",
|
||||||
100.0 * (this->tft_size_ - this->content_length_) / this->tft_size_, this->content_length_);
|
100.0 * (this->tft_size_ - this->content_length_) / this->tft_size_, this->content_length_,
|
||||||
if (recv_string[0] != 0x05) { // 0x05 == "ok"
|
esp_get_free_heap_size());
|
||||||
|
|
||||||
|
if (recv_string[0] == 0x08 && recv_string.size() == 5) { // handle partial upload request
|
||||||
ESP_LOGD(
|
ESP_LOGD(
|
||||||
TAG, "recv_string [%s]",
|
TAG, "recv_string [%s]",
|
||||||
format_hex_pretty(reinterpret_cast<const uint8_t *>(recv_string.data()), recv_string.size()).c_str());
|
format_hex_pretty(reinterpret_cast<const uint8_t *>(recv_string.data()), recv_string.size()).c_str());
|
||||||
}
|
|
||||||
// handle partial upload request
|
|
||||||
if (recv_string[0] == 0x08 && recv_string.size() == 5) {
|
|
||||||
uint32_t result = 0;
|
uint32_t result = 0;
|
||||||
for (int j = 0; j < 4; ++j) {
|
for (int j = 0; j < 4; ++j) {
|
||||||
result += static_cast<uint8_t>(recv_string[j + 1]) << (8 * j);
|
result += static_cast<uint8_t>(recv_string[j + 1]) << (8 * j);
|
||||||
|
@ -101,13 +103,37 @@ int Nextion::upload_range(const std::string &url, int range_start) {
|
||||||
ESP_LOGI(TAG, "Nextion reported new range %" PRIu32, result);
|
ESP_LOGI(TAG, "Nextion reported new range %" PRIu32, result);
|
||||||
this->content_length_ = this->tft_size_ - result;
|
this->content_length_ = this->tft_size_ - result;
|
||||||
// Deallocate the buffer when done
|
// Deallocate the buffer when done
|
||||||
|
ESP_LOGV(TAG, "Deallocate buffer");
|
||||||
|
ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
|
||||||
delete[] buffer;
|
delete[] buffer;
|
||||||
ESP_LOGVV(TAG, "Memory for buffer deallocated");
|
ESP_LOGVV(TAG, "Memory for buffer deallocated");
|
||||||
esp_http_client_cleanup(client);
|
ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
|
||||||
|
ESP_LOGV(TAG, "Close http client");
|
||||||
|
ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
|
||||||
esp_http_client_close(client);
|
esp_http_client_close(client);
|
||||||
|
esp_http_client_cleanup(client);
|
||||||
|
ESP_LOGVV(TAG, "Client closed");
|
||||||
|
ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
} else if (recv_string[0] != 0x05) { // 0x05 == "ok"
|
||||||
|
ESP_LOGE(
|
||||||
|
TAG, "Invalid response from Nextion: [%s]",
|
||||||
|
format_hex_pretty(reinterpret_cast<const uint8_t *>(recv_string.data()), recv_string.size()).c_str());
|
||||||
|
ESP_LOGV(TAG, "Deallocate buffer");
|
||||||
|
ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
|
||||||
|
delete[] buffer;
|
||||||
|
ESP_LOGVV(TAG, "Memory for buffer deallocated");
|
||||||
|
ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
|
||||||
|
ESP_LOGV(TAG, "Close http client");
|
||||||
|
ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
|
||||||
|
esp_http_client_close(client);
|
||||||
|
esp_http_client_cleanup(client);
|
||||||
|
ESP_LOGVV(TAG, "Client closed");
|
||||||
|
ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
|
||||||
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
recv_string.clear();
|
recv_string.clear();
|
||||||
} else if (read_len == 0) {
|
} else if (read_len == 0) {
|
||||||
ESP_LOGV(TAG, "End of HTTP response reached");
|
ESP_LOGV(TAG, "End of HTTP response reached");
|
||||||
|
@ -119,11 +145,18 @@ int Nextion::upload_range(const std::string &url, int range_start) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deallocate the buffer when done
|
// Deallocate the buffer when done
|
||||||
|
ESP_LOGV(TAG, "Deallocate buffer");
|
||||||
|
ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
|
||||||
delete[] buffer;
|
delete[] buffer;
|
||||||
ESP_LOGVV(TAG, "Memory for buffer deallocated");
|
ESP_LOGVV(TAG, "Memory for buffer deallocated");
|
||||||
|
ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
|
||||||
}
|
}
|
||||||
esp_http_client_cleanup(client);
|
ESP_LOGV(TAG, "Close http client");
|
||||||
|
ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
|
||||||
esp_http_client_close(client);
|
esp_http_client_close(client);
|
||||||
|
esp_http_client_cleanup(client);
|
||||||
|
ESP_LOGVV(TAG, "Client closed");
|
||||||
|
ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
|
||||||
return range_end + 1;
|
return range_end + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -145,17 +178,19 @@ bool Nextion::upload_tft() {
|
||||||
|
|
||||||
// Define the configuration for the HTTP client
|
// Define the configuration for the HTTP client
|
||||||
ESP_LOGV(TAG, "Establishing connection to HTTP server");
|
ESP_LOGV(TAG, "Establishing connection to HTTP server");
|
||||||
ESP_LOGVV(TAG, "Available heap: %u", esp_get_free_heap_size());
|
ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
|
||||||
esp_http_client_config_t config = {
|
esp_http_client_config_t config = {
|
||||||
.url = this->tft_url_.c_str(),
|
.url = this->tft_url_.c_str(),
|
||||||
.cert_pem = nullptr,
|
.cert_pem = nullptr,
|
||||||
.method = HTTP_METHOD_HEAD,
|
.method = HTTP_METHOD_HEAD,
|
||||||
.timeout_ms = 15000,
|
.timeout_ms = 15000,
|
||||||
|
.disable_auto_redirect = false,
|
||||||
|
.max_redirection_count = 10,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Initialize the HTTP client with the configuration
|
// Initialize the HTTP client with the configuration
|
||||||
ESP_LOGV(TAG, "Initializing HTTP client");
|
ESP_LOGV(TAG, "Initializing HTTP client");
|
||||||
ESP_LOGV(TAG, "Available heap: %u", esp_get_free_heap_size());
|
ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
|
||||||
esp_http_client_handle_t http = esp_http_client_init(&config);
|
esp_http_client_handle_t http = esp_http_client_init(&config);
|
||||||
if (!http) {
|
if (!http) {
|
||||||
ESP_LOGE(TAG, "Failed to initialize HTTP client.");
|
ESP_LOGE(TAG, "Failed to initialize HTTP client.");
|
||||||
|
@ -164,7 +199,7 @@ bool Nextion::upload_tft() {
|
||||||
|
|
||||||
// Perform the HTTP request
|
// Perform the HTTP request
|
||||||
ESP_LOGV(TAG, "Check if the client could connect");
|
ESP_LOGV(TAG, "Check if the client could connect");
|
||||||
ESP_LOGV(TAG, "Available heap: %u", esp_get_free_heap_size());
|
ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
|
||||||
esp_err_t err = esp_http_client_perform(http);
|
esp_err_t err = esp_http_client_perform(http);
|
||||||
if (err != ESP_OK) {
|
if (err != ESP_OK) {
|
||||||
ESP_LOGE(TAG, "HTTP request failed: %s", esp_err_to_name(err));
|
ESP_LOGE(TAG, "HTTP request failed: %s", esp_err_to_name(err));
|
||||||
|
@ -173,14 +208,22 @@ bool Nextion::upload_tft() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check the HTTP Status Code
|
// Check the HTTP Status Code
|
||||||
|
ESP_LOGV(TAG, "Check the HTTP Status Code");
|
||||||
|
ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
|
||||||
int status_code = esp_http_client_get_status_code(http);
|
int status_code = esp_http_client_get_status_code(http);
|
||||||
ESP_LOGV(TAG, "HTTP Status Code: %d", status_code);
|
ESP_LOGV(TAG, "HTTP Status Code: %d", status_code);
|
||||||
size_t tft_file_size = esp_http_client_get_content_length(http);
|
size_t tft_file_size = esp_http_client_get_content_length(http);
|
||||||
ESP_LOGD(TAG, "TFT file size: %zu", tft_file_size);
|
ESP_LOGD(TAG, "TFT file size: %zu", tft_file_size);
|
||||||
|
|
||||||
|
ESP_LOGD(TAG, "Close HTTP connection");
|
||||||
|
ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
|
||||||
|
esp_http_client_close(http);
|
||||||
|
esp_http_client_cleanup(http);
|
||||||
|
ESP_LOGVV(TAG, "Connection closed");
|
||||||
|
ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
|
||||||
|
|
||||||
if (tft_file_size < 4096) {
|
if (tft_file_size < 4096) {
|
||||||
ESP_LOGE(TAG, "File size check failed. Size: %zu", tft_file_size);
|
ESP_LOGE(TAG, "File size check failed. Size: %zu", tft_file_size);
|
||||||
esp_http_client_cleanup(http);
|
|
||||||
return this->upload_end(false);
|
return this->upload_end(false);
|
||||||
} else {
|
} else {
|
||||||
ESP_LOGV(TAG, "File size check passed. Proceeding...");
|
ESP_LOGV(TAG, "File size check passed. Proceeding...");
|
||||||
|
@ -189,8 +232,10 @@ bool Nextion::upload_tft() {
|
||||||
this->tft_size_ = tft_file_size;
|
this->tft_size_ = tft_file_size;
|
||||||
|
|
||||||
ESP_LOGD(TAG, "Updating Nextion");
|
ESP_LOGD(TAG, "Updating Nextion");
|
||||||
// The Nextion will ignore the update command if it is sleeping
|
|
||||||
|
|
||||||
|
// The Nextion will ignore the update command if it is sleeping
|
||||||
|
ESP_LOGV(TAG, "Wake-up Nextion");
|
||||||
|
ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
|
||||||
this->send_command_("sleep=0");
|
this->send_command_("sleep=0");
|
||||||
this->set_backlight_brightness(1.0);
|
this->set_backlight_brightness(1.0);
|
||||||
vTaskDelay(pdMS_TO_TICKS(250)); // NOLINT
|
vTaskDelay(pdMS_TO_TICKS(250)); // NOLINT
|
||||||
|
@ -203,26 +248,31 @@ bool Nextion::upload_tft() {
|
||||||
sprintf(command, "whmi-wris %d,%" PRIu32 ",1", this->content_length_, this->parent_->get_baud_rate());
|
sprintf(command, "whmi-wris %d,%" PRIu32 ",1", this->content_length_, this->parent_->get_baud_rate());
|
||||||
|
|
||||||
// Clear serial receive buffer
|
// Clear serial receive buffer
|
||||||
|
ESP_LOGV(TAG, "Clear serial receive buffer");
|
||||||
|
ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
|
||||||
uint8_t d;
|
uint8_t d;
|
||||||
while (this->available()) {
|
while (this->available()) {
|
||||||
this->read_byte(&d);
|
this->read_byte(&d);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
ESP_LOGV(TAG, "Send update instruction: %s", command);
|
||||||
|
ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
|
||||||
this->send_command_(command);
|
this->send_command_(command);
|
||||||
|
|
||||||
std::string response;
|
std::string response;
|
||||||
ESP_LOGV(TAG, "Waiting for upgrade response");
|
ESP_LOGV(TAG, "Waiting for upgrade response");
|
||||||
this->recv_ret_string_(response, 2048, true); // This can take some time to return
|
this->recv_ret_string_(response, 5000, true); // This can take some time to return
|
||||||
|
|
||||||
// The Nextion display will, if it's ready to accept data, send a 0x05 byte.
|
// The Nextion display will, if it's ready to accept data, send a 0x05 byte.
|
||||||
ESP_LOGD(TAG, "Upgrade response is [%s]",
|
ESP_LOGD(TAG, "Upgrade response is [%s] - %zu bytes",
|
||||||
format_hex_pretty(reinterpret_cast<const uint8_t *>(response.data()), response.size()).c_str());
|
format_hex_pretty(reinterpret_cast<const uint8_t *>(response.data()), response.size()).c_str(),
|
||||||
|
response.length());
|
||||||
|
ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
|
||||||
|
|
||||||
if (response.find(0x05) != std::string::npos) {
|
if (response.find(0x05) != std::string::npos) {
|
||||||
ESP_LOGV(TAG, "Preparation for tft update done");
|
ESP_LOGV(TAG, "Preparation for tft update done");
|
||||||
} else {
|
} else {
|
||||||
ESP_LOGE(TAG, "Preparation for tft update failed %d \"%s\"", response[0], response.c_str());
|
ESP_LOGE(TAG, "Preparation for tft update failed %d \"%s\"", response[0], response.c_str());
|
||||||
esp_http_client_cleanup(http);
|
|
||||||
return this->upload_end(false);
|
return this->upload_end(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -230,12 +280,12 @@ bool Nextion::upload_tft() {
|
||||||
content_length_, esp_get_free_heap_size());
|
content_length_, esp_get_free_heap_size());
|
||||||
|
|
||||||
ESP_LOGV(TAG, "Starting transfer by chunks loop");
|
ESP_LOGV(TAG, "Starting transfer by chunks loop");
|
||||||
|
ESP_LOGVV(TAG, "Available heap: %" PRIu32, esp_get_free_heap_size());
|
||||||
int result = 0;
|
int result = 0;
|
||||||
while (content_length_ > 0) {
|
while (content_length_ > 0) {
|
||||||
result = upload_range(this->tft_url_.c_str(), result);
|
result = upload_range(this->tft_url_.c_str(), result);
|
||||||
if (result < 0) {
|
if (result < 0) {
|
||||||
ESP_LOGE(TAG, "Error updating Nextion!");
|
ESP_LOGE(TAG, "Error updating Nextion!");
|
||||||
esp_http_client_cleanup(http);
|
|
||||||
return this->upload_end(false);
|
return this->upload_end(false);
|
||||||
}
|
}
|
||||||
App.feed_wdt();
|
App.feed_wdt();
|
||||||
|
@ -244,9 +294,6 @@ bool Nextion::upload_tft() {
|
||||||
|
|
||||||
ESP_LOGD(TAG, "Successfully updated Nextion!");
|
ESP_LOGD(TAG, "Successfully updated Nextion!");
|
||||||
|
|
||||||
ESP_LOGD(TAG, "Close HTTP connection");
|
|
||||||
esp_http_client_close(http);
|
|
||||||
esp_http_client_cleanup(http);
|
|
||||||
return upload_end(true);
|
return upload_end(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -256,7 +303,7 @@ bool Nextion::upload_end(bool successful) {
|
||||||
this->soft_reset();
|
this->soft_reset();
|
||||||
vTaskDelay(pdMS_TO_TICKS(1500)); // NOLINT
|
vTaskDelay(pdMS_TO_TICKS(1500)); // NOLINT
|
||||||
if (successful) {
|
if (successful) {
|
||||||
ESP_LOGD(TAG, "Restarting esphome");
|
ESP_LOGD(TAG, "Restarting ESPHome");
|
||||||
esp_restart(); // NOLINT(readability-static-accessed-through-instance)
|
esp_restart(); // NOLINT(readability-static-accessed-through-instance)
|
||||||
}
|
}
|
||||||
return successful;
|
return successful;
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
from esphome import automation
|
from esphome import automation
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
|
|
||||||
CODEOWNERS = ["@jesserockz"]
|
CODEOWNERS = ["@jesserockz", "@kbx81"]
|
||||||
|
|
||||||
nfc_ns = cg.esphome_ns.namespace("nfc")
|
nfc_ns = cg.esphome_ns.namespace("nfc")
|
||||||
|
|
||||||
|
Nfcc = nfc_ns.class_("Nfcc")
|
||||||
NfcTag = nfc_ns.class_("NfcTag")
|
NfcTag = nfc_ns.class_("NfcTag")
|
||||||
|
NfcTagListener = nfc_ns.class_("NfcTagListener")
|
||||||
NfcOnTagTrigger = nfc_ns.class_(
|
NfcOnTagTrigger = nfc_ns.class_(
|
||||||
"NfcOnTagTrigger", automation.Trigger.template(cg.std_string, NfcTag)
|
"NfcOnTagTrigger", automation.Trigger.template(cg.std_string, NfcTag)
|
||||||
)
|
)
|
||||||
|
|
72
esphome/components/nfc/binary_sensor/__init__.py
Normal file
72
esphome/components/nfc/binary_sensor/__init__.py
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
import esphome.codegen as cg
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
from esphome.components import binary_sensor
|
||||||
|
from esphome.const import CONF_UID
|
||||||
|
from esphome.core import HexInt
|
||||||
|
from .. import nfc_ns, Nfcc, NfcTagListener
|
||||||
|
|
||||||
|
DEPENDENCIES = ["nfc"]
|
||||||
|
|
||||||
|
CONF_NDEF_CONTAINS = "ndef_contains"
|
||||||
|
CONF_NFCC_ID = "nfcc_id"
|
||||||
|
CONF_TAG_ID = "tag_id"
|
||||||
|
|
||||||
|
NfcTagBinarySensor = nfc_ns.class_(
|
||||||
|
"NfcTagBinarySensor",
|
||||||
|
binary_sensor.BinarySensor,
|
||||||
|
cg.Component,
|
||||||
|
NfcTagListener,
|
||||||
|
cg.Parented.template(Nfcc),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def validate_uid(value):
|
||||||
|
value = cv.string_strict(value)
|
||||||
|
for x in value.split("-"):
|
||||||
|
if len(x) != 2:
|
||||||
|
raise cv.Invalid(
|
||||||
|
"Each part (separated by '-') of the UID must be two characters "
|
||||||
|
"long."
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
x = int(x, 16)
|
||||||
|
except ValueError as err:
|
||||||
|
raise cv.Invalid(
|
||||||
|
"Valid characters for parts of a UID are 0123456789ABCDEF."
|
||||||
|
) from err
|
||||||
|
if x < 0 or x > 255:
|
||||||
|
raise cv.Invalid(
|
||||||
|
"Valid values for UID parts (separated by '-') are 00 to FF"
|
||||||
|
)
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = cv.All(
|
||||||
|
binary_sensor.binary_sensor_schema(NfcTagBinarySensor)
|
||||||
|
.extend(
|
||||||
|
{
|
||||||
|
cv.GenerateID(CONF_NFCC_ID): cv.use_id(Nfcc),
|
||||||
|
cv.Optional(CONF_NDEF_CONTAINS): cv.string,
|
||||||
|
cv.Optional(CONF_TAG_ID): cv.string,
|
||||||
|
cv.Optional(CONF_UID): validate_uid,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.extend(cv.COMPONENT_SCHEMA),
|
||||||
|
cv.has_exactly_one_key(CONF_NDEF_CONTAINS, CONF_TAG_ID, CONF_UID),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def to_code(config):
|
||||||
|
var = await binary_sensor.new_binary_sensor(config)
|
||||||
|
await cg.register_component(var, config)
|
||||||
|
await cg.register_parented(var, config[CONF_NFCC_ID])
|
||||||
|
|
||||||
|
hub = await cg.get_variable(config[CONF_NFCC_ID])
|
||||||
|
cg.add(hub.register_listener(var))
|
||||||
|
if CONF_NDEF_CONTAINS in config:
|
||||||
|
cg.add(var.set_ndef_match_string(config[CONF_NDEF_CONTAINS]))
|
||||||
|
if CONF_TAG_ID in config:
|
||||||
|
cg.add(var.set_tag_name(config[CONF_TAG_ID]))
|
||||||
|
elif CONF_UID in config:
|
||||||
|
addr = [HexInt(int(x, 16)) for x in config[CONF_UID].split("-")]
|
||||||
|
cg.add(var.set_uid(addr))
|
114
esphome/components/nfc/binary_sensor/binary_sensor.cpp
Normal file
114
esphome/components/nfc/binary_sensor/binary_sensor.cpp
Normal file
|
@ -0,0 +1,114 @@
|
||||||
|
#include "binary_sensor.h"
|
||||||
|
#include "../nfc_helpers.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace nfc {
|
||||||
|
|
||||||
|
static const char *const TAG = "nfc.binary_sensor";
|
||||||
|
|
||||||
|
void NfcTagBinarySensor::setup() {
|
||||||
|
this->parent_->register_listener(this);
|
||||||
|
this->publish_initial_state(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NfcTagBinarySensor::dump_config() {
|
||||||
|
std::string match_str = "name";
|
||||||
|
|
||||||
|
LOG_BINARY_SENSOR("", "NFC Tag Binary Sensor", this);
|
||||||
|
if (!this->match_string_.empty()) {
|
||||||
|
if (!this->match_tag_name_) {
|
||||||
|
match_str = "contains";
|
||||||
|
}
|
||||||
|
ESP_LOGCONFIG(TAG, " Tag %s: %s", match_str.c_str(), this->match_string_.c_str());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!this->uid_.empty()) {
|
||||||
|
ESP_LOGCONFIG(TAG, " Tag UID: %s", format_bytes(this->uid_).c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void NfcTagBinarySensor::set_ndef_match_string(const std::string &str) {
|
||||||
|
this->match_string_ = str;
|
||||||
|
this->match_tag_name_ = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NfcTagBinarySensor::set_tag_name(const std::string &str) {
|
||||||
|
this->match_string_ = str;
|
||||||
|
this->match_tag_name_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NfcTagBinarySensor::set_uid(const std::vector<uint8_t> &uid) { this->uid_ = uid; }
|
||||||
|
|
||||||
|
bool NfcTagBinarySensor::tag_match_ndef_string(const std::shared_ptr<NdefMessage> &msg) {
|
||||||
|
for (const auto &record : msg->get_records()) {
|
||||||
|
if (record->get_payload().find(this->match_string_) != std::string::npos) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NfcTagBinarySensor::tag_match_tag_name(const std::shared_ptr<NdefMessage> &msg) {
|
||||||
|
for (const auto &record : msg->get_records()) {
|
||||||
|
if (record->get_payload().find(HA_TAG_ID_PREFIX) != std::string::npos) {
|
||||||
|
auto rec_substr = record->get_payload().substr(sizeof(HA_TAG_ID_PREFIX) - 1);
|
||||||
|
if (rec_substr.find(this->match_string_) != std::string::npos) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NfcTagBinarySensor::tag_match_uid(const std::vector<uint8_t> &data) {
|
||||||
|
if (data.size() != this->uid_.size()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t i = 0; i < data.size(); i++) {
|
||||||
|
if (data[i] != this->uid_[i]) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NfcTagBinarySensor::tag_off(NfcTag &tag) {
|
||||||
|
if (!this->match_string_.empty() && tag.has_ndef_message()) {
|
||||||
|
if (this->match_tag_name_) {
|
||||||
|
if (this->tag_match_tag_name(tag.get_ndef_message())) {
|
||||||
|
this->publish_state(false);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (this->tag_match_ndef_string(tag.get_ndef_message())) {
|
||||||
|
this->publish_state(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!this->uid_.empty() && this->tag_match_uid(tag.get_uid())) {
|
||||||
|
this->publish_state(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void NfcTagBinarySensor::tag_on(NfcTag &tag) {
|
||||||
|
if (!this->match_string_.empty() && tag.has_ndef_message()) {
|
||||||
|
if (this->match_tag_name_) {
|
||||||
|
if (this->tag_match_tag_name(tag.get_ndef_message())) {
|
||||||
|
this->publish_state(true);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (this->tag_match_ndef_string(tag.get_ndef_message())) {
|
||||||
|
this->publish_state(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!this->uid_.empty() && this->tag_match_uid(tag.get_uid())) {
|
||||||
|
this->publish_state(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace nfc
|
||||||
|
} // namespace esphome
|
38
esphome/components/nfc/binary_sensor/binary_sensor.h
Normal file
38
esphome/components/nfc/binary_sensor/binary_sensor.h
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/components/binary_sensor/binary_sensor.h"
|
||||||
|
#include "esphome/components/nfc/nfc.h"
|
||||||
|
#include "esphome/components/nfc/nfc_tag.h"
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
#include "esphome/core/helpers.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace nfc {
|
||||||
|
|
||||||
|
class NfcTagBinarySensor : public binary_sensor::BinarySensor,
|
||||||
|
public Component,
|
||||||
|
public NfcTagListener,
|
||||||
|
public Parented<Nfcc> {
|
||||||
|
public:
|
||||||
|
void setup() override;
|
||||||
|
void dump_config() override;
|
||||||
|
|
||||||
|
void set_ndef_match_string(const std::string &str);
|
||||||
|
void set_tag_name(const std::string &str);
|
||||||
|
void set_uid(const std::vector<uint8_t> &uid);
|
||||||
|
|
||||||
|
bool tag_match_ndef_string(const std::shared_ptr<NdefMessage> &msg);
|
||||||
|
bool tag_match_tag_name(const std::shared_ptr<NdefMessage> &msg);
|
||||||
|
bool tag_match_uid(const std::vector<uint8_t> &data);
|
||||||
|
|
||||||
|
void tag_off(NfcTag &tag) override;
|
||||||
|
void tag_on(NfcTag &tag) override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
bool match_tag_name_{false};
|
||||||
|
std::string match_string_;
|
||||||
|
std::vector<uint8_t> uid_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace nfc
|
||||||
|
} // namespace esphome
|
|
@ -66,5 +66,19 @@ bool mifare_classic_is_trailer_block(uint8_t block_num);
|
||||||
|
|
||||||
uint32_t get_mifare_ultralight_buffer_size(uint32_t message_length);
|
uint32_t get_mifare_ultralight_buffer_size(uint32_t message_length);
|
||||||
|
|
||||||
|
class NfcTagListener {
|
||||||
|
public:
|
||||||
|
virtual void tag_off(NfcTag &tag) {}
|
||||||
|
virtual void tag_on(NfcTag &tag) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
class Nfcc {
|
||||||
|
public:
|
||||||
|
void register_listener(NfcTagListener *listener) { this->tag_listeners_.push_back(listener); }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
std::vector<NfcTagListener *> tag_listeners_;
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace nfc
|
} // namespace nfc
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|
|
@ -12,6 +12,7 @@ from esphome.const import (
|
||||||
CONF_TRIGGER_ID,
|
CONF_TRIGGER_ID,
|
||||||
CONF_OTA,
|
CONF_OTA,
|
||||||
KEY_PAST_SAFE_MODE,
|
KEY_PAST_SAFE_MODE,
|
||||||
|
CONF_VERSION,
|
||||||
)
|
)
|
||||||
from esphome.core import CORE, coroutine_with_priority
|
from esphome.core import CORE, coroutine_with_priority
|
||||||
|
|
||||||
|
@ -41,6 +42,7 @@ CONFIG_SCHEMA = cv.Schema(
|
||||||
{
|
{
|
||||||
cv.GenerateID(): cv.declare_id(OTAComponent),
|
cv.GenerateID(): cv.declare_id(OTAComponent),
|
||||||
cv.Optional(CONF_SAFE_MODE, default=True): cv.boolean,
|
cv.Optional(CONF_SAFE_MODE, default=True): cv.boolean,
|
||||||
|
cv.Optional(CONF_VERSION, default=2): cv.one_of(1, 2, int=True),
|
||||||
cv.SplitDefault(
|
cv.SplitDefault(
|
||||||
CONF_PORT,
|
CONF_PORT,
|
||||||
esp8266=8266,
|
esp8266=8266,
|
||||||
|
@ -93,6 +95,7 @@ async def to_code(config):
|
||||||
if CONF_PASSWORD in config:
|
if CONF_PASSWORD in config:
|
||||||
cg.add(var.set_auth_password(config[CONF_PASSWORD]))
|
cg.add(var.set_auth_password(config[CONF_PASSWORD]))
|
||||||
cg.add_define("USE_OTA_PASSWORD")
|
cg.add_define("USE_OTA_PASSWORD")
|
||||||
|
cg.add_define("USE_OTA_VERSION", config[CONF_VERSION])
|
||||||
|
|
||||||
await cg.register_component(var, config)
|
await cg.register_component(var, config)
|
||||||
|
|
||||||
|
|
|
@ -20,8 +20,7 @@ namespace esphome {
|
||||||
namespace ota {
|
namespace ota {
|
||||||
|
|
||||||
static const char *const TAG = "ota";
|
static const char *const TAG = "ota";
|
||||||
|
static constexpr u_int16_t OTA_BLOCK_SIZE = 8192;
|
||||||
static const uint8_t OTA_VERSION_1_0 = 1;
|
|
||||||
|
|
||||||
OTAComponent *global_ota_component = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
OTAComponent *global_ota_component = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||||
|
|
||||||
|
@ -101,6 +100,7 @@ void OTAComponent::dump_config() {
|
||||||
ESP_LOGCONFIG(TAG, " Using Password.");
|
ESP_LOGCONFIG(TAG, " Using Password.");
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
ESP_LOGCONFIG(TAG, " OTA version: %d.", USE_OTA_VERSION);
|
||||||
if (this->has_safe_mode_ && this->safe_mode_rtc_value_ > 1 &&
|
if (this->has_safe_mode_ && this->safe_mode_rtc_value_ > 1 &&
|
||||||
this->safe_mode_rtc_value_ != esphome::ota::OTAComponent::ENTER_SAFE_MODE_MAGIC) {
|
this->safe_mode_rtc_value_ != esphome::ota::OTAComponent::ENTER_SAFE_MODE_MAGIC) {
|
||||||
ESP_LOGW(TAG, "Last Boot was an unhandled reset, will proceed to safe mode in %" PRIu32 " restarts",
|
ESP_LOGW(TAG, "Last Boot was an unhandled reset, will proceed to safe mode in %" PRIu32 " restarts",
|
||||||
|
@ -132,6 +132,9 @@ void OTAComponent::handle_() {
|
||||||
uint8_t ota_features;
|
uint8_t ota_features;
|
||||||
std::unique_ptr<OTABackend> backend;
|
std::unique_ptr<OTABackend> backend;
|
||||||
(void) ota_features;
|
(void) ota_features;
|
||||||
|
#if USE_OTA_VERSION == 2
|
||||||
|
size_t size_acknowledged = 0;
|
||||||
|
#endif
|
||||||
|
|
||||||
if (client_ == nullptr) {
|
if (client_ == nullptr) {
|
||||||
struct sockaddr_storage source_addr;
|
struct sockaddr_storage source_addr;
|
||||||
|
@ -168,7 +171,7 @@ void OTAComponent::handle_() {
|
||||||
|
|
||||||
// Send OK and version - 2 bytes
|
// Send OK and version - 2 bytes
|
||||||
buf[0] = OTA_RESPONSE_OK;
|
buf[0] = OTA_RESPONSE_OK;
|
||||||
buf[1] = OTA_VERSION_1_0;
|
buf[1] = USE_OTA_VERSION;
|
||||||
this->writeall_(buf, 2);
|
this->writeall_(buf, 2);
|
||||||
|
|
||||||
backend = make_ota_backend();
|
backend = make_ota_backend();
|
||||||
|
@ -312,6 +315,13 @@ void OTAComponent::handle_() {
|
||||||
goto error; // NOLINT(cppcoreguidelines-avoid-goto)
|
goto error; // NOLINT(cppcoreguidelines-avoid-goto)
|
||||||
}
|
}
|
||||||
total += read;
|
total += read;
|
||||||
|
#if USE_OTA_VERSION == 2
|
||||||
|
while (size_acknowledged + OTA_BLOCK_SIZE <= total || (total == ota_size && size_acknowledged < ota_size)) {
|
||||||
|
buf[0] = OTA_RESPONSE_CHUNK_OK;
|
||||||
|
this->writeall_(buf, 1);
|
||||||
|
size_acknowledged += OTA_BLOCK_SIZE;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
uint32_t now = millis();
|
uint32_t now = millis();
|
||||||
if (now - last_progress > 1000) {
|
if (now - last_progress > 1000) {
|
||||||
|
|
|
@ -10,31 +10,32 @@ namespace esphome {
|
||||||
namespace ota {
|
namespace ota {
|
||||||
|
|
||||||
enum OTAResponseTypes {
|
enum OTAResponseTypes {
|
||||||
OTA_RESPONSE_OK = 0,
|
OTA_RESPONSE_OK = 0x00,
|
||||||
OTA_RESPONSE_REQUEST_AUTH = 1,
|
OTA_RESPONSE_REQUEST_AUTH = 0x01,
|
||||||
|
|
||||||
OTA_RESPONSE_HEADER_OK = 64,
|
OTA_RESPONSE_HEADER_OK = 0x40,
|
||||||
OTA_RESPONSE_AUTH_OK = 65,
|
OTA_RESPONSE_AUTH_OK = 0x41,
|
||||||
OTA_RESPONSE_UPDATE_PREPARE_OK = 66,
|
OTA_RESPONSE_UPDATE_PREPARE_OK = 0x42,
|
||||||
OTA_RESPONSE_BIN_MD5_OK = 67,
|
OTA_RESPONSE_BIN_MD5_OK = 0x43,
|
||||||
OTA_RESPONSE_RECEIVE_OK = 68,
|
OTA_RESPONSE_RECEIVE_OK = 0x44,
|
||||||
OTA_RESPONSE_UPDATE_END_OK = 69,
|
OTA_RESPONSE_UPDATE_END_OK = 0x45,
|
||||||
OTA_RESPONSE_SUPPORTS_COMPRESSION = 70,
|
OTA_RESPONSE_SUPPORTS_COMPRESSION = 0x46,
|
||||||
|
OTA_RESPONSE_CHUNK_OK = 0x47,
|
||||||
|
|
||||||
OTA_RESPONSE_ERROR_MAGIC = 128,
|
OTA_RESPONSE_ERROR_MAGIC = 0x80,
|
||||||
OTA_RESPONSE_ERROR_UPDATE_PREPARE = 129,
|
OTA_RESPONSE_ERROR_UPDATE_PREPARE = 0x81,
|
||||||
OTA_RESPONSE_ERROR_AUTH_INVALID = 130,
|
OTA_RESPONSE_ERROR_AUTH_INVALID = 0x82,
|
||||||
OTA_RESPONSE_ERROR_WRITING_FLASH = 131,
|
OTA_RESPONSE_ERROR_WRITING_FLASH = 0x83,
|
||||||
OTA_RESPONSE_ERROR_UPDATE_END = 132,
|
OTA_RESPONSE_ERROR_UPDATE_END = 0x84,
|
||||||
OTA_RESPONSE_ERROR_INVALID_BOOTSTRAPPING = 133,
|
OTA_RESPONSE_ERROR_INVALID_BOOTSTRAPPING = 0x85,
|
||||||
OTA_RESPONSE_ERROR_WRONG_CURRENT_FLASH_CONFIG = 134,
|
OTA_RESPONSE_ERROR_WRONG_CURRENT_FLASH_CONFIG = 0x86,
|
||||||
OTA_RESPONSE_ERROR_WRONG_NEW_FLASH_CONFIG = 135,
|
OTA_RESPONSE_ERROR_WRONG_NEW_FLASH_CONFIG = 0x87,
|
||||||
OTA_RESPONSE_ERROR_ESP8266_NOT_ENOUGH_SPACE = 136,
|
OTA_RESPONSE_ERROR_ESP8266_NOT_ENOUGH_SPACE = 0x88,
|
||||||
OTA_RESPONSE_ERROR_ESP32_NOT_ENOUGH_SPACE = 137,
|
OTA_RESPONSE_ERROR_ESP32_NOT_ENOUGH_SPACE = 0x89,
|
||||||
OTA_RESPONSE_ERROR_NO_UPDATE_PARTITION = 138,
|
OTA_RESPONSE_ERROR_NO_UPDATE_PARTITION = 0x8A,
|
||||||
OTA_RESPONSE_ERROR_MD5_MISMATCH = 139,
|
OTA_RESPONSE_ERROR_MD5_MISMATCH = 0x8B,
|
||||||
OTA_RESPONSE_ERROR_RP2040_NOT_ENOUGH_SPACE = 140,
|
OTA_RESPONSE_ERROR_RP2040_NOT_ENOUGH_SPACE = 0x8C,
|
||||||
OTA_RESPONSE_ERROR_UNKNOWN = 255,
|
OTA_RESPONSE_ERROR_UNKNOWN = 0xFF,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum OTAState { OTA_COMPLETED = 0, OTA_STARTED, OTA_IN_PROGRESS, OTA_ERROR };
|
enum OTAState { OTA_COMPLETED = 0, OTA_STARTED, OTA_IN_PROGRESS, OTA_ERROR };
|
||||||
|
|
|
@ -195,7 +195,7 @@ void PMSX003Component::send_command_(uint8_t cmd, uint16_t data) {
|
||||||
void PMSX003Component::parse_data_() {
|
void PMSX003Component::parse_data_() {
|
||||||
switch (this->type_) {
|
switch (this->type_) {
|
||||||
case PMSX003_TYPE_5003ST: {
|
case PMSX003_TYPE_5003ST: {
|
||||||
float temperature = this->get_16_bit_uint_(30) / 10.0f;
|
float temperature = (int16_t) this->get_16_bit_uint_(30) / 10.0f;
|
||||||
float humidity = this->get_16_bit_uint_(32) / 10.0f;
|
float humidity = this->get_16_bit_uint_(32) / 10.0f;
|
||||||
|
|
||||||
ESP_LOGD(TAG, "Got Temperature: %.1f°C, Humidity: %.1f%%", temperature, humidity);
|
ESP_LOGD(TAG, "Got Temperature: %.1f°C, Humidity: %.1f%%", temperature, humidity);
|
||||||
|
@ -279,7 +279,7 @@ void PMSX003Component::parse_data_() {
|
||||||
// Note the pm particles 50um & 100um are not returned,
|
// Note the pm particles 50um & 100um are not returned,
|
||||||
// as PMS5003T uses those data values for temperature and humidity.
|
// as PMS5003T uses those data values for temperature and humidity.
|
||||||
|
|
||||||
float temperature = this->get_16_bit_uint_(24) / 10.0f;
|
float temperature = (int16_t) this->get_16_bit_uint_(24) / 10.0f;
|
||||||
float humidity = this->get_16_bit_uint_(26) / 10.0f;
|
float humidity = this->get_16_bit_uint_(26) / 10.0f;
|
||||||
|
|
||||||
ESP_LOGD(TAG,
|
ESP_LOGD(TAG,
|
||||||
|
|
|
@ -34,7 +34,7 @@ CONF_TAG_TTL = "tag_ttl"
|
||||||
CONF_VEN_PIN = "ven_pin"
|
CONF_VEN_PIN = "ven_pin"
|
||||||
|
|
||||||
pn7150_ns = cg.esphome_ns.namespace("pn7150")
|
pn7150_ns = cg.esphome_ns.namespace("pn7150")
|
||||||
PN7150 = pn7150_ns.class_("PN7150", cg.Component)
|
PN7150 = pn7150_ns.class_("PN7150", nfc.Nfcc, cg.Component)
|
||||||
|
|
||||||
EmulationOffAction = pn7150_ns.class_("EmulationOffAction", automation.Action)
|
EmulationOffAction = pn7150_ns.class_("EmulationOffAction", automation.Action)
|
||||||
EmulationOnAction = pn7150_ns.class_("EmulationOnAction", automation.Action)
|
EmulationOnAction = pn7150_ns.class_("EmulationOnAction", automation.Action)
|
||||||
|
|
|
@ -566,6 +566,9 @@ void PN7150::erase_tag_(const uint8_t tag_index) {
|
||||||
for (auto *trigger : this->triggers_ontagremoved_) {
|
for (auto *trigger : this->triggers_ontagremoved_) {
|
||||||
trigger->process(this->discovered_endpoint_[tag_index].tag);
|
trigger->process(this->discovered_endpoint_[tag_index].tag);
|
||||||
}
|
}
|
||||||
|
for (auto *listener : this->tag_listeners_) {
|
||||||
|
listener->tag_off(*this->discovered_endpoint_[tag_index].tag);
|
||||||
|
}
|
||||||
ESP_LOGI(TAG, "Tag %s removed", nfc::format_uid(this->discovered_endpoint_[tag_index].tag->get_uid()).c_str());
|
ESP_LOGI(TAG, "Tag %s removed", nfc::format_uid(this->discovered_endpoint_[tag_index].tag->get_uid()).c_str());
|
||||||
this->discovered_endpoint_.erase(this->discovered_endpoint_.begin() + tag_index);
|
this->discovered_endpoint_.erase(this->discovered_endpoint_.begin() + tag_index);
|
||||||
}
|
}
|
||||||
|
@ -881,6 +884,9 @@ void PN7150::process_rf_intf_activated_oid_(nfc::NciMessage &rx) { // an endpoi
|
||||||
for (auto *trigger : this->triggers_ontag_) {
|
for (auto *trigger : this->triggers_ontag_) {
|
||||||
trigger->process(working_endpoint.tag);
|
trigger->process(working_endpoint.tag);
|
||||||
}
|
}
|
||||||
|
for (auto *listener : this->tag_listeners_) {
|
||||||
|
listener->tag_on(*working_endpoint.tag);
|
||||||
|
}
|
||||||
working_endpoint.trig_called = true;
|
working_endpoint.trig_called = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -142,7 +142,7 @@ struct DiscoveredEndpoint {
|
||||||
bool trig_called;
|
bool trig_called;
|
||||||
};
|
};
|
||||||
|
|
||||||
class PN7150 : public Component {
|
class PN7150 : public nfc::Nfcc, public Component {
|
||||||
public:
|
public:
|
||||||
void setup() override;
|
void setup() override;
|
||||||
void dump_config() override;
|
void dump_config() override;
|
||||||
|
|
|
@ -36,7 +36,7 @@ CONF_VEN_PIN = "ven_pin"
|
||||||
CONF_WKUP_REQ_PIN = "wkup_req_pin"
|
CONF_WKUP_REQ_PIN = "wkup_req_pin"
|
||||||
|
|
||||||
pn7160_ns = cg.esphome_ns.namespace("pn7160")
|
pn7160_ns = cg.esphome_ns.namespace("pn7160")
|
||||||
PN7160 = pn7160_ns.class_("PN7160", cg.Component)
|
PN7160 = pn7160_ns.class_("PN7160", nfc.Nfcc, cg.Component)
|
||||||
|
|
||||||
EmulationOffAction = pn7160_ns.class_("EmulationOffAction", automation.Action)
|
EmulationOffAction = pn7160_ns.class_("EmulationOffAction", automation.Action)
|
||||||
EmulationOnAction = pn7160_ns.class_("EmulationOnAction", automation.Action)
|
EmulationOnAction = pn7160_ns.class_("EmulationOnAction", automation.Action)
|
||||||
|
|
|
@ -591,6 +591,9 @@ void PN7160::erase_tag_(const uint8_t tag_index) {
|
||||||
for (auto *trigger : this->triggers_ontagremoved_) {
|
for (auto *trigger : this->triggers_ontagremoved_) {
|
||||||
trigger->process(this->discovered_endpoint_[tag_index].tag);
|
trigger->process(this->discovered_endpoint_[tag_index].tag);
|
||||||
}
|
}
|
||||||
|
for (auto *listener : this->tag_listeners_) {
|
||||||
|
listener->tag_off(*this->discovered_endpoint_[tag_index].tag);
|
||||||
|
}
|
||||||
ESP_LOGI(TAG, "Tag %s removed", nfc::format_uid(this->discovered_endpoint_[tag_index].tag->get_uid()).c_str());
|
ESP_LOGI(TAG, "Tag %s removed", nfc::format_uid(this->discovered_endpoint_[tag_index].tag->get_uid()).c_str());
|
||||||
this->discovered_endpoint_.erase(this->discovered_endpoint_.begin() + tag_index);
|
this->discovered_endpoint_.erase(this->discovered_endpoint_.begin() + tag_index);
|
||||||
}
|
}
|
||||||
|
@ -905,6 +908,9 @@ void PN7160::process_rf_intf_activated_oid_(nfc::NciMessage &rx) { // an endpoi
|
||||||
for (auto *trigger : this->triggers_ontag_) {
|
for (auto *trigger : this->triggers_ontag_) {
|
||||||
trigger->process(working_endpoint.tag);
|
trigger->process(working_endpoint.tag);
|
||||||
}
|
}
|
||||||
|
for (auto *listener : this->tag_listeners_) {
|
||||||
|
listener->tag_on(*working_endpoint.tag);
|
||||||
|
}
|
||||||
working_endpoint.trig_called = true;
|
working_endpoint.trig_called = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -157,7 +157,7 @@ struct DiscoveredEndpoint {
|
||||||
bool trig_called;
|
bool trig_called;
|
||||||
};
|
};
|
||||||
|
|
||||||
class PN7160 : public Component {
|
class PN7160 : public nfc::Nfcc, public Component {
|
||||||
public:
|
public:
|
||||||
void setup() override;
|
void setup() override;
|
||||||
void dump_config() override;
|
void dump_config() override;
|
||||||
|
|
|
@ -19,7 +19,7 @@ PylontechComponent = pylontech_ns.class_(
|
||||||
)
|
)
|
||||||
PylontechBattery = pylontech_ns.class_("PylontechBattery")
|
PylontechBattery = pylontech_ns.class_("PylontechBattery")
|
||||||
|
|
||||||
CV_NUM_BATTERIES = cv.int_range(1, 6)
|
CV_NUM_BATTERIES = cv.int_range(1, 16)
|
||||||
|
|
||||||
PYLONTECH_COMPONENT_SCHEMA = cv.Schema(
|
PYLONTECH_COMPONENT_SCHEMA = cv.Schema(
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
#include "pylontech.h"
|
#include "pylontech.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
#include "esphome/core/helpers.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace pylontech {
|
namespace pylontech {
|
||||||
|
@ -34,13 +35,15 @@ void PylontechComponent::setup() {
|
||||||
void PylontechComponent::update() { this->write_str("pwr\n"); }
|
void PylontechComponent::update() { this->write_str("pwr\n"); }
|
||||||
|
|
||||||
void PylontechComponent::loop() {
|
void PylontechComponent::loop() {
|
||||||
uint8_t data;
|
if (this->available() > 0) {
|
||||||
|
|
||||||
// pylontech sends a lot of data very suddenly
|
// pylontech sends a lot of data very suddenly
|
||||||
// we need to quickly put it all into our own buffer, otherwise the uart's buffer will overflow
|
// we need to quickly put it all into our own buffer, otherwise the uart's buffer will overflow
|
||||||
|
uint8_t data;
|
||||||
|
int recv = 0;
|
||||||
while (this->available() > 0) {
|
while (this->available() > 0) {
|
||||||
if (this->read_byte(&data)) {
|
if (this->read_byte(&data)) {
|
||||||
buffer_[buffer_index_write_] += (char) data;
|
buffer_[buffer_index_write_] += (char) data;
|
||||||
|
recv++;
|
||||||
if (buffer_[buffer_index_write_].back() == static_cast<char>(ASCII_LF) ||
|
if (buffer_[buffer_index_write_].back() == static_cast<char>(ASCII_LF) ||
|
||||||
buffer_[buffer_index_write_].length() >= MAX_DATA_LENGTH_BYTES) {
|
buffer_[buffer_index_write_].length() >= MAX_DATA_LENGTH_BYTES) {
|
||||||
// complete line received
|
// complete line received
|
||||||
|
@ -48,7 +51,8 @@ void PylontechComponent::loop() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
ESP_LOGV(TAG, "received %d bytes", recv);
|
||||||
|
} else {
|
||||||
// only process one line per call of loop() to not block esphome for too long
|
// only process one line per call of loop() to not block esphome for too long
|
||||||
if (buffer_index_read_ != buffer_index_write_) {
|
if (buffer_index_read_ != buffer_index_write_) {
|
||||||
this->process_line_(buffer_[buffer_index_read_]);
|
this->process_line_(buffer_[buffer_index_read_]);
|
||||||
|
@ -56,6 +60,7 @@ void PylontechComponent::loop() {
|
||||||
buffer_index_read_ = (buffer_index_read_ + 1) % NUM_BUFFERS;
|
buffer_index_read_ = (buffer_index_read_ + 1) % NUM_BUFFERS;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void PylontechComponent::process_line_(std::string &buffer) {
|
void PylontechComponent::process_line_(std::string &buffer) {
|
||||||
ESP_LOGV(TAG, "Read from serial: %s", buffer.substr(0, buffer.size() - 2).c_str());
|
ESP_LOGV(TAG, "Read from serial: %s", buffer.substr(0, buffer.size() - 2).c_str());
|
||||||
|
@ -66,10 +71,11 @@ void PylontechComponent::process_line_(std::string &buffer) {
|
||||||
// clang-format on
|
// clang-format on
|
||||||
|
|
||||||
PylontechListener::LineContents l{};
|
PylontechListener::LineContents l{};
|
||||||
|
char mostempr_s[6];
|
||||||
const int parsed = sscanf( // NOLINT
|
const int parsed = sscanf( // NOLINT
|
||||||
buffer.c_str(), "%d %d %d %d %d %d %d %d %7s %7s %7s %7s %d%% %*d-%*d-%*d %*d:%*d:%*d %*s %*s %d %*s", // NOLINT
|
buffer.c_str(), "%d %d %d %d %d %d %d %d %7s %7s %7s %7s %d%% %*d-%*d-%*d %*d:%*d:%*d %*s %*s %5s %*s", // NOLINT
|
||||||
&l.bat_num, &l.volt, &l.curr, &l.tempr, &l.tlow, &l.thigh, &l.vlow, &l.vhigh, l.base_st, l.volt_st, // NOLINT
|
&l.bat_num, &l.volt, &l.curr, &l.tempr, &l.tlow, &l.thigh, &l.vlow, &l.vhigh, l.base_st, l.volt_st, // NOLINT
|
||||||
l.curr_st, l.temp_st, &l.coulomb, &l.mostempr); // NOLINT
|
l.curr_st, l.temp_st, &l.coulomb, mostempr_s); // NOLINT
|
||||||
|
|
||||||
if (l.bat_num <= 0) {
|
if (l.bat_num <= 0) {
|
||||||
ESP_LOGD(TAG, "invalid bat_num in line %s", buffer.substr(0, buffer.size() - 2).c_str());
|
ESP_LOGD(TAG, "invalid bat_num in line %s", buffer.substr(0, buffer.size() - 2).c_str());
|
||||||
|
@ -79,6 +85,13 @@ void PylontechComponent::process_line_(std::string &buffer) {
|
||||||
ESP_LOGW(TAG, "invalid line: found only %d items in %s", parsed, buffer.substr(0, buffer.size() - 2).c_str());
|
ESP_LOGW(TAG, "invalid line: found only %d items in %s", parsed, buffer.substr(0, buffer.size() - 2).c_str());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
auto mostempr_parsed = parse_number<int>(mostempr_s);
|
||||||
|
if (mostempr_parsed.has_value()) {
|
||||||
|
l.mostempr = mostempr_parsed.value();
|
||||||
|
} else {
|
||||||
|
l.mostempr = -300;
|
||||||
|
ESP_LOGW(TAG, "bat_num %d: received no mostempr", l.bat_num);
|
||||||
|
}
|
||||||
|
|
||||||
for (PylontechListener *listener : this->listeners_) {
|
for (PylontechListener *listener : this->listeners_) {
|
||||||
listener->on_line_read(&l);
|
listener->on_line_read(&l);
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue