mirror of
https://github.com/esphome/esphome.git
synced 2024-11-27 17:27:59 +01:00
Merge branch 'dev' into waveshare-4.2inv2-fix
This commit is contained in:
commit
16c6583d3c
2018 changed files with 8956 additions and 3832 deletions
4
.github/actions/build-image/action.yaml
vendored
4
.github/actions/build-image/action.yaml
vendored
|
@ -46,7 +46,7 @@ runs:
|
|||
|
||||
- name: Build and push to ghcr by digest
|
||||
id: build-ghcr
|
||||
uses: docker/build-push-action@v5.3.0
|
||||
uses: docker/build-push-action@v6.1.0
|
||||
with:
|
||||
context: .
|
||||
file: ./docker/Dockerfile
|
||||
|
@ -69,7 +69,7 @@ runs:
|
|||
|
||||
- name: Build and push to dockerhub by digest
|
||||
id: build-dockerhub
|
||||
uses: docker/build-push-action@v5.3.0
|
||||
uses: docker/build-push-action@v6.1.0
|
||||
with:
|
||||
context: .
|
||||
file: ./docker/Dockerfile
|
||||
|
|
2
.github/workflows/ci-api-proto.yml
vendored
2
.github/workflows/ci-api-proto.yml
vendored
|
@ -21,7 +21,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4.1.6
|
||||
uses: actions/checkout@v4.1.7
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5.1.0
|
||||
with:
|
||||
|
|
2
.github/workflows/ci-docker.yml
vendored
2
.github/workflows/ci-docker.yml
vendored
|
@ -40,7 +40,7 @@ jobs:
|
|||
arch: [amd64, armv7, aarch64]
|
||||
build_type: ["ha-addon", "docker", "lint"]
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
- uses: actions/checkout@v4.1.7
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5.1.0
|
||||
with:
|
||||
|
|
47
.github/workflows/ci.yml
vendored
47
.github/workflows/ci.yml
vendored
|
@ -34,7 +34,7 @@ jobs:
|
|||
cache-key: ${{ steps.cache-key.outputs.key }}
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.1.6
|
||||
uses: actions/checkout@v4.1.7
|
||||
- name: Generate cache-key
|
||||
id: cache-key
|
||||
run: echo key="${{ hashFiles('requirements.txt', 'requirements_optional.txt', 'requirements_test.txt') }}" >> $GITHUB_OUTPUT
|
||||
|
@ -66,7 +66,7 @@ jobs:
|
|||
- common
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.1.6
|
||||
uses: actions/checkout@v4.1.7
|
||||
- name: Restore Python
|
||||
uses: ./.github/actions/restore-python
|
||||
with:
|
||||
|
@ -87,7 +87,7 @@ jobs:
|
|||
- common
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.1.6
|
||||
uses: actions/checkout@v4.1.7
|
||||
- name: Restore Python
|
||||
uses: ./.github/actions/restore-python
|
||||
with:
|
||||
|
@ -108,7 +108,7 @@ jobs:
|
|||
- common
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.1.6
|
||||
uses: actions/checkout@v4.1.7
|
||||
- name: Restore Python
|
||||
uses: ./.github/actions/restore-python
|
||||
with:
|
||||
|
@ -129,7 +129,7 @@ jobs:
|
|||
- common
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.1.6
|
||||
uses: actions/checkout@v4.1.7
|
||||
- name: Restore Python
|
||||
uses: ./.github/actions/restore-python
|
||||
with:
|
||||
|
@ -150,7 +150,7 @@ jobs:
|
|||
- common
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.1.6
|
||||
uses: actions/checkout@v4.1.7
|
||||
- name: Restore Python
|
||||
uses: ./.github/actions/restore-python
|
||||
with:
|
||||
|
@ -199,7 +199,7 @@ jobs:
|
|||
- common
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.1.6
|
||||
uses: actions/checkout@v4.1.7
|
||||
- name: Restore Python
|
||||
uses: ./.github/actions/restore-python
|
||||
with:
|
||||
|
@ -229,7 +229,7 @@ jobs:
|
|||
- common
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.1.6
|
||||
uses: actions/checkout@v4.1.7
|
||||
- name: Restore Python
|
||||
uses: ./.github/actions/restore-python
|
||||
with:
|
||||
|
@ -254,7 +254,7 @@ jobs:
|
|||
matrix: ${{ steps.set-matrix.outputs.matrix }}
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.1.6
|
||||
uses: actions/checkout@v4.1.7
|
||||
- name: Find all YAML test files
|
||||
id: set-matrix
|
||||
run: echo "matrix=$(ls tests/test*.yaml | jq -R -s -c 'split("\n")[:-1]')" >> $GITHUB_OUTPUT
|
||||
|
@ -271,7 +271,7 @@ jobs:
|
|||
file: ${{ fromJson(needs.compile-tests-list.outputs.matrix) }}
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.1.6
|
||||
uses: actions/checkout@v4.1.7
|
||||
- name: Restore Python
|
||||
uses: ./.github/actions/restore-python
|
||||
with:
|
||||
|
@ -303,7 +303,7 @@ jobs:
|
|||
file: ${{ fromJson(needs.compile-tests-list.outputs.matrix) }}
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.1.6
|
||||
uses: actions/checkout@v4.1.7
|
||||
- name: Restore Python
|
||||
uses: ./.github/actions/restore-python
|
||||
with:
|
||||
|
@ -358,7 +358,7 @@ jobs:
|
|||
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.1.6
|
||||
uses: actions/checkout@v4.1.7
|
||||
- name: Restore Python
|
||||
uses: ./.github/actions/restore-python
|
||||
with:
|
||||
|
@ -387,6 +387,13 @@ jobs:
|
|||
echo "::add-matcher::.github/workflows/matchers/gcc.json"
|
||||
echo "::add-matcher::.github/workflows/matchers/clang-tidy.json"
|
||||
|
||||
- name: Run 'pio run --list-targets -e esp32-idf-tidy'
|
||||
if: matrix.name == 'Run script/clang-tidy for ESP32 IDF'
|
||||
run: |
|
||||
. venv/bin/activate
|
||||
mkdir -p .temp
|
||||
pio run --list-targets -e esp32-idf-tidy
|
||||
|
||||
- name: Run clang-tidy
|
||||
run: |
|
||||
. venv/bin/activate
|
||||
|
@ -410,7 +417,7 @@ jobs:
|
|||
count: ${{ steps.list-components.outputs.count }}
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.1.6
|
||||
uses: actions/checkout@v4.1.7
|
||||
with:
|
||||
# Fetch enough history so `git merge-base refs/remotes/origin/dev HEAD` works.
|
||||
fetch-depth: 500
|
||||
|
@ -454,11 +461,11 @@ jobs:
|
|||
matrix:
|
||||
file: ${{ fromJson(needs.list-components.outputs.components) }}
|
||||
steps:
|
||||
- name: Install libsodium
|
||||
run: sudo apt-get install libsodium-dev
|
||||
- name: Install dependencies
|
||||
run: sudo apt-get install libsodium-dev libsdl2-dev
|
||||
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.1.6
|
||||
uses: actions/checkout@v4.1.7
|
||||
- name: Restore Python
|
||||
uses: ./.github/actions/restore-python
|
||||
with:
|
||||
|
@ -484,7 +491,7 @@ jobs:
|
|||
matrix: ${{ steps.split.outputs.components }}
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.1.6
|
||||
uses: actions/checkout@v4.1.7
|
||||
- name: Split components into 20 groups
|
||||
id: split
|
||||
run: |
|
||||
|
@ -508,11 +515,11 @@ jobs:
|
|||
- name: List components
|
||||
run: echo ${{ matrix.components }}
|
||||
|
||||
- name: Install libsodium
|
||||
run: sudo apt-get install libsodium-dev
|
||||
- name: Install dependencies
|
||||
run: sudo apt-get install libsodium-dev libsdl2-dev
|
||||
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.1.6
|
||||
uses: actions/checkout@v4.1.7
|
||||
- name: Restore Python
|
||||
uses: ./.github/actions/restore-python
|
||||
with:
|
||||
|
|
18
.github/workflows/release.yml
vendored
18
.github/workflows/release.yml
vendored
|
@ -19,7 +19,7 @@ jobs:
|
|||
tag: ${{ steps.tag.outputs.tag }}
|
||||
branch_build: ${{ steps.tag.outputs.branch_build }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
- uses: actions/checkout@v4.1.7
|
||||
- name: Get tag
|
||||
id: tag
|
||||
# yamllint disable rule:line-length
|
||||
|
@ -51,7 +51,7 @@ jobs:
|
|||
contents: read
|
||||
id-token: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
- uses: actions/checkout@v4.1.7
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5.1.0
|
||||
with:
|
||||
|
@ -65,7 +65,7 @@ jobs:
|
|||
pip3 install build
|
||||
python3 -m build
|
||||
- name: Publish
|
||||
uses: pypa/gh-action-pypi-publish@v1.8.14
|
||||
uses: pypa/gh-action-pypi-publish@v1.9.0
|
||||
|
||||
deploy-docker:
|
||||
name: Build ESPHome ${{ matrix.platform }}
|
||||
|
@ -83,7 +83,7 @@ jobs:
|
|||
- linux/arm/v7
|
||||
- linux/arm64
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
- uses: actions/checkout@v4.1.7
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5.1.0
|
||||
with:
|
||||
|
@ -96,12 +96,12 @@ jobs:
|
|||
uses: docker/setup-qemu-action@v3.0.0
|
||||
|
||||
- name: Log in to docker hub
|
||||
uses: docker/login-action@v3.1.0
|
||||
uses: docker/login-action@v3.2.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USER }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
- name: Log in to the GitHub container registry
|
||||
uses: docker/login-action@v3.1.0
|
||||
uses: docker/login-action@v3.2.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
|
@ -174,7 +174,7 @@ jobs:
|
|||
- ghcr
|
||||
- dockerhub
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
- uses: actions/checkout@v4.1.7
|
||||
|
||||
- name: Download digests
|
||||
uses: actions/download-artifact@v4.1.7
|
||||
|
@ -188,13 +188,13 @@ jobs:
|
|||
|
||||
- name: Log in to docker hub
|
||||
if: matrix.registry == 'dockerhub'
|
||||
uses: docker/login-action@v3.1.0
|
||||
uses: docker/login-action@v3.2.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USER }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
- name: Log in to the GitHub container registry
|
||||
if: matrix.registry == 'ghcr'
|
||||
uses: docker/login-action@v3.1.0
|
||||
uses: docker/login-action@v3.2.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
|
|
6
.github/workflows/sync-device-classes.yml
vendored
6
.github/workflows/sync-device-classes.yml
vendored
|
@ -13,10 +13,10 @@ jobs:
|
|||
if: github.repository == 'esphome/esphome'
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4.1.6
|
||||
uses: actions/checkout@v4.1.7
|
||||
|
||||
- name: Checkout Home Assistant
|
||||
uses: actions/checkout@v4.1.6
|
||||
uses: actions/checkout@v4.1.7
|
||||
with:
|
||||
repository: home-assistant/core
|
||||
path: lib/home-assistant
|
||||
|
@ -36,7 +36,7 @@ jobs:
|
|||
python ./script/sync-device_class.py
|
||||
|
||||
- name: Commit changes
|
||||
uses: peter-evans/create-pull-request@v6.0.4
|
||||
uses: peter-evans/create-pull-request@v6.1.0
|
||||
with:
|
||||
commit-message: "Synchronise Device Classes from Home Assistant"
|
||||
committer: esphomebot <esphome@nabucasa.com>
|
||||
|
|
2
.github/workflows/yaml-lint.yml
vendored
2
.github/workflows/yaml-lint.yml
vendored
|
@ -18,7 +18,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.1.6
|
||||
uses: actions/checkout@v4.1.7
|
||||
- name: Run yamllint
|
||||
uses: frenck/action-yamllint@v1.5.0
|
||||
with:
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
# See https://pre-commit.com/hooks.html for more hooks
|
||||
repos:
|
||||
- repo: https://github.com/psf/black-pre-commit-mirror
|
||||
rev: 24.2.0
|
||||
rev: 24.4.2
|
||||
hooks:
|
||||
- id: black
|
||||
args:
|
||||
|
|
13
CODEOWNERS
13
CODEOWNERS
|
@ -94,6 +94,7 @@ esphome/components/current_based/* @djwmarcx
|
|||
esphome/components/dac7678/* @NickB1
|
||||
esphome/components/daikin_arc/* @MagicBear
|
||||
esphome/components/daikin_brc/* @hagak
|
||||
esphome/components/dallas_temp/* @ssieb
|
||||
esphome/components/daly_bms/* @s1lvi0
|
||||
esphome/components/dashboard_import/* @esphome/core
|
||||
esphome/components/datetime/* @jesserockz @rfdarter
|
||||
|
@ -144,6 +145,7 @@ esphome/components/gdk101/* @Szewcson
|
|||
esphome/components/globals/* @esphome/core
|
||||
esphome/components/gp8403/* @jesserockz
|
||||
esphome/components/gpio/* @esphome/core
|
||||
esphome/components/gpio/one_wire/* @ssieb
|
||||
esphome/components/gps/* @coogle
|
||||
esphome/components/graph/* @synco
|
||||
esphome/components/graphical_display_menu/* @MrMDavidson
|
||||
|
@ -167,9 +169,12 @@ esphome/components/homeassistant/* @OttoWinter
|
|||
esphome/components/honeywell_hih_i2c/* @Benichou34
|
||||
esphome/components/honeywellabp/* @RubyBailey
|
||||
esphome/components/honeywellabp2_i2c/* @jpfaff
|
||||
esphome/components/host/* @esphome/core
|
||||
esphome/components/host/* @clydebarrow @esphome/core
|
||||
esphome/components/host/time/* @clydebarrow
|
||||
esphome/components/hrxl_maxsonar_wr/* @netmikey
|
||||
esphome/components/hte501/* @Stock-M
|
||||
esphome/components/http_request/ota/* @oarcher
|
||||
esphome/components/http_request/update/* @jesserockz
|
||||
esphome/components/htu31d/* @betterengineering
|
||||
esphome/components/hydreon_rgxx/* @functionpointer
|
||||
esphome/components/hyt271/* @Philippe12
|
||||
|
@ -210,6 +215,7 @@ esphome/components/lilygo_t5_47/touchscreen/* @jesserockz
|
|||
esphome/components/lock/* @esphome/core
|
||||
esphome/components/logger/* @esphome/core
|
||||
esphome/components/ltr390/* @sjtrny
|
||||
esphome/components/ltr_als_ps/* @latonita
|
||||
esphome/components/matrix_keypad/* @ssieb
|
||||
esphome/components/max31865/* @DAVe3283
|
||||
esphome/components/max44009/* @berfenger
|
||||
|
@ -266,6 +272,7 @@ esphome/components/nextion/text_sensor/* @senexcrenshaw
|
|||
esphome/components/nfc/* @jesserockz @kbx81
|
||||
esphome/components/noblex/* @AGalfra
|
||||
esphome/components/number/* @esphome/core
|
||||
esphome/components/one_wire/* @ssieb
|
||||
esphome/components/ota/* @esphome/core
|
||||
esphome/components/output/* @esphome/core
|
||||
esphome/components/pca6416a/* @Mat931
|
||||
|
@ -313,6 +320,7 @@ esphome/components/rtttl/* @glmnet
|
|||
esphome/components/safe_mode/* @jsuanet @kbx81 @paulmonigatti
|
||||
esphome/components/scd4x/* @martgras @sjtrny
|
||||
esphome/components/script/* @esphome/core
|
||||
esphome/components/sdl/* @clydebarrow
|
||||
esphome/components/sdm_meter/* @jesserockz @polyfaces
|
||||
esphome/components/sdp3x/* @Azimath
|
||||
esphome/components/seeed_mr24hpc1/* @limengdu
|
||||
|
@ -407,6 +415,7 @@ esphome/components/uart/button/* @ssieb
|
|||
esphome/components/ufire_ec/* @pvizeli
|
||||
esphome/components/ufire_ise/* @pvizeli
|
||||
esphome/components/ultrasonic/* @OttoWinter
|
||||
esphome/components/update/* @jesserockz
|
||||
esphome/components/uponor_smatrix/* @kroimon
|
||||
esphome/components/valve/* @esphome/core
|
||||
esphome/components/vbus/* @ssieb
|
||||
|
@ -414,7 +423,7 @@ esphome/components/veml3235/* @kbx81
|
|||
esphome/components/veml7700/* @latonita
|
||||
esphome/components/version/* @esphome/core
|
||||
esphome/components/voice_assistant/* @jesserockz
|
||||
esphome/components/wake_on_lan/* @willwill2will54
|
||||
esphome/components/wake_on_lan/* @clydebarrow @willwill2will54
|
||||
esphome/components/waveshare_epaper/* @clydebarrow
|
||||
esphome/components/web_server_base/* @OttoWinter
|
||||
esphome/components/web_server_idf/* @dentra
|
||||
|
|
|
@ -81,7 +81,8 @@ RUN \
|
|||
fi; \
|
||||
pip3 install \
|
||||
--break-system-packages --no-cache-dir \
|
||||
platformio==6.1.13 \
|
||||
# Keep platformio version in sync with requirements.txt
|
||||
platformio==6.1.15 \
|
||||
# Change some platformio settings
|
||||
&& platformio settings set enable_telemetry No \
|
||||
&& platformio settings set check_platformio_interval 1000000 \
|
||||
|
@ -100,6 +101,9 @@ RUN --mount=type=tmpfs,target=/root/.cargo if [ "$TARGETARCH$TARGETVARIANT" = "a
|
|||
--break-system-packages --no-cache-dir -r /requirements.txt -r /requirements_optional.txt \
|
||||
&& /platformio_install_deps.py /platformio.ini --libraries
|
||||
|
||||
# Avoid unsafe git error when container user and file config volume permissions don't match
|
||||
RUN git config --system --add safe.directory '*'
|
||||
|
||||
|
||||
# ======================= docker-type image =======================
|
||||
FROM base AS docker
|
||||
|
|
|
@ -488,6 +488,15 @@ def command_run(args, config):
|
|||
if exit_code != 0:
|
||||
return exit_code
|
||||
_LOGGER.info("Successfully compiled program.")
|
||||
if CORE.is_host:
|
||||
from esphome.platformio_api import get_idedata
|
||||
|
||||
idedata = get_idedata(config)
|
||||
if idedata is None:
|
||||
return 1
|
||||
program_path = idedata.raw["prog_path"]
|
||||
return run_external_process(program_path)
|
||||
|
||||
port = choose_upload_log_host(
|
||||
default=args.device,
|
||||
check_default=None,
|
||||
|
|
|
@ -58,6 +58,7 @@ from esphome.cpp_types import ( # noqa
|
|||
bool_,
|
||||
int_,
|
||||
std_ns,
|
||||
std_shared_ptr,
|
||||
std_string,
|
||||
std_vector,
|
||||
uint8,
|
||||
|
|
|
@ -4,11 +4,11 @@ from esphome.const import (
|
|||
STATE_CLASS_MEASUREMENT,
|
||||
ICON_ARROW_EXPAND_VERTICAL,
|
||||
DEVICE_CLASS_DISTANCE,
|
||||
UNIT_MILLIMETER,
|
||||
)
|
||||
|
||||
CODEOWNERS = ["@TH-Braemer"]
|
||||
DEPENDENCIES = ["uart"]
|
||||
UNIT_MILLIMETERS = "mm"
|
||||
|
||||
a02yyuw_ns = cg.esphome_ns.namespace("a02yyuw")
|
||||
A02yyuwComponent = a02yyuw_ns.class_(
|
||||
|
@ -17,7 +17,7 @@ A02yyuwComponent = a02yyuw_ns.class_(
|
|||
|
||||
CONFIG_SCHEMA = sensor.sensor_schema(
|
||||
A02yyuwComponent,
|
||||
unit_of_measurement=UNIT_MILLIMETERS,
|
||||
unit_of_measurement=UNIT_MILLIMETER,
|
||||
icon=ICON_ARROW_EXPAND_VERTICAL,
|
||||
accuracy_decimals=0,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
|
|
|
@ -11,6 +11,8 @@
|
|||
#include "ade7880_registers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#include <cinttypes>
|
||||
|
||||
namespace esphome {
|
||||
namespace ade7880 {
|
||||
|
||||
|
@ -156,7 +158,7 @@ void ADE7880::update() {
|
|||
});
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, "update took %u ms", millis() - start);
|
||||
ESP_LOGD(TAG, "update took %" PRIu32 " ms", millis() - start);
|
||||
}
|
||||
|
||||
void ADE7880::dump_config() {
|
||||
|
@ -176,9 +178,9 @@ void ADE7880::dump_config() {
|
|||
LOG_SENSOR(" ", "Forward Active Energy", this->channel_a_->forward_active_energy);
|
||||
LOG_SENSOR(" ", "Reverse Active Energy", this->channel_a_->reverse_active_energy);
|
||||
ESP_LOGCONFIG(TAG, " Calibration:");
|
||||
ESP_LOGCONFIG(TAG, " Current: %u", this->channel_a_->current_gain_calibration);
|
||||
ESP_LOGCONFIG(TAG, " Voltage: %d", this->channel_a_->voltage_gain_calibration);
|
||||
ESP_LOGCONFIG(TAG, " Power: %d", this->channel_a_->power_gain_calibration);
|
||||
ESP_LOGCONFIG(TAG, " Current: %" PRId32, this->channel_a_->current_gain_calibration);
|
||||
ESP_LOGCONFIG(TAG, " Voltage: %" PRId32, this->channel_a_->voltage_gain_calibration);
|
||||
ESP_LOGCONFIG(TAG, " Power: %" PRId32, this->channel_a_->power_gain_calibration);
|
||||
ESP_LOGCONFIG(TAG, " Phase Angle: %u", this->channel_a_->phase_angle_calibration);
|
||||
}
|
||||
|
||||
|
@ -192,9 +194,9 @@ void ADE7880::dump_config() {
|
|||
LOG_SENSOR(" ", "Forward Active Energy", this->channel_b_->forward_active_energy);
|
||||
LOG_SENSOR(" ", "Reverse Active Energy", this->channel_b_->reverse_active_energy);
|
||||
ESP_LOGCONFIG(TAG, " Calibration:");
|
||||
ESP_LOGCONFIG(TAG, " Current: %u", this->channel_b_->current_gain_calibration);
|
||||
ESP_LOGCONFIG(TAG, " Voltage: %d", this->channel_b_->voltage_gain_calibration);
|
||||
ESP_LOGCONFIG(TAG, " Power: %d", this->channel_b_->power_gain_calibration);
|
||||
ESP_LOGCONFIG(TAG, " Current: %" PRId32, this->channel_b_->current_gain_calibration);
|
||||
ESP_LOGCONFIG(TAG, " Voltage: %" PRId32, this->channel_b_->voltage_gain_calibration);
|
||||
ESP_LOGCONFIG(TAG, " Power: %" PRId32, this->channel_b_->power_gain_calibration);
|
||||
ESP_LOGCONFIG(TAG, " Phase Angle: %u", this->channel_b_->phase_angle_calibration);
|
||||
}
|
||||
|
||||
|
@ -208,9 +210,9 @@ void ADE7880::dump_config() {
|
|||
LOG_SENSOR(" ", "Forward Active Energy", this->channel_c_->forward_active_energy);
|
||||
LOG_SENSOR(" ", "Reverse Active Energy", this->channel_c_->reverse_active_energy);
|
||||
ESP_LOGCONFIG(TAG, " Calibration:");
|
||||
ESP_LOGCONFIG(TAG, " Current: %u", this->channel_c_->current_gain_calibration);
|
||||
ESP_LOGCONFIG(TAG, " Voltage: %d", this->channel_c_->voltage_gain_calibration);
|
||||
ESP_LOGCONFIG(TAG, " Power: %d", this->channel_c_->power_gain_calibration);
|
||||
ESP_LOGCONFIG(TAG, " Current: %" PRId32, this->channel_c_->current_gain_calibration);
|
||||
ESP_LOGCONFIG(TAG, " Voltage: %" PRId32, this->channel_c_->voltage_gain_calibration);
|
||||
ESP_LOGCONFIG(TAG, " Power: %" PRId32, this->channel_c_->power_gain_calibration);
|
||||
ESP_LOGCONFIG(TAG, " Phase Angle: %u", this->channel_c_->phase_angle_calibration);
|
||||
}
|
||||
|
||||
|
@ -218,7 +220,7 @@ void ADE7880::dump_config() {
|
|||
ESP_LOGCONFIG(TAG, " Neutral:");
|
||||
LOG_SENSOR(" ", "Current", this->channel_n_->current);
|
||||
ESP_LOGCONFIG(TAG, " Calibration:");
|
||||
ESP_LOGCONFIG(TAG, " Current: %u", this->channel_n_->current_gain_calibration);
|
||||
ESP_LOGCONFIG(TAG, " Current: %" PRId32, this->channel_n_->current_gain_calibration);
|
||||
}
|
||||
|
||||
LOG_I2C_DEVICE(this);
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
#include "ade7953_base.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#include <cinttypes>
|
||||
|
||||
namespace esphome {
|
||||
namespace ade7953_base {
|
||||
|
||||
|
@ -105,7 +107,7 @@ void ADE7953::update() {
|
|||
this->last_update_ = now;
|
||||
// prevent DIV/0
|
||||
pf = ADE_WATTSEC_POWER_FACTOR * (diff < 10 ? 10 : diff) / 1000;
|
||||
ESP_LOGVV(TAG, "ADE7953::update() diff=%d pf=%f", diff, pf);
|
||||
ESP_LOGVV(TAG, "ADE7953::update() diff=%" PRIu32 " pf=%f", diff, pf);
|
||||
}
|
||||
|
||||
// Apparent power
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
#include "ags10.h"
|
||||
|
||||
#include <cinttypes>
|
||||
|
||||
namespace esphome {
|
||||
namespace ags10 {
|
||||
static const char *const TAG = "ags10";
|
||||
|
@ -35,7 +37,7 @@ void AGS10Component::setup() {
|
|||
|
||||
auto resistance = this->read_resistance_();
|
||||
if (resistance) {
|
||||
ESP_LOGD(TAG, "AGS10 Sensor Resistance: 0x%08X", *resistance);
|
||||
ESP_LOGD(TAG, "AGS10 Sensor Resistance: 0x%08" PRIX32, *resistance);
|
||||
if (this->resistance_ != nullptr) {
|
||||
this->resistance_->publish_state(*resistance);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import web_server
|
||||
from esphome import automation
|
||||
from esphome.automation import maybe_simple_id
|
||||
from esphome.core import CORE, coroutine_with_priority
|
||||
|
@ -8,6 +9,7 @@ from esphome.const import (
|
|||
CONF_ON_STATE,
|
||||
CONF_TRIGGER_ID,
|
||||
CONF_CODE,
|
||||
CONF_WEB_SERVER_ID,
|
||||
)
|
||||
from esphome.cpp_helpers import setup_entity
|
||||
|
||||
|
@ -76,6 +78,8 @@ AlarmControlPanelCondition = alarm_control_panel_ns.class_(
|
|||
)
|
||||
|
||||
ALARM_CONTROL_PANEL_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(
|
||||
web_server.WEBSERVER_SORTING_SCHEMA
|
||||
).extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(AlarmControlPanel),
|
||||
cv.Optional(CONF_ON_STATE): automation.validate_automation(
|
||||
|
@ -185,6 +189,9 @@ async def setup_alarm_control_panel_core_(var, config):
|
|||
for conf in config.get(CONF_ON_READY, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [], conf)
|
||||
if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None:
|
||||
web_server_ = await cg.get_variable(webserver_id)
|
||||
web_server.add_entity_to_sorting_list(web_server_, var, config)
|
||||
|
||||
|
||||
async def register_alarm_control_panel(var, config):
|
||||
|
|
|
@ -3,7 +3,13 @@ import logging
|
|||
from esphome import automation, core
|
||||
from esphome.components import font
|
||||
import esphome.components.image as espImage
|
||||
from esphome.components.image import CONF_USE_TRANSPARENCY
|
||||
from esphome.components.image import (
|
||||
CONF_USE_TRANSPARENCY,
|
||||
LOCAL_SCHEMA,
|
||||
WEB_SCHEMA,
|
||||
SOURCE_WEB,
|
||||
SOURCE_LOCAL,
|
||||
)
|
||||
import esphome.config_validation as cv
|
||||
import esphome.codegen as cg
|
||||
from esphome.const import (
|
||||
|
@ -13,6 +19,9 @@ from esphome.const import (
|
|||
CONF_REPEAT,
|
||||
CONF_RESIZE,
|
||||
CONF_TYPE,
|
||||
CONF_SOURCE,
|
||||
CONF_PATH,
|
||||
CONF_URL,
|
||||
)
|
||||
from esphome.core import CORE, HexInt
|
||||
|
||||
|
@ -43,6 +52,40 @@ SetFrameAction = animation_ns.class_(
|
|||
"AnimationSetFrameAction", automation.Action, cg.Parented.template(Animation_)
|
||||
)
|
||||
|
||||
TYPED_FILE_SCHEMA = cv.typed_schema(
|
||||
{
|
||||
SOURCE_LOCAL: LOCAL_SCHEMA,
|
||||
SOURCE_WEB: WEB_SCHEMA,
|
||||
},
|
||||
key=CONF_SOURCE,
|
||||
)
|
||||
|
||||
|
||||
def _file_schema(value):
|
||||
if isinstance(value, str):
|
||||
return validate_file_shorthand(value)
|
||||
return TYPED_FILE_SCHEMA(value)
|
||||
|
||||
|
||||
FILE_SCHEMA = cv.Schema(_file_schema)
|
||||
|
||||
|
||||
def validate_file_shorthand(value):
|
||||
value = cv.string_strict(value)
|
||||
if value.startswith("http://") or value.startswith("https://"):
|
||||
return FILE_SCHEMA(
|
||||
{
|
||||
CONF_SOURCE: SOURCE_WEB,
|
||||
CONF_URL: value,
|
||||
}
|
||||
)
|
||||
return FILE_SCHEMA(
|
||||
{
|
||||
CONF_SOURCE: SOURCE_LOCAL,
|
||||
CONF_PATH: value,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def validate_cross_dependencies(config):
|
||||
"""
|
||||
|
@ -67,7 +110,7 @@ ANIMATION_SCHEMA = cv.Schema(
|
|||
cv.All(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.declare_id(Animation_),
|
||||
cv.Required(CONF_FILE): cv.file_,
|
||||
cv.Required(CONF_FILE): FILE_SCHEMA,
|
||||
cv.Optional(CONF_RESIZE): cv.dimensions,
|
||||
cv.Optional(CONF_TYPE, default="BINARY"): cv.enum(
|
||||
espImage.IMAGE_TYPE, upper=True
|
||||
|
@ -124,7 +167,11 @@ async def animation_action_to_code(config, action_id, template_arg, args):
|
|||
async def to_code(config):
|
||||
from PIL import Image
|
||||
|
||||
path = CORE.relative_config_path(config[CONF_FILE])
|
||||
conf_file = config[CONF_FILE]
|
||||
if conf_file[CONF_SOURCE] == SOURCE_LOCAL:
|
||||
path = CORE.relative_config_path(conf_file[CONF_PATH])
|
||||
elif conf_file[CONF_SOURCE] == SOURCE_WEB:
|
||||
path = espImage.compute_local_image_path(conf_file).as_posix()
|
||||
try:
|
||||
image = Image.open(path)
|
||||
except Exception as e:
|
||||
|
|
|
@ -48,6 +48,7 @@ service APIConnection {
|
|||
rpc date_command (DateCommandRequest) returns (void) {}
|
||||
rpc time_command (TimeCommandRequest) returns (void) {}
|
||||
rpc datetime_command (DateTimeCommandRequest) returns (void) {}
|
||||
rpc update_command (UpdateCommandRequest) returns (void) {}
|
||||
|
||||
rpc subscribe_bluetooth_le_advertisements(SubscribeBluetoothLEAdvertisementsRequest) returns (void) {}
|
||||
rpc bluetooth_device_request(BluetoothDeviceRequest) returns (void) {}
|
||||
|
@ -1517,6 +1518,25 @@ message VoiceAssistantAudio {
|
|||
bool end = 2;
|
||||
}
|
||||
|
||||
enum VoiceAssistantTimerEvent {
|
||||
VOICE_ASSISTANT_TIMER_STARTED = 0;
|
||||
VOICE_ASSISTANT_TIMER_UPDATED = 1;
|
||||
VOICE_ASSISTANT_TIMER_CANCELLED = 2;
|
||||
VOICE_ASSISTANT_TIMER_FINISHED = 3;
|
||||
}
|
||||
|
||||
message VoiceAssistantTimerEventResponse {
|
||||
option (id) = 115;
|
||||
option (source) = SOURCE_CLIENT;
|
||||
option (ifdef) = "USE_VOICE_ASSISTANT";
|
||||
|
||||
VoiceAssistantTimerEvent event_type = 1;
|
||||
string timer_id = 2;
|
||||
string name = 3;
|
||||
uint32 total_seconds = 4;
|
||||
uint32 seconds_left = 5;
|
||||
bool is_active = 6;
|
||||
}
|
||||
|
||||
// ==================== ALARM CONTROL PANEL ====================
|
||||
enum AlarmControlPanelState {
|
||||
|
@ -1818,3 +1838,46 @@ message DateTimeCommandRequest {
|
|||
fixed32 key = 1;
|
||||
fixed32 epoch_seconds = 2;
|
||||
}
|
||||
|
||||
// ==================== UPDATE ====================
|
||||
message ListEntitiesUpdateResponse {
|
||||
option (id) = 116;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_UPDATE";
|
||||
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
string name = 3;
|
||||
string unique_id = 4;
|
||||
|
||||
string icon = 5;
|
||||
bool disabled_by_default = 6;
|
||||
EntityCategory entity_category = 7;
|
||||
string device_class = 8;
|
||||
}
|
||||
message UpdateStateResponse {
|
||||
option (id) = 117;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_UPDATE";
|
||||
option (no_delay) = true;
|
||||
|
||||
fixed32 key = 1;
|
||||
bool missing_state = 2;
|
||||
bool in_progress = 3;
|
||||
bool has_progress = 4;
|
||||
float progress = 5;
|
||||
string current_version = 6;
|
||||
string latest_version = 7;
|
||||
string title = 8;
|
||||
string release_summary = 9;
|
||||
string release_url = 10;
|
||||
}
|
||||
message UpdateCommandRequest {
|
||||
option (id) = 118;
|
||||
option (source) = SOURCE_CLIENT;
|
||||
option (ifdef) = "USE_UPDATE";
|
||||
option (no_delay) = true;
|
||||
|
||||
fixed32 key = 1;
|
||||
bool install = 2;
|
||||
}
|
||||
|
|
|
@ -1193,6 +1193,15 @@ void APIConnection::on_voice_assistant_audio(const VoiceAssistantAudio &msg) {
|
|||
voice_assistant::global_voice_assistant->on_audio(msg);
|
||||
}
|
||||
};
|
||||
void APIConnection::on_voice_assistant_timer_event_response(const VoiceAssistantTimerEventResponse &msg) {
|
||||
if (voice_assistant::global_voice_assistant != nullptr) {
|
||||
if (voice_assistant::global_voice_assistant->get_api_connection() != this) {
|
||||
return;
|
||||
}
|
||||
|
||||
voice_assistant::global_voice_assistant->on_timer_event(msg);
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
|
@ -1278,6 +1287,51 @@ bool APIConnection::send_event_info(event::Event *event) {
|
|||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_UPDATE
|
||||
bool APIConnection::send_update_state(update::UpdateEntity *update) {
|
||||
if (!this->state_subscription_)
|
||||
return false;
|
||||
|
||||
UpdateStateResponse resp{};
|
||||
resp.key = update->get_object_id_hash();
|
||||
resp.missing_state = !update->has_state();
|
||||
if (update->has_state()) {
|
||||
resp.in_progress = update->state == update::UpdateState::UPDATE_STATE_INSTALLING;
|
||||
if (update->update_info.has_progress) {
|
||||
resp.has_progress = true;
|
||||
resp.progress = update->update_info.progress;
|
||||
}
|
||||
resp.current_version = update->update_info.current_version;
|
||||
resp.latest_version = update->update_info.latest_version;
|
||||
resp.title = update->update_info.title;
|
||||
resp.release_summary = update->update_info.summary;
|
||||
resp.release_url = update->update_info.release_url;
|
||||
}
|
||||
|
||||
return this->send_update_state_response(resp);
|
||||
}
|
||||
bool APIConnection::send_update_info(update::UpdateEntity *update) {
|
||||
ListEntitiesUpdateResponse msg;
|
||||
msg.key = update->get_object_id_hash();
|
||||
msg.object_id = update->get_object_id();
|
||||
if (update->has_own_name())
|
||||
msg.name = update->get_name();
|
||||
msg.unique_id = get_default_unique_id("update", update);
|
||||
msg.icon = update->get_icon();
|
||||
msg.disabled_by_default = update->is_disabled_by_default();
|
||||
msg.entity_category = static_cast<enums::EntityCategory>(update->get_entity_category());
|
||||
msg.device_class = update->get_device_class();
|
||||
return this->send_list_entities_update_response(msg);
|
||||
}
|
||||
void APIConnection::update_command(const UpdateCommandRequest &msg) {
|
||||
update::UpdateEntity *update = App.get_update_by_key(msg.key);
|
||||
if (update == nullptr)
|
||||
return;
|
||||
|
||||
update->perform();
|
||||
}
|
||||
#endif
|
||||
|
||||
bool APIConnection::send_log_message(int level, const char *tag, const char *line) {
|
||||
if (this->log_subscription_ < level)
|
||||
return false;
|
||||
|
|
|
@ -150,6 +150,7 @@ class APIConnection : public APIServerConnection {
|
|||
void on_voice_assistant_response(const VoiceAssistantResponse &msg) override;
|
||||
void on_voice_assistant_event_response(const VoiceAssistantEventResponse &msg) override;
|
||||
void on_voice_assistant_audio(const VoiceAssistantAudio &msg) override;
|
||||
void on_voice_assistant_timer_event_response(const VoiceAssistantTimerEventResponse &msg) override;
|
||||
#endif
|
||||
|
||||
#ifdef USE_ALARM_CONTROL_PANEL
|
||||
|
@ -163,6 +164,12 @@ class APIConnection : public APIServerConnection {
|
|||
bool send_event_info(event::Event *event);
|
||||
#endif
|
||||
|
||||
#ifdef USE_UPDATE
|
||||
bool send_update_state(update::UpdateEntity *update);
|
||||
bool send_update_info(update::UpdateEntity *update);
|
||||
void update_command(const UpdateCommandRequest &msg) override;
|
||||
#endif
|
||||
|
||||
void on_disconnect_response(const DisconnectResponse &value) override;
|
||||
void on_ping_response(const PingResponse &value) override {
|
||||
// we initiated ping
|
||||
|
|
|
@ -475,6 +475,22 @@ template<> const char *proto_enum_to_string<enums::VoiceAssistantEvent>(enums::V
|
|||
}
|
||||
#endif
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
template<> const char *proto_enum_to_string<enums::VoiceAssistantTimerEvent>(enums::VoiceAssistantTimerEvent value) {
|
||||
switch (value) {
|
||||
case enums::VOICE_ASSISTANT_TIMER_STARTED:
|
||||
return "VOICE_ASSISTANT_TIMER_STARTED";
|
||||
case enums::VOICE_ASSISTANT_TIMER_UPDATED:
|
||||
return "VOICE_ASSISTANT_TIMER_UPDATED";
|
||||
case enums::VOICE_ASSISTANT_TIMER_CANCELLED:
|
||||
return "VOICE_ASSISTANT_TIMER_CANCELLED";
|
||||
case enums::VOICE_ASSISTANT_TIMER_FINISHED:
|
||||
return "VOICE_ASSISTANT_TIMER_FINISHED";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
template<> const char *proto_enum_to_string<enums::AlarmControlPanelState>(enums::AlarmControlPanelState value) {
|
||||
switch (value) {
|
||||
case enums::ALARM_STATE_DISARMED:
|
||||
|
@ -6857,6 +6873,82 @@ void VoiceAssistantAudio::dump_to(std::string &out) const {
|
|||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
bool VoiceAssistantTimerEventResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
switch (field_id) {
|
||||
case 1: {
|
||||
this->event_type = value.as_enum<enums::VoiceAssistantTimerEvent>();
|
||||
return true;
|
||||
}
|
||||
case 4: {
|
||||
this->total_seconds = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
case 5: {
|
||||
this->seconds_left = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
case 6: {
|
||||
this->is_active = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bool VoiceAssistantTimerEventResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
||||
switch (field_id) {
|
||||
case 2: {
|
||||
this->timer_id = value.as_string();
|
||||
return true;
|
||||
}
|
||||
case 3: {
|
||||
this->name = value.as_string();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
void VoiceAssistantTimerEventResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_enum<enums::VoiceAssistantTimerEvent>(1, this->event_type);
|
||||
buffer.encode_string(2, this->timer_id);
|
||||
buffer.encode_string(3, this->name);
|
||||
buffer.encode_uint32(4, this->total_seconds);
|
||||
buffer.encode_uint32(5, this->seconds_left);
|
||||
buffer.encode_bool(6, this->is_active);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void VoiceAssistantTimerEventResponse::dump_to(std::string &out) const {
|
||||
__attribute__((unused)) char buffer[64];
|
||||
out.append("VoiceAssistantTimerEventResponse {\n");
|
||||
out.append(" event_type: ");
|
||||
out.append(proto_enum_to_string<enums::VoiceAssistantTimerEvent>(this->event_type));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" timer_id: ");
|
||||
out.append("'").append(this->timer_id).append("'");
|
||||
out.append("\n");
|
||||
|
||||
out.append(" name: ");
|
||||
out.append("'").append(this->name).append("'");
|
||||
out.append("\n");
|
||||
|
||||
out.append(" total_seconds: ");
|
||||
sprintf(buffer, "%" PRIu32, this->total_seconds);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" seconds_left: ");
|
||||
sprintf(buffer, "%" PRIu32, this->seconds_left);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" is_active: ");
|
||||
out.append(YESNO(this->is_active));
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
bool ListEntitiesAlarmControlPanelResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
switch (field_id) {
|
||||
case 6: {
|
||||
|
@ -8284,6 +8376,262 @@ void DateTimeCommandRequest::dump_to(std::string &out) const {
|
|||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
bool ListEntitiesUpdateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
switch (field_id) {
|
||||
case 6: {
|
||||
this->disabled_by_default = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
case 7: {
|
||||
this->entity_category = value.as_enum<enums::EntityCategory>();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bool ListEntitiesUpdateResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
||||
switch (field_id) {
|
||||
case 1: {
|
||||
this->object_id = value.as_string();
|
||||
return true;
|
||||
}
|
||||
case 3: {
|
||||
this->name = value.as_string();
|
||||
return true;
|
||||
}
|
||||
case 4: {
|
||||
this->unique_id = value.as_string();
|
||||
return true;
|
||||
}
|
||||
case 5: {
|
||||
this->icon = value.as_string();
|
||||
return true;
|
||||
}
|
||||
case 8: {
|
||||
this->device_class = value.as_string();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bool ListEntitiesUpdateResponse::decode_32bit(uint32_t field_id, Proto32Bit value) {
|
||||
switch (field_id) {
|
||||
case 2: {
|
||||
this->key = value.as_fixed32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
void ListEntitiesUpdateResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_string(1, this->object_id);
|
||||
buffer.encode_fixed32(2, this->key);
|
||||
buffer.encode_string(3, this->name);
|
||||
buffer.encode_string(4, this->unique_id);
|
||||
buffer.encode_string(5, this->icon);
|
||||
buffer.encode_bool(6, this->disabled_by_default);
|
||||
buffer.encode_enum<enums::EntityCategory>(7, this->entity_category);
|
||||
buffer.encode_string(8, this->device_class);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void ListEntitiesUpdateResponse::dump_to(std::string &out) const {
|
||||
__attribute__((unused)) char buffer[64];
|
||||
out.append("ListEntitiesUpdateResponse {\n");
|
||||
out.append(" object_id: ");
|
||||
out.append("'").append(this->object_id).append("'");
|
||||
out.append("\n");
|
||||
|
||||
out.append(" key: ");
|
||||
sprintf(buffer, "%" PRIu32, this->key);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" name: ");
|
||||
out.append("'").append(this->name).append("'");
|
||||
out.append("\n");
|
||||
|
||||
out.append(" unique_id: ");
|
||||
out.append("'").append(this->unique_id).append("'");
|
||||
out.append("\n");
|
||||
|
||||
out.append(" icon: ");
|
||||
out.append("'").append(this->icon).append("'");
|
||||
out.append("\n");
|
||||
|
||||
out.append(" disabled_by_default: ");
|
||||
out.append(YESNO(this->disabled_by_default));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" entity_category: ");
|
||||
out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" device_class: ");
|
||||
out.append("'").append(this->device_class).append("'");
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
bool UpdateStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
switch (field_id) {
|
||||
case 2: {
|
||||
this->missing_state = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
case 3: {
|
||||
this->in_progress = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
case 4: {
|
||||
this->has_progress = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bool UpdateStateResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
||||
switch (field_id) {
|
||||
case 6: {
|
||||
this->current_version = value.as_string();
|
||||
return true;
|
||||
}
|
||||
case 7: {
|
||||
this->latest_version = value.as_string();
|
||||
return true;
|
||||
}
|
||||
case 8: {
|
||||
this->title = value.as_string();
|
||||
return true;
|
||||
}
|
||||
case 9: {
|
||||
this->release_summary = value.as_string();
|
||||
return true;
|
||||
}
|
||||
case 10: {
|
||||
this->release_url = value.as_string();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bool UpdateStateResponse::decode_32bit(uint32_t field_id, Proto32Bit value) {
|
||||
switch (field_id) {
|
||||
case 1: {
|
||||
this->key = value.as_fixed32();
|
||||
return true;
|
||||
}
|
||||
case 5: {
|
||||
this->progress = value.as_float();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
void UpdateStateResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_fixed32(1, this->key);
|
||||
buffer.encode_bool(2, this->missing_state);
|
||||
buffer.encode_bool(3, this->in_progress);
|
||||
buffer.encode_bool(4, this->has_progress);
|
||||
buffer.encode_float(5, this->progress);
|
||||
buffer.encode_string(6, this->current_version);
|
||||
buffer.encode_string(7, this->latest_version);
|
||||
buffer.encode_string(8, this->title);
|
||||
buffer.encode_string(9, this->release_summary);
|
||||
buffer.encode_string(10, this->release_url);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void UpdateStateResponse::dump_to(std::string &out) const {
|
||||
__attribute__((unused)) char buffer[64];
|
||||
out.append("UpdateStateResponse {\n");
|
||||
out.append(" key: ");
|
||||
sprintf(buffer, "%" PRIu32, this->key);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" missing_state: ");
|
||||
out.append(YESNO(this->missing_state));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" in_progress: ");
|
||||
out.append(YESNO(this->in_progress));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" has_progress: ");
|
||||
out.append(YESNO(this->has_progress));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" progress: ");
|
||||
sprintf(buffer, "%g", this->progress);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" current_version: ");
|
||||
out.append("'").append(this->current_version).append("'");
|
||||
out.append("\n");
|
||||
|
||||
out.append(" latest_version: ");
|
||||
out.append("'").append(this->latest_version).append("'");
|
||||
out.append("\n");
|
||||
|
||||
out.append(" title: ");
|
||||
out.append("'").append(this->title).append("'");
|
||||
out.append("\n");
|
||||
|
||||
out.append(" release_summary: ");
|
||||
out.append("'").append(this->release_summary).append("'");
|
||||
out.append("\n");
|
||||
|
||||
out.append(" release_url: ");
|
||||
out.append("'").append(this->release_url).append("'");
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
bool UpdateCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
switch (field_id) {
|
||||
case 2: {
|
||||
this->install = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bool UpdateCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
|
||||
switch (field_id) {
|
||||
case 1: {
|
||||
this->key = value.as_fixed32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
void UpdateCommandRequest::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_fixed32(1, this->key);
|
||||
buffer.encode_bool(2, this->install);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void UpdateCommandRequest::dump_to(std::string &out) const {
|
||||
__attribute__((unused)) char buffer[64];
|
||||
out.append("UpdateCommandRequest {\n");
|
||||
out.append(" key: ");
|
||||
sprintf(buffer, "%" PRIu32, this->key);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" install: ");
|
||||
out.append(YESNO(this->install));
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace api
|
||||
} // namespace esphome
|
||||
|
|
|
@ -191,6 +191,12 @@ enum VoiceAssistantEvent : uint32_t {
|
|||
VOICE_ASSISTANT_TTS_STREAM_START = 98,
|
||||
VOICE_ASSISTANT_TTS_STREAM_END = 99,
|
||||
};
|
||||
enum VoiceAssistantTimerEvent : uint32_t {
|
||||
VOICE_ASSISTANT_TIMER_STARTED = 0,
|
||||
VOICE_ASSISTANT_TIMER_UPDATED = 1,
|
||||
VOICE_ASSISTANT_TIMER_CANCELLED = 2,
|
||||
VOICE_ASSISTANT_TIMER_FINISHED = 3,
|
||||
};
|
||||
enum AlarmControlPanelState : uint32_t {
|
||||
ALARM_STATE_DISARMED = 0,
|
||||
ALARM_STATE_ARMED_HOME = 1,
|
||||
|
@ -1775,6 +1781,23 @@ class VoiceAssistantAudio : public ProtoMessage {
|
|||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class VoiceAssistantTimerEventResponse : public ProtoMessage {
|
||||
public:
|
||||
enums::VoiceAssistantTimerEvent event_type{};
|
||||
std::string timer_id{};
|
||||
std::string name{};
|
||||
uint32_t total_seconds{0};
|
||||
uint32_t seconds_left{0};
|
||||
bool is_active{false};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class ListEntitiesAlarmControlPanelResponse : public ProtoMessage {
|
||||
public:
|
||||
std::string object_id{};
|
||||
|
@ -2107,6 +2130,61 @@ class DateTimeCommandRequest : public ProtoMessage {
|
|||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
};
|
||||
class ListEntitiesUpdateResponse : public ProtoMessage {
|
||||
public:
|
||||
std::string object_id{};
|
||||
uint32_t key{0};
|
||||
std::string name{};
|
||||
std::string unique_id{};
|
||||
std::string icon{};
|
||||
bool disabled_by_default{false};
|
||||
enums::EntityCategory entity_category{};
|
||||
std::string device_class{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class UpdateStateResponse : public ProtoMessage {
|
||||
public:
|
||||
uint32_t key{0};
|
||||
bool missing_state{false};
|
||||
bool in_progress{false};
|
||||
bool has_progress{false};
|
||||
float progress{0.0f};
|
||||
std::string current_version{};
|
||||
std::string latest_version{};
|
||||
std::string title{};
|
||||
std::string release_summary{};
|
||||
std::string release_url{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class UpdateCommandRequest : public ProtoMessage {
|
||||
public:
|
||||
uint32_t key{0};
|
||||
bool install{false};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
|
||||
} // namespace api
|
||||
} // namespace esphome
|
||||
|
|
|
@ -484,6 +484,8 @@ bool APIServerConnectionBase::send_voice_assistant_audio(const VoiceAssistantAud
|
|||
return this->send_message_<VoiceAssistantAudio>(msg, 106);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_VOICE_ASSISTANT
|
||||
#endif
|
||||
#ifdef USE_ALARM_CONTROL_PANEL
|
||||
bool APIServerConnectionBase::send_list_entities_alarm_control_panel_response(
|
||||
const ListEntitiesAlarmControlPanelResponse &msg) {
|
||||
|
@ -609,6 +611,24 @@ bool APIServerConnectionBase::send_date_time_state_response(const DateTimeStateR
|
|||
#endif
|
||||
#ifdef USE_DATETIME_DATETIME
|
||||
#endif
|
||||
#ifdef USE_UPDATE
|
||||
bool APIServerConnectionBase::send_list_entities_update_response(const ListEntitiesUpdateResponse &msg) {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "send_list_entities_update_response: %s", msg.dump().c_str());
|
||||
#endif
|
||||
return this->send_message_<ListEntitiesUpdateResponse>(msg, 116);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_UPDATE
|
||||
bool APIServerConnectionBase::send_update_state_response(const UpdateStateResponse &msg) {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "send_update_state_response: %s", msg.dump().c_str());
|
||||
#endif
|
||||
return this->send_message_<UpdateStateResponse>(msg, 117);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_UPDATE
|
||||
#endif
|
||||
bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) {
|
||||
switch (msg_type) {
|
||||
case 1: {
|
||||
|
@ -1093,6 +1113,28 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
|||
ESP_LOGVV(TAG, "on_date_time_command_request: %s", msg.dump().c_str());
|
||||
#endif
|
||||
this->on_date_time_command_request(msg);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
case 115: {
|
||||
#ifdef USE_VOICE_ASSISTANT
|
||||
VoiceAssistantTimerEventResponse msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_voice_assistant_timer_event_response: %s", msg.dump().c_str());
|
||||
#endif
|
||||
this->on_voice_assistant_timer_event_response(msg);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
case 118: {
|
||||
#ifdef USE_UPDATE
|
||||
UpdateCommandRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_update_command_request: %s", msg.dump().c_str());
|
||||
#endif
|
||||
this->on_update_command_request(msg);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
|
@ -1421,6 +1463,19 @@ void APIServerConnection::on_date_time_command_request(const DateTimeCommandRequ
|
|||
this->datetime_command(msg);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_UPDATE
|
||||
void APIServerConnection::on_update_command_request(const UpdateCommandRequest &msg) {
|
||||
if (!this->is_connection_setup()) {
|
||||
this->on_no_setup_connection();
|
||||
return;
|
||||
}
|
||||
if (!this->is_authenticated()) {
|
||||
this->on_unauthenticated_access();
|
||||
return;
|
||||
}
|
||||
this->update_command(msg);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
void APIServerConnection::on_subscribe_bluetooth_le_advertisements_request(
|
||||
const SubscribeBluetoothLEAdvertisementsRequest &msg) {
|
||||
|
|
|
@ -244,6 +244,9 @@ class APIServerConnectionBase : public ProtoService {
|
|||
bool send_voice_assistant_audio(const VoiceAssistantAudio &msg);
|
||||
virtual void on_voice_assistant_audio(const VoiceAssistantAudio &value){};
|
||||
#endif
|
||||
#ifdef USE_VOICE_ASSISTANT
|
||||
virtual void on_voice_assistant_timer_event_response(const VoiceAssistantTimerEventResponse &value){};
|
||||
#endif
|
||||
#ifdef USE_ALARM_CONTROL_PANEL
|
||||
bool send_list_entities_alarm_control_panel_response(const ListEntitiesAlarmControlPanelResponse &msg);
|
||||
#endif
|
||||
|
@ -303,6 +306,15 @@ class APIServerConnectionBase : public ProtoService {
|
|||
#endif
|
||||
#ifdef USE_DATETIME_DATETIME
|
||||
virtual void on_date_time_command_request(const DateTimeCommandRequest &value){};
|
||||
#endif
|
||||
#ifdef USE_UPDATE
|
||||
bool send_list_entities_update_response(const ListEntitiesUpdateResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_UPDATE
|
||||
bool send_update_state_response(const UpdateStateResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_UPDATE
|
||||
virtual void on_update_command_request(const UpdateCommandRequest &value){};
|
||||
#endif
|
||||
protected:
|
||||
bool read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override;
|
||||
|
@ -370,6 +382,9 @@ class APIServerConnection : public APIServerConnectionBase {
|
|||
#ifdef USE_DATETIME_DATETIME
|
||||
virtual void datetime_command(const DateTimeCommandRequest &msg) = 0;
|
||||
#endif
|
||||
#ifdef USE_UPDATE
|
||||
virtual void update_command(const UpdateCommandRequest &msg) = 0;
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
virtual void subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) = 0;
|
||||
#endif
|
||||
|
@ -468,6 +483,9 @@ class APIServerConnection : public APIServerConnectionBase {
|
|||
#ifdef USE_DATETIME_DATETIME
|
||||
void on_date_time_command_request(const DateTimeCommandRequest &msg) override;
|
||||
#endif
|
||||
#ifdef USE_UPDATE
|
||||
void on_update_command_request(const UpdateCommandRequest &msg) override;
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
void on_subscribe_bluetooth_le_advertisements_request(const SubscribeBluetoothLEAdvertisementsRequest &msg) override;
|
||||
#endif
|
||||
|
|
|
@ -334,6 +334,13 @@ void APIServer::on_event(event::Event *obj, const std::string &event_type) {
|
|||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_UPDATE
|
||||
void APIServer::on_update(update::UpdateEntity *obj) {
|
||||
for (auto &c : this->clients_)
|
||||
c->send_update_state(obj);
|
||||
}
|
||||
#endif
|
||||
|
||||
float APIServer::get_setup_priority() const { return setup_priority::AFTER_WIFI; }
|
||||
void APIServer::set_port(uint16_t port) { this->port_ = port; }
|
||||
APIServer *global_api_server = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
|
|
@ -102,6 +102,9 @@ class APIServer : public Component, public Controller {
|
|||
#ifdef USE_EVENT
|
||||
void on_event(event::Event *obj, const std::string &event_type) override;
|
||||
#endif
|
||||
#ifdef USE_UPDATE
|
||||
void on_update(update::UpdateEntity *obj) override;
|
||||
#endif
|
||||
|
||||
bool is_connected() const;
|
||||
|
||||
|
|
|
@ -105,7 +105,7 @@ class CustomAPIDevice {
|
|||
/** Subscribe to the state (or attribute state) of an entity from Home Assistant.
|
||||
*
|
||||
* Usage:
|
||||
*å
|
||||
*
|
||||
* ```cpp
|
||||
* void setup() override {
|
||||
* subscribe_homeassistant_state(&CustomNativeAPI::on_state_changed, "sensor.weather_forecast");
|
||||
|
|
|
@ -98,6 +98,9 @@ bool ListEntitiesIterator::on_alarm_control_panel(alarm_control_panel::AlarmCont
|
|||
#ifdef USE_EVENT
|
||||
bool ListEntitiesIterator::on_event(event::Event *event) { return this->client_->send_event_info(event); }
|
||||
#endif
|
||||
#ifdef USE_UPDATE
|
||||
bool ListEntitiesIterator::on_update(update::UpdateEntity *update) { return this->client_->send_update_info(update); }
|
||||
#endif
|
||||
|
||||
} // namespace api
|
||||
} // namespace esphome
|
||||
|
|
|
@ -75,6 +75,9 @@ class ListEntitiesIterator : public ComponentIterator {
|
|||
#endif
|
||||
#ifdef USE_EVENT
|
||||
bool on_event(event::Event *event) override;
|
||||
#endif
|
||||
#ifdef USE_UPDATE
|
||||
bool on_update(update::UpdateEntity *update) override;
|
||||
#endif
|
||||
bool on_end() override;
|
||||
|
||||
|
|
|
@ -77,6 +77,9 @@ bool InitialStateIterator::on_alarm_control_panel(alarm_control_panel::AlarmCont
|
|||
return this->client_->send_alarm_control_panel_state(a_alarm_control_panel);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_UPDATE
|
||||
bool InitialStateIterator::on_update(update::UpdateEntity *update) { return this->client_->send_update_state(update); }
|
||||
#endif
|
||||
InitialStateIterator::InitialStateIterator(APIConnection *client) : client_(client) {}
|
||||
|
||||
} // namespace api
|
||||
|
|
|
@ -72,6 +72,9 @@ class InitialStateIterator : public ComponentIterator {
|
|||
#endif
|
||||
#ifdef USE_EVENT
|
||||
bool on_event(event::Event *event) override { return true; };
|
||||
#endif
|
||||
#ifdef USE_UPDATE
|
||||
bool on_update(update::UpdateEntity *update) override;
|
||||
#endif
|
||||
protected:
|
||||
APIConnection *client_;
|
||||
|
|
|
@ -4,7 +4,7 @@ from esphome.cpp_generator import MockObjClass
|
|||
from esphome.cpp_helpers import setup_entity
|
||||
from esphome import automation, core
|
||||
from esphome.automation import Condition, maybe_simple_id
|
||||
from esphome.components import mqtt
|
||||
from esphome.components import mqtt, web_server
|
||||
from esphome.const import (
|
||||
CONF_DELAY,
|
||||
CONF_DEVICE_CLASS,
|
||||
|
@ -27,6 +27,7 @@ from esphome.const import (
|
|||
CONF_TIMING,
|
||||
CONF_TRIGGER_ID,
|
||||
CONF_MQTT_ID,
|
||||
CONF_WEB_SERVER_ID,
|
||||
DEVICE_CLASS_BATTERY,
|
||||
DEVICE_CLASS_BATTERY_CHARGING,
|
||||
DEVICE_CLASS_CARBON_MONOXIDE,
|
||||
|
@ -385,70 +386,76 @@ def validate_click_timing(value):
|
|||
return value
|
||||
|
||||
|
||||
BINARY_SENSOR_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(BinarySensor),
|
||||
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(
|
||||
mqtt.MQTTBinarySensorComponent
|
||||
),
|
||||
cv.Optional(CONF_PUBLISH_INITIAL_STATE): cv.boolean,
|
||||
cv.Optional(CONF_DEVICE_CLASS): validate_device_class,
|
||||
cv.Optional(CONF_FILTERS): validate_filters,
|
||||
cv.Optional(CONF_ON_PRESS): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(PressTrigger),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_RELEASE): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ReleaseTrigger),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_CLICK): cv.All(
|
||||
automation.validate_automation(
|
||||
BINARY_SENSOR_SCHEMA = (
|
||||
cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA)
|
||||
.extend(cv.MQTT_COMPONENT_SCHEMA)
|
||||
.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(BinarySensor),
|
||||
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(
|
||||
mqtt.MQTTBinarySensorComponent
|
||||
),
|
||||
cv.Optional(CONF_PUBLISH_INITIAL_STATE): cv.boolean,
|
||||
cv.Optional(CONF_DEVICE_CLASS): validate_device_class,
|
||||
cv.Optional(CONF_FILTERS): validate_filters,
|
||||
cv.Optional(CONF_ON_PRESS): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ClickTrigger),
|
||||
cv.Optional(
|
||||
CONF_MIN_LENGTH, default="50ms"
|
||||
): cv.positive_time_period_milliseconds,
|
||||
cv.Optional(
|
||||
CONF_MAX_LENGTH, default="350ms"
|
||||
): cv.positive_time_period_milliseconds,
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(PressTrigger),
|
||||
}
|
||||
),
|
||||
validate_click_timing,
|
||||
),
|
||||
cv.Optional(CONF_ON_DOUBLE_CLICK): cv.All(
|
||||
automation.validate_automation(
|
||||
cv.Optional(CONF_ON_RELEASE): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(DoubleClickTrigger),
|
||||
cv.Optional(
|
||||
CONF_MIN_LENGTH, default="50ms"
|
||||
): cv.positive_time_period_milliseconds,
|
||||
cv.Optional(
|
||||
CONF_MAX_LENGTH, default="350ms"
|
||||
): cv.positive_time_period_milliseconds,
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ReleaseTrigger),
|
||||
}
|
||||
),
|
||||
validate_click_timing,
|
||||
),
|
||||
cv.Optional(CONF_ON_MULTI_CLICK): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(MultiClickTrigger),
|
||||
cv.Required(CONF_TIMING): cv.All(
|
||||
[parse_multi_click_timing_str], validate_multi_click_timing
|
||||
cv.Optional(CONF_ON_CLICK): cv.All(
|
||||
automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ClickTrigger),
|
||||
cv.Optional(
|
||||
CONF_MIN_LENGTH, default="50ms"
|
||||
): cv.positive_time_period_milliseconds,
|
||||
cv.Optional(
|
||||
CONF_MAX_LENGTH, default="350ms"
|
||||
): cv.positive_time_period_milliseconds,
|
||||
}
|
||||
),
|
||||
cv.Optional(
|
||||
CONF_INVALID_COOLDOWN, default="1s"
|
||||
): cv.positive_time_period_milliseconds,
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_STATE): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StateTrigger),
|
||||
}
|
||||
),
|
||||
}
|
||||
validate_click_timing,
|
||||
),
|
||||
cv.Optional(CONF_ON_DOUBLE_CLICK): cv.All(
|
||||
automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
||||
DoubleClickTrigger
|
||||
),
|
||||
cv.Optional(
|
||||
CONF_MIN_LENGTH, default="50ms"
|
||||
): cv.positive_time_period_milliseconds,
|
||||
cv.Optional(
|
||||
CONF_MAX_LENGTH, default="350ms"
|
||||
): cv.positive_time_period_milliseconds,
|
||||
}
|
||||
),
|
||||
validate_click_timing,
|
||||
),
|
||||
cv.Optional(CONF_ON_MULTI_CLICK): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(MultiClickTrigger),
|
||||
cv.Required(CONF_TIMING): cv.All(
|
||||
[parse_multi_click_timing_str], validate_multi_click_timing
|
||||
),
|
||||
cv.Optional(
|
||||
CONF_INVALID_COOLDOWN, default="1s"
|
||||
): cv.positive_time_period_milliseconds,
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_STATE): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StateTrigger),
|
||||
}
|
||||
),
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
_UNDEF = object()
|
||||
|
@ -536,6 +543,10 @@ async def setup_binary_sensor_core_(var, config):
|
|||
mqtt_ = cg.new_Pvariable(mqtt_id, var)
|
||||
await mqtt.register_mqtt_component(mqtt_, config)
|
||||
|
||||
if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None:
|
||||
web_server_ = await cg.get_variable(webserver_id)
|
||||
web_server.add_entity_to_sorting_list(web_server_, var, config)
|
||||
|
||||
|
||||
async def register_binary_sensor(var, config):
|
||||
if not CORE.has_id(config[CONF_ID]):
|
||||
|
|
|
@ -2,7 +2,7 @@ import esphome.codegen as cg
|
|||
import esphome.config_validation as cv
|
||||
from esphome import automation
|
||||
from esphome.automation import maybe_simple_id
|
||||
from esphome.components import mqtt
|
||||
from esphome.components import mqtt, web_server
|
||||
from esphome.const import (
|
||||
CONF_DEVICE_CLASS,
|
||||
CONF_ENTITY_CATEGORY,
|
||||
|
@ -11,6 +11,7 @@ from esphome.const import (
|
|||
CONF_ON_PRESS,
|
||||
CONF_TRIGGER_ID,
|
||||
CONF_MQTT_ID,
|
||||
CONF_WEB_SERVER_ID,
|
||||
DEVICE_CLASS_EMPTY,
|
||||
DEVICE_CLASS_IDENTIFY,
|
||||
DEVICE_CLASS_RESTART,
|
||||
|
@ -43,16 +44,20 @@ ButtonPressTrigger = button_ns.class_(
|
|||
validate_device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space="_")
|
||||
|
||||
|
||||
BUTTON_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend(
|
||||
{
|
||||
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTButtonComponent),
|
||||
cv.Optional(CONF_DEVICE_CLASS): validate_device_class,
|
||||
cv.Optional(CONF_ON_PRESS): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ButtonPressTrigger),
|
||||
}
|
||||
),
|
||||
}
|
||||
BUTTON_SCHEMA = (
|
||||
cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA)
|
||||
.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA)
|
||||
.extend(
|
||||
{
|
||||
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTButtonComponent),
|
||||
cv.Optional(CONF_DEVICE_CLASS): validate_device_class,
|
||||
cv.Optional(CONF_ON_PRESS): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ButtonPressTrigger),
|
||||
}
|
||||
),
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
_UNDEF = object()
|
||||
|
@ -92,6 +97,10 @@ async def setup_button_core_(var, config):
|
|||
mqtt_ = cg.new_Pvariable(mqtt_id, var)
|
||||
await mqtt.register_mqtt_component(mqtt_, config)
|
||||
|
||||
if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None:
|
||||
web_server_ = await cg.get_variable(webserver_id)
|
||||
web_server.add_entity_to_sorting_list(web_server_, var, config)
|
||||
|
||||
|
||||
async def register_button(var, config):
|
||||
if not CORE.has_id(config[CONF_ID]):
|
||||
|
|
|
@ -2,7 +2,7 @@ import esphome.codegen as cg
|
|||
import esphome.config_validation as cv
|
||||
from esphome.cpp_helpers import setup_entity
|
||||
from esphome import automation
|
||||
from esphome.components import mqtt
|
||||
from esphome.components import mqtt, web_server
|
||||
from esphome.const import (
|
||||
CONF_ACTION_STATE_TOPIC,
|
||||
CONF_AWAY,
|
||||
|
@ -44,6 +44,7 @@ from esphome.const import (
|
|||
CONF_TRIGGER_ID,
|
||||
CONF_VISUAL,
|
||||
CONF_MQTT_ID,
|
||||
CONF_WEB_SERVER_ID,
|
||||
)
|
||||
from esphome.core import CORE, coroutine_with_priority
|
||||
|
||||
|
@ -150,93 +151,97 @@ VISUAL_TEMPERATURE_STEP_SCHEMA = cv.Any(
|
|||
),
|
||||
)
|
||||
|
||||
CLIMATE_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(Climate),
|
||||
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTClimateComponent),
|
||||
cv.Optional(CONF_VISUAL, default={}): cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_MIN_TEMPERATURE): cv.temperature,
|
||||
cv.Optional(CONF_MAX_TEMPERATURE): cv.temperature,
|
||||
cv.Optional(CONF_TEMPERATURE_STEP): VISUAL_TEMPERATURE_STEP_SCHEMA,
|
||||
cv.Optional(CONF_MIN_HUMIDITY): cv.percentage_int,
|
||||
cv.Optional(CONF_MAX_HUMIDITY): cv.percentage_int,
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ACTION_STATE_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.publish_topic
|
||||
),
|
||||
cv.Optional(CONF_AWAY_COMMAND_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.publish_topic
|
||||
),
|
||||
cv.Optional(CONF_AWAY_STATE_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.publish_topic
|
||||
),
|
||||
cv.Optional(CONF_CURRENT_TEMPERATURE_STATE_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.publish_topic
|
||||
),
|
||||
cv.Optional(CONF_CURRENT_HUMIDITY_STATE_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.publish_topic
|
||||
),
|
||||
cv.Optional(CONF_FAN_MODE_COMMAND_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.publish_topic
|
||||
),
|
||||
cv.Optional(CONF_FAN_MODE_STATE_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.publish_topic
|
||||
),
|
||||
cv.Optional(CONF_MODE_COMMAND_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.publish_topic
|
||||
),
|
||||
cv.Optional(CONF_MODE_STATE_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.publish_topic
|
||||
),
|
||||
cv.Optional(CONF_PRESET_COMMAND_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.publish_topic
|
||||
),
|
||||
cv.Optional(CONF_PRESET_STATE_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.publish_topic
|
||||
),
|
||||
cv.Optional(CONF_SWING_MODE_COMMAND_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.publish_topic
|
||||
),
|
||||
cv.Optional(CONF_SWING_MODE_STATE_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.publish_topic
|
||||
),
|
||||
cv.Optional(CONF_TARGET_TEMPERATURE_COMMAND_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.publish_topic
|
||||
),
|
||||
cv.Optional(CONF_TARGET_TEMPERATURE_STATE_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.publish_topic
|
||||
),
|
||||
cv.Optional(CONF_TARGET_TEMPERATURE_HIGH_COMMAND_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.publish_topic
|
||||
),
|
||||
cv.Optional(CONF_TARGET_TEMPERATURE_HIGH_STATE_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.publish_topic
|
||||
),
|
||||
cv.Optional(CONF_TARGET_TEMPERATURE_LOW_COMMAND_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.publish_topic
|
||||
),
|
||||
cv.Optional(CONF_TARGET_TEMPERATURE_LOW_STATE_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.publish_topic
|
||||
),
|
||||
cv.Optional(CONF_TARGET_HUMIDITY_COMMAND_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.publish_topic
|
||||
),
|
||||
cv.Optional(CONF_TARGET_HUMIDITY_STATE_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.publish_topic
|
||||
),
|
||||
cv.Optional(CONF_ON_CONTROL): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ControlTrigger),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_STATE): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StateTrigger),
|
||||
}
|
||||
),
|
||||
}
|
||||
CLIMATE_SCHEMA = (
|
||||
cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA)
|
||||
.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA)
|
||||
.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(Climate),
|
||||
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTClimateComponent),
|
||||
cv.Optional(CONF_VISUAL, default={}): cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_MIN_TEMPERATURE): cv.temperature,
|
||||
cv.Optional(CONF_MAX_TEMPERATURE): cv.temperature,
|
||||
cv.Optional(CONF_TEMPERATURE_STEP): VISUAL_TEMPERATURE_STEP_SCHEMA,
|
||||
cv.Optional(CONF_MIN_HUMIDITY): cv.percentage_int,
|
||||
cv.Optional(CONF_MAX_HUMIDITY): cv.percentage_int,
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ACTION_STATE_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.publish_topic
|
||||
),
|
||||
cv.Optional(CONF_AWAY_COMMAND_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.publish_topic
|
||||
),
|
||||
cv.Optional(CONF_AWAY_STATE_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.publish_topic
|
||||
),
|
||||
cv.Optional(CONF_CURRENT_TEMPERATURE_STATE_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.publish_topic
|
||||
),
|
||||
cv.Optional(CONF_CURRENT_HUMIDITY_STATE_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.publish_topic
|
||||
),
|
||||
cv.Optional(CONF_FAN_MODE_COMMAND_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.publish_topic
|
||||
),
|
||||
cv.Optional(CONF_FAN_MODE_STATE_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.publish_topic
|
||||
),
|
||||
cv.Optional(CONF_MODE_COMMAND_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.publish_topic
|
||||
),
|
||||
cv.Optional(CONF_MODE_STATE_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.publish_topic
|
||||
),
|
||||
cv.Optional(CONF_PRESET_COMMAND_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.publish_topic
|
||||
),
|
||||
cv.Optional(CONF_PRESET_STATE_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.publish_topic
|
||||
),
|
||||
cv.Optional(CONF_SWING_MODE_COMMAND_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.publish_topic
|
||||
),
|
||||
cv.Optional(CONF_SWING_MODE_STATE_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.publish_topic
|
||||
),
|
||||
cv.Optional(CONF_TARGET_TEMPERATURE_COMMAND_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.publish_topic
|
||||
),
|
||||
cv.Optional(CONF_TARGET_TEMPERATURE_STATE_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.publish_topic
|
||||
),
|
||||
cv.Optional(CONF_TARGET_TEMPERATURE_HIGH_COMMAND_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.publish_topic
|
||||
),
|
||||
cv.Optional(CONF_TARGET_TEMPERATURE_HIGH_STATE_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.publish_topic
|
||||
),
|
||||
cv.Optional(CONF_TARGET_TEMPERATURE_LOW_COMMAND_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.publish_topic
|
||||
),
|
||||
cv.Optional(CONF_TARGET_TEMPERATURE_LOW_STATE_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.publish_topic
|
||||
),
|
||||
cv.Optional(CONF_TARGET_HUMIDITY_COMMAND_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.publish_topic
|
||||
),
|
||||
cv.Optional(CONF_TARGET_HUMIDITY_STATE_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.publish_topic
|
||||
),
|
||||
cv.Optional(CONF_ON_CONTROL): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ControlTrigger),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_STATE): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StateTrigger),
|
||||
}
|
||||
),
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
|
@ -403,6 +408,10 @@ async def setup_climate_core_(var, config):
|
|||
trigger, [(ClimateCall.operator("ref"), "x")], conf
|
||||
)
|
||||
|
||||
if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None:
|
||||
web_server_ = await cg.get_variable(webserver_id)
|
||||
web_server.add_entity_to_sorting_list(web_server_, var, config)
|
||||
|
||||
|
||||
async def register_climate(var, config):
|
||||
if not CORE.has_id(config[CONF_ID]):
|
||||
|
|
|
@ -6,18 +6,24 @@ namespace climate_ir_lg {
|
|||
|
||||
static const char *const TAG = "climate.climate_ir_lg";
|
||||
|
||||
const uint32_t COMMAND_ON = 0x00000;
|
||||
const uint32_t COMMAND_ON_AI = 0x03000;
|
||||
const uint32_t COMMAND_COOL = 0x08000;
|
||||
const uint32_t COMMAND_HEAT = 0x0C000;
|
||||
// Commands
|
||||
const uint32_t COMMAND_MASK = 0xFF000;
|
||||
const uint32_t COMMAND_OFF = 0xC0000;
|
||||
const uint32_t COMMAND_SWING = 0x10000;
|
||||
// On, 25C, Mode: Auto, Fan: Auto, Zone Follow: Off, Sensor Temp: Ignore.
|
||||
const uint32_t COMMAND_AUTO = 0x0B000;
|
||||
const uint32_t COMMAND_DRY_FAN = 0x09000;
|
||||
|
||||
const uint32_t COMMAND_MASK = 0xFF000;
|
||||
const uint32_t COMMAND_ON_COOL = 0x00000;
|
||||
const uint32_t COMMAND_ON_DRY = 0x01000;
|
||||
const uint32_t COMMAND_ON_FAN_ONLY = 0x02000;
|
||||
const uint32_t COMMAND_ON_AI = 0x03000;
|
||||
const uint32_t COMMAND_ON_HEAT = 0x04000;
|
||||
|
||||
const uint32_t COMMAND_COOL = 0x08000;
|
||||
const uint32_t COMMAND_DRY = 0x09000;
|
||||
const uint32_t COMMAND_FAN_ONLY = 0x0A000;
|
||||
const uint32_t COMMAND_AI = 0x0B000;
|
||||
const uint32_t COMMAND_HEAT = 0x0C000;
|
||||
|
||||
// Fan speed
|
||||
const uint32_t FAN_MASK = 0xF0;
|
||||
const uint32_t FAN_AUTO = 0x50;
|
||||
const uint32_t FAN_MIN = 0x00;
|
||||
|
@ -35,69 +41,67 @@ void LgIrClimate::transmit_state() {
|
|||
uint32_t remote_state = 0x8800000;
|
||||
|
||||
// ESP_LOGD(TAG, "climate_lg_ir mode_before_ code: 0x%02X", modeBefore_);
|
||||
|
||||
// Set command
|
||||
if (send_swing_cmd_) {
|
||||
send_swing_cmd_ = false;
|
||||
remote_state |= COMMAND_SWING;
|
||||
} else {
|
||||
if (mode_before_ == climate::CLIMATE_MODE_OFF && this->mode == climate::CLIMATE_MODE_HEAT_COOL) {
|
||||
remote_state |= COMMAND_ON_AI;
|
||||
} else if (mode_before_ == climate::CLIMATE_MODE_OFF && this->mode != climate::CLIMATE_MODE_OFF) {
|
||||
remote_state |= COMMAND_ON;
|
||||
this->mode = climate::CLIMATE_MODE_COOL;
|
||||
} else {
|
||||
switch (this->mode) {
|
||||
case climate::CLIMATE_MODE_COOL:
|
||||
remote_state |= COMMAND_COOL;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_HEAT:
|
||||
remote_state |= COMMAND_HEAT;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_HEAT_COOL:
|
||||
remote_state |= COMMAND_AUTO;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_DRY:
|
||||
remote_state |= COMMAND_DRY_FAN;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_OFF:
|
||||
default:
|
||||
remote_state |= COMMAND_OFF;
|
||||
break;
|
||||
}
|
||||
}
|
||||
mode_before_ = this->mode;
|
||||
|
||||
ESP_LOGD(TAG, "climate_lg_ir mode code: 0x%02X", this->mode);
|
||||
|
||||
if (this->mode == climate::CLIMATE_MODE_OFF) {
|
||||
remote_state |= FAN_AUTO;
|
||||
} else if (this->mode == climate::CLIMATE_MODE_COOL || this->mode == climate::CLIMATE_MODE_DRY ||
|
||||
this->mode == climate::CLIMATE_MODE_HEAT) {
|
||||
switch (this->fan_mode.value()) {
|
||||
case climate::CLIMATE_FAN_HIGH:
|
||||
remote_state |= FAN_MAX;
|
||||
break;
|
||||
case climate::CLIMATE_FAN_MEDIUM:
|
||||
remote_state |= FAN_MED;
|
||||
break;
|
||||
case climate::CLIMATE_FAN_LOW:
|
||||
remote_state |= FAN_MIN;
|
||||
break;
|
||||
case climate::CLIMATE_FAN_AUTO:
|
||||
default:
|
||||
remote_state |= FAN_AUTO;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (this->mode == climate::CLIMATE_MODE_HEAT_COOL) {
|
||||
this->fan_mode = climate::CLIMATE_FAN_AUTO;
|
||||
// remote_state |= FAN_MODE_AUTO_DRY;
|
||||
}
|
||||
if (this->mode == climate::CLIMATE_MODE_COOL || this->mode == climate::CLIMATE_MODE_HEAT) {
|
||||
auto temp = (uint8_t) roundf(clamp<float>(this->target_temperature, TEMP_MIN, TEMP_MAX));
|
||||
remote_state |= ((temp - 15) << TEMP_SHIFT);
|
||||
bool climate_is_off = (mode_before_ == climate::CLIMATE_MODE_OFF);
|
||||
switch (this->mode) {
|
||||
case climate::CLIMATE_MODE_COOL:
|
||||
remote_state |= climate_is_off ? COMMAND_ON_COOL : COMMAND_COOL;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_DRY:
|
||||
remote_state |= climate_is_off ? COMMAND_ON_DRY : COMMAND_DRY;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_FAN_ONLY:
|
||||
remote_state |= climate_is_off ? COMMAND_ON_FAN_ONLY : COMMAND_FAN_ONLY;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_HEAT_COOL:
|
||||
remote_state |= climate_is_off ? COMMAND_ON_AI : COMMAND_AI;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_HEAT:
|
||||
remote_state |= climate_is_off ? COMMAND_ON_HEAT : COMMAND_HEAT;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_OFF:
|
||||
default:
|
||||
remote_state |= COMMAND_OFF;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
mode_before_ = this->mode;
|
||||
|
||||
ESP_LOGD(TAG, "climate_lg_ir mode code: 0x%02X", this->mode);
|
||||
|
||||
// Set fan speed
|
||||
if (this->mode == climate::CLIMATE_MODE_OFF) {
|
||||
remote_state |= FAN_AUTO;
|
||||
} else {
|
||||
switch (this->fan_mode.value()) {
|
||||
case climate::CLIMATE_FAN_HIGH:
|
||||
remote_state |= FAN_MAX;
|
||||
break;
|
||||
case climate::CLIMATE_FAN_MEDIUM:
|
||||
remote_state |= FAN_MED;
|
||||
break;
|
||||
case climate::CLIMATE_FAN_LOW:
|
||||
remote_state |= FAN_MIN;
|
||||
break;
|
||||
case climate::CLIMATE_FAN_AUTO:
|
||||
default:
|
||||
remote_state |= FAN_AUTO;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Set temperature
|
||||
if (this->mode == climate::CLIMATE_MODE_COOL || this->mode == climate::CLIMATE_MODE_HEAT) {
|
||||
auto temp = (uint8_t) roundf(clamp<float>(this->target_temperature, TEMP_MIN, TEMP_MAX));
|
||||
remote_state |= ((temp - 15) << TEMP_SHIFT);
|
||||
}
|
||||
|
||||
transmit_(remote_state);
|
||||
this->publish_state();
|
||||
}
|
||||
|
@ -125,37 +129,42 @@ bool LgIrClimate::on_receive(remote_base::RemoteReceiveData data) {
|
|||
if ((remote_state & 0xFF00000) != 0x8800000)
|
||||
return false;
|
||||
|
||||
if ((remote_state & COMMAND_MASK) == COMMAND_ON) {
|
||||
this->mode = climate::CLIMATE_MODE_COOL;
|
||||
} else if ((remote_state & COMMAND_MASK) == COMMAND_ON_AI) {
|
||||
this->mode = climate::CLIMATE_MODE_HEAT_COOL;
|
||||
}
|
||||
|
||||
// Get command
|
||||
if ((remote_state & COMMAND_MASK) == COMMAND_OFF) {
|
||||
this->mode = climate::CLIMATE_MODE_OFF;
|
||||
} else if ((remote_state & COMMAND_MASK) == COMMAND_SWING) {
|
||||
this->swing_mode =
|
||||
this->swing_mode == climate::CLIMATE_SWING_OFF ? climate::CLIMATE_SWING_VERTICAL : climate::CLIMATE_SWING_OFF;
|
||||
} else {
|
||||
if ((remote_state & COMMAND_MASK) == COMMAND_AUTO) {
|
||||
this->mode = climate::CLIMATE_MODE_HEAT_COOL;
|
||||
} else if ((remote_state & COMMAND_MASK) == COMMAND_DRY_FAN) {
|
||||
this->mode = climate::CLIMATE_MODE_DRY;
|
||||
} else if ((remote_state & COMMAND_MASK) == COMMAND_HEAT) {
|
||||
this->mode = climate::CLIMATE_MODE_HEAT;
|
||||
} else {
|
||||
this->mode = climate::CLIMATE_MODE_COOL;
|
||||
switch (remote_state & COMMAND_MASK) {
|
||||
case COMMAND_DRY:
|
||||
case COMMAND_ON_DRY:
|
||||
this->mode = climate::CLIMATE_MODE_DRY;
|
||||
break;
|
||||
case COMMAND_FAN_ONLY:
|
||||
case COMMAND_ON_FAN_ONLY:
|
||||
this->mode = climate::CLIMATE_MODE_FAN_ONLY;
|
||||
break;
|
||||
case COMMAND_AI:
|
||||
case COMMAND_ON_AI:
|
||||
this->mode = climate::CLIMATE_MODE_HEAT_COOL;
|
||||
break;
|
||||
case COMMAND_HEAT:
|
||||
case COMMAND_ON_HEAT:
|
||||
this->mode = climate::CLIMATE_MODE_HEAT;
|
||||
break;
|
||||
case COMMAND_COOL:
|
||||
case COMMAND_ON_COOL:
|
||||
default:
|
||||
this->mode = climate::CLIMATE_MODE_COOL;
|
||||
break;
|
||||
}
|
||||
|
||||
// Temperature
|
||||
if (this->mode == climate::CLIMATE_MODE_COOL || this->mode == climate::CLIMATE_MODE_HEAT)
|
||||
this->target_temperature = ((remote_state & TEMP_MASK) >> TEMP_SHIFT) + 15;
|
||||
|
||||
// Fan Speed
|
||||
// Get fan speed
|
||||
if (this->mode == climate::CLIMATE_MODE_HEAT_COOL) {
|
||||
this->fan_mode = climate::CLIMATE_FAN_AUTO;
|
||||
} else if (this->mode == climate::CLIMATE_MODE_COOL || this->mode == climate::CLIMATE_MODE_HEAT ||
|
||||
this->mode == climate::CLIMATE_MODE_DRY) {
|
||||
} else if (this->mode == climate::CLIMATE_MODE_COOL || this->mode == climate::CLIMATE_MODE_DRY ||
|
||||
this->mode == climate::CLIMATE_MODE_FAN_ONLY || this->mode == climate::CLIMATE_MODE_HEAT) {
|
||||
if ((remote_state & FAN_MASK) == FAN_AUTO) {
|
||||
this->fan_mode = climate::CLIMATE_FAN_AUTO;
|
||||
} else if ((remote_state & FAN_MASK) == FAN_MIN) {
|
||||
|
@ -166,11 +175,17 @@ bool LgIrClimate::on_receive(remote_base::RemoteReceiveData data) {
|
|||
this->fan_mode = climate::CLIMATE_FAN_HIGH;
|
||||
}
|
||||
}
|
||||
|
||||
// Get temperature
|
||||
if (this->mode == climate::CLIMATE_MODE_COOL || this->mode == climate::CLIMATE_MODE_HEAT) {
|
||||
this->target_temperature = ((remote_state & TEMP_MASK) >> TEMP_SHIFT) + 15;
|
||||
}
|
||||
}
|
||||
this->publish_state();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void LgIrClimate::transmit_(uint32_t value) {
|
||||
calc_checksum_(value);
|
||||
ESP_LOGD(TAG, "Sending climate_lg_ir code: 0x%02" PRIX32, value);
|
||||
|
|
|
@ -14,7 +14,7 @@ const uint8_t TEMP_MAX = 30; // Celsius
|
|||
class LgIrClimate : public climate_ir::ClimateIR {
|
||||
public:
|
||||
LgIrClimate()
|
||||
: climate_ir::ClimateIR(TEMP_MIN, TEMP_MAX, 1.0f, true, false,
|
||||
: climate_ir::ClimateIR(TEMP_MIN, TEMP_MAX, 1.0f, true, true,
|
||||
{climate::CLIMATE_FAN_AUTO, climate::CLIMATE_FAN_LOW, climate::CLIMATE_FAN_MEDIUM,
|
||||
climate::CLIMATE_FAN_HIGH},
|
||||
{climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_VERTICAL}) {}
|
||||
|
|
|
@ -2,7 +2,7 @@ import esphome.codegen as cg
|
|||
import esphome.config_validation as cv
|
||||
from esphome import automation
|
||||
from esphome.automation import maybe_simple_id, Condition
|
||||
from esphome.components import mqtt
|
||||
from esphome.components import mqtt, web_server
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_DEVICE_CLASS,
|
||||
|
@ -16,6 +16,7 @@ from esphome.const import (
|
|||
CONF_TILT_STATE_TOPIC,
|
||||
CONF_STOP,
|
||||
CONF_MQTT_ID,
|
||||
CONF_WEB_SERVER_ID,
|
||||
CONF_TRIGGER_ID,
|
||||
DEVICE_CLASS_AWNING,
|
||||
DEVICE_CLASS_BLIND,
|
||||
|
@ -88,34 +89,38 @@ CoverClosedTrigger = cover_ns.class_(
|
|||
|
||||
CONF_ON_CLOSED = "on_closed"
|
||||
|
||||
COVER_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(Cover),
|
||||
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTCoverComponent),
|
||||
cv.Optional(CONF_DEVICE_CLASS): cv.one_of(*DEVICE_CLASSES, lower=True),
|
||||
cv.Optional(CONF_POSITION_COMMAND_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.subscribe_topic
|
||||
),
|
||||
cv.Optional(CONF_POSITION_STATE_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.subscribe_topic
|
||||
),
|
||||
cv.Optional(CONF_TILT_COMMAND_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.subscribe_topic
|
||||
),
|
||||
cv.Optional(CONF_TILT_STATE_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.subscribe_topic
|
||||
),
|
||||
cv.Optional(CONF_ON_OPEN): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(CoverOpenTrigger),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_CLOSED): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(CoverClosedTrigger),
|
||||
}
|
||||
),
|
||||
}
|
||||
COVER_SCHEMA = (
|
||||
cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA)
|
||||
.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA)
|
||||
.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(Cover),
|
||||
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTCoverComponent),
|
||||
cv.Optional(CONF_DEVICE_CLASS): cv.one_of(*DEVICE_CLASSES, lower=True),
|
||||
cv.Optional(CONF_POSITION_COMMAND_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.subscribe_topic
|
||||
),
|
||||
cv.Optional(CONF_POSITION_STATE_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.subscribe_topic
|
||||
),
|
||||
cv.Optional(CONF_TILT_COMMAND_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.subscribe_topic
|
||||
),
|
||||
cv.Optional(CONF_TILT_STATE_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.subscribe_topic
|
||||
),
|
||||
cv.Optional(CONF_ON_OPEN): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(CoverOpenTrigger),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_CLOSED): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(CoverClosedTrigger),
|
||||
}
|
||||
),
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
|
@ -132,6 +137,10 @@ async def setup_cover_core_(var, config):
|
|||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [], conf)
|
||||
|
||||
if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None:
|
||||
web_server_ = await cg.get_variable(webserver_id)
|
||||
web_server.add_entity_to_sorting_list(web_server_, var, config)
|
||||
|
||||
if (mqtt_id := config.get(CONF_MQTT_ID)) is not None:
|
||||
mqtt_ = cg.new_Pvariable(mqtt_id, var)
|
||||
await mqtt.register_mqtt_component(mqtt_, config)
|
||||
|
|
|
@ -129,13 +129,13 @@ class Cover : public EntityBase, public EntityBase_DeviceClass {
|
|||
*
|
||||
* This is a legacy method and may be removed later, please use `.make_call()` instead.
|
||||
*/
|
||||
ESPDEPRECATED("open() is deprecated, use make_call().set_command_open() instead.", "2021.9")
|
||||
ESPDEPRECATED("open() is deprecated, use make_call().set_command_open().perform() instead.", "2021.9")
|
||||
void open();
|
||||
/** Close the cover.
|
||||
*
|
||||
* This is a legacy method and may be removed later, please use `.make_call()` instead.
|
||||
*/
|
||||
ESPDEPRECATED("close() is deprecated, use make_call().set_command_close() instead.", "2021.9")
|
||||
ESPDEPRECATED("close() is deprecated, use make_call().set_command_close().perform() instead.", "2021.9")
|
||||
void close();
|
||||
/** Stop the cover.
|
||||
*
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#include "ct_clamp_sensor.h"
|
||||
|
||||
#include "esphome/core/log.h"
|
||||
#include <cinttypes>
|
||||
#include <cmath>
|
||||
|
||||
namespace esphome {
|
||||
|
@ -37,8 +38,8 @@ void CTClampSensor::update() {
|
|||
float rms_ac = 0;
|
||||
if (rms_ac_squared > 0)
|
||||
rms_ac = std::sqrt(rms_ac_squared);
|
||||
ESP_LOGD(TAG, "'%s' - Raw AC Value: %.3fA after %d different samples (%d SPS)", this->name_.c_str(), rms_ac,
|
||||
this->num_samples_, 1000 * this->num_samples_ / this->sample_duration_);
|
||||
ESP_LOGD(TAG, "'%s' - Raw AC Value: %.3fA after %" PRIu32 " different samples (%" PRIu32 " SPS)",
|
||||
this->name_.c_str(), rms_ac, this->num_samples_, 1000 * this->num_samples_ / this->sample_duration_);
|
||||
this->publish_state(rms_ac);
|
||||
});
|
||||
|
||||
|
|
|
@ -1,25 +1,7 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import pins
|
||||
from esphome.const import CONF_ID, CONF_PIN
|
||||
|
||||
MULTI_CONF = True
|
||||
AUTO_LOAD = ["sensor"]
|
||||
|
||||
dallas_ns = cg.esphome_ns.namespace("dallas")
|
||||
DallasComponent = dallas_ns.class_("DallasComponent", cg.PollingComponent)
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(DallasComponent),
|
||||
cv.Required(CONF_PIN): pins.internal_gpio_output_pin_schema,
|
||||
}
|
||||
).extend(cv.polling_component_schema("60s"))
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
|
||||
pin = await cg.gpio_pin_expression(config[CONF_PIN])
|
||||
cg.add(var.set_pin(pin))
|
||||
CONFIG_SCHEMA = cv.invalid(
|
||||
'The "dallas" component has been replaced by the "one_wire" component.\nhttps://esphome.io/components/one_wire'
|
||||
)
|
||||
|
|
|
@ -1,287 +0,0 @@
|
|||
#include "dallas_component.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace dallas {
|
||||
|
||||
static const char *const TAG = "dallas.sensor";
|
||||
|
||||
static const uint8_t DALLAS_MODEL_DS18S20 = 0x10;
|
||||
static const uint8_t DALLAS_MODEL_DS1822 = 0x22;
|
||||
static const uint8_t DALLAS_MODEL_DS18B20 = 0x28;
|
||||
static const uint8_t DALLAS_MODEL_DS1825 = 0x3B;
|
||||
static const uint8_t DALLAS_MODEL_DS28EA00 = 0x42;
|
||||
static const uint8_t DALLAS_COMMAND_START_CONVERSION = 0x44;
|
||||
static const uint8_t DALLAS_COMMAND_READ_SCRATCH_PAD = 0xBE;
|
||||
static const uint8_t DALLAS_COMMAND_WRITE_SCRATCH_PAD = 0x4E;
|
||||
|
||||
uint16_t DallasTemperatureSensor::millis_to_wait_for_conversion() const {
|
||||
switch (this->resolution_) {
|
||||
case 9:
|
||||
return 94;
|
||||
case 10:
|
||||
return 188;
|
||||
case 11:
|
||||
return 375;
|
||||
default:
|
||||
return 750;
|
||||
}
|
||||
}
|
||||
|
||||
void DallasComponent::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up DallasComponent...");
|
||||
|
||||
pin_->setup();
|
||||
|
||||
// clear bus with 480µs high, otherwise initial reset in search_vec() fails
|
||||
pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
|
||||
delayMicroseconds(480);
|
||||
|
||||
one_wire_ = new ESPOneWire(pin_); // NOLINT(cppcoreguidelines-owning-memory)
|
||||
|
||||
std::vector<uint64_t> raw_sensors;
|
||||
raw_sensors = this->one_wire_->search_vec();
|
||||
|
||||
for (auto &address : raw_sensors) {
|
||||
auto *address8 = reinterpret_cast<uint8_t *>(&address);
|
||||
if (crc8(address8, 7) != address8[7]) {
|
||||
ESP_LOGW(TAG, "Dallas device 0x%s has invalid CRC.", format_hex(address).c_str());
|
||||
continue;
|
||||
}
|
||||
if (address8[0] != DALLAS_MODEL_DS18S20 && address8[0] != DALLAS_MODEL_DS1822 &&
|
||||
address8[0] != DALLAS_MODEL_DS18B20 && address8[0] != DALLAS_MODEL_DS1825 &&
|
||||
address8[0] != DALLAS_MODEL_DS28EA00) {
|
||||
ESP_LOGW(TAG, "Unknown device type 0x%02X.", address8[0]);
|
||||
continue;
|
||||
}
|
||||
this->found_sensors_.push_back(address);
|
||||
}
|
||||
|
||||
for (auto *sensor : this->sensors_) {
|
||||
if (sensor->get_index().has_value()) {
|
||||
if (*sensor->get_index() >= this->found_sensors_.size()) {
|
||||
this->status_set_error("Sensor configured by index but not found");
|
||||
continue;
|
||||
}
|
||||
sensor->set_address(this->found_sensors_[*sensor->get_index()]);
|
||||
}
|
||||
|
||||
if (!sensor->setup_sensor()) {
|
||||
this->status_set_error();
|
||||
}
|
||||
}
|
||||
}
|
||||
void DallasComponent::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "DallasComponent:");
|
||||
LOG_PIN(" Pin: ", this->pin_);
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
|
||||
if (this->found_sensors_.empty()) {
|
||||
ESP_LOGW(TAG, " Found no sensors!");
|
||||
} else {
|
||||
ESP_LOGD(TAG, " Found sensors:");
|
||||
for (auto &address : this->found_sensors_) {
|
||||
ESP_LOGD(TAG, " 0x%s", format_hex(address).c_str());
|
||||
}
|
||||
}
|
||||
|
||||
for (auto *sensor : this->sensors_) {
|
||||
LOG_SENSOR(" ", "Device", sensor);
|
||||
if (sensor->get_index().has_value()) {
|
||||
ESP_LOGCONFIG(TAG, " Index %u", *sensor->get_index());
|
||||
if (*sensor->get_index() >= this->found_sensors_.size()) {
|
||||
ESP_LOGE(TAG, "Couldn't find sensor by index - not connected. Proceeding without it.");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
ESP_LOGCONFIG(TAG, " Address: %s", sensor->get_address_name().c_str());
|
||||
ESP_LOGCONFIG(TAG, " Resolution: %u", sensor->get_resolution());
|
||||
}
|
||||
}
|
||||
|
||||
void DallasComponent::register_sensor(DallasTemperatureSensor *sensor) { this->sensors_.push_back(sensor); }
|
||||
void DallasComponent::update() {
|
||||
this->status_clear_warning();
|
||||
|
||||
bool result;
|
||||
{
|
||||
InterruptLock lock;
|
||||
result = this->one_wire_->reset();
|
||||
}
|
||||
if (!result) {
|
||||
if (!this->found_sensors_.empty()) {
|
||||
// Only log error if at the start sensors were found (and thus are disconnected during uptime)
|
||||
ESP_LOGE(TAG, "Requesting conversion failed");
|
||||
this->status_set_warning();
|
||||
}
|
||||
|
||||
for (auto *sensor : this->sensors_) {
|
||||
sensor->publish_state(NAN);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
InterruptLock lock;
|
||||
this->one_wire_->skip();
|
||||
this->one_wire_->write8(DALLAS_COMMAND_START_CONVERSION);
|
||||
}
|
||||
|
||||
for (auto *sensor : this->sensors_) {
|
||||
if (sensor->get_address() == 0) {
|
||||
ESP_LOGV(TAG, "'%s' - Indexed sensor not found at startup, skipping update", sensor->get_name().c_str());
|
||||
sensor->publish_state(NAN);
|
||||
continue;
|
||||
}
|
||||
|
||||
this->set_timeout(sensor->get_address_name(), sensor->millis_to_wait_for_conversion(), [this, sensor] {
|
||||
bool res = sensor->read_scratch_pad();
|
||||
|
||||
if (!res) {
|
||||
ESP_LOGW(TAG, "'%s' - Resetting bus for read failed!", sensor->get_name().c_str());
|
||||
sensor->publish_state(NAN);
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
if (!sensor->check_scratch_pad()) {
|
||||
sensor->publish_state(NAN);
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
|
||||
float tempc = sensor->get_temp_c();
|
||||
ESP_LOGD(TAG, "'%s': Got Temperature=%.1f°C", sensor->get_name().c_str(), tempc);
|
||||
sensor->publish_state(tempc);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void DallasTemperatureSensor::set_address(uint64_t address) { this->address_ = address; }
|
||||
uint8_t DallasTemperatureSensor::get_resolution() const { return this->resolution_; }
|
||||
void DallasTemperatureSensor::set_resolution(uint8_t resolution) { this->resolution_ = resolution; }
|
||||
optional<uint8_t> DallasTemperatureSensor::get_index() const { return this->index_; }
|
||||
void DallasTemperatureSensor::set_index(uint8_t index) { this->index_ = index; }
|
||||
uint8_t *DallasTemperatureSensor::get_address8() { return reinterpret_cast<uint8_t *>(&this->address_); }
|
||||
uint64_t DallasTemperatureSensor::get_address() { return this->address_; }
|
||||
|
||||
const std::string &DallasTemperatureSensor::get_address_name() {
|
||||
if (this->address_name_.empty()) {
|
||||
this->address_name_ = std::string("0x") + format_hex(this->address_);
|
||||
}
|
||||
|
||||
return this->address_name_;
|
||||
}
|
||||
bool IRAM_ATTR DallasTemperatureSensor::read_scratch_pad() {
|
||||
auto *wire = this->parent_->one_wire_;
|
||||
|
||||
{
|
||||
InterruptLock lock;
|
||||
|
||||
if (!wire->reset()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
wire->select(this->address_);
|
||||
wire->write8(DALLAS_COMMAND_READ_SCRATCH_PAD);
|
||||
|
||||
for (unsigned char &i : this->scratch_pad_) {
|
||||
i = wire->read8();
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
bool DallasTemperatureSensor::setup_sensor() {
|
||||
bool r = this->read_scratch_pad();
|
||||
|
||||
if (!r) {
|
||||
ESP_LOGE(TAG, "Reading scratchpad failed: reset");
|
||||
return false;
|
||||
}
|
||||
if (!this->check_scratch_pad())
|
||||
return false;
|
||||
|
||||
if (this->scratch_pad_[4] == this->resolution_)
|
||||
return false;
|
||||
|
||||
if (this->get_address8()[0] == DALLAS_MODEL_DS18S20) {
|
||||
// DS18S20 doesn't support resolution.
|
||||
ESP_LOGW(TAG, "DS18S20 doesn't support setting resolution.");
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (this->resolution_) {
|
||||
case 12:
|
||||
this->scratch_pad_[4] = 0x7F;
|
||||
break;
|
||||
case 11:
|
||||
this->scratch_pad_[4] = 0x5F;
|
||||
break;
|
||||
case 10:
|
||||
this->scratch_pad_[4] = 0x3F;
|
||||
break;
|
||||
case 9:
|
||||
default:
|
||||
this->scratch_pad_[4] = 0x1F;
|
||||
break;
|
||||
}
|
||||
|
||||
auto *wire = this->parent_->one_wire_;
|
||||
{
|
||||
InterruptLock lock;
|
||||
if (wire->reset()) {
|
||||
wire->select(this->address_);
|
||||
wire->write8(DALLAS_COMMAND_WRITE_SCRATCH_PAD);
|
||||
wire->write8(this->scratch_pad_[2]); // high alarm temp
|
||||
wire->write8(this->scratch_pad_[3]); // low alarm temp
|
||||
wire->write8(this->scratch_pad_[4]); // resolution
|
||||
wire->reset();
|
||||
|
||||
// write value to EEPROM
|
||||
wire->select(this->address_);
|
||||
wire->write8(0x48);
|
||||
}
|
||||
}
|
||||
|
||||
delay(20); // allow it to finish operation
|
||||
wire->reset();
|
||||
return true;
|
||||
}
|
||||
bool DallasTemperatureSensor::check_scratch_pad() {
|
||||
bool chksum_validity = (crc8(this->scratch_pad_, 8) == this->scratch_pad_[8]);
|
||||
bool config_validity = false;
|
||||
|
||||
switch (this->get_address8()[0]) {
|
||||
case DALLAS_MODEL_DS18B20:
|
||||
config_validity = ((this->scratch_pad_[4] & 0x9F) == 0x1F);
|
||||
break;
|
||||
default:
|
||||
config_validity = ((this->scratch_pad_[4] & 0x10) == 0x10);
|
||||
}
|
||||
|
||||
#ifdef ESPHOME_LOG_LEVEL_VERY_VERBOSE
|
||||
ESP_LOGVV(TAG, "Scratch pad: %02X.%02X.%02X.%02X.%02X.%02X.%02X.%02X.%02X (%02X)", this->scratch_pad_[0],
|
||||
this->scratch_pad_[1], this->scratch_pad_[2], this->scratch_pad_[3], this->scratch_pad_[4],
|
||||
this->scratch_pad_[5], this->scratch_pad_[6], this->scratch_pad_[7], this->scratch_pad_[8],
|
||||
crc8(this->scratch_pad_, 8));
|
||||
#endif
|
||||
if (!chksum_validity) {
|
||||
ESP_LOGW(TAG, "'%s' - Scratch pad checksum invalid!", this->get_name().c_str());
|
||||
} else if (!config_validity) {
|
||||
ESP_LOGW(TAG, "'%s' - Scratch pad config register invalid!", this->get_name().c_str());
|
||||
}
|
||||
return chksum_validity && config_validity;
|
||||
}
|
||||
float DallasTemperatureSensor::get_temp_c() {
|
||||
int16_t temp = (int16_t(this->scratch_pad_[1]) << 11) | (int16_t(this->scratch_pad_[0]) << 3);
|
||||
if (this->get_address8()[0] == DALLAS_MODEL_DS18S20) {
|
||||
int diff = (this->scratch_pad_[7] - this->scratch_pad_[6]) << 7;
|
||||
temp = ((temp & 0xFFF0) << 3) - 16 + (diff / this->scratch_pad_[7]);
|
||||
}
|
||||
|
||||
return temp / 128.0f;
|
||||
}
|
||||
std::string DallasTemperatureSensor::unique_id() { return "dallas-" + str_lower_case(format_hex(this->address_)); }
|
||||
|
||||
} // namespace dallas
|
||||
} // namespace esphome
|
|
@ -1,79 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esp_one_wire.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace esphome {
|
||||
namespace dallas {
|
||||
|
||||
class DallasTemperatureSensor;
|
||||
|
||||
class DallasComponent : public PollingComponent {
|
||||
public:
|
||||
void set_pin(InternalGPIOPin *pin) { pin_ = pin; }
|
||||
void register_sensor(DallasTemperatureSensor *sensor);
|
||||
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||
|
||||
void update() override;
|
||||
|
||||
protected:
|
||||
friend DallasTemperatureSensor;
|
||||
|
||||
InternalGPIOPin *pin_;
|
||||
ESPOneWire *one_wire_;
|
||||
std::vector<DallasTemperatureSensor *> sensors_;
|
||||
std::vector<uint64_t> found_sensors_;
|
||||
};
|
||||
|
||||
/// Internal class that helps us create multiple sensors for one Dallas hub.
|
||||
class DallasTemperatureSensor : public sensor::Sensor {
|
||||
public:
|
||||
void set_parent(DallasComponent *parent) { parent_ = parent; }
|
||||
/// Helper to get a pointer to the address as uint8_t.
|
||||
uint8_t *get_address8();
|
||||
uint64_t get_address();
|
||||
/// Helper to create (and cache) the name for this sensor. For example "0xfe0000031f1eaf29".
|
||||
const std::string &get_address_name();
|
||||
|
||||
/// Set the 64-bit unsigned address for this sensor.
|
||||
void set_address(uint64_t address);
|
||||
/// Get the index of this sensor. (0 if using address.)
|
||||
optional<uint8_t> get_index() const;
|
||||
/// Set the index of this sensor. If using index, address will be set after setup.
|
||||
void set_index(uint8_t index);
|
||||
/// Get the set resolution for this sensor.
|
||||
uint8_t get_resolution() const;
|
||||
/// Set the resolution for this sensor.
|
||||
void set_resolution(uint8_t resolution);
|
||||
/// Get the number of milliseconds we have to wait for the conversion phase.
|
||||
uint16_t millis_to_wait_for_conversion() const;
|
||||
|
||||
bool setup_sensor();
|
||||
bool read_scratch_pad();
|
||||
|
||||
bool check_scratch_pad();
|
||||
|
||||
float get_temp_c();
|
||||
|
||||
std::string unique_id() override;
|
||||
|
||||
protected:
|
||||
DallasComponent *parent_;
|
||||
uint64_t address_;
|
||||
optional<uint8_t> index_;
|
||||
|
||||
uint8_t resolution_;
|
||||
std::string address_name_;
|
||||
uint8_t scratch_pad_[9] = {
|
||||
0,
|
||||
};
|
||||
};
|
||||
|
||||
} // namespace dallas
|
||||
} // namespace esphome
|
|
@ -1,252 +0,0 @@
|
|||
#include "esp_one_wire.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace dallas {
|
||||
|
||||
static const char *const TAG = "dallas.one_wire";
|
||||
|
||||
const uint8_t ONE_WIRE_ROM_SELECT = 0x55;
|
||||
const int ONE_WIRE_ROM_SEARCH = 0xF0;
|
||||
|
||||
ESPOneWire::ESPOneWire(InternalGPIOPin *pin) { pin_ = pin->to_isr(); }
|
||||
|
||||
bool HOT IRAM_ATTR ESPOneWire::reset() {
|
||||
// See reset here:
|
||||
// https://www.maximintegrated.com/en/design/technical-documents/app-notes/1/126.html
|
||||
// Wait for communication to clear (delay G)
|
||||
pin_.pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
|
||||
uint8_t retries = 125;
|
||||
do {
|
||||
if (--retries == 0)
|
||||
return false;
|
||||
delayMicroseconds(2);
|
||||
} while (!pin_.digital_read());
|
||||
|
||||
// Send 480µs LOW TX reset pulse (drive bus low, delay H)
|
||||
pin_.pin_mode(gpio::FLAG_OUTPUT);
|
||||
pin_.digital_write(false);
|
||||
delayMicroseconds(480);
|
||||
|
||||
// Release the bus, delay I
|
||||
pin_.pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
|
||||
delayMicroseconds(70);
|
||||
|
||||
// sample bus, 0=device(s) present, 1=no device present
|
||||
bool r = !pin_.digital_read();
|
||||
// delay J
|
||||
delayMicroseconds(410);
|
||||
return r;
|
||||
}
|
||||
|
||||
void HOT IRAM_ATTR ESPOneWire::write_bit(bool bit) {
|
||||
// drive bus low
|
||||
pin_.pin_mode(gpio::FLAG_OUTPUT);
|
||||
pin_.digital_write(false);
|
||||
|
||||
// from datasheet:
|
||||
// write 0 low time: t_low0: min=60µs, max=120µs
|
||||
// write 1 low time: t_low1: min=1µs, max=15µs
|
||||
// time slot: t_slot: min=60µs, max=120µs
|
||||
// recovery time: t_rec: min=1µs
|
||||
// ds18b20 appears to read the bus after roughly 14µs
|
||||
uint32_t delay0 = bit ? 6 : 60;
|
||||
uint32_t delay1 = bit ? 54 : 5;
|
||||
|
||||
// delay A/C
|
||||
delayMicroseconds(delay0);
|
||||
// release bus
|
||||
pin_.digital_write(true);
|
||||
// delay B/D
|
||||
delayMicroseconds(delay1);
|
||||
}
|
||||
|
||||
bool HOT IRAM_ATTR ESPOneWire::read_bit() {
|
||||
// drive bus low
|
||||
pin_.pin_mode(gpio::FLAG_OUTPUT);
|
||||
pin_.digital_write(false);
|
||||
|
||||
// note: for reading we'll need very accurate timing, as the
|
||||
// timing for the digital_read() is tight; according to the datasheet,
|
||||
// we should read at the end of 16µs starting from the bus low
|
||||
// typically, the ds18b20 pulls the line high after 11µs for a logical 1
|
||||
// and 29µs for a logical 0
|
||||
|
||||
uint32_t start = micros();
|
||||
// datasheet says >1µs
|
||||
delayMicroseconds(3);
|
||||
|
||||
// release bus, delay E
|
||||
pin_.pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
|
||||
|
||||
// Unfortunately some frameworks have different characteristics than others
|
||||
// esp32 arduino appears to pull the bus low only after the digital_write(false),
|
||||
// whereas on esp-idf it already happens during the pin_mode(OUTPUT)
|
||||
// manually correct for this with these constants.
|
||||
|
||||
#ifdef USE_ESP32
|
||||
uint32_t timing_constant = 12;
|
||||
#else
|
||||
uint32_t timing_constant = 14;
|
||||
#endif
|
||||
|
||||
// measure from start value directly, to get best accurate timing no matter
|
||||
// how long pin_mode/delayMicroseconds took
|
||||
while (micros() - start < timing_constant)
|
||||
;
|
||||
|
||||
// sample bus to read bit from peer
|
||||
bool r = pin_.digital_read();
|
||||
|
||||
// read slot is at least 60µs; get as close to 60µs to spend less time with interrupts locked
|
||||
uint32_t now = micros();
|
||||
if (now - start < 60)
|
||||
delayMicroseconds(60 - (now - start));
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
void IRAM_ATTR ESPOneWire::write8(uint8_t val) {
|
||||
for (uint8_t i = 0; i < 8; i++) {
|
||||
this->write_bit(bool((1u << i) & val));
|
||||
}
|
||||
}
|
||||
|
||||
void IRAM_ATTR ESPOneWire::write64(uint64_t val) {
|
||||
for (uint8_t i = 0; i < 64; i++) {
|
||||
this->write_bit(bool((1ULL << i) & val));
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t IRAM_ATTR ESPOneWire::read8() {
|
||||
uint8_t ret = 0;
|
||||
for (uint8_t i = 0; i < 8; i++) {
|
||||
ret |= (uint8_t(this->read_bit()) << i);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
uint64_t IRAM_ATTR ESPOneWire::read64() {
|
||||
uint64_t ret = 0;
|
||||
for (uint8_t i = 0; i < 8; i++) {
|
||||
ret |= (uint64_t(this->read_bit()) << i);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
void IRAM_ATTR ESPOneWire::select(uint64_t address) {
|
||||
this->write8(ONE_WIRE_ROM_SELECT);
|
||||
this->write64(address);
|
||||
}
|
||||
void IRAM_ATTR ESPOneWire::reset_search() {
|
||||
this->last_discrepancy_ = 0;
|
||||
this->last_device_flag_ = false;
|
||||
this->rom_number_ = 0;
|
||||
}
|
||||
uint64_t IRAM_ATTR ESPOneWire::search() {
|
||||
if (this->last_device_flag_) {
|
||||
return 0u;
|
||||
}
|
||||
|
||||
{
|
||||
InterruptLock lock;
|
||||
if (!this->reset()) {
|
||||
// Reset failed or no devices present
|
||||
this->reset_search();
|
||||
return 0u;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t id_bit_number = 1;
|
||||
uint8_t last_zero = 0;
|
||||
uint8_t rom_byte_number = 0;
|
||||
bool search_result = false;
|
||||
uint8_t rom_byte_mask = 1;
|
||||
|
||||
{
|
||||
InterruptLock lock;
|
||||
// Initiate search
|
||||
this->write8(ONE_WIRE_ROM_SEARCH);
|
||||
do {
|
||||
// read bit
|
||||
bool id_bit = this->read_bit();
|
||||
// read its complement
|
||||
bool cmp_id_bit = this->read_bit();
|
||||
|
||||
if (id_bit && cmp_id_bit) {
|
||||
// No devices participating in search
|
||||
break;
|
||||
}
|
||||
|
||||
bool branch;
|
||||
|
||||
if (id_bit != cmp_id_bit) {
|
||||
// only chose one branch, the other one doesn't have any devices.
|
||||
branch = id_bit;
|
||||
} else {
|
||||
// there are devices with both 0s and 1s at this bit
|
||||
if (id_bit_number < this->last_discrepancy_) {
|
||||
branch = (this->rom_number8_()[rom_byte_number] & rom_byte_mask) > 0;
|
||||
} else {
|
||||
branch = id_bit_number == this->last_discrepancy_;
|
||||
}
|
||||
|
||||
if (!branch) {
|
||||
last_zero = id_bit_number;
|
||||
}
|
||||
}
|
||||
|
||||
if (branch) {
|
||||
// set bit
|
||||
this->rom_number8_()[rom_byte_number] |= rom_byte_mask;
|
||||
} else {
|
||||
// clear bit
|
||||
this->rom_number8_()[rom_byte_number] &= ~rom_byte_mask;
|
||||
}
|
||||
|
||||
// choose/announce branch
|
||||
this->write_bit(branch);
|
||||
id_bit_number++;
|
||||
rom_byte_mask <<= 1;
|
||||
if (rom_byte_mask == 0u) {
|
||||
// go to next byte
|
||||
rom_byte_number++;
|
||||
rom_byte_mask = 1;
|
||||
}
|
||||
} while (rom_byte_number < 8); // loop through all bytes
|
||||
}
|
||||
|
||||
if (id_bit_number >= 65) {
|
||||
this->last_discrepancy_ = last_zero;
|
||||
if (this->last_discrepancy_ == 0) {
|
||||
// we're at root and have no choices left, so this was the last one.
|
||||
this->last_device_flag_ = true;
|
||||
}
|
||||
search_result = true;
|
||||
}
|
||||
|
||||
search_result = search_result && (this->rom_number8_()[0] != 0);
|
||||
if (!search_result) {
|
||||
this->reset_search();
|
||||
return 0u;
|
||||
}
|
||||
|
||||
return this->rom_number_;
|
||||
}
|
||||
std::vector<uint64_t> ESPOneWire::search_vec() {
|
||||
std::vector<uint64_t> res;
|
||||
|
||||
this->reset_search();
|
||||
uint64_t address;
|
||||
while ((address = this->search()) != 0u)
|
||||
res.push_back(address);
|
||||
|
||||
return res;
|
||||
}
|
||||
void IRAM_ATTR ESPOneWire::skip() {
|
||||
this->write8(0xCC); // skip ROM
|
||||
}
|
||||
|
||||
uint8_t IRAM_ATTR *ESPOneWire::rom_number8_() { return reinterpret_cast<uint8_t *>(&this->rom_number_); }
|
||||
|
||||
} // namespace dallas
|
||||
} // namespace esphome
|
|
@ -1,68 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "esphome/core/hal.h"
|
||||
#include <vector>
|
||||
|
||||
namespace esphome {
|
||||
namespace dallas {
|
||||
|
||||
extern const uint8_t ONE_WIRE_ROM_SELECT;
|
||||
extern const int ONE_WIRE_ROM_SEARCH;
|
||||
|
||||
class ESPOneWire {
|
||||
public:
|
||||
explicit ESPOneWire(InternalGPIOPin *pin);
|
||||
|
||||
/** Reset the bus, should be done before all write operations.
|
||||
*
|
||||
* Takes approximately 1ms.
|
||||
*
|
||||
* @return Whether the operation was successful.
|
||||
*/
|
||||
bool reset();
|
||||
|
||||
/// Write a single bit to the bus, takes about 70µs.
|
||||
void write_bit(bool bit);
|
||||
|
||||
/// Read a single bit from the bus, takes about 70µs
|
||||
bool read_bit();
|
||||
|
||||
/// Write a word to the bus. LSB first.
|
||||
void write8(uint8_t val);
|
||||
|
||||
/// Write a 64 bit unsigned integer to the bus. LSB first.
|
||||
void write64(uint64_t val);
|
||||
|
||||
/// Write a command to the bus that addresses all devices by skipping the ROM.
|
||||
void skip();
|
||||
|
||||
/// Read an 8 bit word from the bus.
|
||||
uint8_t read8();
|
||||
|
||||
/// Read an 64-bit unsigned integer from the bus.
|
||||
uint64_t read64();
|
||||
|
||||
/// Select a specific address on the bus for the following command.
|
||||
void select(uint64_t address);
|
||||
|
||||
/// Reset the device search.
|
||||
void reset_search();
|
||||
|
||||
/// Search for a 1-Wire device on the bus. Returns 0 if all devices have been found.
|
||||
uint64_t search();
|
||||
|
||||
/// Helper that wraps search in a std::vector.
|
||||
std::vector<uint64_t> search_vec();
|
||||
|
||||
protected:
|
||||
/// Helper to get the internal 64-bit unsigned rom number as a 8-bit integer pointer.
|
||||
inline uint8_t *rom_number8_();
|
||||
|
||||
ISRInternalGPIOPin pin_;
|
||||
uint8_t last_discrepancy_{0};
|
||||
bool last_device_flag_{false};
|
||||
uint64_t rom_number_{0};
|
||||
};
|
||||
|
||||
} // namespace dallas
|
||||
} // namespace esphome
|
|
@ -1,50 +1,5 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import sensor
|
||||
from esphome.const import (
|
||||
CONF_ADDRESS,
|
||||
CONF_DALLAS_ID,
|
||||
CONF_INDEX,
|
||||
CONF_RESOLUTION,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_CELSIUS,
|
||||
|
||||
CONFIG_SCHEMA = cv.invalid(
|
||||
'The "dallas" sensor is now "dallas_temp"\nhttps://esphome.io/components/sensor/dallas_temp'
|
||||
)
|
||||
from . import DallasComponent, dallas_ns
|
||||
|
||||
DallasTemperatureSensor = dallas_ns.class_("DallasTemperatureSensor", sensor.Sensor)
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
sensor.sensor_schema(
|
||||
DallasTemperatureSensor,
|
||||
unit_of_measurement=UNIT_CELSIUS,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
).extend(
|
||||
{
|
||||
cv.GenerateID(CONF_DALLAS_ID): cv.use_id(DallasComponent),
|
||||
cv.Optional(CONF_ADDRESS): cv.hex_uint64_t,
|
||||
cv.Optional(CONF_INDEX): cv.positive_int,
|
||||
cv.Optional(CONF_RESOLUTION, default=12): cv.int_range(min=9, max=12),
|
||||
}
|
||||
),
|
||||
cv.has_exactly_one_key(CONF_ADDRESS, CONF_INDEX),
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
hub = await cg.get_variable(config[CONF_DALLAS_ID])
|
||||
var = await sensor.new_sensor(config)
|
||||
|
||||
if CONF_ADDRESS in config:
|
||||
cg.add(var.set_address(config[CONF_ADDRESS]))
|
||||
else:
|
||||
cg.add(var.set_index(config[CONF_INDEX]))
|
||||
|
||||
if CONF_RESOLUTION in config:
|
||||
cg.add(var.set_resolution(config[CONF_RESOLUTION]))
|
||||
|
||||
cg.add(var.set_parent(hub))
|
||||
|
||||
cg.add(hub.register_sensor(var))
|
||||
|
|
1
esphome/components/dallas_temp/__init__.py
Normal file
1
esphome/components/dallas_temp/__init__.py
Normal file
|
@ -0,0 +1 @@
|
|||
CODEOWNERS = ["@ssieb"]
|
172
esphome/components/dallas_temp/dallas_temp.cpp
Normal file
172
esphome/components/dallas_temp/dallas_temp.cpp
Normal file
|
@ -0,0 +1,172 @@
|
|||
#include "dallas_temp.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace dallas_temp {
|
||||
|
||||
static const char *const TAG = "dallas.temp.sensor";
|
||||
|
||||
static const uint8_t DALLAS_MODEL_DS18S20 = 0x10;
|
||||
static const uint8_t DALLAS_COMMAND_START_CONVERSION = 0x44;
|
||||
static const uint8_t DALLAS_COMMAND_READ_SCRATCH_PAD = 0xBE;
|
||||
static const uint8_t DALLAS_COMMAND_WRITE_SCRATCH_PAD = 0x4E;
|
||||
static const uint8_t DALLAS_COMMAND_COPY_SCRATCH_PAD = 0x48;
|
||||
|
||||
uint16_t DallasTemperatureSensor::millis_to_wait_for_conversion_() const {
|
||||
switch (this->resolution_) {
|
||||
case 9:
|
||||
return 94;
|
||||
case 10:
|
||||
return 188;
|
||||
case 11:
|
||||
return 375;
|
||||
default:
|
||||
return 750;
|
||||
}
|
||||
}
|
||||
|
||||
void DallasTemperatureSensor::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "Dallas Temperature Sensor:");
|
||||
if (this->address_ == 0) {
|
||||
ESP_LOGW(TAG, " Unable to select an address");
|
||||
return;
|
||||
}
|
||||
LOG_ONE_WIRE_DEVICE(this);
|
||||
ESP_LOGCONFIG(TAG, " Resolution: %u bits", this->resolution_);
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
}
|
||||
|
||||
void DallasTemperatureSensor::update() {
|
||||
if (this->address_ == 0)
|
||||
return;
|
||||
|
||||
this->status_clear_warning();
|
||||
|
||||
this->send_command_(DALLAS_COMMAND_START_CONVERSION);
|
||||
|
||||
this->set_timeout(this->get_address_name(), this->millis_to_wait_for_conversion_(), [this] {
|
||||
if (!this->read_scratch_pad_() || !this->check_scratch_pad_()) {
|
||||
this->publish_state(NAN);
|
||||
return;
|
||||
}
|
||||
|
||||
float tempc = this->get_temp_c_();
|
||||
ESP_LOGD(TAG, "'%s': Got Temperature=%.1f°C", this->get_name().c_str(), tempc);
|
||||
this->publish_state(tempc);
|
||||
});
|
||||
}
|
||||
|
||||
void IRAM_ATTR DallasTemperatureSensor::read_scratch_pad_int_() {
|
||||
for (uint8_t &i : this->scratch_pad_) {
|
||||
i = this->bus_->read8();
|
||||
}
|
||||
}
|
||||
|
||||
bool DallasTemperatureSensor::read_scratch_pad_() {
|
||||
bool success;
|
||||
{
|
||||
InterruptLock lock;
|
||||
success = this->send_command_(DALLAS_COMMAND_READ_SCRATCH_PAD);
|
||||
if (success)
|
||||
this->read_scratch_pad_int_();
|
||||
}
|
||||
if (!success) {
|
||||
ESP_LOGW(TAG, "'%s' - reading scratch pad failed bus reset", this->get_name().c_str());
|
||||
this->status_set_warning("bus reset failed");
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
void DallasTemperatureSensor::setup() {
|
||||
ESP_LOGCONFIG(TAG, "setting up Dallas temperature sensor...");
|
||||
if (!this->check_address_())
|
||||
return;
|
||||
if (!this->read_scratch_pad_())
|
||||
return;
|
||||
if (!this->check_scratch_pad_())
|
||||
return;
|
||||
|
||||
if ((this->address_ & 0xff) == DALLAS_MODEL_DS18S20) {
|
||||
// DS18S20 doesn't support resolution.
|
||||
ESP_LOGW(TAG, "DS18S20 doesn't support setting resolution.");
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t res;
|
||||
switch (this->resolution_) {
|
||||
case 12:
|
||||
res = 0x7F;
|
||||
break;
|
||||
case 11:
|
||||
res = 0x5F;
|
||||
break;
|
||||
case 10:
|
||||
res = 0x3F;
|
||||
break;
|
||||
case 9:
|
||||
default:
|
||||
res = 0x1F;
|
||||
break;
|
||||
}
|
||||
|
||||
if (this->scratch_pad_[4] == res)
|
||||
return;
|
||||
this->scratch_pad_[4] = res;
|
||||
|
||||
{
|
||||
InterruptLock lock;
|
||||
if (this->send_command_(DALLAS_COMMAND_WRITE_SCRATCH_PAD)) {
|
||||
this->bus_->write8(this->scratch_pad_[2]); // high alarm temp
|
||||
this->bus_->write8(this->scratch_pad_[3]); // low alarm temp
|
||||
this->bus_->write8(this->scratch_pad_[4]); // resolution
|
||||
}
|
||||
|
||||
// write value to EEPROM
|
||||
this->send_command_(DALLAS_COMMAND_COPY_SCRATCH_PAD);
|
||||
}
|
||||
}
|
||||
|
||||
bool DallasTemperatureSensor::check_scratch_pad_() {
|
||||
bool chksum_validity = (crc8(this->scratch_pad_, 8) == this->scratch_pad_[8]);
|
||||
|
||||
#ifdef ESPHOME_LOG_LEVEL_VERY_VERBOSE
|
||||
ESP_LOGVV(TAG, "Scratch pad: %02X.%02X.%02X.%02X.%02X.%02X.%02X.%02X.%02X (%02X)", this->scratch_pad_[0],
|
||||
this->scratch_pad_[1], this->scratch_pad_[2], this->scratch_pad_[3], this->scratch_pad_[4],
|
||||
this->scratch_pad_[5], this->scratch_pad_[6], this->scratch_pad_[7], this->scratch_pad_[8],
|
||||
crc8(this->scratch_pad_, 8));
|
||||
#endif
|
||||
if (!chksum_validity) {
|
||||
ESP_LOGW(TAG, "'%s' - Scratch pad checksum invalid!", this->get_name().c_str());
|
||||
this->status_set_warning("scratch pad checksum invalid");
|
||||
}
|
||||
return chksum_validity;
|
||||
}
|
||||
|
||||
float DallasTemperatureSensor::get_temp_c_() {
|
||||
int16_t temp = (this->scratch_pad_[1] << 8) | this->scratch_pad_[0];
|
||||
if ((this->address_ & 0xff) == DALLAS_MODEL_DS18S20) {
|
||||
if (this->scratch_pad_[7] != 0x10)
|
||||
ESP_LOGE(TAG, "unexpected COUNT_PER_C value: %u", this->scratch_pad_[7]);
|
||||
temp = ((temp & 0xfff7) << 3) + (0x10 - this->scratch_pad_[6]) - 4;
|
||||
} else {
|
||||
switch (this->resolution_) {
|
||||
case 9:
|
||||
temp &= 0xfff8;
|
||||
break;
|
||||
case 10:
|
||||
temp &= 0xfffc;
|
||||
break;
|
||||
case 11:
|
||||
temp &= 0xfffe;
|
||||
break;
|
||||
case 12:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return temp / 16.0f;
|
||||
}
|
||||
|
||||
} // namespace dallas_temp
|
||||
} // namespace esphome
|
32
esphome/components/dallas_temp/dallas_temp.h
Normal file
32
esphome/components/dallas_temp/dallas_temp.h
Normal file
|
@ -0,0 +1,32 @@
|
|||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/one_wire/one_wire.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace dallas_temp {
|
||||
|
||||
class DallasTemperatureSensor : public PollingComponent, public sensor::Sensor, public one_wire::OneWireDevice {
|
||||
public:
|
||||
void setup() override;
|
||||
void update() override;
|
||||
void dump_config() override;
|
||||
|
||||
/// Set the resolution for this sensor.
|
||||
void set_resolution(uint8_t resolution) { this->resolution_ = resolution; }
|
||||
|
||||
protected:
|
||||
uint8_t resolution_;
|
||||
uint8_t scratch_pad_[9] = {0};
|
||||
|
||||
/// Get the number of milliseconds we have to wait for the conversion phase.
|
||||
uint16_t millis_to_wait_for_conversion_() const;
|
||||
bool read_scratch_pad_();
|
||||
void read_scratch_pad_int_();
|
||||
bool check_scratch_pad_();
|
||||
float get_temp_c_();
|
||||
};
|
||||
|
||||
} // namespace dallas_temp
|
||||
} // namespace esphome
|
43
esphome/components/dallas_temp/sensor.py
Normal file
43
esphome/components/dallas_temp/sensor.py
Normal file
|
@ -0,0 +1,43 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import one_wire, sensor
|
||||
from esphome.const import (
|
||||
CONF_RESOLUTION,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_CELSIUS,
|
||||
)
|
||||
|
||||
dallas_temp_ns = cg.esphome_ns.namespace("dallas_temp")
|
||||
|
||||
DallasTemperatureSensor = dallas_temp_ns.class_(
|
||||
"DallasTemperatureSensor",
|
||||
cg.PollingComponent,
|
||||
sensor.Sensor,
|
||||
one_wire.OneWireDevice,
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
sensor.sensor_schema(
|
||||
DallasTemperatureSensor,
|
||||
unit_of_measurement=UNIT_CELSIUS,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
)
|
||||
.extend(
|
||||
{
|
||||
cv.Optional(CONF_RESOLUTION, default=12): cv.int_range(min=9, max=12),
|
||||
}
|
||||
)
|
||||
.extend(one_wire.one_wire_device_schema())
|
||||
.extend(cv.polling_component_schema("60s"))
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = await sensor.new_sensor(config)
|
||||
await cg.register_component(var, config)
|
||||
await one_wire.register_one_wire_device(var, config)
|
||||
|
||||
cg.add(var.set_resolution(config[CONF_RESOLUTION]))
|
|
@ -2,7 +2,7 @@ import esphome.codegen as cg
|
|||
|
||||
import esphome.config_validation as cv
|
||||
from esphome import automation
|
||||
from esphome.components import mqtt, time
|
||||
from esphome.components import mqtt, web_server, time
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_ON_TIME,
|
||||
|
@ -11,6 +11,7 @@ from esphome.const import (
|
|||
CONF_TRIGGER_ID,
|
||||
CONF_TYPE,
|
||||
CONF_MQTT_ID,
|
||||
CONF_WEB_SERVER_ID,
|
||||
CONF_DATE,
|
||||
CONF_DATETIME,
|
||||
CONF_TIME,
|
||||
|
@ -63,16 +64,20 @@ DATETIME_MODES = [
|
|||
]
|
||||
|
||||
|
||||
_DATETIME_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_ON_VALUE): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(DateTimeStateTrigger),
|
||||
}
|
||||
),
|
||||
cv.GenerateID(CONF_TIME_ID): cv.use_id(time.RealTimeClock),
|
||||
}
|
||||
).extend(cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA))
|
||||
_DATETIME_SCHEMA = (
|
||||
cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA)
|
||||
.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA)
|
||||
.extend(
|
||||
{
|
||||
cv.Optional(CONF_ON_VALUE): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(DateTimeStateTrigger),
|
||||
}
|
||||
),
|
||||
cv.GenerateID(CONF_TIME_ID): cv.use_id(time.RealTimeClock),
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def date_schema(class_: MockObjClass) -> cv.Schema:
|
||||
|
@ -128,6 +133,9 @@ async def setup_datetime_core_(var, config):
|
|||
if (mqtt_id := config.get(CONF_MQTT_ID)) is not None:
|
||||
mqtt_ = cg.new_Pvariable(mqtt_id, var)
|
||||
await mqtt.register_mqtt_component(mqtt_, config)
|
||||
if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None:
|
||||
web_server_ = await cg.get_variable(webserver_id)
|
||||
web_server.add_entity_to_sorting_list(web_server_, var, config)
|
||||
for conf in config.get(CONF_ON_VALUE, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [(cg.ESPTime, "x")], conf)
|
||||
|
|
|
@ -80,6 +80,17 @@ void DateCall::validate_() {
|
|||
|
||||
void DateCall::perform() {
|
||||
this->validate_();
|
||||
ESP_LOGD(TAG, "'%s' - Setting", this->parent_->get_name().c_str());
|
||||
|
||||
if (this->year_.has_value()) {
|
||||
ESP_LOGD(TAG, " Year: %d", *this->year_);
|
||||
}
|
||||
if (this->month_.has_value()) {
|
||||
ESP_LOGD(TAG, " Month: %d", *this->month_);
|
||||
}
|
||||
if (this->day_.has_value()) {
|
||||
ESP_LOGD(TAG, " Day: %d", *this->day_);
|
||||
}
|
||||
this->parent_->control(*this);
|
||||
}
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ std::string DebugComponent::get_reset_reason_() { return lt_get_reboot_reason_na
|
|||
uint32_t DebugComponent::get_free_heap_() { return lt_heap_get_free(); }
|
||||
|
||||
void DebugComponent::get_device_info_(std::string &device_info) {
|
||||
str::string reset_reason = get_reset_reason_();
|
||||
std::string reset_reason = get_reset_reason_();
|
||||
ESP_LOGD(TAG, "LibreTiny Version: %s", lt_get_version());
|
||||
ESP_LOGD(TAG, "Chip: %s (%04x) @ %u MHz", lt_cpu_get_model_name(), lt_cpu_get_model(), lt_cpu_get_freq_mhz());
|
||||
ESP_LOGD(TAG, "Chip ID: 0x%06X", lt_cpu_get_mac_id());
|
||||
|
|
|
@ -34,10 +34,12 @@ enum WakeupPinMode {
|
|||
WAKEUP_PIN_MODE_INVERT_WAKEUP,
|
||||
};
|
||||
|
||||
#if defined(USE_ESP32) && !defined(USE_ESP32_VARIANT_ESP32C3)
|
||||
struct Ext1Wakeup {
|
||||
uint64_t mask;
|
||||
esp_sleep_ext1_wakeup_mode_t wakeup_mode;
|
||||
};
|
||||
#endif
|
||||
|
||||
struct WakeupCauseToRunDuration {
|
||||
// Run duration if woken up by timer or any other reason besides those below.
|
||||
|
@ -114,7 +116,11 @@ class DeepSleepComponent : public Component {
|
|||
#ifdef USE_ESP32
|
||||
InternalGPIOPin *wakeup_pin_;
|
||||
WakeupPinMode wakeup_pin_mode_{WAKEUP_PIN_MODE_IGNORE};
|
||||
|
||||
#if !defined(USE_ESP32_VARIANT_ESP32C3)
|
||||
optional<Ext1Wakeup> ext1_wakeup_;
|
||||
#endif
|
||||
|
||||
optional<bool> touch_wakeup_;
|
||||
optional<WakeupCauseToRunDuration> wakeup_cause_to_run_duration_;
|
||||
#endif
|
||||
|
|
|
@ -86,9 +86,14 @@ bool HOT IRAM_ATTR DHT::read_sensor_(float *temperature, float *humidity, bool r
|
|||
if (this->model_ == DHT_MODEL_DHT11) {
|
||||
delayMicroseconds(18000);
|
||||
} else if (this->model_ == DHT_MODEL_SI7021) {
|
||||
#ifdef USE_ESP8266
|
||||
delayMicroseconds(500);
|
||||
this->pin_->digital_write(true);
|
||||
delayMicroseconds(40);
|
||||
#else
|
||||
delayMicroseconds(400);
|
||||
this->pin_->digital_write(true);
|
||||
#endif
|
||||
} else if (this->model_ == DHT_MODEL_DHT22_TYPE2) {
|
||||
delayMicroseconds(2000);
|
||||
} else if (this->model_ == DHT_MODEL_AM2120 || this->model_ == DHT_MODEL_AM2302) {
|
||||
|
|
|
@ -96,16 +96,16 @@ def get_board(core_obj=None):
|
|||
def get_download_types(storage_json):
|
||||
return [
|
||||
{
|
||||
"title": "Modern format",
|
||||
"title": "Factory format (Previously Modern)",
|
||||
"description": "For use with ESPHome Web and other tools.",
|
||||
"file": "firmware-factory.bin",
|
||||
"download": f"{storage_json.name}-factory.bin",
|
||||
"file": "firmware.factory.bin",
|
||||
"download": f"{storage_json.name}.factory.bin",
|
||||
},
|
||||
{
|
||||
"title": "Legacy format",
|
||||
"description": "For use with ESPHome Flasher.",
|
||||
"file": "firmware.bin",
|
||||
"download": f"{storage_json.name}.bin",
|
||||
"title": "OTA format (Previously Legacy)",
|
||||
"description": "For OTA updating a device.",
|
||||
"file": "firmware.ota.bin",
|
||||
"download": f"{storage_json.name}.ota.bin",
|
||||
},
|
||||
]
|
||||
|
||||
|
|
|
@ -17,17 +17,19 @@ from SCons.Script import ARGUMENTS
|
|||
|
||||
# Copy over the default sdkconfig.
|
||||
from os import path
|
||||
|
||||
if path.exists("./sdkconfig.defaults"):
|
||||
os.makedirs(".temp", exist_ok=True)
|
||||
shutil.copy("./sdkconfig.defaults", "./.temp/sdkconfig-esp32-idf")
|
||||
|
||||
|
||||
def esp32_create_combined_bin(source, target, env):
|
||||
verbose = bool(int(ARGUMENTS.get("PIOVERBOSE", "0")))
|
||||
if verbose:
|
||||
print("Generating combined binary for serial flashing")
|
||||
app_offset = 0x10000
|
||||
|
||||
new_file_name = env.subst("$BUILD_DIR/${PROGNAME}-factory.bin")
|
||||
new_file_name = env.subst("$BUILD_DIR/${PROGNAME}.factory.bin")
|
||||
sections = env.subst(env.get("FLASH_EXTRA_IMAGES"))
|
||||
firmware_name = env.subst("$BUILD_DIR/${PROGNAME}.bin")
|
||||
chip = env.get("BOARD_MCU")
|
||||
|
@ -62,5 +64,14 @@ def esp32_create_combined_bin(source, target, env):
|
|||
else:
|
||||
subprocess.run(["esptool.py", *cmd])
|
||||
|
||||
|
||||
def esp32_copy_ota_bin(source, target, env):
|
||||
firmware_name = env.subst("$BUILD_DIR/${PROGNAME}.bin")
|
||||
new_file_name = env.subst("$BUILD_DIR/${PROGNAME}.ota.bin")
|
||||
|
||||
shutil.copyfile(firmware_name, new_file_name)
|
||||
|
||||
|
||||
# pylint: disable=E0602
|
||||
env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", esp32_create_combined_bin) # noqa
|
||||
env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", esp32_copy_ota_bin) # noqa
|
||||
|
|
|
@ -17,7 +17,7 @@ from esphome.const import (
|
|||
CONF_VSYNC_PIN,
|
||||
)
|
||||
from esphome.core import CORE
|
||||
from esphome.components.esp32 import add_idf_sdkconfig_option
|
||||
from esphome.components.esp32 import add_idf_component
|
||||
from esphome.cpp_helpers import setup_entity
|
||||
|
||||
DEPENDENCIES = ["esp32"]
|
||||
|
@ -290,8 +290,11 @@ async def to_code(config):
|
|||
cg.add_define("USE_ESP32_CAMERA")
|
||||
|
||||
if CORE.using_esp_idf:
|
||||
cg.add_library("espressif/esp32-camera", "1.0.0")
|
||||
add_idf_sdkconfig_option("CONFIG_RTCIO_SUPPORT_RTC_GPIO_DESC", True)
|
||||
add_idf_component(
|
||||
name="esp32-camera",
|
||||
repo="https://github.com/espressif/esp32-camera.git",
|
||||
ref="v2.0.9",
|
||||
)
|
||||
|
||||
for conf in config.get(CONF_ON_STREAM_START, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
|
|
|
@ -6,10 +6,18 @@ Import("env") # noqa
|
|||
|
||||
def esp8266_copy_factory_bin(source, target, env):
|
||||
firmware_name = env.subst("$BUILD_DIR/${PROGNAME}.bin")
|
||||
new_file_name = env.subst("$BUILD_DIR/${PROGNAME}-factory.bin")
|
||||
new_file_name = env.subst("$BUILD_DIR/${PROGNAME}.factory.bin")
|
||||
|
||||
shutil.copyfile(firmware_name, new_file_name)
|
||||
|
||||
|
||||
def esp8266_copy_ota_bin(source, target, env):
|
||||
firmware_name = env.subst("$BUILD_DIR/${PROGNAME}.bin")
|
||||
new_file_name = env.subst("$BUILD_DIR/${PROGNAME}.ota.bin")
|
||||
|
||||
shutil.copyfile(firmware_name, new_file_name)
|
||||
|
||||
|
||||
# pylint: disable=E0602
|
||||
env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", esp8266_copy_factory_bin) # noqa
|
||||
env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", esp8266_copy_ota_bin) # noqa
|
||||
|
|
|
@ -11,6 +11,7 @@ from esphome.components.esp32.const import (
|
|||
from esphome.const import (
|
||||
CONF_DOMAIN,
|
||||
CONF_ID,
|
||||
CONF_VALUE,
|
||||
CONF_MANUAL_IP,
|
||||
CONF_STATIC_IP,
|
||||
CONF_TYPE,
|
||||
|
@ -26,6 +27,8 @@ from esphome.const import (
|
|||
CONF_INTERRUPT_PIN,
|
||||
CONF_RESET_PIN,
|
||||
CONF_SPI,
|
||||
CONF_PAGE_ID,
|
||||
CONF_ADDRESS,
|
||||
)
|
||||
from esphome.core import CORE, coroutine_with_priority
|
||||
from esphome.components.network import IPAddress
|
||||
|
@ -36,11 +39,13 @@ DEPENDENCIES = ["esp32"]
|
|||
AUTO_LOAD = ["network"]
|
||||
|
||||
ethernet_ns = cg.esphome_ns.namespace("ethernet")
|
||||
PHYRegister = ethernet_ns.struct("PHYRegister")
|
||||
CONF_PHY_ADDR = "phy_addr"
|
||||
CONF_MDC_PIN = "mdc_pin"
|
||||
CONF_MDIO_PIN = "mdio_pin"
|
||||
CONF_CLK_MODE = "clk_mode"
|
||||
CONF_POWER_PIN = "power_pin"
|
||||
CONF_PHY_REGISTERS = "phy_registers"
|
||||
|
||||
CONF_CLOCK_SPEED = "clock_speed"
|
||||
|
||||
|
@ -117,6 +122,13 @@ BASE_SCHEMA = cv.Schema(
|
|||
}
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
PHY_REGISTER_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_ADDRESS): cv.hex_int,
|
||||
cv.Required(CONF_VALUE): cv.hex_int,
|
||||
cv.Optional(CONF_PAGE_ID): cv.hex_int,
|
||||
}
|
||||
)
|
||||
RMII_SCHEMA = BASE_SCHEMA.extend(
|
||||
cv.Schema(
|
||||
{
|
||||
|
@ -127,6 +139,7 @@ RMII_SCHEMA = BASE_SCHEMA.extend(
|
|||
),
|
||||
cv.Optional(CONF_PHY_ADDR, default=0): cv.int_range(min=0, max=31),
|
||||
cv.Optional(CONF_POWER_PIN): pins.internal_gpio_output_pin_number,
|
||||
cv.Optional(CONF_PHY_REGISTERS): cv.ensure_list(PHY_REGISTER_SCHEMA),
|
||||
}
|
||||
)
|
||||
)
|
||||
|
@ -198,6 +211,15 @@ def manual_ip(config):
|
|||
)
|
||||
|
||||
|
||||
def phy_register(address: int, value: int, page: int):
|
||||
return cg.StructInitializer(
|
||||
PHYRegister,
|
||||
("address", address),
|
||||
("value", value),
|
||||
("page", page),
|
||||
)
|
||||
|
||||
|
||||
@coroutine_with_priority(60.0)
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
|
@ -225,6 +247,13 @@ async def to_code(config):
|
|||
cg.add(var.set_clk_mode(*CLK_MODES[config[CONF_CLK_MODE]]))
|
||||
if CONF_POWER_PIN in config:
|
||||
cg.add(var.set_power_pin(config[CONF_POWER_PIN]))
|
||||
for register_value in config.get(CONF_PHY_REGISTERS, []):
|
||||
reg = phy_register(
|
||||
register_value.get(CONF_ADDRESS),
|
||||
register_value.get(CONF_VALUE),
|
||||
register_value.get(CONF_PAGE_ID),
|
||||
)
|
||||
cg.add(var.add_phy_register(reg))
|
||||
|
||||
cg.add(var.set_type(ETHERNET_TYPES[config[CONF_TYPE]]))
|
||||
cg.add(var.set_use_address(config[CONF_USE_ADDRESS]))
|
||||
|
|
|
@ -28,6 +28,13 @@ EthernetComponent *global_eth_component; // NOLINT(cppcoreguidelines-avoid-non-
|
|||
return; \
|
||||
}
|
||||
|
||||
#define ESPHL_ERROR_CHECK_RET(err, message, ret) \
|
||||
if ((err) != ESP_OK) { \
|
||||
ESP_LOGE(TAG, message ": (%d) %s", err, esp_err_to_name(err)); \
|
||||
this->mark_failed(); \
|
||||
return ret; \
|
||||
}
|
||||
|
||||
EthernetComponent::EthernetComponent() { global_eth_component = this; }
|
||||
|
||||
void EthernetComponent::setup() {
|
||||
|
@ -98,11 +105,15 @@ void EthernetComponent::setup() {
|
|||
.post_cb = nullptr,
|
||||
};
|
||||
|
||||
#if USE_ESP_IDF && (ESP_IDF_VERSION_MAJOR >= 5)
|
||||
eth_w5500_config_t w5500_config = ETH_W5500_DEFAULT_CONFIG(host, &devcfg);
|
||||
#else
|
||||
spi_device_handle_t spi_handle = nullptr;
|
||||
err = spi_bus_add_device(host, &devcfg, &spi_handle);
|
||||
ESPHL_ERROR_CHECK(err, "SPI bus add device error");
|
||||
|
||||
eth_w5500_config_t w5500_config = ETH_W5500_DEFAULT_CONFIG(spi_handle);
|
||||
#endif
|
||||
w5500_config.int_gpio_num = this->interrupt_pin_;
|
||||
phy_config.phy_addr = this->phy_addr_spi_;
|
||||
phy_config.reset_gpio_num = this->reset_pin_;
|
||||
|
@ -184,9 +195,9 @@ void EthernetComponent::setup() {
|
|||
// KSZ8081RNA default is incorrect. It expects a 25MHz clock instead of the 50MHz we provide.
|
||||
this->ksz8081_set_clock_reference_(mac);
|
||||
}
|
||||
if (this->type_ == ETHERNET_TYPE_RTL8201 && this->clk_mode_ == EMAC_CLK_EXT_IN) {
|
||||
// Change in default behavior of RTL8201FI may require register setting to enable external clock
|
||||
this->rtl8201_set_rmii_mode_(mac);
|
||||
|
||||
for (const auto &phy_register : this->phy_registers_) {
|
||||
this->write_phy_register_(mac, phy_register);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
@ -406,7 +417,7 @@ void EthernetComponent::start_connect_() {
|
|||
global_eth_component->ipv6_count_ = 0;
|
||||
#endif /* USE_NETWORK_IPV6 */
|
||||
this->connect_begin_ = millis();
|
||||
this->status_set_warning();
|
||||
this->status_set_warning("waiting for IP configuration");
|
||||
|
||||
esp_err_t err;
|
||||
err = esp_netif_set_hostname(this->eth_netif_, App.get_name().c_str());
|
||||
|
@ -494,22 +505,9 @@ void EthernetComponent::dump_connect_params_() {
|
|||
}
|
||||
#endif /* USE_NETWORK_IPV6 */
|
||||
|
||||
esp_err_t err;
|
||||
|
||||
uint8_t mac[6];
|
||||
err = esp_eth_ioctl(this->eth_handle_, ETH_CMD_G_MAC_ADDR, &mac);
|
||||
ESPHL_ERROR_CHECK(err, "ETH_CMD_G_MAC error");
|
||||
ESP_LOGCONFIG(TAG, " MAC Address: %02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
|
||||
|
||||
eth_duplex_t duplex_mode;
|
||||
err = esp_eth_ioctl(this->eth_handle_, ETH_CMD_G_DUPLEX_MODE, &duplex_mode);
|
||||
ESPHL_ERROR_CHECK(err, "ETH_CMD_G_DUPLEX_MODE error");
|
||||
ESP_LOGCONFIG(TAG, " Is Full Duplex: %s", YESNO(duplex_mode == ETH_DUPLEX_FULL));
|
||||
|
||||
eth_speed_t speed;
|
||||
err = esp_eth_ioctl(this->eth_handle_, ETH_CMD_G_SPEED, &speed);
|
||||
ESPHL_ERROR_CHECK(err, "ETH_CMD_G_SPEED error");
|
||||
ESP_LOGCONFIG(TAG, " Link Speed: %u", speed == ETH_SPEED_100M ? 100 : 10);
|
||||
ESP_LOGCONFIG(TAG, " MAC Address: %s", this->get_eth_mac_address_pretty().c_str());
|
||||
ESP_LOGCONFIG(TAG, " Is Full Duplex: %s", YESNO(this->get_duplex_mode() == ETH_DUPLEX_FULL));
|
||||
ESP_LOGCONFIG(TAG, " Link Speed: %u", this->get_link_speed() == ETH_SPEED_100M ? 100 : 10);
|
||||
}
|
||||
|
||||
#ifdef USE_ETHERNET_SPI
|
||||
|
@ -529,6 +527,7 @@ void EthernetComponent::set_clk_mode(emac_rmii_clock_mode_t clk_mode, emac_rmii_
|
|||
this->clk_mode_ = clk_mode;
|
||||
this->clk_gpio_ = clk_gpio;
|
||||
}
|
||||
void EthernetComponent::add_phy_register(PHYRegister register_value) { this->phy_registers_.push_back(register_value); }
|
||||
#endif
|
||||
void EthernetComponent::set_type(EthernetType type) { this->type_ = type; }
|
||||
void EthernetComponent::set_manual_ip(const ManualIP &manual_ip) { this->manual_ip_ = manual_ip; }
|
||||
|
@ -542,6 +541,34 @@ std::string EthernetComponent::get_use_address() const {
|
|||
|
||||
void EthernetComponent::set_use_address(const std::string &use_address) { this->use_address_ = use_address; }
|
||||
|
||||
void EthernetComponent::get_eth_mac_address_raw(uint8_t *mac) {
|
||||
esp_err_t err;
|
||||
err = esp_eth_ioctl(this->eth_handle_, ETH_CMD_G_MAC_ADDR, mac);
|
||||
ESPHL_ERROR_CHECK(err, "ETH_CMD_G_MAC error");
|
||||
}
|
||||
|
||||
std::string EthernetComponent::get_eth_mac_address_pretty() {
|
||||
uint8_t mac[6];
|
||||
get_mac_address_raw(mac);
|
||||
return str_snprintf("%02X:%02X:%02X:%02X:%02X:%02X", 17, mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
|
||||
}
|
||||
|
||||
eth_duplex_t EthernetComponent::get_duplex_mode() {
|
||||
esp_err_t err;
|
||||
eth_duplex_t duplex_mode;
|
||||
err = esp_eth_ioctl(this->eth_handle_, ETH_CMD_G_DUPLEX_MODE, &duplex_mode);
|
||||
ESPHL_ERROR_CHECK_RET(err, "ETH_CMD_G_DUPLEX_MODE error", ETH_DUPLEX_HALF);
|
||||
return duplex_mode;
|
||||
}
|
||||
|
||||
eth_speed_t EthernetComponent::get_link_speed() {
|
||||
esp_err_t err;
|
||||
eth_speed_t speed;
|
||||
err = esp_eth_ioctl(this->eth_handle_, ETH_CMD_G_SPEED, &speed);
|
||||
ESPHL_ERROR_CHECK_RET(err, "ETH_CMD_G_SPEED error", ETH_SPEED_10M);
|
||||
return speed;
|
||||
}
|
||||
|
||||
bool EthernetComponent::powerdown() {
|
||||
ESP_LOGI(TAG, "Powering down ethernet PHY");
|
||||
if (this->phy_ == nullptr) {
|
||||
|
@ -572,11 +599,11 @@ void EthernetComponent::ksz8081_set_clock_reference_(esp_eth_mac_t *mac) {
|
|||
/*
|
||||
* Bit 7 is `RMII Reference Clock Select`. Default is `0`.
|
||||
* KSZ8081RNA:
|
||||
* 0 - clock input to XI (Pin 8) is 25 MHz for RMII – 25 MHz clock mode.
|
||||
* 1 - clock input to XI (Pin 8) is 50 MHz for RMII – 50 MHz clock mode.
|
||||
* 0 - clock input to XI (Pin 8) is 25 MHz for RMII - 25 MHz clock mode.
|
||||
* 1 - clock input to XI (Pin 8) is 50 MHz for RMII - 50 MHz clock mode.
|
||||
* KSZ8081RND:
|
||||
* 0 - clock input to XI (Pin 8) is 50 MHz for RMII – 50 MHz clock mode.
|
||||
* 1 - clock input to XI (Pin 8) is 25 MHz (driven clock only, not a crystal) for RMII – 25 MHz clock mode.
|
||||
* 0 - clock input to XI (Pin 8) is 50 MHz for RMII - 50 MHz clock mode.
|
||||
* 1 - clock input to XI (Pin 8) is 25 MHz (driven clock only, not a crystal) for RMII - 25 MHz clock mode.
|
||||
*/
|
||||
if ((phy_control_2 & (1 << 7)) != (1 << 7)) {
|
||||
phy_control_2 |= 1 << 7;
|
||||
|
@ -587,44 +614,27 @@ void EthernetComponent::ksz8081_set_clock_reference_(esp_eth_mac_t *mac) {
|
|||
ESP_LOGVV(TAG, "KSZ8081 PHY Control 2: %s", format_hex_pretty((u_int8_t *) &phy_control_2, 2).c_str());
|
||||
}
|
||||
}
|
||||
constexpr uint8_t RTL8201_RMSR_REG_ADDR = 0x10;
|
||||
void EthernetComponent::rtl8201_set_rmii_mode_(esp_eth_mac_t *mac) {
|
||||
|
||||
void EthernetComponent::write_phy_register_(esp_eth_mac_t *mac, PHYRegister register_data) {
|
||||
esp_err_t err;
|
||||
uint32_t phy_rmii_mode;
|
||||
err = mac->write_phy_reg(mac, this->phy_addr_, 0x1f, 0x07);
|
||||
ESPHL_ERROR_CHECK(err, "Setting Page 7 failed");
|
||||
constexpr uint8_t eth_phy_psr_reg_addr = 0x1F;
|
||||
|
||||
/*
|
||||
* RTL8201 RMII Mode Setting Register (RMSR)
|
||||
* Page 7 Register 16
|
||||
*
|
||||
* bit 0 Reserved 0
|
||||
* bit 1 Rg_rmii_rxdsel 1 (default)
|
||||
* bit 2 Rg_rmii_rxdv_sel: 0 (default)
|
||||
* bit 3 RMII Mode: 1 (RMII Mode)
|
||||
* bit 4~7 Rg_rmii_rx_offset: 1111 (default)
|
||||
* bit 8~11 Rg_rmii_tx_offset: 1111 (default)
|
||||
* bit 12 Rg_rmii_clkdir: 1 (Input)
|
||||
* bit 13~15 Reserved 000
|
||||
*
|
||||
* Binary: 0001 1111 1111 1010
|
||||
* Hex: 0x1FFA
|
||||
*
|
||||
*/
|
||||
if (this->type_ == ETHERNET_TYPE_RTL8201 && register_data.page) {
|
||||
ESP_LOGD(TAG, "Select PHY Register Page: 0x%02" PRIX32, register_data.page);
|
||||
err = mac->write_phy_reg(mac, this->phy_addr_, eth_phy_psr_reg_addr, register_data.page);
|
||||
ESPHL_ERROR_CHECK(err, "Select PHY Register page failed");
|
||||
}
|
||||
|
||||
err = mac->read_phy_reg(mac, this->phy_addr_, RTL8201_RMSR_REG_ADDR, &(phy_rmii_mode));
|
||||
ESPHL_ERROR_CHECK(err, "Read PHY RMSR Register failed");
|
||||
ESP_LOGV(TAG, "Hardware default RTL8201 RMII Mode Register is: 0x%04X", phy_rmii_mode);
|
||||
ESP_LOGD(TAG, "Writing to PHY Register Address: 0x%02" PRIX32, register_data.address);
|
||||
ESP_LOGD(TAG, "Writing to PHY Register Value: 0x%04" PRIX32, register_data.value);
|
||||
err = mac->write_phy_reg(mac, this->phy_addr_, register_data.address, register_data.value);
|
||||
ESPHL_ERROR_CHECK(err, "Writing PHY Register failed");
|
||||
|
||||
err = mac->write_phy_reg(mac, this->phy_addr_, RTL8201_RMSR_REG_ADDR, 0x1FFA);
|
||||
ESPHL_ERROR_CHECK(err, "Setting Register 16 RMII Mode Setting failed");
|
||||
|
||||
err = mac->read_phy_reg(mac, this->phy_addr_, RTL8201_RMSR_REG_ADDR, &(phy_rmii_mode));
|
||||
ESPHL_ERROR_CHECK(err, "Read PHY RMSR Register failed");
|
||||
ESP_LOGV(TAG, "Setting RTL8201 RMII Mode Register to: 0x%04X", phy_rmii_mode);
|
||||
|
||||
err = mac->write_phy_reg(mac, this->phy_addr_, 0x1f, 0x0);
|
||||
ESPHL_ERROR_CHECK(err, "Setting Page 0 failed");
|
||||
if (this->type_ == ETHERNET_TYPE_RTL8201 && register_data.page) {
|
||||
ESP_LOGD(TAG, "Select PHY Register Page 0x00");
|
||||
err = mac->write_phy_reg(mac, this->phy_addr_, eth_phy_psr_reg_addr, 0x0);
|
||||
ESPHL_ERROR_CHECK(err, "Select PHY Register Page 0 failed");
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#include "esp_eth.h"
|
||||
#include "esp_eth_mac.h"
|
||||
#include "esp_netif.h"
|
||||
#include "esp_mac.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ethernet {
|
||||
|
@ -34,6 +35,12 @@ struct ManualIP {
|
|||
network::IPAddress dns2; ///< The second DNS server. 0.0.0.0 for default.
|
||||
};
|
||||
|
||||
struct PHYRegister {
|
||||
uint32_t address;
|
||||
uint32_t value;
|
||||
uint32_t page;
|
||||
};
|
||||
|
||||
enum class EthernetComponentState {
|
||||
STOPPED,
|
||||
CONNECTING,
|
||||
|
@ -65,6 +72,7 @@ class EthernetComponent : public Component {
|
|||
void set_mdc_pin(uint8_t mdc_pin);
|
||||
void set_mdio_pin(uint8_t mdio_pin);
|
||||
void set_clk_mode(emac_rmii_clock_mode_t clk_mode, emac_rmii_clock_gpio_t clk_gpio);
|
||||
void add_phy_register(PHYRegister register_value);
|
||||
#endif
|
||||
void set_type(EthernetType type);
|
||||
void set_manual_ip(const ManualIP &manual_ip);
|
||||
|
@ -73,6 +81,10 @@ class EthernetComponent : public Component {
|
|||
network::IPAddress get_dns_address(uint8_t num);
|
||||
std::string get_use_address() const;
|
||||
void set_use_address(const std::string &use_address);
|
||||
void get_eth_mac_address_raw(uint8_t *mac);
|
||||
std::string get_eth_mac_address_pretty();
|
||||
eth_duplex_t get_duplex_mode();
|
||||
eth_speed_t get_link_speed();
|
||||
bool powerdown();
|
||||
|
||||
protected:
|
||||
|
@ -86,8 +98,8 @@ class EthernetComponent : public Component {
|
|||
void dump_connect_params_();
|
||||
/// @brief Set `RMII Reference Clock Select` bit for KSZ8081.
|
||||
void ksz8081_set_clock_reference_(esp_eth_mac_t *mac);
|
||||
/// @brief Set `RMII Mode Setting Register` for RTL8201.
|
||||
void rtl8201_set_rmii_mode_(esp_eth_mac_t *mac);
|
||||
/// @brief Set arbitratry PHY registers from config.
|
||||
void write_phy_register_(esp_eth_mac_t *mac, PHYRegister register_data);
|
||||
|
||||
std::string use_address_;
|
||||
#ifdef USE_ETHERNET_SPI
|
||||
|
@ -106,6 +118,7 @@ class EthernetComponent : public Component {
|
|||
uint8_t mdio_pin_{18};
|
||||
emac_rmii_clock_mode_t clk_mode_{EMAC_CLK_EXT_IN};
|
||||
emac_rmii_clock_gpio_t clk_gpio_{EMAC_CLK_IN_GPIO};
|
||||
std::vector<PHYRegister> phy_registers_{};
|
||||
#endif
|
||||
EthernetType type_{ETHERNET_TYPE_UNKNOWN};
|
||||
optional<ManualIP> manual_ip_{};
|
||||
|
|
|
@ -10,6 +10,7 @@ static const char *const TAG = "ethernet_info";
|
|||
|
||||
void IPAddressEthernetInfo::dump_config() { LOG_TEXT_SENSOR("", "EthernetInfo IPAddress", this); }
|
||||
void DNSAddressEthernetInfo::dump_config() { LOG_TEXT_SENSOR("", "EthernetInfo DNS Address", this); }
|
||||
void MACAddressEthernetInfo::dump_config() { LOG_TEXT_SENSOR("", "EthernetInfo MAC Address", this); }
|
||||
|
||||
} // namespace ethernet_info
|
||||
} // namespace esphome
|
||||
|
|
|
@ -59,6 +59,13 @@ class DNSAddressEthernetInfo : public PollingComponent, public text_sensor::Text
|
|||
std::string last_results_;
|
||||
};
|
||||
|
||||
class MACAddressEthernetInfo : public Component, public text_sensor::TextSensor {
|
||||
public:
|
||||
void setup() override { this->publish_state(ethernet::global_eth_component->get_eth_mac_address_pretty()); }
|
||||
std::string unique_id() override { return get_mac_address() + "-ethernetinfo-mac"; }
|
||||
void dump_config() override;
|
||||
};
|
||||
|
||||
} // namespace ethernet_info
|
||||
} // namespace esphome
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ from esphome.components import text_sensor
|
|||
from esphome.const import (
|
||||
CONF_IP_ADDRESS,
|
||||
CONF_DNS_ADDRESS,
|
||||
CONF_MAC_ADDRESS,
|
||||
ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
)
|
||||
|
||||
|
@ -19,6 +20,10 @@ DNSAddressEthernetInfo = ethernet_info_ns.class_(
|
|||
"DNSAddressEthernetInfo", text_sensor.TextSensor, cg.PollingComponent
|
||||
)
|
||||
|
||||
MACAddressEthernetInfo = ethernet_info_ns.class_(
|
||||
"MACAddressEthernetInfo", text_sensor.TextSensor, cg.PollingComponent
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_IP_ADDRESS): text_sensor.text_sensor_schema(
|
||||
|
@ -36,6 +41,9 @@ CONFIG_SCHEMA = cv.Schema(
|
|||
cv.Optional(CONF_DNS_ADDRESS): text_sensor.text_sensor_schema(
|
||||
DNSAddressEthernetInfo, entity_category=ENTITY_CATEGORY_DIAGNOSTIC
|
||||
).extend(cv.polling_component_schema("1s")),
|
||||
cv.Optional(CONF_MAC_ADDRESS): text_sensor.text_sensor_schema(
|
||||
MACAddressEthernetInfo, entity_category=ENTITY_CATEGORY_DIAGNOSTIC
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -51,3 +59,6 @@ async def to_code(config):
|
|||
if conf := config.get(CONF_DNS_ADDRESS):
|
||||
dns_info = await text_sensor.new_text_sensor(config[CONF_DNS_ADDRESS])
|
||||
await cg.register_component(dns_info, config[CONF_DNS_ADDRESS])
|
||||
if conf := config.get(CONF_MAC_ADDRESS):
|
||||
mac_info = await text_sensor.new_text_sensor(config[CONF_MAC_ADDRESS])
|
||||
await cg.register_component(mac_info, config[CONF_MAC_ADDRESS])
|
||||
|
|
|
@ -2,10 +2,11 @@ import esphome.codegen as cg
|
|||
import esphome.config_validation as cv
|
||||
from esphome import automation
|
||||
from esphome.automation import maybe_simple_id
|
||||
from esphome.components import mqtt
|
||||
from esphome.components import mqtt, web_server
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_MQTT_ID,
|
||||
CONF_WEB_SERVER_ID,
|
||||
CONF_OSCILLATING,
|
||||
CONF_OSCILLATION_COMMAND_TOPIC,
|
||||
CONF_OSCILLATION_STATE_TOPIC,
|
||||
|
@ -79,67 +80,75 @@ FanPresetSetTrigger = fan_ns.class_(
|
|||
FanIsOnCondition = fan_ns.class_("FanIsOnCondition", automation.Condition.template())
|
||||
FanIsOffCondition = fan_ns.class_("FanIsOffCondition", automation.Condition.template())
|
||||
|
||||
FAN_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(Fan),
|
||||
cv.Optional(CONF_RESTORE_MODE, default="ALWAYS_OFF"): cv.enum(
|
||||
RESTORE_MODES, upper=True, space="_"
|
||||
),
|
||||
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTFanComponent),
|
||||
cv.Optional(CONF_OSCILLATION_STATE_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.publish_topic
|
||||
),
|
||||
cv.Optional(CONF_OSCILLATION_COMMAND_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.subscribe_topic
|
||||
),
|
||||
cv.Optional(CONF_SPEED_LEVEL_STATE_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.publish_topic
|
||||
),
|
||||
cv.Optional(CONF_SPEED_LEVEL_COMMAND_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.subscribe_topic
|
||||
),
|
||||
cv.Optional(CONF_SPEED_STATE_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.publish_topic
|
||||
),
|
||||
cv.Optional(CONF_SPEED_COMMAND_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.subscribe_topic
|
||||
),
|
||||
cv.Optional(CONF_ON_STATE): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(FanStateTrigger),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_TURN_ON): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(FanTurnOnTrigger),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_TURN_OFF): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(FanTurnOffTrigger),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_DIRECTION_SET): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(FanDirectionSetTrigger),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_OSCILLATING_SET): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(FanOscillatingSetTrigger),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_SPEED_SET): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(FanSpeedSetTrigger),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_PRESET_SET): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(FanPresetSetTrigger),
|
||||
}
|
||||
),
|
||||
}
|
||||
FAN_SCHEMA = (
|
||||
cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA)
|
||||
.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA)
|
||||
.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(Fan),
|
||||
cv.Optional(CONF_RESTORE_MODE, default="ALWAYS_OFF"): cv.enum(
|
||||
RESTORE_MODES, upper=True, space="_"
|
||||
),
|
||||
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTFanComponent),
|
||||
cv.Optional(CONF_OSCILLATION_STATE_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.publish_topic
|
||||
),
|
||||
cv.Optional(CONF_OSCILLATION_COMMAND_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.subscribe_topic
|
||||
),
|
||||
cv.Optional(CONF_SPEED_LEVEL_STATE_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.publish_topic
|
||||
),
|
||||
cv.Optional(CONF_SPEED_LEVEL_COMMAND_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.subscribe_topic
|
||||
),
|
||||
cv.Optional(CONF_SPEED_STATE_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.publish_topic
|
||||
),
|
||||
cv.Optional(CONF_SPEED_COMMAND_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.subscribe_topic
|
||||
),
|
||||
cv.Optional(CONF_ON_STATE): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(FanStateTrigger),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_TURN_ON): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(FanTurnOnTrigger),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_TURN_OFF): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(FanTurnOffTrigger),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_DIRECTION_SET): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
||||
FanDirectionSetTrigger
|
||||
),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_OSCILLATING_SET): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
||||
FanOscillatingSetTrigger
|
||||
),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_SPEED_SET): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(FanSpeedSetTrigger),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_PRESET_SET): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(FanPresetSetTrigger),
|
||||
}
|
||||
),
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
_PRESET_MODES_SCHEMA = cv.All(
|
||||
|
@ -209,6 +218,10 @@ async def setup_fan_core_(var, config):
|
|||
if (speed_command_topic := config.get(CONF_SPEED_COMMAND_TOPIC)) is not None:
|
||||
cg.add(mqtt_.set_custom_speed_command_topic(speed_command_topic))
|
||||
|
||||
if (webserver_id := config.get(CONF_WEB_SERVER_ID)) is not None:
|
||||
web_server_ = await cg.get_variable(webserver_id)
|
||||
web_server.add_entity_to_sorting_list(web_server_, var, config)
|
||||
|
||||
for conf in config.get(CONF_ON_STATE, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [(Fan.operator("ptr"), "x")], conf)
|
||||
|
|
|
@ -244,7 +244,7 @@ void FeedbackCover::loop() {
|
|||
|
||||
// update current position at requested interval, regardless of who started the movement
|
||||
// so that we also update UI if there was an external movement
|
||||
// don´t save intermediate positions
|
||||
// don't save intermediate positions
|
||||
if (now - this->last_publish_time_ > this->update_interval_) {
|
||||
this->publish_state(false);
|
||||
this->last_publish_time_ = now;
|
||||
|
@ -274,7 +274,7 @@ void FeedbackCover::control(const CoverCall &call) {
|
|||
if (pos == this->position) {
|
||||
// already at target,
|
||||
|
||||
// for covers with built in end stop, if we don´t have sensors we should send the command again
|
||||
// for covers with built in end stop, if we don't have sensors we should send the command again
|
||||
// to make sure the assumed state is not wrong
|
||||
if (this->has_built_in_endstop_ && ((pos == COVER_OPEN
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
|
|
|
@ -377,7 +377,7 @@ uint8_t FingerprintGrowComponent::transfer_(std::vector<uint8_t> *p_data_buffer)
|
|||
this->write((uint8_t) (wire_length >> 8));
|
||||
this->write((uint8_t) (wire_length & 0xFF));
|
||||
|
||||
uint16_t sum = ((wire_length) >> 8) + ((wire_length) &0xFF) + COMMAND;
|
||||
uint16_t sum = (wire_length >> 8) + (wire_length & 0xFF) + COMMAND;
|
||||
for (auto data : *p_data_buffer) {
|
||||
this->write(data);
|
||||
sum += data;
|
||||
|
@ -541,34 +541,34 @@ void FingerprintGrowComponent::dump_config() {
|
|||
ESP_LOGCONFIG(TAG, " Sensor Power Pin: %s",
|
||||
this->has_power_pin_ ? this->sensor_power_pin_->dump_summary().c_str() : "None");
|
||||
if (this->idle_period_to_sleep_ms_ < UINT32_MAX) {
|
||||
ESP_LOGCONFIG(TAG, " Idle Period to Sleep: %u ms", this->idle_period_to_sleep_ms_);
|
||||
ESP_LOGCONFIG(TAG, " Idle Period to Sleep: %" PRIu32 " ms", this->idle_period_to_sleep_ms_);
|
||||
} else {
|
||||
ESP_LOGCONFIG(TAG, " Idle Period to Sleep: Never");
|
||||
}
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
if (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());
|
||||
ESP_LOGCONFIG(TAG, " Current Value: %u", (uint16_t) this->fingerprint_count_sensor_->get_state());
|
||||
}
|
||||
if (this->status_sensor_) {
|
||||
LOG_SENSOR(" ", "Status", this->status_sensor_);
|
||||
ESP_LOGCONFIG(TAG, " Current Value: %d", (uint8_t) this->status_sensor_->get_state());
|
||||
ESP_LOGCONFIG(TAG, " Current Value: %u", (uint8_t) this->status_sensor_->get_state());
|
||||
}
|
||||
if (this->capacity_sensor_) {
|
||||
LOG_SENSOR(" ", "Capacity", this->capacity_sensor_);
|
||||
ESP_LOGCONFIG(TAG, " Current Value: %d", (uint16_t) this->capacity_sensor_->get_state());
|
||||
ESP_LOGCONFIG(TAG, " Current Value: %u", (uint16_t) this->capacity_sensor_->get_state());
|
||||
}
|
||||
if (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());
|
||||
ESP_LOGCONFIG(TAG, " Current Value: %u", (uint8_t) this->security_level_sensor_->get_state());
|
||||
}
|
||||
if (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());
|
||||
ESP_LOGCONFIG(TAG, " Current Value: %" PRIu32, (uint32_t) this->last_finger_id_sensor_->get_state());
|
||||
}
|
||||
if (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());
|
||||
ESP_LOGCONFIG(TAG, " Current Value: %" PRIu32, (uint32_t) this->last_confidence_sensor_->get_state());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
from esphome import pins
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
|
||||
from esphome.components import i2c, touchscreen
|
||||
from esphome.const import CONF_ID
|
||||
from esphome.const import CONF_ID, CONF_INTERRUPT_PIN
|
||||
from .. import ft5x06_ns
|
||||
|
||||
FT5x06ButtonListener = ft5x06_ns.class_("FT5x06ButtonListener")
|
||||
|
@ -16,6 +17,7 @@ FT5x06Touchscreen = ft5x06_ns.class_(
|
|||
CONFIG_SCHEMA = touchscreen.TOUCHSCREEN_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(FT5x06Touchscreen),
|
||||
cv.Optional(CONF_INTERRUPT_PIN): pins.internal_gpio_input_pin_schema,
|
||||
}
|
||||
).extend(i2c.i2c_device_schema(0x48))
|
||||
|
||||
|
@ -24,3 +26,7 @@ async def to_code(config):
|
|||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await i2c.register_i2c_device(var, config)
|
||||
await touchscreen.register_touchscreen(var, config)
|
||||
|
||||
if interrupt_pin := config.get(CONF_INTERRUPT_PIN):
|
||||
pin = await cg.gpio_pin_expression(interrupt_pin)
|
||||
cg.add(var.set_interrupt_pin(pin))
|
||||
|
|
102
esphome/components/ft5x06/touchscreen/ft5x06_touchscreen.cpp
Normal file
102
esphome/components/ft5x06/touchscreen/ft5x06_touchscreen.cpp
Normal file
|
@ -0,0 +1,102 @@
|
|||
#include "ft5x06_touchscreen.h"
|
||||
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ft5x06 {
|
||||
|
||||
static const char *const TAG = "ft5x06.touchscreen";
|
||||
|
||||
void FT5x06Touchscreen::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up FT5x06 Touchscreen...");
|
||||
if (this->interrupt_pin_ != nullptr) {
|
||||
this->interrupt_pin_->setup();
|
||||
this->interrupt_pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
|
||||
this->interrupt_pin_->setup();
|
||||
this->attach_interrupt_(this->interrupt_pin_, gpio::INTERRUPT_FALLING_EDGE);
|
||||
}
|
||||
|
||||
// wait 200ms after reset.
|
||||
this->set_timeout(200, [this] { this->continue_setup_(); });
|
||||
}
|
||||
|
||||
void FT5x06Touchscreen::continue_setup_() {
|
||||
uint8_t data[4];
|
||||
if (!this->set_mode_(FT5X06_OP_MODE))
|
||||
return;
|
||||
|
||||
if (!this->err_check_(this->read_register(FT5X06_VENDOR_ID_REG, data, 1), "Read Vendor ID"))
|
||||
return;
|
||||
switch (data[0]) {
|
||||
case FT5X06_ID_1:
|
||||
case FT5X06_ID_2:
|
||||
case FT5X06_ID_3:
|
||||
this->vendor_id_ = (VendorId) data[0];
|
||||
ESP_LOGD(TAG, "Read vendor ID 0x%X", data[0]);
|
||||
break;
|
||||
|
||||
default:
|
||||
ESP_LOGE(TAG, "Unknown vendor ID 0x%X", data[0]);
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
// reading the chip registers to get max x/y does not seem to work.
|
||||
if (this->display_ != nullptr) {
|
||||
if (this->x_raw_max_ == this->x_raw_min_) {
|
||||
this->x_raw_max_ = this->display_->get_native_width();
|
||||
}
|
||||
if (this->y_raw_max_ == this->y_raw_min_) {
|
||||
this->y_raw_max_ = this->display_->get_native_height();
|
||||
}
|
||||
}
|
||||
ESP_LOGCONFIG(TAG, "FT5x06 Touchscreen setup complete");
|
||||
}
|
||||
|
||||
void FT5x06Touchscreen::update_touches() {
|
||||
uint8_t touch_cnt;
|
||||
uint8_t data[MAX_TOUCHES][6];
|
||||
|
||||
if (!this->read_byte(FT5X06_TD_STATUS, &touch_cnt) || touch_cnt > MAX_TOUCHES) {
|
||||
ESP_LOGW(TAG, "Failed to read status");
|
||||
return;
|
||||
}
|
||||
if (touch_cnt == 0)
|
||||
return;
|
||||
|
||||
if (!this->read_bytes(FT5X06_TOUCH_DATA, (uint8_t *) data, touch_cnt * 6)) {
|
||||
ESP_LOGW(TAG, "Failed to read touch data");
|
||||
return;
|
||||
}
|
||||
for (uint8_t i = 0; i != touch_cnt; i++) {
|
||||
uint8_t status = data[i][0] >> 6;
|
||||
uint8_t id = data[i][2] >> 3;
|
||||
uint16_t x = encode_uint16(data[i][0] & 0x0F, data[i][1]);
|
||||
uint16_t y = encode_uint16(data[i][2] & 0xF, data[i][3]);
|
||||
|
||||
ESP_LOGD(TAG, "Read %X status, id: %d, pos %d/%d", status, id, x, y);
|
||||
if (status == 0 || status == 2) {
|
||||
this->add_raw_touch_position_(id, x, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FT5x06Touchscreen::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "FT5x06 Touchscreen:");
|
||||
ESP_LOGCONFIG(TAG, " Address: 0x%02X", this->address_);
|
||||
ESP_LOGCONFIG(TAG, " Vendor ID: 0x%X", (int) this->vendor_id_);
|
||||
}
|
||||
|
||||
bool FT5x06Touchscreen::err_check_(i2c::ErrorCode err, const char *msg) {
|
||||
if (err != i2c::ERROR_OK) {
|
||||
this->mark_failed();
|
||||
ESP_LOGE(TAG, "%s failed - err 0x%X", msg, err);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
bool FT5x06Touchscreen::set_mode_(FTMode mode) {
|
||||
return this->err_check_(this->write_register(FT5X06_MODE_REG, (uint8_t *) &mode, 1), "Set mode");
|
||||
}
|
||||
|
||||
} // namespace ft5x06
|
||||
} // namespace esphome
|
|
@ -3,14 +3,12 @@
|
|||
#include "esphome/components/i2c/i2c.h"
|
||||
#include "esphome/components/touchscreen/touchscreen.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/gpio.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ft5x06 {
|
||||
|
||||
static const char *const TAG = "ft5x06.touchscreen";
|
||||
|
||||
enum VendorId {
|
||||
FT5X06_ID_UNKNOWN = 0,
|
||||
FT5X06_ID_1 = 0x51,
|
||||
|
@ -39,91 +37,19 @@ static const size_t MAX_TOUCHES = 5; // max number of possible touches reported
|
|||
|
||||
class FT5x06Touchscreen : public touchscreen::Touchscreen, public i2c::I2CDevice {
|
||||
public:
|
||||
void setup() override {
|
||||
esph_log_config(TAG, "Setting up FT5x06 Touchscreen...");
|
||||
// wait 200ms after reset.
|
||||
this->set_timeout(200, [this] { this->continue_setup_(); });
|
||||
}
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
void update_touches() override;
|
||||
|
||||
void continue_setup_(void) {
|
||||
uint8_t data[4];
|
||||
if (!this->set_mode_(FT5X06_OP_MODE))
|
||||
return;
|
||||
|
||||
if (!this->err_check_(this->read_register(FT5X06_VENDOR_ID_REG, data, 1), "Read Vendor ID"))
|
||||
return;
|
||||
switch (data[0]) {
|
||||
case FT5X06_ID_1:
|
||||
case FT5X06_ID_2:
|
||||
case FT5X06_ID_3:
|
||||
this->vendor_id_ = (VendorId) data[0];
|
||||
esph_log_d(TAG, "Read vendor ID 0x%X", data[0]);
|
||||
break;
|
||||
|
||||
default:
|
||||
esph_log_e(TAG, "Unknown vendor ID 0x%X", data[0]);
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
// reading the chip registers to get max x/y does not seem to work.
|
||||
if (this->display_ != nullptr) {
|
||||
if (this->x_raw_max_ == this->x_raw_min_) {
|
||||
this->x_raw_max_ = this->display_->get_native_width();
|
||||
}
|
||||
if (this->y_raw_max_ == this->y_raw_min_) {
|
||||
this->y_raw_max_ = this->display_->get_native_height();
|
||||
}
|
||||
}
|
||||
esph_log_config(TAG, "FT5x06 Touchscreen setup complete");
|
||||
}
|
||||
|
||||
void update_touches() override {
|
||||
uint8_t touch_cnt;
|
||||
uint8_t data[MAX_TOUCHES][6];
|
||||
|
||||
if (!this->read_byte(FT5X06_TD_STATUS, &touch_cnt) || touch_cnt > MAX_TOUCHES) {
|
||||
esph_log_w(TAG, "Failed to read status");
|
||||
return;
|
||||
}
|
||||
if (touch_cnt == 0)
|
||||
return;
|
||||
|
||||
if (!this->read_bytes(FT5X06_TOUCH_DATA, (uint8_t *) data, touch_cnt * 6)) {
|
||||
esph_log_w(TAG, "Failed to read touch data");
|
||||
return;
|
||||
}
|
||||
for (uint8_t i = 0; i != touch_cnt; i++) {
|
||||
uint8_t status = data[i][0] >> 6;
|
||||
uint8_t id = data[i][2] >> 3;
|
||||
uint16_t x = encode_uint16(data[i][0] & 0x0F, data[i][1]);
|
||||
uint16_t y = encode_uint16(data[i][2] & 0xF, data[i][3]);
|
||||
|
||||
esph_log_d(TAG, "Read %X status, id: %d, pos %d/%d", status, id, x, y);
|
||||
if (status == 0 || status == 2) {
|
||||
this->add_raw_touch_position_(id, x, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void dump_config() override {
|
||||
esph_log_config(TAG, "FT5x06 Touchscreen:");
|
||||
esph_log_config(TAG, " Address: 0x%02X", this->address_);
|
||||
esph_log_config(TAG, " Vendor ID: 0x%X", (int) this->vendor_id_);
|
||||
}
|
||||
void set_interrupt_pin(InternalGPIOPin *interrupt_pin) { this->interrupt_pin_ = interrupt_pin; }
|
||||
|
||||
protected:
|
||||
bool err_check_(i2c::ErrorCode err, const char *msg) {
|
||||
if (err != i2c::ERROR_OK) {
|
||||
this->mark_failed();
|
||||
esph_log_e(TAG, "%s failed - err 0x%X", msg, err);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
bool set_mode_(FTMode mode) {
|
||||
return this->err_check_(this->write_register(FT5X06_MODE_REG, (uint8_t *) &mode, 1), "Set mode");
|
||||
}
|
||||
void continue_setup_();
|
||||
bool err_check_(i2c::ErrorCode err, const char *msg);
|
||||
bool set_mode_(FTMode mode);
|
||||
VendorId vendor_id_{FT5X06_ID_UNKNOWN};
|
||||
|
||||
InternalGPIOPin *interrupt_pin_{nullptr};
|
||||
};
|
||||
|
||||
} // namespace ft5x06
|
||||
|
|
25
esphome/components/gpio/one_wire/__init__.py
Normal file
25
esphome/components/gpio/one_wire/__init__.py
Normal file
|
@ -0,0 +1,25 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import pins
|
||||
from esphome.const import CONF_ID, CONF_PIN
|
||||
from esphome.components.one_wire import OneWireBus
|
||||
from .. import gpio_ns
|
||||
|
||||
CODEOWNERS = ["@ssieb"]
|
||||
|
||||
GPIOOneWireBus = gpio_ns.class_("GPIOOneWireBus", OneWireBus, cg.Component)
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(GPIOOneWireBus),
|
||||
cv.Required(CONF_PIN): pins.internal_gpio_output_pin_schema,
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
|
||||
pin = await cg.gpio_pin_expression(config[CONF_PIN])
|
||||
cg.add(var.set_pin(pin))
|
203
esphome/components/gpio/one_wire/gpio_one_wire.cpp
Normal file
203
esphome/components/gpio/one_wire/gpio_one_wire.cpp
Normal file
|
@ -0,0 +1,203 @@
|
|||
#include "gpio_one_wire.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace gpio {
|
||||
|
||||
static const char *const TAG = "gpio.one_wire";
|
||||
|
||||
void GPIOOneWireBus::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up 1-wire bus...");
|
||||
this->t_pin_->setup();
|
||||
// clear bus with 480µs high, otherwise initial reset in search might fail
|
||||
this->t_pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
|
||||
delayMicroseconds(480);
|
||||
this->search();
|
||||
}
|
||||
|
||||
void GPIOOneWireBus::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "GPIO 1-wire bus:");
|
||||
LOG_PIN(" Pin: ", this->t_pin_);
|
||||
this->dump_devices_(TAG);
|
||||
}
|
||||
|
||||
bool HOT IRAM_ATTR GPIOOneWireBus::reset() {
|
||||
// See reset here:
|
||||
// https://www.maximintegrated.com/en/design/technical-documents/app-notes/1/126.html
|
||||
// Wait for communication to clear (delay G)
|
||||
pin_.pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
|
||||
uint8_t retries = 125;
|
||||
do {
|
||||
if (--retries == 0)
|
||||
return false;
|
||||
delayMicroseconds(2);
|
||||
} while (!pin_.digital_read());
|
||||
|
||||
bool r;
|
||||
|
||||
// Send 480µs LOW TX reset pulse (drive bus low, delay H)
|
||||
pin_.pin_mode(gpio::FLAG_OUTPUT);
|
||||
pin_.digital_write(false);
|
||||
delayMicroseconds(480);
|
||||
|
||||
// Release the bus, delay I
|
||||
pin_.pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
|
||||
delayMicroseconds(70);
|
||||
|
||||
// sample bus, 0=device(s) present, 1=no device present
|
||||
r = !pin_.digital_read();
|
||||
// delay J
|
||||
delayMicroseconds(410);
|
||||
return r;
|
||||
}
|
||||
|
||||
void HOT IRAM_ATTR GPIOOneWireBus::write_bit_(bool bit) {
|
||||
// drive bus low
|
||||
pin_.pin_mode(gpio::FLAG_OUTPUT);
|
||||
pin_.digital_write(false);
|
||||
|
||||
// from datasheet:
|
||||
// write 0 low time: t_low0: min=60µs, max=120µs
|
||||
// write 1 low time: t_low1: min=1µs, max=15µs
|
||||
// time slot: t_slot: min=60µs, max=120µs
|
||||
// recovery time: t_rec: min=1µs
|
||||
// ds18b20 appears to read the bus after roughly 14µs
|
||||
uint32_t delay0 = bit ? 6 : 60;
|
||||
uint32_t delay1 = bit ? 59 : 5;
|
||||
|
||||
// delay A/C
|
||||
delayMicroseconds(delay0);
|
||||
// release bus
|
||||
pin_.digital_write(true);
|
||||
// delay B/D
|
||||
delayMicroseconds(delay1);
|
||||
}
|
||||
|
||||
bool HOT IRAM_ATTR GPIOOneWireBus::read_bit_() {
|
||||
// drive bus low
|
||||
pin_.pin_mode(gpio::FLAG_OUTPUT);
|
||||
pin_.digital_write(false);
|
||||
|
||||
// note: for reading we'll need very accurate timing, as the
|
||||
// timing for the digital_read() is tight; according to the datasheet,
|
||||
// we should read at the end of 16µs starting from the bus low
|
||||
// typically, the ds18b20 pulls the line high after 11µs for a logical 1
|
||||
// and 29µs for a logical 0
|
||||
|
||||
uint32_t start = micros();
|
||||
// datasheet says >1µs
|
||||
delayMicroseconds(2);
|
||||
|
||||
// release bus, delay E
|
||||
pin_.pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
|
||||
|
||||
// measure from start value directly, to get best accurate timing no matter
|
||||
// how long pin_mode/delayMicroseconds took
|
||||
delayMicroseconds(12 - (micros() - start));
|
||||
|
||||
// sample bus to read bit from peer
|
||||
bool r = pin_.digital_read();
|
||||
|
||||
// read slot is at least 60µs; get as close to 60µs to spend less time with interrupts locked
|
||||
uint32_t now = micros();
|
||||
if (now - start < 60)
|
||||
delayMicroseconds(60 - (now - start));
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
void IRAM_ATTR GPIOOneWireBus::write8(uint8_t val) {
|
||||
for (uint8_t i = 0; i < 8; i++) {
|
||||
this->write_bit_(bool((1u << i) & val));
|
||||
}
|
||||
}
|
||||
|
||||
void IRAM_ATTR GPIOOneWireBus::write64(uint64_t val) {
|
||||
for (uint8_t i = 0; i < 64; i++) {
|
||||
this->write_bit_(bool((1ULL << i) & val));
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t IRAM_ATTR GPIOOneWireBus::read8() {
|
||||
uint8_t ret = 0;
|
||||
for (uint8_t i = 0; i < 8; i++) {
|
||||
ret |= (uint8_t(this->read_bit_()) << i);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
uint64_t IRAM_ATTR GPIOOneWireBus::read64() {
|
||||
uint64_t ret = 0;
|
||||
for (uint8_t i = 0; i < 8; i++) {
|
||||
ret |= (uint64_t(this->read_bit_()) << i);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void GPIOOneWireBus::reset_search() {
|
||||
this->last_discrepancy_ = 0;
|
||||
this->last_device_flag_ = false;
|
||||
this->address_ = 0;
|
||||
}
|
||||
|
||||
uint64_t IRAM_ATTR GPIOOneWireBus::search_int() {
|
||||
if (this->last_device_flag_)
|
||||
return 0u;
|
||||
|
||||
uint8_t last_zero = 0;
|
||||
uint64_t bit_mask = 1;
|
||||
uint64_t address = this->address_;
|
||||
|
||||
// Initiate search
|
||||
for (int bit_number = 1; bit_number <= 64; bit_number++, bit_mask <<= 1) {
|
||||
// read bit
|
||||
bool id_bit = this->read_bit_();
|
||||
// read its complement
|
||||
bool cmp_id_bit = this->read_bit_();
|
||||
|
||||
if (id_bit && cmp_id_bit) {
|
||||
// No devices participating in search
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool branch;
|
||||
|
||||
if (id_bit != cmp_id_bit) {
|
||||
// only chose one branch, the other one doesn't have any devices.
|
||||
branch = id_bit;
|
||||
} else {
|
||||
// there are devices with both 0s and 1s at this bit
|
||||
if (bit_number < this->last_discrepancy_) {
|
||||
branch = (address & bit_mask) > 0;
|
||||
} else {
|
||||
branch = bit_number == this->last_discrepancy_;
|
||||
}
|
||||
|
||||
if (!branch) {
|
||||
last_zero = bit_number;
|
||||
}
|
||||
}
|
||||
|
||||
if (branch) {
|
||||
address |= bit_mask;
|
||||
} else {
|
||||
address &= ~bit_mask;
|
||||
}
|
||||
|
||||
// choose/announce branch
|
||||
this->write_bit_(branch);
|
||||
}
|
||||
|
||||
this->last_discrepancy_ = last_zero;
|
||||
if (this->last_discrepancy_ == 0) {
|
||||
// we're at root and have no choices left, so this was the last one.
|
||||
this->last_device_flag_ = true;
|
||||
}
|
||||
|
||||
this->address_ = address;
|
||||
return address;
|
||||
}
|
||||
|
||||
} // namespace gpio
|
||||
} // namespace esphome
|
41
esphome/components/gpio/one_wire/gpio_one_wire.h
Normal file
41
esphome/components/gpio/one_wire/gpio_one_wire.h
Normal file
|
@ -0,0 +1,41 @@
|
|||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/components/one_wire/one_wire.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace gpio {
|
||||
|
||||
class GPIOOneWireBus : public one_wire::OneWireBus, public Component {
|
||||
public:
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override { return setup_priority::BUS; }
|
||||
|
||||
void set_pin(InternalGPIOPin *pin) {
|
||||
this->t_pin_ = pin;
|
||||
this->pin_ = pin->to_isr();
|
||||
}
|
||||
|
||||
bool reset() override;
|
||||
void write8(uint8_t val) override;
|
||||
void write64(uint64_t val) override;
|
||||
uint8_t read8() override;
|
||||
uint64_t read64() override;
|
||||
|
||||
protected:
|
||||
InternalGPIOPin *t_pin_;
|
||||
ISRInternalGPIOPin pin_;
|
||||
uint8_t last_discrepancy_{0};
|
||||
bool last_device_flag_{false};
|
||||
uint64_t address_;
|
||||
|
||||
void reset_search() override;
|
||||
uint64_t search_int() override;
|
||||
void write_bit_(bool bit);
|
||||
bool read_bit_();
|
||||
};
|
||||
|
||||
} // namespace gpio
|
||||
} // namespace esphome
|
|
@ -2,6 +2,8 @@
|
|||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#include <cinttypes>
|
||||
|
||||
namespace esphome {
|
||||
namespace he60r {
|
||||
|
||||
|
@ -54,7 +56,7 @@ void HE60rCover::endstop_reached_(CoverOperation operation) {
|
|||
this->position = new_position;
|
||||
this->current_operation = COVER_OPERATION_IDLE;
|
||||
if (this->last_command_ == operation) {
|
||||
float dur = (now - this->start_dir_time_) / 1e3f;
|
||||
float dur = (float) (now - this->start_dir_time_) / 1e3f;
|
||||
ESP_LOGD(TAG, "'%s' - %s endstop reached. Took %.1fs.", this->name_.c_str(),
|
||||
operation == COVER_OPERATION_OPENING ? "Open" : "Close", dur);
|
||||
}
|
||||
|
@ -67,7 +69,6 @@ void HE60rCover::set_current_operation_(cover::CoverOperation operation) {
|
|||
this->current_operation = operation;
|
||||
if (operation != COVER_OPERATION_IDLE)
|
||||
this->last_recompute_time_ = millis();
|
||||
this->publish_state();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -124,10 +125,10 @@ void HE60rCover::process_rx_(uint8_t data) {
|
|||
}
|
||||
|
||||
void HE60rCover::update_() {
|
||||
if (toggles_needed_ != 0) {
|
||||
if (this->toggles_needed_ != 0) {
|
||||
if ((this->counter_++ & 0x3) == 0) {
|
||||
toggles_needed_--;
|
||||
ESP_LOGD(TAG, "Writing byte 0x30, still needed=%d", toggles_needed_);
|
||||
this->toggles_needed_--;
|
||||
ESP_LOGD(TAG, "Writing byte 0x30, still needed=%u", this->toggles_needed_);
|
||||
this->write_byte(TOGGLE_BYTE);
|
||||
} else {
|
||||
this->write_byte(QUERY_BYTE);
|
||||
|
@ -233,31 +234,28 @@ void HE60rCover::recompute_position_() {
|
|||
return;
|
||||
|
||||
const uint32_t now = millis();
|
||||
float dir;
|
||||
float action_dur;
|
||||
|
||||
switch (this->current_operation) {
|
||||
case COVER_OPERATION_OPENING:
|
||||
dir = 1.0f;
|
||||
action_dur = this->open_duration_;
|
||||
break;
|
||||
case COVER_OPERATION_CLOSING:
|
||||
dir = -1.0f;
|
||||
action_dur = this->close_duration_;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
if (now > this->last_recompute_time_) {
|
||||
auto diff = now - last_recompute_time_;
|
||||
auto delta = dir * diff / action_dur;
|
||||
auto diff = (unsigned) (now - last_recompute_time_);
|
||||
float delta;
|
||||
switch (this->current_operation) {
|
||||
case COVER_OPERATION_OPENING:
|
||||
delta = (float) diff / (float) this->open_duration_;
|
||||
break;
|
||||
case COVER_OPERATION_CLOSING:
|
||||
delta = -(float) diff / (float) this->close_duration_;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
// make sure our guesstimate never reaches full open or close.
|
||||
this->position = clamp(delta + this->position, COVER_CLOSED + 0.01f, COVER_OPEN - 0.01f);
|
||||
ESP_LOGD(TAG, "Recompute %dms, dir=%f, action_dur=%f, delta=%f, pos=%f", (int) diff, dir, action_dur, delta,
|
||||
this->position);
|
||||
auto new_position = clamp(delta + this->position, COVER_CLOSED + 0.01f, COVER_OPEN - 0.01f);
|
||||
ESP_LOGD(TAG, "Recompute %ums, dir=%u, delta=%f, pos=%f", diff, this->current_operation, delta, new_position);
|
||||
this->last_recompute_time_ = now;
|
||||
this->publish_state();
|
||||
if (this->position != new_position) {
|
||||
this->position = new_position;
|
||||
this->publish_state();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -25,15 +25,14 @@ class HE60rCover : public cover::Cover, public Component, public uart::UARTDevic
|
|||
void control(const cover::CoverCall &call) override;
|
||||
bool is_at_target_() const;
|
||||
void start_direction_(cover::CoverOperation dir);
|
||||
void update_operation_(cover::CoverOperation dir);
|
||||
void endstop_reached_(cover::CoverOperation operation);
|
||||
void recompute_position_();
|
||||
void set_current_operation_(cover::CoverOperation operation);
|
||||
void process_rx_(uint8_t data);
|
||||
|
||||
uint32_t open_duration_{0};
|
||||
uint32_t close_duration_{0};
|
||||
uint32_t toggles_needed_{0};
|
||||
unsigned open_duration_{0};
|
||||
unsigned close_duration_{0};
|
||||
unsigned toggles_needed_{0};
|
||||
cover::CoverOperation next_direction_{cover::COVER_OPERATION_IDLE};
|
||||
cover::CoverOperation last_command_{cover::COVER_OPERATION_IDLE};
|
||||
uint32_t last_recompute_time_{0};
|
||||
|
|
|
@ -16,7 +16,7 @@ from .const import KEY_HOST
|
|||
# force import gpio to register pin schema
|
||||
from .gpio import host_pin_to_code # noqa
|
||||
|
||||
CODEOWNERS = ["@esphome/core"]
|
||||
CODEOWNERS = ["@esphome/core", "@clydebarrow"]
|
||||
AUTO_LOAD = ["network"]
|
||||
|
||||
|
||||
|
|
20
esphome/components/host/time/__init__.py
Normal file
20
esphome/components/host/time/__init__.py
Normal file
|
@ -0,0 +1,20 @@
|
|||
import esphome.codegen as cg
|
||||
from esphome.const import CONF_ID
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import time as time_
|
||||
|
||||
CODEOWNERS = ["@clydebarrow"]
|
||||
|
||||
time_ns = cg.esphome_ns.namespace("host")
|
||||
HostTime = time_ns.class_("HostTime", time_.RealTimeClock)
|
||||
CONFIG_SCHEMA = time_.TIME_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(HostTime),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await time_.register_time(var, config)
|
15
esphome/components/host/time/host_time.h
Normal file
15
esphome/components/host/time/host_time.h
Normal file
|
@ -0,0 +1,15 @@
|
|||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/time/real_time_clock.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace host {
|
||||
|
||||
class HostTime : public time::RealTimeClock {
|
||||
public:
|
||||
void update() override {}
|
||||
};
|
||||
|
||||
} // namespace host
|
||||
} // namespace esphome
|
|
@ -1,9 +1,8 @@
|
|||
import urllib.parse as urlparse
|
||||
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import automation
|
||||
from esphome.const import (
|
||||
__version__,
|
||||
CONF_ID,
|
||||
CONF_TIMEOUT,
|
||||
CONF_METHOD,
|
||||
|
@ -12,67 +11,91 @@ from esphome.const import (
|
|||
CONF_ESP8266_DISABLE_SSL_SUPPORT,
|
||||
)
|
||||
from esphome.core import Lambda, CORE
|
||||
from esphome.components import esp32
|
||||
|
||||
DEPENDENCIES = ["network"]
|
||||
AUTO_LOAD = ["json"]
|
||||
|
||||
http_request_ns = cg.esphome_ns.namespace("http_request")
|
||||
HttpRequestComponent = http_request_ns.class_("HttpRequestComponent", cg.Component)
|
||||
HttpRequestArduino = http_request_ns.class_("HttpRequestArduino", HttpRequestComponent)
|
||||
HttpRequestIDF = http_request_ns.class_("HttpRequestIDF", HttpRequestComponent)
|
||||
|
||||
HttpContainer = http_request_ns.class_("HttpContainer")
|
||||
|
||||
HttpRequestSendAction = http_request_ns.class_(
|
||||
"HttpRequestSendAction", automation.Action
|
||||
)
|
||||
HttpRequestResponseTrigger = http_request_ns.class_(
|
||||
"HttpRequestResponseTrigger", automation.Trigger
|
||||
"HttpRequestResponseTrigger",
|
||||
automation.Trigger.template(
|
||||
cg.std_shared_ptr.template(HttpContainer), cg.std_string
|
||||
),
|
||||
)
|
||||
|
||||
CONF_HEADERS = "headers"
|
||||
CONF_HTTP_REQUEST_ID = "http_request_id"
|
||||
|
||||
CONF_USERAGENT = "useragent"
|
||||
CONF_BODY = "body"
|
||||
CONF_JSON = "json"
|
||||
CONF_VERIFY_SSL = "verify_ssl"
|
||||
CONF_ON_RESPONSE = "on_response"
|
||||
CONF_FOLLOW_REDIRECTS = "follow_redirects"
|
||||
CONF_REDIRECT_LIMIT = "redirect_limit"
|
||||
CONF_WATCHDOG_TIMEOUT = "watchdog_timeout"
|
||||
|
||||
CONF_MAX_RESPONSE_BUFFER_SIZE = "max_response_buffer_size"
|
||||
CONF_ON_RESPONSE = "on_response"
|
||||
CONF_HEADERS = "headers"
|
||||
CONF_BODY = "body"
|
||||
CONF_JSON = "json"
|
||||
CONF_CAPTURE_RESPONSE = "capture_response"
|
||||
|
||||
|
||||
def validate_url(value):
|
||||
value = cv.string(value)
|
||||
try:
|
||||
parsed = list(urlparse.urlparse(value))
|
||||
except Exception as err:
|
||||
raise cv.Invalid("Invalid URL") from err
|
||||
|
||||
if not parsed[0] or not parsed[1]:
|
||||
raise cv.Invalid("URL must have a URL scheme and host")
|
||||
|
||||
if parsed[0] not in ["http", "https"]:
|
||||
raise cv.Invalid("Scheme must be http or https")
|
||||
|
||||
if not parsed[2]:
|
||||
parsed[2] = "/"
|
||||
|
||||
return urlparse.urlunparse(parsed)
|
||||
value = cv.url(value)
|
||||
if value.startswith("http://") or value.startswith("https://"):
|
||||
return value
|
||||
raise cv.Invalid("URL must start with 'http://' or 'https://'")
|
||||
|
||||
|
||||
def validate_secure_url(config):
|
||||
url_ = config[CONF_URL]
|
||||
def validate_ssl_verification(config):
|
||||
error_message = ""
|
||||
|
||||
if CORE.is_esp32:
|
||||
if not CORE.using_esp_idf and config[CONF_VERIFY_SSL]:
|
||||
error_message = "ESPHome supports certificate verification only via ESP-IDF"
|
||||
|
||||
if CORE.is_rp2040 and config[CONF_VERIFY_SSL]:
|
||||
error_message = "ESPHome does not support certificate verification on RP2040"
|
||||
|
||||
if (
|
||||
config.get(CONF_VERIFY_SSL)
|
||||
and not isinstance(url_, Lambda)
|
||||
and url_.lower().startswith("https:")
|
||||
CORE.is_esp8266
|
||||
and not config[CONF_ESP8266_DISABLE_SSL_SUPPORT]
|
||||
and config[CONF_VERIFY_SSL]
|
||||
):
|
||||
error_message = "ESPHome does not support certificate verification on ESP8266"
|
||||
|
||||
if len(error_message) > 0:
|
||||
raise cv.Invalid(
|
||||
"Currently ESPHome doesn't support SSL verification. "
|
||||
"Set 'verify_ssl: false' to make insecure HTTPS requests."
|
||||
f"{error_message}. Set '{CONF_VERIFY_SSL}: false' to skip certificate validation and allow less secure HTTPS connections."
|
||||
)
|
||||
|
||||
return config
|
||||
|
||||
|
||||
def _declare_request_class(value):
|
||||
if CORE.using_esp_idf:
|
||||
return cv.declare_id(HttpRequestIDF)(value)
|
||||
if CORE.is_esp8266 or CORE.is_esp32 or CORE.is_rp2040:
|
||||
return cv.declare_id(HttpRequestArduino)(value)
|
||||
return NotImplementedError
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(HttpRequestComponent),
|
||||
cv.Optional(CONF_USERAGENT, "ESPHome"): cv.string,
|
||||
cv.GenerateID(): _declare_request_class,
|
||||
cv.Optional(
|
||||
CONF_USERAGENT, f"ESPHome/{__version__} (https://esphome.io)"
|
||||
): cv.string,
|
||||
cv.Optional(CONF_FOLLOW_REDIRECTS, True): cv.boolean,
|
||||
cv.Optional(CONF_REDIRECT_LIMIT, 3): cv.int_,
|
||||
cv.Optional(
|
||||
|
@ -81,12 +104,21 @@ CONFIG_SCHEMA = cv.All(
|
|||
cv.SplitDefault(CONF_ESP8266_DISABLE_SSL_SUPPORT, esp8266=False): cv.All(
|
||||
cv.only_on_esp8266, cv.boolean
|
||||
),
|
||||
cv.Optional(CONF_VERIFY_SSL, default=True): cv.boolean,
|
||||
cv.Optional(CONF_WATCHDOG_TIMEOUT): cv.All(
|
||||
cv.Any(cv.only_on_esp32, cv.only_on_rp2040),
|
||||
cv.positive_not_null_time_period,
|
||||
cv.positive_time_period_milliseconds,
|
||||
),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA),
|
||||
cv.require_framework_version(
|
||||
esp8266_arduino=cv.Version(2, 5, 1),
|
||||
esp32_arduino=cv.Version(0, 0, 0),
|
||||
esp_idf=cv.Version(0, 0, 0),
|
||||
rp2040_arduino=cv.Version(0, 0, 0),
|
||||
),
|
||||
validate_ssl_verification,
|
||||
)
|
||||
|
||||
|
||||
|
@ -100,11 +132,30 @@ async def to_code(config):
|
|||
if CORE.is_esp8266 and not config[CONF_ESP8266_DISABLE_SSL_SUPPORT]:
|
||||
cg.add_define("USE_HTTP_REQUEST_ESP8266_HTTPS")
|
||||
|
||||
if timeout_ms := config.get(CONF_WATCHDOG_TIMEOUT):
|
||||
cg.add(var.set_watchdog_timeout(timeout_ms))
|
||||
|
||||
if CORE.is_esp32:
|
||||
cg.add_library("WiFiClientSecure", None)
|
||||
cg.add_library("HTTPClient", None)
|
||||
if CORE.using_esp_idf:
|
||||
esp32.add_idf_sdkconfig_option(
|
||||
"CONFIG_MBEDTLS_CERTIFICATE_BUNDLE",
|
||||
config.get(CONF_VERIFY_SSL),
|
||||
)
|
||||
esp32.add_idf_sdkconfig_option(
|
||||
"CONFIG_ESP_TLS_INSECURE",
|
||||
not config.get(CONF_VERIFY_SSL),
|
||||
)
|
||||
esp32.add_idf_sdkconfig_option(
|
||||
"CONFIG_ESP_TLS_SKIP_SERVER_CERT_VERIFY",
|
||||
not config.get(CONF_VERIFY_SSL),
|
||||
)
|
||||
else:
|
||||
cg.add_library("WiFiClientSecure", None)
|
||||
cg.add_library("HTTPClient", None)
|
||||
if CORE.is_esp8266:
|
||||
cg.add_library("ESP8266HTTPClient", None)
|
||||
if CORE.is_rp2040 and CORE.using_arduino:
|
||||
cg.add_library("HTTPClient", None)
|
||||
|
||||
await cg.register_component(var, config)
|
||||
|
||||
|
@ -116,12 +167,16 @@ HTTP_REQUEST_ACTION_SCHEMA = cv.Schema(
|
|||
cv.Optional(CONF_HEADERS): cv.All(
|
||||
cv.Schema({cv.string: cv.templatable(cv.string)})
|
||||
),
|
||||
cv.Optional(CONF_VERIFY_SSL, default=True): cv.boolean,
|
||||
cv.Optional(CONF_VERIFY_SSL): cv.invalid(
|
||||
f"{CONF_VERIFY_SSL} has moved to the base component configuration."
|
||||
),
|
||||
cv.Optional(CONF_CAPTURE_RESPONSE, default=False): cv.boolean,
|
||||
cv.Optional(CONF_ON_RESPONSE): automation.validate_automation(
|
||||
{cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(HttpRequestResponseTrigger)}
|
||||
),
|
||||
cv.Optional(CONF_MAX_RESPONSE_BUFFER_SIZE, default="1kB"): cv.validate_bytes,
|
||||
}
|
||||
).add_extra(validate_secure_url)
|
||||
)
|
||||
HTTP_REQUEST_GET_ACTION_SCHEMA = automation.maybe_conf(
|
||||
CONF_URL,
|
||||
HTTP_REQUEST_ACTION_SCHEMA.extend(
|
||||
|
@ -173,6 +228,9 @@ async def http_request_action_to_code(config, action_id, template_arg, args):
|
|||
template_ = await cg.templatable(config[CONF_URL], args, cg.std_string)
|
||||
cg.add(var.set_url(template_))
|
||||
cg.add(var.set_method(config[CONF_METHOD]))
|
||||
cg.add(var.set_capture_response(config[CONF_CAPTURE_RESPONSE]))
|
||||
cg.add(var.set_max_response_buffer_size(config[CONF_MAX_RESPONSE_BUFFER_SIZE]))
|
||||
|
||||
if CONF_BODY in config:
|
||||
template_ = await cg.templatable(config[CONF_BODY], args, cg.std_string)
|
||||
cg.add(var.set_body(template_))
|
||||
|
@ -196,7 +254,12 @@ async def http_request_action_to_code(config, action_id, template_arg, args):
|
|||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID])
|
||||
cg.add(var.register_response_trigger(trigger))
|
||||
await automation.build_automation(
|
||||
trigger, [(int, "status_code"), (cg.uint32, "duration_ms")], conf
|
||||
trigger,
|
||||
[
|
||||
(cg.std_shared_ptr.template(HttpContainer), "response"),
|
||||
(cg.std_string, "body"),
|
||||
],
|
||||
conf,
|
||||
)
|
||||
|
||||
return var
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
#ifdef USE_ARDUINO
|
||||
|
||||
#include "http_request.h"
|
||||
#include "esphome/core/defines.h"
|
||||
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/components/network/util.h"
|
||||
|
||||
#include <cinttypes>
|
||||
|
||||
namespace esphome {
|
||||
namespace http_request {
|
||||
|
@ -14,131 +13,12 @@ void HttpRequestComponent::dump_config() {
|
|||
ESP_LOGCONFIG(TAG, "HTTP Request:");
|
||||
ESP_LOGCONFIG(TAG, " Timeout: %ums", this->timeout_);
|
||||
ESP_LOGCONFIG(TAG, " User-Agent: %s", this->useragent_);
|
||||
ESP_LOGCONFIG(TAG, " Follow Redirects: %d", this->follow_redirects_);
|
||||
ESP_LOGCONFIG(TAG, " Follow redirects: %s", YESNO(this->follow_redirects_));
|
||||
ESP_LOGCONFIG(TAG, " Redirect limit: %d", this->redirect_limit_);
|
||||
}
|
||||
|
||||
void HttpRequestComponent::set_url(std::string url) {
|
||||
this->url_ = std::move(url);
|
||||
this->secure_ = this->url_.compare(0, 6, "https:") == 0;
|
||||
|
||||
if (!this->last_url_.empty() && this->url_ != this->last_url_) {
|
||||
// Close connection if url has been changed
|
||||
this->client_.setReuse(false);
|
||||
this->client_.end();
|
||||
if (this->watchdog_timeout_ > 0) {
|
||||
ESP_LOGCONFIG(TAG, " Watchdog Timeout: %" PRIu32 "ms", this->watchdog_timeout_);
|
||||
}
|
||||
this->client_.setReuse(true);
|
||||
}
|
||||
|
||||
void HttpRequestComponent::send(const std::vector<HttpRequestResponseTrigger *> &response_triggers) {
|
||||
if (!network::is_connected()) {
|
||||
this->client_.end();
|
||||
this->status_set_warning();
|
||||
ESP_LOGW(TAG, "HTTP Request failed; Not connected to network");
|
||||
return;
|
||||
}
|
||||
|
||||
bool begin_status = false;
|
||||
const String url = this->url_.c_str();
|
||||
#if defined(USE_ESP32) || (defined(USE_ESP8266) && USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 6, 0))
|
||||
#if defined(USE_ESP32) || USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 7, 0)
|
||||
if (this->follow_redirects_) {
|
||||
this->client_.setFollowRedirects(HTTPC_FORCE_FOLLOW_REDIRECTS);
|
||||
} else {
|
||||
this->client_.setFollowRedirects(HTTPC_DISABLE_FOLLOW_REDIRECTS);
|
||||
}
|
||||
#else
|
||||
this->client_.setFollowRedirects(this->follow_redirects_);
|
||||
#endif
|
||||
this->client_.setRedirectLimit(this->redirect_limit_);
|
||||
#endif
|
||||
#if defined(USE_ESP32)
|
||||
begin_status = this->client_.begin(url);
|
||||
#elif defined(USE_ESP8266)
|
||||
begin_status = this->client_.begin(*this->get_wifi_client_(), url);
|
||||
#endif
|
||||
|
||||
if (!begin_status) {
|
||||
this->client_.end();
|
||||
this->status_set_warning();
|
||||
ESP_LOGW(TAG, "HTTP Request failed at the begin phase. Please check the configuration");
|
||||
return;
|
||||
}
|
||||
|
||||
this->client_.setTimeout(this->timeout_);
|
||||
#if defined(USE_ESP32)
|
||||
this->client_.setConnectTimeout(this->timeout_);
|
||||
#endif
|
||||
if (this->useragent_ != nullptr) {
|
||||
this->client_.setUserAgent(this->useragent_);
|
||||
}
|
||||
for (const auto &header : this->headers_) {
|
||||
this->client_.addHeader(header.name, header.value, false, true);
|
||||
}
|
||||
|
||||
uint32_t start_time = millis();
|
||||
int http_code = this->client_.sendRequest(this->method_, this->body_.c_str());
|
||||
uint32_t duration = millis() - start_time;
|
||||
for (auto *trigger : response_triggers)
|
||||
trigger->process(http_code, duration);
|
||||
|
||||
if (http_code < 0) {
|
||||
ESP_LOGW(TAG, "HTTP Request failed; URL: %s; Error: %s; Duration: %u ms", this->url_.c_str(),
|
||||
HTTPClient::errorToString(http_code).c_str(), duration);
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
|
||||
if (http_code < 200 || http_code >= 300) {
|
||||
ESP_LOGW(TAG, "HTTP Request failed; URL: %s; Code: %d; Duration: %u ms", this->url_.c_str(), http_code, duration);
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
|
||||
this->status_clear_warning();
|
||||
ESP_LOGD(TAG, "HTTP Request completed; URL: %s; Code: %d; Duration: %u ms", this->url_.c_str(), http_code, duration);
|
||||
}
|
||||
|
||||
#ifdef USE_ESP8266
|
||||
std::shared_ptr<WiFiClient> HttpRequestComponent::get_wifi_client_() {
|
||||
#ifdef USE_HTTP_REQUEST_ESP8266_HTTPS
|
||||
if (this->secure_) {
|
||||
if (this->wifi_client_secure_ == nullptr) {
|
||||
this->wifi_client_secure_ = std::make_shared<BearSSL::WiFiClientSecure>();
|
||||
this->wifi_client_secure_->setInsecure();
|
||||
this->wifi_client_secure_->setBufferSizes(512, 512);
|
||||
}
|
||||
return this->wifi_client_secure_;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (this->wifi_client_ == nullptr) {
|
||||
this->wifi_client_ = std::make_shared<WiFiClient>();
|
||||
}
|
||||
return this->wifi_client_;
|
||||
}
|
||||
#endif
|
||||
|
||||
void HttpRequestComponent::close() {
|
||||
this->last_url_ = this->url_;
|
||||
this->client_.end();
|
||||
}
|
||||
|
||||
const char *HttpRequestComponent::get_string() {
|
||||
#if defined(ESP32)
|
||||
// The static variable is here because HTTPClient::getString() returns a String on ESP32,
|
||||
// and we need something to keep a buffer alive.
|
||||
static String str;
|
||||
#else
|
||||
// However on ESP8266, HTTPClient::getString() returns a String& to a member variable.
|
||||
// Leaving this the default so that any new platform either doesn't copy, or encounters a compilation error.
|
||||
auto &
|
||||
#endif
|
||||
str = this->client_.getString();
|
||||
return str.c_str();
|
||||
}
|
||||
|
||||
} // namespace http_request
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ARDUINO
|
||||
|
|
|
@ -1,27 +1,18 @@
|
|||
#pragma once
|
||||
|
||||
#ifdef USE_ARDUINO
|
||||
|
||||
#include "esphome/components/json/json_util.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/defines.h"
|
||||
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#ifdef USE_ESP32
|
||||
#include <HTTPClient.h>
|
||||
#endif
|
||||
#ifdef USE_ESP8266
|
||||
#include <ESP8266HTTPClient.h>
|
||||
#ifdef USE_HTTP_REQUEST_ESP8266_HTTPS
|
||||
#include <WiFiClientSecure.h>
|
||||
#endif
|
||||
#endif
|
||||
#include "esphome/components/json/json_util.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace http_request {
|
||||
|
@ -31,9 +22,32 @@ struct Header {
|
|||
const char *value;
|
||||
};
|
||||
|
||||
class HttpRequestResponseTrigger : public Trigger<int32_t, uint32_t> {
|
||||
class HttpRequestComponent;
|
||||
|
||||
class HttpContainer : public Parented<HttpRequestComponent> {
|
||||
public:
|
||||
void process(int32_t status_code, uint32_t duration_ms) { this->trigger(status_code, duration_ms); }
|
||||
virtual ~HttpContainer() = default;
|
||||
size_t content_length;
|
||||
int status_code;
|
||||
uint32_t duration_ms;
|
||||
|
||||
virtual int read(uint8_t *buf, size_t max_len) = 0;
|
||||
virtual void end() = 0;
|
||||
|
||||
void set_secure(bool secure) { this->secure_ = secure; }
|
||||
|
||||
size_t get_bytes_read() const { return this->bytes_read_; }
|
||||
|
||||
protected:
|
||||
size_t bytes_read_{0};
|
||||
bool secure_{false};
|
||||
};
|
||||
|
||||
class HttpRequestResponseTrigger : public Trigger<std::shared_ptr<HttpContainer>, std::string> {
|
||||
public:
|
||||
void process(std::shared_ptr<HttpContainer> container, std::string response_body) {
|
||||
this->trigger(std::move(container), std::move(response_body));
|
||||
}
|
||||
};
|
||||
|
||||
class HttpRequestComponent : public Component {
|
||||
|
@ -41,37 +55,33 @@ class HttpRequestComponent : public Component {
|
|||
void dump_config() override;
|
||||
float get_setup_priority() const override { return setup_priority::AFTER_WIFI; }
|
||||
|
||||
void set_url(std::string url);
|
||||
void set_method(const char *method) { this->method_ = method; }
|
||||
void set_useragent(const char *useragent) { this->useragent_ = useragent; }
|
||||
void set_timeout(uint16_t timeout) { this->timeout_ = timeout; }
|
||||
void set_watchdog_timeout(uint32_t watchdog_timeout) { this->watchdog_timeout_ = watchdog_timeout; }
|
||||
uint32_t get_watchdog_timeout() const { return this->watchdog_timeout_; }
|
||||
void set_follow_redirects(bool follow_redirects) { this->follow_redirects_ = follow_redirects; }
|
||||
void set_redirect_limit(uint16_t limit) { this->redirect_limit_ = limit; }
|
||||
void set_body(const std::string &body) { this->body_ = body; }
|
||||
void set_headers(std::list<Header> headers) { this->headers_ = std::move(headers); }
|
||||
void send(const std::vector<HttpRequestResponseTrigger *> &response_triggers);
|
||||
void close();
|
||||
const char *get_string();
|
||||
|
||||
std::shared_ptr<HttpContainer> get(std::string url) { return this->start(std::move(url), "GET", "", {}); }
|
||||
std::shared_ptr<HttpContainer> get(std::string url, std::list<Header> headers) {
|
||||
return this->start(std::move(url), "GET", "", std::move(headers));
|
||||
}
|
||||
std::shared_ptr<HttpContainer> post(std::string url, std::string body) {
|
||||
return this->start(std::move(url), "POST", std::move(body), {});
|
||||
}
|
||||
std::shared_ptr<HttpContainer> post(std::string url, std::string body, std::list<Header> headers) {
|
||||
return this->start(std::move(url), "POST", std::move(body), std::move(headers));
|
||||
}
|
||||
|
||||
virtual std::shared_ptr<HttpContainer> start(std::string url, std::string method, std::string body,
|
||||
std::list<Header> headers) = 0;
|
||||
|
||||
protected:
|
||||
HTTPClient client_{};
|
||||
std::string url_;
|
||||
std::string last_url_;
|
||||
const char *method_;
|
||||
const char *useragent_{nullptr};
|
||||
bool secure_;
|
||||
bool follow_redirects_;
|
||||
uint16_t redirect_limit_;
|
||||
uint16_t timeout_{5000};
|
||||
std::string body_;
|
||||
std::list<Header> headers_;
|
||||
#ifdef USE_ESP8266
|
||||
std::shared_ptr<WiFiClient> wifi_client_;
|
||||
#ifdef USE_HTTP_REQUEST_ESP8266_HTTPS
|
||||
std::shared_ptr<BearSSL::WiFiClientSecure> wifi_client_secure_;
|
||||
#endif
|
||||
std::shared_ptr<WiFiClient> get_wifi_client_();
|
||||
#endif
|
||||
uint32_t watchdog_timeout_{0};
|
||||
};
|
||||
|
||||
template<typename... Ts> class HttpRequestSendAction : public Action<Ts...> {
|
||||
|
@ -80,6 +90,7 @@ template<typename... Ts> class HttpRequestSendAction : public Action<Ts...> {
|
|||
TEMPLATABLE_VALUE(std::string, url)
|
||||
TEMPLATABLE_VALUE(const char *, method)
|
||||
TEMPLATABLE_VALUE(std::string, body)
|
||||
TEMPLATABLE_VALUE(bool, capture_response)
|
||||
|
||||
void add_header(const char *key, TemplatableValue<const char *, Ts...> value) { this->headers_.insert({key, value}); }
|
||||
|
||||
|
@ -89,19 +100,22 @@ template<typename... Ts> class HttpRequestSendAction : public Action<Ts...> {
|
|||
|
||||
void register_response_trigger(HttpRequestResponseTrigger *trigger) { this->response_triggers_.push_back(trigger); }
|
||||
|
||||
void set_max_response_buffer_size(size_t max_response_buffer_size) {
|
||||
this->max_response_buffer_size_ = max_response_buffer_size;
|
||||
}
|
||||
|
||||
void play(Ts... x) override {
|
||||
this->parent_->set_url(this->url_.value(x...));
|
||||
this->parent_->set_method(this->method_.value(x...));
|
||||
std::string body;
|
||||
if (this->body_.has_value()) {
|
||||
this->parent_->set_body(this->body_.value(x...));
|
||||
body = this->body_.value(x...);
|
||||
}
|
||||
if (!this->json_.empty()) {
|
||||
auto f = std::bind(&HttpRequestSendAction<Ts...>::encode_json_, this, x..., std::placeholders::_1);
|
||||
this->parent_->set_body(json::build_json(f));
|
||||
body = json::build_json(f);
|
||||
}
|
||||
if (this->json_func_ != nullptr) {
|
||||
auto f = std::bind(&HttpRequestSendAction<Ts...>::encode_json_func_, this, x..., std::placeholders::_1);
|
||||
this->parent_->set_body(json::build_json(f));
|
||||
body = json::build_json(f);
|
||||
}
|
||||
std::list<Header> headers;
|
||||
for (const auto &item : this->headers_) {
|
||||
|
@ -111,10 +125,37 @@ template<typename... Ts> class HttpRequestSendAction : public Action<Ts...> {
|
|||
header.value = val.value(x...);
|
||||
headers.push_back(header);
|
||||
}
|
||||
this->parent_->set_headers(headers);
|
||||
this->parent_->send(this->response_triggers_);
|
||||
this->parent_->close();
|
||||
this->parent_->set_body("");
|
||||
|
||||
auto container = this->parent_->start(this->url_.value(x...), this->method_.value(x...), body, headers);
|
||||
|
||||
if (container == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
size_t content_length = container->content_length;
|
||||
size_t max_length = std::min(content_length, this->max_response_buffer_size_);
|
||||
|
||||
std::string response_body;
|
||||
if (this->capture_response_.value(x...)) {
|
||||
ExternalRAMAllocator<uint8_t> allocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE);
|
||||
uint8_t *buf = allocator.allocate(max_length);
|
||||
if (buf != nullptr) {
|
||||
size_t read_index = 0;
|
||||
while (container->get_bytes_read() < max_length) {
|
||||
int read = container->read(buf + read_index, std::min<size_t>(max_length - read_index, 512));
|
||||
App.feed_wdt();
|
||||
yield();
|
||||
read_index += read;
|
||||
}
|
||||
response_body.reserve(read_index);
|
||||
response_body.assign((char *) buf, read_index);
|
||||
}
|
||||
}
|
||||
|
||||
for (auto *trigger : this->response_triggers_) {
|
||||
trigger->process(container, response_body);
|
||||
}
|
||||
container->end();
|
||||
}
|
||||
|
||||
protected:
|
||||
|
@ -130,9 +171,9 @@ template<typename... Ts> class HttpRequestSendAction : public Action<Ts...> {
|
|||
std::map<const char *, TemplatableValue<std::string, Ts...>> json_{};
|
||||
std::function<void(Ts..., JsonObject)> json_func_{nullptr};
|
||||
std::vector<HttpRequestResponseTrigger *> response_triggers_;
|
||||
|
||||
size_t max_response_buffer_size_{SIZE_MAX};
|
||||
};
|
||||
|
||||
} // namespace http_request
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ARDUINO
|
||||
|
|
161
esphome/components/http_request/http_request_arduino.cpp
Normal file
161
esphome/components/http_request/http_request_arduino.cpp
Normal file
|
@ -0,0 +1,161 @@
|
|||
#include "http_request_arduino.h"
|
||||
|
||||
#ifdef USE_ARDUINO
|
||||
|
||||
#include "esphome/components/network/util.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#include "watchdog.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace http_request {
|
||||
|
||||
static const char *const TAG = "http_request.arduino";
|
||||
|
||||
std::shared_ptr<HttpContainer> HttpRequestArduino::start(std::string url, std::string method, std::string body,
|
||||
std::list<Header> headers) {
|
||||
if (!network::is_connected()) {
|
||||
this->status_momentary_error("failed", 1000);
|
||||
ESP_LOGW(TAG, "HTTP Request failed; Not connected to network");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::shared_ptr<HttpContainerArduino> container = std::make_shared<HttpContainerArduino>();
|
||||
container->set_parent(this);
|
||||
|
||||
const uint32_t start = millis();
|
||||
|
||||
bool secure = url.find("https:") != std::string::npos;
|
||||
container->set_secure(secure);
|
||||
|
||||
watchdog::WatchdogManager wdm(this->get_watchdog_timeout());
|
||||
|
||||
#if defined(USE_ESP8266)
|
||||
std::unique_ptr<WiFiClient> stream_ptr;
|
||||
#ifdef USE_HTTP_REQUEST_ESP8266_HTTPS
|
||||
if (secure) {
|
||||
ESP_LOGV(TAG, "ESP8266 HTTPS connection with WiFiClientSecure");
|
||||
stream_ptr = std::make_unique<WiFiClientSecure>();
|
||||
WiFiClientSecure *secure_client = static_cast<WiFiClientSecure *>(stream_ptr.get());
|
||||
secure_client->setBufferSizes(512, 512);
|
||||
secure_client->setInsecure();
|
||||
} else {
|
||||
stream_ptr = std::make_unique<WiFiClient>();
|
||||
}
|
||||
#else
|
||||
ESP_LOGV(TAG, "ESP8266 HTTP connection with WiFiClient");
|
||||
if (secure) {
|
||||
ESP_LOGE(TAG, "Can't use HTTPS connection with esp8266_disable_ssl_support");
|
||||
return nullptr;
|
||||
}
|
||||
stream_ptr = std::make_unique<WiFiClient>();
|
||||
#endif // USE_HTTP_REQUEST_ESP8266_HTTPS
|
||||
|
||||
#if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(3, 1, 0) // && USE_ARDUINO_VERSION_CODE < VERSION_CODE(?, ?, ?)
|
||||
if (!secure) {
|
||||
ESP_LOGW(TAG, "Using HTTP on Arduino version >= 3.1 is **very** slow. Consider setting framework version to 3.0.2 "
|
||||
"in your YAML, or use HTTPS");
|
||||
}
|
||||
#endif // USE_ARDUINO_VERSION_CODE
|
||||
|
||||
container->client_.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
|
||||
bool status = container->client_.begin(*stream_ptr, url.c_str());
|
||||
|
||||
#elif defined(USE_RP2040)
|
||||
if (secure) {
|
||||
container->client_.setInsecure();
|
||||
}
|
||||
bool status = container->client_.begin(url.c_str());
|
||||
#elif defined(USE_ESP32)
|
||||
bool status = container->client_.begin(url.c_str());
|
||||
#endif
|
||||
|
||||
App.feed_wdt();
|
||||
|
||||
if (!status) {
|
||||
ESP_LOGW(TAG, "HTTP Request failed; URL: %s", url.c_str());
|
||||
container->end();
|
||||
this->status_momentary_error("failed", 1000);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
container->client_.setReuse(true);
|
||||
container->client_.setTimeout(this->timeout_);
|
||||
#if defined(USE_ESP32)
|
||||
container->client_.setConnectTimeout(this->timeout_);
|
||||
#endif
|
||||
|
||||
if (this->useragent_ != nullptr) {
|
||||
container->client_.setUserAgent(this->useragent_);
|
||||
}
|
||||
for (const auto &header : headers) {
|
||||
container->client_.addHeader(header.name, header.value, false, true);
|
||||
}
|
||||
|
||||
// returned needed headers must be collected before the requests
|
||||
static const char *header_keys[] = {"Content-Length", "Content-Type"};
|
||||
static const size_t HEADER_COUNT = sizeof(header_keys) / sizeof(header_keys[0]);
|
||||
container->client_.collectHeaders(header_keys, HEADER_COUNT);
|
||||
|
||||
container->status_code = container->client_.sendRequest(method.c_str(), body.c_str());
|
||||
if (container->status_code < 0) {
|
||||
ESP_LOGW(TAG, "HTTP Request failed; URL: %s; Error: %s", url.c_str(),
|
||||
HTTPClient::errorToString(container->status_code).c_str());
|
||||
this->status_momentary_error("failed", 1000);
|
||||
container->end();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (container->status_code < 200 || container->status_code >= 300) {
|
||||
ESP_LOGE(TAG, "HTTP Request failed; URL: %s; Code: %d", url.c_str(), container->status_code);
|
||||
this->status_momentary_error("failed", 1000);
|
||||
container->end();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int content_length = container->client_.getSize();
|
||||
ESP_LOGD(TAG, "Content-Length: %d", content_length);
|
||||
container->content_length = (size_t) content_length;
|
||||
container->duration_ms = millis() - start;
|
||||
|
||||
return container;
|
||||
}
|
||||
|
||||
int HttpContainerArduino::read(uint8_t *buf, size_t max_len) {
|
||||
const uint32_t start = millis();
|
||||
watchdog::WatchdogManager wdm(this->parent_->get_watchdog_timeout());
|
||||
|
||||
WiFiClient *stream_ptr = this->client_.getStreamPtr();
|
||||
if (stream_ptr == nullptr) {
|
||||
ESP_LOGE(TAG, "Stream pointer vanished!");
|
||||
return -1;
|
||||
}
|
||||
|
||||
int available_data = stream_ptr->available();
|
||||
int bufsize = std::min(max_len, std::min(this->content_length - this->bytes_read_, (size_t) available_data));
|
||||
|
||||
if (bufsize == 0) {
|
||||
this->duration_ms += (millis() - start);
|
||||
return 0;
|
||||
}
|
||||
|
||||
App.feed_wdt();
|
||||
int read_len = stream_ptr->readBytes(buf, bufsize);
|
||||
this->bytes_read_ += read_len;
|
||||
|
||||
this->duration_ms += (millis() - start);
|
||||
|
||||
return read_len;
|
||||
}
|
||||
|
||||
void HttpContainerArduino::end() {
|
||||
watchdog::WatchdogManager wdm(this->parent_->get_watchdog_timeout());
|
||||
this->client_.end();
|
||||
}
|
||||
|
||||
} // namespace http_request
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ARDUINO
|
40
esphome/components/http_request/http_request_arduino.h
Normal file
40
esphome/components/http_request/http_request_arduino.h
Normal file
|
@ -0,0 +1,40 @@
|
|||
#pragma once
|
||||
|
||||
#include "http_request.h"
|
||||
|
||||
#ifdef USE_ARDUINO
|
||||
|
||||
#if defined(USE_ESP32) || defined(USE_RP2040)
|
||||
#include <HTTPClient.h>
|
||||
#endif
|
||||
#ifdef USE_ESP8266
|
||||
#include <ESP8266HTTPClient.h>
|
||||
#ifdef USE_HTTP_REQUEST_ESP8266_HTTPS
|
||||
#include <WiFiClientSecure.h>
|
||||
#endif
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
namespace http_request {
|
||||
|
||||
class HttpRequestArduino;
|
||||
class HttpContainerArduino : public HttpContainer {
|
||||
public:
|
||||
int read(uint8_t *buf, size_t max_len) override;
|
||||
void end() override;
|
||||
|
||||
protected:
|
||||
friend class HttpRequestArduino;
|
||||
HTTPClient client_{};
|
||||
};
|
||||
|
||||
class HttpRequestArduino : public HttpRequestComponent {
|
||||
public:
|
||||
std::shared_ptr<HttpContainer> start(std::string url, std::string method, std::string body,
|
||||
std::list<Header> headers) override;
|
||||
};
|
||||
|
||||
} // namespace http_request
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ARDUINO
|
155
esphome/components/http_request/http_request_idf.cpp
Normal file
155
esphome/components/http_request/http_request_idf.cpp
Normal file
|
@ -0,0 +1,155 @@
|
|||
#include "http_request_idf.h"
|
||||
|
||||
#ifdef USE_ESP_IDF
|
||||
|
||||
#include "esphome/components/network/util.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#if CONFIG_MBEDTLS_CERTIFICATE_BUNDLE
|
||||
#include "esp_crt_bundle.h"
|
||||
#endif
|
||||
|
||||
#include "watchdog.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace http_request {
|
||||
|
||||
static const char *const TAG = "http_request.idf";
|
||||
|
||||
std::shared_ptr<HttpContainer> HttpRequestIDF::start(std::string url, std::string method, std::string body,
|
||||
std::list<Header> headers) {
|
||||
if (!network::is_connected()) {
|
||||
this->status_momentary_error("failed", 1000);
|
||||
ESP_LOGE(TAG, "HTTP Request failed; Not connected to network");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
esp_http_client_method_t method_idf;
|
||||
if (method == "GET") {
|
||||
method_idf = HTTP_METHOD_GET;
|
||||
} else if (method == "POST") {
|
||||
method_idf = HTTP_METHOD_POST;
|
||||
} else if (method == "PUT") {
|
||||
method_idf = HTTP_METHOD_PUT;
|
||||
} else if (method == "DELETE") {
|
||||
method_idf = HTTP_METHOD_DELETE;
|
||||
} else if (method == "PATCH") {
|
||||
method_idf = HTTP_METHOD_PATCH;
|
||||
} else {
|
||||
this->status_momentary_error("failed", 1000);
|
||||
ESP_LOGE(TAG, "HTTP Request failed; Unsupported method");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool secure = url.find("https:") != std::string::npos;
|
||||
|
||||
esp_http_client_config_t config = {};
|
||||
|
||||
config.url = url.c_str();
|
||||
config.method = method_idf;
|
||||
config.timeout_ms = this->timeout_;
|
||||
config.disable_auto_redirect = !this->follow_redirects_;
|
||||
config.max_redirection_count = this->redirect_limit_;
|
||||
#if CONFIG_MBEDTLS_CERTIFICATE_BUNDLE
|
||||
if (secure) {
|
||||
config.crt_bundle_attach = esp_crt_bundle_attach;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (this->useragent_ != nullptr) {
|
||||
config.user_agent = this->useragent_;
|
||||
}
|
||||
|
||||
const uint32_t start = millis();
|
||||
watchdog::WatchdogManager wdm(this->get_watchdog_timeout());
|
||||
|
||||
esp_http_client_handle_t client = esp_http_client_init(&config);
|
||||
|
||||
std::shared_ptr<HttpContainerIDF> container = std::make_shared<HttpContainerIDF>(client);
|
||||
container->set_parent(this);
|
||||
|
||||
container->set_secure(secure);
|
||||
|
||||
for (const auto &header : headers) {
|
||||
esp_http_client_set_header(client, header.name, header.value);
|
||||
}
|
||||
|
||||
int body_len = body.length();
|
||||
|
||||
esp_err_t err = esp_http_client_open(client, body_len);
|
||||
if (err != ESP_OK) {
|
||||
this->status_momentary_error("failed", 1000);
|
||||
ESP_LOGE(TAG, "HTTP Request failed: %s", esp_err_to_name(err));
|
||||
esp_http_client_cleanup(client);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (body_len > 0) {
|
||||
int write_left = body_len;
|
||||
int write_index = 0;
|
||||
const char *buf = body.c_str();
|
||||
while (write_left > 0) {
|
||||
int written = esp_http_client_write(client, buf + write_index, write_left);
|
||||
if (written < 0) {
|
||||
err = ESP_FAIL;
|
||||
break;
|
||||
}
|
||||
write_left -= written;
|
||||
write_index += written;
|
||||
}
|
||||
}
|
||||
|
||||
if (err != ESP_OK) {
|
||||
this->status_momentary_error("failed", 1000);
|
||||
ESP_LOGE(TAG, "HTTP Request failed: %s", esp_err_to_name(err));
|
||||
esp_http_client_cleanup(client);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
container->content_length = esp_http_client_fetch_headers(client);
|
||||
const auto status_code = esp_http_client_get_status_code(client);
|
||||
container->status_code = status_code;
|
||||
|
||||
if (status_code < 200 || status_code >= 300) {
|
||||
ESP_LOGE(TAG, "HTTP Request failed; URL: %s; Code: %d", url.c_str(), status_code);
|
||||
this->status_momentary_error("failed", 1000);
|
||||
esp_http_client_cleanup(client);
|
||||
return nullptr;
|
||||
}
|
||||
container->duration_ms = millis() - start;
|
||||
return container;
|
||||
}
|
||||
|
||||
int HttpContainerIDF::read(uint8_t *buf, size_t max_len) {
|
||||
const uint32_t start = millis();
|
||||
watchdog::WatchdogManager wdm(this->parent_->get_watchdog_timeout());
|
||||
|
||||
int bufsize = std::min(max_len, this->content_length - this->bytes_read_);
|
||||
|
||||
if (bufsize == 0) {
|
||||
this->duration_ms += (millis() - start);
|
||||
return 0;
|
||||
}
|
||||
|
||||
App.feed_wdt();
|
||||
int read_len = esp_http_client_read(this->client_, (char *) buf, bufsize);
|
||||
this->bytes_read_ += read_len;
|
||||
|
||||
this->duration_ms += (millis() - start);
|
||||
|
||||
return read_len;
|
||||
}
|
||||
|
||||
void HttpContainerIDF::end() {
|
||||
watchdog::WatchdogManager wdm(this->parent_->get_watchdog_timeout());
|
||||
|
||||
esp_http_client_close(this->client_);
|
||||
esp_http_client_cleanup(this->client_);
|
||||
}
|
||||
|
||||
} // namespace http_request
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ESP_IDF
|
34
esphome/components/http_request/http_request_idf.h
Normal file
34
esphome/components/http_request/http_request_idf.h
Normal file
|
@ -0,0 +1,34 @@
|
|||
#pragma once
|
||||
|
||||
#include "http_request.h"
|
||||
|
||||
#ifdef USE_ESP_IDF
|
||||
|
||||
#include <esp_event.h>
|
||||
#include <esp_http_client.h>
|
||||
#include <esp_netif.h>
|
||||
#include <esp_tls.h>
|
||||
|
||||
namespace esphome {
|
||||
namespace http_request {
|
||||
|
||||
class HttpContainerIDF : public HttpContainer {
|
||||
public:
|
||||
HttpContainerIDF(esp_http_client_handle_t client) : client_(client) {}
|
||||
int read(uint8_t *buf, size_t max_len) override;
|
||||
void end() override;
|
||||
|
||||
protected:
|
||||
esp_http_client_handle_t client_;
|
||||
};
|
||||
|
||||
class HttpRequestIDF : public HttpRequestComponent {
|
||||
public:
|
||||
std::shared_ptr<HttpContainer> start(std::string url, std::string method, std::string body,
|
||||
std::list<Header> headers) override;
|
||||
};
|
||||
|
||||
} // namespace http_request
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ESP_IDF
|
100
esphome/components/http_request/ota/__init__.py
Normal file
100
esphome/components/http_request/ota/__init__.py
Normal file
|
@ -0,0 +1,100 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import automation
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_PASSWORD,
|
||||
CONF_URL,
|
||||
CONF_USERNAME,
|
||||
)
|
||||
from esphome.components.ota import BASE_OTA_SCHEMA, ota_to_code, OTAComponent
|
||||
from esphome.core import coroutine_with_priority
|
||||
from .. import CONF_HTTP_REQUEST_ID, http_request_ns, HttpRequestComponent
|
||||
|
||||
CODEOWNERS = ["@oarcher"]
|
||||
|
||||
AUTO_LOAD = ["md5"]
|
||||
DEPENDENCIES = ["network", "http_request"]
|
||||
|
||||
CONF_MD5 = "md5"
|
||||
CONF_MD5_URL = "md5_url"
|
||||
|
||||
OtaHttpRequestComponent = http_request_ns.class_(
|
||||
"OtaHttpRequestComponent", OTAComponent
|
||||
)
|
||||
OtaHttpRequestComponentFlashAction = http_request_ns.class_(
|
||||
"OtaHttpRequestComponentFlashAction", automation.Action
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(OtaHttpRequestComponent),
|
||||
cv.GenerateID(CONF_HTTP_REQUEST_ID): cv.use_id(HttpRequestComponent),
|
||||
}
|
||||
)
|
||||
.extend(BASE_OTA_SCHEMA)
|
||||
.extend(cv.COMPONENT_SCHEMA),
|
||||
cv.require_framework_version(
|
||||
esp8266_arduino=cv.Version(2, 5, 1),
|
||||
esp32_arduino=cv.Version(0, 0, 0),
|
||||
esp_idf=cv.Version(0, 0, 0),
|
||||
rp2040_arduino=cv.Version(0, 0, 0),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@coroutine_with_priority(52.0)
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await ota_to_code(var, config)
|
||||
await cg.register_component(var, config)
|
||||
await cg.register_parented(var, config[CONF_HTTP_REQUEST_ID])
|
||||
|
||||
|
||||
OTA_HTTP_REQUEST_FLASH_ACTION_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(OtaHttpRequestComponent),
|
||||
cv.Optional(CONF_MD5_URL): cv.templatable(cv.url),
|
||||
cv.Optional(CONF_MD5): cv.templatable(
|
||||
cv.All(cv.string, cv.Length(min=32, max=32))
|
||||
),
|
||||
cv.Optional(CONF_PASSWORD): cv.templatable(cv.string),
|
||||
cv.Optional(CONF_USERNAME): cv.templatable(cv.string),
|
||||
cv.Required(CONF_URL): cv.templatable(cv.url),
|
||||
}
|
||||
),
|
||||
cv.has_exactly_one_key(CONF_MD5, CONF_MD5_URL),
|
||||
)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"ota.http_request.flash",
|
||||
OtaHttpRequestComponentFlashAction,
|
||||
OTA_HTTP_REQUEST_FLASH_ACTION_SCHEMA,
|
||||
)
|
||||
async def ota_http_request_action_to_code(config, action_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
var = cg.new_Pvariable(action_id, template_arg, paren)
|
||||
|
||||
if md5_url := config.get(CONF_MD5_URL):
|
||||
template_ = await cg.templatable(md5_url, args, cg.std_string)
|
||||
cg.add(var.set_md5_url(template_))
|
||||
|
||||
if md5_str := config.get(CONF_MD5):
|
||||
template_ = await cg.templatable(md5_str, args, cg.std_string)
|
||||
cg.add(var.set_md5(template_))
|
||||
|
||||
if password_str := config.get(CONF_PASSWORD):
|
||||
template_ = await cg.templatable(password_str, args, cg.std_string)
|
||||
cg.add(var.set_password(template_))
|
||||
|
||||
if username_str := config.get(CONF_USERNAME):
|
||||
template_ = await cg.templatable(username_str, args, cg.std_string)
|
||||
cg.add(var.set_username(template_))
|
||||
|
||||
template_ = await cg.templatable(config[CONF_URL], args, cg.std_string)
|
||||
cg.add(var.set_url(template_))
|
||||
|
||||
return var
|
42
esphome/components/http_request/ota/automation.h
Normal file
42
esphome/components/http_request/ota/automation.h
Normal file
|
@ -0,0 +1,42 @@
|
|||
#pragma once
|
||||
#include "ota_http_request.h"
|
||||
|
||||
#include "esphome/core/automation.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace http_request {
|
||||
|
||||
template<typename... Ts> class OtaHttpRequestComponentFlashAction : public Action<Ts...> {
|
||||
public:
|
||||
OtaHttpRequestComponentFlashAction(OtaHttpRequestComponent *parent) : parent_(parent) {}
|
||||
TEMPLATABLE_VALUE(std::string, md5_url)
|
||||
TEMPLATABLE_VALUE(std::string, md5)
|
||||
TEMPLATABLE_VALUE(std::string, password)
|
||||
TEMPLATABLE_VALUE(std::string, url)
|
||||
TEMPLATABLE_VALUE(std::string, username)
|
||||
|
||||
void play(Ts... x) override {
|
||||
if (this->md5_url_.has_value()) {
|
||||
this->parent_->set_md5_url(this->md5_url_.value(x...));
|
||||
}
|
||||
if (this->md5_.has_value()) {
|
||||
this->parent_->set_md5(this->md5_.value(x...));
|
||||
}
|
||||
if (this->password_.has_value()) {
|
||||
this->parent_->set_password(this->password_.value(x...));
|
||||
}
|
||||
if (this->username_.has_value()) {
|
||||
this->parent_->set_username(this->username_.value(x...));
|
||||
}
|
||||
this->parent_->set_url(this->url_.value(x...));
|
||||
|
||||
this->parent_->flash();
|
||||
// Normally never reached due to reboot
|
||||
}
|
||||
|
||||
protected:
|
||||
OtaHttpRequestComponent *parent_;
|
||||
};
|
||||
|
||||
} // namespace http_request
|
||||
} // namespace esphome
|
269
esphome/components/http_request/ota/ota_http_request.cpp
Normal file
269
esphome/components/http_request/ota/ota_http_request.cpp
Normal file
|
@ -0,0 +1,269 @@
|
|||
#include "ota_http_request.h"
|
||||
#include "../watchdog.h"
|
||||
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#include "esphome/components/md5/md5.h"
|
||||
#include "esphome/components/ota/ota_backend.h"
|
||||
#include "esphome/components/ota/ota_backend_arduino_esp32.h"
|
||||
#include "esphome/components/ota/ota_backend_arduino_esp8266.h"
|
||||
#include "esphome/components/ota/ota_backend_arduino_rp2040.h"
|
||||
#include "esphome/components/ota/ota_backend_esp_idf.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace http_request {
|
||||
|
||||
static const char *const TAG = "http_request.ota";
|
||||
|
||||
void OtaHttpRequestComponent::setup() {
|
||||
#ifdef USE_OTA_STATE_CALLBACK
|
||||
ota::register_ota_platform(this);
|
||||
#endif
|
||||
}
|
||||
|
||||
void OtaHttpRequestComponent::dump_config() { ESP_LOGCONFIG(TAG, "Over-The-Air updates via HTTP request"); };
|
||||
|
||||
void OtaHttpRequestComponent::set_md5_url(const std::string &url) {
|
||||
if (!this->validate_url_(url)) {
|
||||
this->md5_url_.clear(); // URL was not valid; prevent flashing until it is
|
||||
return;
|
||||
}
|
||||
this->md5_url_ = url;
|
||||
this->md5_expected_.clear(); // to be retrieved later
|
||||
}
|
||||
|
||||
void OtaHttpRequestComponent::set_url(const std::string &url) {
|
||||
if (!this->validate_url_(url)) {
|
||||
this->url_.clear(); // URL was not valid; prevent flashing until it is
|
||||
return;
|
||||
}
|
||||
this->url_ = url;
|
||||
}
|
||||
|
||||
void OtaHttpRequestComponent::flash() {
|
||||
if (this->url_.empty()) {
|
||||
ESP_LOGE(TAG, "URL not set; cannot start update");
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Starting update...");
|
||||
#ifdef USE_OTA_STATE_CALLBACK
|
||||
this->state_callback_.call(ota::OTA_STARTED, 0.0f, 0);
|
||||
#endif
|
||||
|
||||
auto ota_status = this->do_ota_();
|
||||
|
||||
switch (ota_status) {
|
||||
case ota::OTA_RESPONSE_OK:
|
||||
#ifdef USE_OTA_STATE_CALLBACK
|
||||
this->state_callback_.call(ota::OTA_COMPLETED, 100.0f, ota_status);
|
||||
#endif
|
||||
delay(10);
|
||||
App.safe_reboot();
|
||||
break;
|
||||
|
||||
default:
|
||||
#ifdef USE_OTA_STATE_CALLBACK
|
||||
this->state_callback_.call(ota::OTA_ERROR, 0.0f, ota_status);
|
||||
#endif
|
||||
this->md5_computed_.clear(); // will be reset at next attempt
|
||||
this->md5_expected_.clear(); // will be reset at next attempt
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void OtaHttpRequestComponent::cleanup_(std::unique_ptr<ota::OTABackend> backend,
|
||||
const std::shared_ptr<HttpContainer> &container) {
|
||||
if (this->update_started_) {
|
||||
ESP_LOGV(TAG, "Aborting OTA backend");
|
||||
backend->abort();
|
||||
}
|
||||
ESP_LOGV(TAG, "Aborting HTTP connection");
|
||||
container->end();
|
||||
};
|
||||
|
||||
uint8_t OtaHttpRequestComponent::do_ota_() {
|
||||
uint8_t buf[OtaHttpRequestComponent::HTTP_RECV_BUFFER + 1];
|
||||
uint32_t last_progress = 0;
|
||||
uint32_t update_start_time = millis();
|
||||
md5::MD5Digest md5_receive;
|
||||
std::unique_ptr<char[]> md5_receive_str(new char[33]);
|
||||
|
||||
if (this->md5_expected_.empty() && !this->http_get_md5_()) {
|
||||
return OTA_MD5_INVALID;
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, "MD5 expected: %s", this->md5_expected_.c_str());
|
||||
|
||||
auto url_with_auth = this->get_url_with_auth_(this->url_);
|
||||
if (url_with_auth.empty()) {
|
||||
return OTA_BAD_URL;
|
||||
}
|
||||
ESP_LOGVV(TAG, "url_with_auth: %s", url_with_auth.c_str());
|
||||
ESP_LOGI(TAG, "Connecting to: %s", this->url_.c_str());
|
||||
|
||||
auto container = this->parent_->get(url_with_auth);
|
||||
|
||||
if (container == nullptr) {
|
||||
return OTA_CONNECTION_ERROR;
|
||||
}
|
||||
|
||||
// we will compute MD5 on the fly for verification -- Arduino OTA seems to ignore it
|
||||
md5_receive.init();
|
||||
ESP_LOGV(TAG, "MD5Digest initialized");
|
||||
|
||||
ESP_LOGV(TAG, "OTA backend begin");
|
||||
auto backend = ota::make_ota_backend();
|
||||
auto error_code = backend->begin(container->content_length);
|
||||
if (error_code != ota::OTA_RESPONSE_OK) {
|
||||
ESP_LOGW(TAG, "backend->begin error: %d", error_code);
|
||||
this->cleanup_(std::move(backend), container);
|
||||
return error_code;
|
||||
}
|
||||
|
||||
while (container->get_bytes_read() < container->content_length) {
|
||||
// read a maximum of chunk_size bytes into buf. (real read size returned)
|
||||
int bufsize = container->read(buf, OtaHttpRequestComponent::HTTP_RECV_BUFFER);
|
||||
ESP_LOGVV(TAG, "bytes_read_ = %u, body_length_ = %u, bufsize = %i", container->get_bytes_read(),
|
||||
container->content_length, bufsize);
|
||||
|
||||
// feed watchdog and give other tasks a chance to run
|
||||
App.feed_wdt();
|
||||
yield();
|
||||
|
||||
if (bufsize < 0) {
|
||||
ESP_LOGE(TAG, "Stream closed");
|
||||
this->cleanup_(std::move(backend), container);
|
||||
return OTA_CONNECTION_ERROR;
|
||||
} else if (bufsize > 0 && bufsize <= OtaHttpRequestComponent::HTTP_RECV_BUFFER) {
|
||||
// add read bytes to MD5
|
||||
md5_receive.add(buf, bufsize);
|
||||
|
||||
// write bytes to OTA backend
|
||||
this->update_started_ = true;
|
||||
error_code = backend->write(buf, bufsize);
|
||||
if (error_code != ota::OTA_RESPONSE_OK) {
|
||||
// error code explanation available at
|
||||
// https://github.com/esphome/esphome/blob/dev/esphome/components/ota/ota_backend.h
|
||||
ESP_LOGE(TAG, "Error code (%02X) writing binary data to flash at offset %d and size %d", error_code,
|
||||
container->get_bytes_read() - bufsize, container->content_length);
|
||||
this->cleanup_(std::move(backend), container);
|
||||
return error_code;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t now = millis();
|
||||
if ((now - last_progress > 1000) or (container->get_bytes_read() == container->content_length)) {
|
||||
last_progress = now;
|
||||
float percentage = container->get_bytes_read() * 100.0f / container->content_length;
|
||||
ESP_LOGD(TAG, "Progress: %0.1f%%", percentage);
|
||||
#ifdef USE_OTA_STATE_CALLBACK
|
||||
this->state_callback_.call(ota::OTA_IN_PROGRESS, percentage, 0);
|
||||
#endif
|
||||
}
|
||||
} // while
|
||||
|
||||
ESP_LOGI(TAG, "Done in %.0f seconds", float(millis() - update_start_time) / 1000);
|
||||
|
||||
// verify MD5 is as expected and act accordingly
|
||||
md5_receive.calculate();
|
||||
md5_receive.get_hex(md5_receive_str.get());
|
||||
this->md5_computed_ = md5_receive_str.get();
|
||||
if (strncmp(this->md5_computed_.c_str(), this->md5_expected_.c_str(), MD5_SIZE) != 0) {
|
||||
ESP_LOGE(TAG, "MD5 computed: %s - Aborting due to MD5 mismatch", this->md5_computed_.c_str());
|
||||
this->cleanup_(std::move(backend), container);
|
||||
return ota::OTA_RESPONSE_ERROR_MD5_MISMATCH;
|
||||
} else {
|
||||
backend->set_update_md5(md5_receive_str.get());
|
||||
}
|
||||
|
||||
container->end();
|
||||
|
||||
// feed watchdog and give other tasks a chance to run
|
||||
App.feed_wdt();
|
||||
yield();
|
||||
delay(100); // NOLINT
|
||||
|
||||
error_code = backend->end();
|
||||
if (error_code != ota::OTA_RESPONSE_OK) {
|
||||
ESP_LOGW(TAG, "Error ending update! error_code: %d", error_code);
|
||||
this->cleanup_(std::move(backend), container);
|
||||
return error_code;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Update complete");
|
||||
return ota::OTA_RESPONSE_OK;
|
||||
}
|
||||
|
||||
std::string OtaHttpRequestComponent::get_url_with_auth_(const std::string &url) {
|
||||
if (this->username_.empty() || this->password_.empty()) {
|
||||
return url;
|
||||
}
|
||||
|
||||
auto start_char = url.find("://");
|
||||
if ((start_char == std::string::npos) || (start_char < 4)) {
|
||||
ESP_LOGE(TAG, "Incorrect URL prefix");
|
||||
return {};
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, "Using basic HTTP authentication");
|
||||
|
||||
start_char += 3; // skip '://' characters
|
||||
auto url_with_auth =
|
||||
url.substr(0, start_char) + this->username_ + ":" + this->password_ + "@" + url.substr(start_char);
|
||||
return url_with_auth;
|
||||
}
|
||||
|
||||
bool OtaHttpRequestComponent::http_get_md5_() {
|
||||
if (this->md5_url_.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto url_with_auth = this->get_url_with_auth_(this->md5_url_);
|
||||
if (url_with_auth.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ESP_LOGVV(TAG, "url_with_auth: %s", url_with_auth.c_str());
|
||||
ESP_LOGI(TAG, "Connecting to: %s", this->md5_url_.c_str());
|
||||
auto container = this->parent_->get(url_with_auth);
|
||||
if (container == nullptr) {
|
||||
ESP_LOGE(TAG, "Failed to connect to MD5 URL");
|
||||
return false;
|
||||
}
|
||||
size_t length = container->content_length;
|
||||
if (length == 0) {
|
||||
container->end();
|
||||
return false;
|
||||
}
|
||||
if (length < MD5_SIZE) {
|
||||
ESP_LOGE(TAG, "MD5 file must be %u bytes; %u bytes reported by HTTP server. Aborting", MD5_SIZE, length);
|
||||
container->end();
|
||||
return false;
|
||||
}
|
||||
|
||||
this->md5_expected_.resize(MD5_SIZE);
|
||||
int read_len = 0;
|
||||
while (container->get_bytes_read() < MD5_SIZE) {
|
||||
read_len = container->read((uint8_t *) this->md5_expected_.data(), MD5_SIZE);
|
||||
App.feed_wdt();
|
||||
yield();
|
||||
}
|
||||
container->end();
|
||||
|
||||
ESP_LOGV(TAG, "Read len: %u, MD5 expected: %u", read_len, MD5_SIZE);
|
||||
return read_len == MD5_SIZE;
|
||||
}
|
||||
|
||||
bool OtaHttpRequestComponent::validate_url_(const std::string &url) {
|
||||
if ((url.length() < 8) || (url.find("http") != 0) || (url.find("://") == std::string::npos)) {
|
||||
ESP_LOGE(TAG, "URL is invalid and/or must be prefixed with 'http://' or 'https://'");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace http_request
|
||||
} // namespace esphome
|
61
esphome/components/http_request/ota/ota_http_request.h
Normal file
61
esphome/components/http_request/ota/ota_http_request.h
Normal file
|
@ -0,0 +1,61 @@
|
|||
#pragma once
|
||||
|
||||
#include "esphome/components/ota/ota_backend.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "../http_request.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace http_request {
|
||||
|
||||
static const uint8_t MD5_SIZE = 32;
|
||||
|
||||
enum OtaHttpRequestError : uint8_t {
|
||||
OTA_MD5_INVALID = 0x10,
|
||||
OTA_BAD_URL = 0x11,
|
||||
OTA_CONNECTION_ERROR = 0x12,
|
||||
};
|
||||
|
||||
class OtaHttpRequestComponent : public ota::OTAComponent, public Parented<HttpRequestComponent> {
|
||||
public:
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override { return setup_priority::AFTER_WIFI; }
|
||||
|
||||
void set_md5_url(const std::string &md5_url);
|
||||
void set_md5(const std::string &md5) { this->md5_expected_ = md5; }
|
||||
void set_password(const std::string &password) { this->password_ = password; }
|
||||
void set_url(const std::string &url);
|
||||
void set_username(const std::string &username) { this->username_ = username; }
|
||||
|
||||
std::string md5_computed() { return this->md5_computed_; }
|
||||
std::string md5_expected() { return this->md5_expected_; }
|
||||
|
||||
void flash();
|
||||
|
||||
protected:
|
||||
void cleanup_(std::unique_ptr<ota::OTABackend> backend, const std::shared_ptr<HttpContainer> &container);
|
||||
uint8_t do_ota_();
|
||||
std::string get_url_with_auth_(const std::string &url);
|
||||
bool http_get_md5_();
|
||||
bool validate_url_(const std::string &url);
|
||||
|
||||
std::string md5_computed_{};
|
||||
std::string md5_expected_{};
|
||||
std::string md5_url_{};
|
||||
std::string password_{};
|
||||
std::string username_{};
|
||||
std::string url_{};
|
||||
int status_ = -1;
|
||||
bool update_started_ = false;
|
||||
static const uint16_t HTTP_RECV_BUFFER = 256; // the firmware GET chunk size
|
||||
};
|
||||
|
||||
} // namespace http_request
|
||||
} // namespace esphome
|
44
esphome/components/http_request/update/__init__.py
Normal file
44
esphome/components/http_request/update/__init__.py
Normal file
|
@ -0,0 +1,44 @@
|
|||
import esphome.config_validation as cv
|
||||
import esphome.codegen as cg
|
||||
|
||||
from esphome.components import update
|
||||
from esphome.const import (
|
||||
CONF_SOURCE,
|
||||
)
|
||||
|
||||
from .. import http_request_ns, CONF_HTTP_REQUEST_ID, HttpRequestComponent
|
||||
from ..ota import OtaHttpRequestComponent
|
||||
|
||||
|
||||
AUTO_LOAD = ["json"]
|
||||
CODEOWNERS = ["@jesserockz"]
|
||||
DEPENDENCIES = ["ota.http_request"]
|
||||
|
||||
HttpRequestUpdate = http_request_ns.class_(
|
||||
"HttpRequestUpdate", update.UpdateEntity, cg.PollingComponent
|
||||
)
|
||||
|
||||
CONF_OTA_ID = "ota_id"
|
||||
|
||||
CONFIG_SCHEMA = update.UPDATE_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(HttpRequestUpdate),
|
||||
cv.GenerateID(CONF_OTA_ID): cv.use_id(OtaHttpRequestComponent),
|
||||
cv.GenerateID(CONF_HTTP_REQUEST_ID): cv.use_id(HttpRequestComponent),
|
||||
cv.Required(CONF_SOURCE): cv.url,
|
||||
}
|
||||
).extend(cv.polling_component_schema("6h"))
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = await update.new_update(config)
|
||||
ota_parent = await cg.get_variable(config[CONF_OTA_ID])
|
||||
cg.add(var.set_ota_parent(ota_parent))
|
||||
request_parent = await cg.get_variable(config[CONF_HTTP_REQUEST_ID])
|
||||
cg.add(var.set_request_parent(request_parent))
|
||||
|
||||
cg.add(var.set_source_url(config[CONF_SOURCE]))
|
||||
|
||||
cg.add_define("USE_OTA_STATE_CALLBACK")
|
||||
|
||||
await cg.register_component(var, config)
|
157
esphome/components/http_request/update/http_request_update.cpp
Normal file
157
esphome/components/http_request/update/http_request_update.cpp
Normal file
|
@ -0,0 +1,157 @@
|
|||
#include "http_request_update.h"
|
||||
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/version.h"
|
||||
|
||||
#include "esphome/components/json/json_util.h"
|
||||
#include "esphome/components/network/util.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace http_request {
|
||||
|
||||
static const char *const TAG = "http_request.update";
|
||||
|
||||
static const size_t MAX_READ_SIZE = 256;
|
||||
|
||||
void HttpRequestUpdate::setup() {
|
||||
this->ota_parent_->add_on_state_callback([this](ota::OTAState state, float progress, uint8_t err) {
|
||||
if (state == ota::OTAState::OTA_IN_PROGRESS) {
|
||||
this->state_ = update::UPDATE_STATE_INSTALLING;
|
||||
this->update_info_.has_progress = true;
|
||||
this->update_info_.progress = progress;
|
||||
this->publish_state();
|
||||
} else if (state == ota::OTAState::OTA_ABORT || state == ota::OTAState::OTA_ERROR) {
|
||||
this->state_ = update::UPDATE_STATE_AVAILABLE;
|
||||
this->status_set_error("Failed to install firmware");
|
||||
this->publish_state();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void HttpRequestUpdate::update() {
|
||||
auto container = this->request_parent_->get(this->source_url_);
|
||||
|
||||
if (container == nullptr) {
|
||||
std::string msg = str_sprintf("Failed to fetch manifest from %s", this->source_url_.c_str());
|
||||
this->status_set_error(msg.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
ExternalRAMAllocator<uint8_t> allocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE);
|
||||
uint8_t *data = allocator.allocate(container->content_length);
|
||||
if (data == nullptr) {
|
||||
std::string msg = str_sprintf("Failed to allocate %d bytes for manifest", container->content_length);
|
||||
this->status_set_error(msg.c_str());
|
||||
container->end();
|
||||
return;
|
||||
}
|
||||
|
||||
size_t read_index = 0;
|
||||
while (container->get_bytes_read() < container->content_length) {
|
||||
int read_bytes = container->read(data + read_index, MAX_READ_SIZE);
|
||||
|
||||
App.feed_wdt();
|
||||
yield();
|
||||
|
||||
read_index += read_bytes;
|
||||
}
|
||||
|
||||
std::string response((char *) data, read_index);
|
||||
allocator.deallocate(data, container->content_length);
|
||||
|
||||
container->end();
|
||||
|
||||
bool valid = json::parse_json(response, [this](JsonObject root) -> bool {
|
||||
if (!root.containsKey("name") || !root.containsKey("version") || !root.containsKey("builds")) {
|
||||
ESP_LOGE(TAG, "Manifest does not contain required fields");
|
||||
return false;
|
||||
}
|
||||
this->update_info_.title = root["name"].as<std::string>();
|
||||
this->update_info_.latest_version = root["version"].as<std::string>();
|
||||
|
||||
for (auto build : root["builds"].as<JsonArray>()) {
|
||||
if (!build.containsKey("chipFamily")) {
|
||||
ESP_LOGE(TAG, "Manifest does not contain required fields");
|
||||
return false;
|
||||
}
|
||||
if (build["chipFamily"] == ESPHOME_VARIANT) {
|
||||
if (!build.containsKey("ota")) {
|
||||
ESP_LOGE(TAG, "Manifest does not contain required fields");
|
||||
return false;
|
||||
}
|
||||
auto ota = build["ota"];
|
||||
if (!ota.containsKey("path") || !ota.containsKey("md5")) {
|
||||
ESP_LOGE(TAG, "Manifest does not contain required fields");
|
||||
return false;
|
||||
}
|
||||
this->update_info_.firmware_url = ota["path"].as<std::string>();
|
||||
this->update_info_.md5 = ota["md5"].as<std::string>();
|
||||
|
||||
if (ota.containsKey("summary"))
|
||||
this->update_info_.summary = ota["summary"].as<std::string>();
|
||||
if (ota.containsKey("release_url"))
|
||||
this->update_info_.release_url = ota["release_url"].as<std::string>();
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
if (!valid) {
|
||||
std::string msg = str_sprintf("Failed to parse JSON from %s", this->source_url_.c_str());
|
||||
this->status_set_error(msg.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
// Merge source_url_ and this->update_info_.firmware_url
|
||||
if (this->update_info_.firmware_url.find("http") == std::string::npos) {
|
||||
std::string path = this->update_info_.firmware_url;
|
||||
if (path[0] == '/') {
|
||||
std::string domain = this->source_url_.substr(0, this->source_url_.find('/', 8));
|
||||
this->update_info_.firmware_url = domain + path;
|
||||
} else {
|
||||
std::string domain = this->source_url_.substr(0, this->source_url_.rfind('/') + 1);
|
||||
this->update_info_.firmware_url = domain + path;
|
||||
}
|
||||
}
|
||||
|
||||
std::string current_version = this->current_version_;
|
||||
if (current_version.empty()) {
|
||||
#ifdef ESPHOME_PROJECT_VERSION
|
||||
current_version = ESPHOME_PROJECT_VERSION;
|
||||
#else
|
||||
current_version = ESPHOME_VERSION;
|
||||
#endif
|
||||
}
|
||||
this->update_info_.current_version = current_version;
|
||||
|
||||
if (this->update_info_.latest_version.empty()) {
|
||||
this->state_ = update::UPDATE_STATE_NO_UPDATE;
|
||||
} else if (this->update_info_.latest_version != this->current_version_) {
|
||||
this->state_ = update::UPDATE_STATE_AVAILABLE;
|
||||
}
|
||||
|
||||
this->update_info_.has_progress = false;
|
||||
this->update_info_.progress = 0.0f;
|
||||
|
||||
this->status_clear_error();
|
||||
this->publish_state();
|
||||
}
|
||||
|
||||
void HttpRequestUpdate::perform() {
|
||||
if (this->state_ != update::UPDATE_STATE_AVAILABLE) {
|
||||
return;
|
||||
}
|
||||
|
||||
this->state_ = update::UPDATE_STATE_INSTALLING;
|
||||
this->publish_state();
|
||||
|
||||
this->ota_parent_->set_md5(this->update_info.md5);
|
||||
this->ota_parent_->set_url(this->update_info.firmware_url);
|
||||
// Flash in the next loop
|
||||
this->defer([this]() { this->ota_parent_->flash(); });
|
||||
}
|
||||
|
||||
} // namespace http_request
|
||||
} // namespace esphome
|
37
esphome/components/http_request/update/http_request_update.h
Normal file
37
esphome/components/http_request/update/http_request_update.h
Normal file
|
@ -0,0 +1,37 @@
|
|||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
#include "esphome/components/http_request/http_request.h"
|
||||
#include "esphome/components/http_request/ota/ota_http_request.h"
|
||||
#include "esphome/components/update/update_entity.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace http_request {
|
||||
|
||||
class HttpRequestUpdate : public update::UpdateEntity, public PollingComponent {
|
||||
public:
|
||||
void setup() override;
|
||||
void update() override;
|
||||
|
||||
void perform() override;
|
||||
|
||||
void set_source_url(const std::string &source_url) { this->source_url_ = source_url; }
|
||||
|
||||
void set_request_parent(HttpRequestComponent *request_parent) { this->request_parent_ = request_parent; }
|
||||
void set_ota_parent(OtaHttpRequestComponent *ota_parent) { this->ota_parent_ = ota_parent; }
|
||||
|
||||
void set_current_version(const std::string ¤t_version) { this->current_version_ = current_version; }
|
||||
|
||||
float get_setup_priority() const override { return setup_priority::AFTER_WIFI; }
|
||||
|
||||
protected:
|
||||
HttpRequestComponent *request_parent_;
|
||||
OtaHttpRequestComponent *ota_parent_;
|
||||
std::string source_url_;
|
||||
std::string current_version_{""};
|
||||
};
|
||||
|
||||
} // namespace http_request
|
||||
} // namespace esphome
|
76
esphome/components/http_request/watchdog.cpp
Normal file
76
esphome/components/http_request/watchdog.cpp
Normal file
|
@ -0,0 +1,76 @@
|
|||
#include "watchdog.h"
|
||||
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#include <cinttypes>
|
||||
#include <cstdint>
|
||||
#ifdef USE_ESP32
|
||||
#include "esp_idf_version.h"
|
||||
#include "esp_task_wdt.h"
|
||||
#endif
|
||||
#ifdef USE_RP2040
|
||||
#include "hardware/watchdog.h"
|
||||
#include "pico/stdlib.h"
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
namespace http_request {
|
||||
namespace watchdog {
|
||||
|
||||
static const char *const TAG = "http_request.watchdog";
|
||||
|
||||
WatchdogManager::WatchdogManager(uint32_t timeout_ms) : timeout_ms_(timeout_ms) {
|
||||
if (timeout_ms == 0) {
|
||||
return;
|
||||
}
|
||||
this->saved_timeout_ms_ = this->get_timeout_();
|
||||
this->set_timeout_(timeout_ms);
|
||||
}
|
||||
|
||||
WatchdogManager::~WatchdogManager() {
|
||||
if (this->timeout_ms_ == 0) {
|
||||
return;
|
||||
}
|
||||
this->set_timeout_(this->saved_timeout_ms_);
|
||||
}
|
||||
|
||||
void WatchdogManager::set_timeout_(uint32_t timeout_ms) {
|
||||
ESP_LOGV(TAG, "Adjusting WDT to %" PRIu32 "ms", timeout_ms);
|
||||
#ifdef USE_ESP32
|
||||
#if ESP_IDF_VERSION_MAJOR >= 5
|
||||
esp_task_wdt_config_t wdt_config = {
|
||||
.timeout_ms = timeout_ms,
|
||||
.idle_core_mask = 0x03,
|
||||
.trigger_panic = true,
|
||||
};
|
||||
esp_task_wdt_reconfigure(&wdt_config);
|
||||
#else
|
||||
esp_task_wdt_init(timeout_ms / 1000, true);
|
||||
#endif // ESP_IDF_VERSION_MAJOR
|
||||
#endif // USE_ESP32
|
||||
|
||||
#ifdef USE_RP2040
|
||||
watchdog_enable(timeout_ms, true);
|
||||
#endif
|
||||
}
|
||||
|
||||
uint32_t WatchdogManager::get_timeout_() {
|
||||
uint32_t timeout_ms = 0;
|
||||
|
||||
#ifdef USE_ESP32
|
||||
timeout_ms = (uint32_t) CONFIG_ESP_TASK_WDT_TIMEOUT_S * 1000;
|
||||
#endif // USE_ESP32
|
||||
|
||||
#ifdef USE_RP2040
|
||||
timeout_ms = watchdog_get_count() / 1000;
|
||||
#endif
|
||||
|
||||
ESP_LOGVV(TAG, "get_timeout: %" PRIu32 "ms", timeout_ms);
|
||||
|
||||
return timeout_ms;
|
||||
}
|
||||
|
||||
} // namespace watchdog
|
||||
} // namespace http_request
|
||||
} // namespace esphome
|
26
esphome/components/http_request/watchdog.h
Normal file
26
esphome/components/http_request/watchdog.h
Normal file
|
@ -0,0 +1,26 @@
|
|||
#pragma once
|
||||
|
||||
#include "esphome/core/defines.h"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace esphome {
|
||||
namespace http_request {
|
||||
namespace watchdog {
|
||||
|
||||
class WatchdogManager {
|
||||
public:
|
||||
WatchdogManager(uint32_t timeout_ms);
|
||||
~WatchdogManager();
|
||||
|
||||
private:
|
||||
uint32_t get_timeout_();
|
||||
void set_timeout_(uint32_t timeout_ms);
|
||||
|
||||
uint32_t saved_timeout_ms_{0};
|
||||
uint32_t timeout_ms_{0};
|
||||
};
|
||||
|
||||
} // namespace watchdog
|
||||
} // namespace http_request
|
||||
} // namespace esphome
|
|
@ -12,6 +12,8 @@
|
|||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#include <cinttypes>
|
||||
|
||||
namespace esphome {
|
||||
namespace htu31d {
|
||||
|
||||
|
@ -204,7 +206,7 @@ uint32_t HTU31DComponent::read_serial_num_() {
|
|||
return 0;
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, "Found serial: 0x%X", serial);
|
||||
ESP_LOGD(TAG, "Found serial: 0x%" PRIX32, serial);
|
||||
|
||||
return serial;
|
||||
}
|
||||
|
|
|
@ -12,13 +12,13 @@ from esphome.const import (
|
|||
STATE_CLASS_MEASUREMENT,
|
||||
STATE_CLASS_TOTAL_INCREASING,
|
||||
UNIT_CELSIUS,
|
||||
UNIT_MILLIMETER,
|
||||
ICON_THERMOMETER,
|
||||
)
|
||||
|
||||
from . import RGModel, RG15Resolution, HydreonRGxxComponent
|
||||
|
||||
UNIT_INTENSITY = "intensity"
|
||||
UNIT_MILLIMETERS = "mm"
|
||||
UNIT_MILLIMETERS_PER_HOUR = "mm/h"
|
||||
|
||||
CONF_ACC = "acc"
|
||||
|
@ -85,19 +85,19 @@ CONFIG_SCHEMA = cv.All(
|
|||
),
|
||||
cv.Optional(CONF_RESOLUTION): cv.enum(RG15_RESOLUTION, upper=False),
|
||||
cv.Optional(CONF_ACC): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_MILLIMETERS,
|
||||
unit_of_measurement=UNIT_MILLIMETER,
|
||||
accuracy_decimals=2,
|
||||
device_class=DEVICE_CLASS_PRECIPITATION,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_EVENT_ACC): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_MILLIMETERS,
|
||||
unit_of_measurement=UNIT_MILLIMETER,
|
||||
accuracy_decimals=2,
|
||||
device_class=DEVICE_CLASS_PRECIPITATION,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_TOTAL_ACC): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_MILLIMETERS,
|
||||
unit_of_measurement=UNIT_MILLIMETER,
|
||||
accuracy_decimals=2,
|
||||
device_class=DEVICE_CLASS_PRECIPITATION,
|
||||
state_class=STATE_CLASS_TOTAL_INCREASING,
|
||||
|
|
|
@ -9,6 +9,10 @@ namespace i2s_audio {
|
|||
|
||||
static const char *const TAG = "i2s_audio";
|
||||
|
||||
#if defined(USE_ESP_IDF) && (ESP_IDF_VERSION_MAJOR >= 5)
|
||||
static const uint8_t I2S_NUM_MAX = SOC_I2S_NUM; // because IDF 5+ took this away :(
|
||||
#endif
|
||||
|
||||
void I2SAudioComponent::setup() {
|
||||
static i2s_port_t next_port_num = I2S_NUM_0;
|
||||
|
||||
|
|
|
@ -27,43 +27,24 @@ void I2SAudioMediaPlayer::control(const media_player::MediaPlayerCall &call) {
|
|||
this->start();
|
||||
}
|
||||
}
|
||||
|
||||
if (play_state == media_player::MEDIA_PLAYER_STATE_ANNOUNCING) {
|
||||
this->is_announcement_ = true;
|
||||
}
|
||||
|
||||
if (call.get_volume().has_value()) {
|
||||
this->volume = call.get_volume().value();
|
||||
this->set_volume_(volume);
|
||||
this->unmute_();
|
||||
}
|
||||
if (this->i2s_state_ != I2S_STATE_RUNNING) {
|
||||
return;
|
||||
}
|
||||
if (call.get_command().has_value()) {
|
||||
switch (call.get_command().value()) {
|
||||
case media_player::MEDIA_PLAYER_COMMAND_PLAY:
|
||||
if (!this->audio_->isRunning())
|
||||
this->audio_->pauseResume();
|
||||
this->state = play_state;
|
||||
break;
|
||||
case media_player::MEDIA_PLAYER_COMMAND_PAUSE:
|
||||
if (this->audio_->isRunning())
|
||||
this->audio_->pauseResume();
|
||||
this->state = media_player::MEDIA_PLAYER_STATE_PAUSED;
|
||||
break;
|
||||
case media_player::MEDIA_PLAYER_COMMAND_STOP:
|
||||
this->stop();
|
||||
break;
|
||||
case media_player::MEDIA_PLAYER_COMMAND_MUTE:
|
||||
this->mute_();
|
||||
break;
|
||||
case media_player::MEDIA_PLAYER_COMMAND_UNMUTE:
|
||||
this->unmute_();
|
||||
break;
|
||||
case media_player::MEDIA_PLAYER_COMMAND_TOGGLE:
|
||||
this->audio_->pauseResume();
|
||||
if (this->audio_->isRunning()) {
|
||||
this->state = media_player::MEDIA_PLAYER_STATE_PLAYING;
|
||||
} else {
|
||||
this->state = media_player::MEDIA_PLAYER_STATE_PAUSED;
|
||||
}
|
||||
break;
|
||||
case media_player::MEDIA_PLAYER_COMMAND_VOLUME_UP: {
|
||||
float new_volume = this->volume + 0.1f;
|
||||
if (new_volume > 1.0f)
|
||||
|
@ -80,6 +61,36 @@ void I2SAudioMediaPlayer::control(const media_player::MediaPlayerCall &call) {
|
|||
this->unmute_();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (this->i2s_state_ != I2S_STATE_RUNNING) {
|
||||
return;
|
||||
}
|
||||
switch (call.get_command().value()) {
|
||||
case media_player::MEDIA_PLAYER_COMMAND_PLAY:
|
||||
if (!this->audio_->isRunning())
|
||||
this->audio_->pauseResume();
|
||||
this->state = play_state;
|
||||
break;
|
||||
case media_player::MEDIA_PLAYER_COMMAND_PAUSE:
|
||||
if (this->audio_->isRunning())
|
||||
this->audio_->pauseResume();
|
||||
this->state = media_player::MEDIA_PLAYER_STATE_PAUSED;
|
||||
break;
|
||||
case media_player::MEDIA_PLAYER_COMMAND_STOP:
|
||||
this->stop();
|
||||
break;
|
||||
case media_player::MEDIA_PLAYER_COMMAND_TOGGLE:
|
||||
this->audio_->pauseResume();
|
||||
if (this->audio_->isRunning()) {
|
||||
this->state = media_player::MEDIA_PLAYER_STATE_PLAYING;
|
||||
} else {
|
||||
this->state = media_player::MEDIA_PLAYER_STATE_PAUSED;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
this->publish_state();
|
||||
|
@ -171,9 +182,8 @@ void I2SAudioMediaPlayer::start_() {
|
|||
if (this->current_url_.has_value()) {
|
||||
this->audio_->connecttohost(this->current_url_.value().c_str());
|
||||
this->state = media_player::MEDIA_PLAYER_STATE_PLAYING;
|
||||
if (this->is_announcement_.has_value()) {
|
||||
this->state = this->is_announcement_.value() ? media_player::MEDIA_PLAYER_STATE_ANNOUNCING
|
||||
: media_player::MEDIA_PLAYER_STATE_PLAYING;
|
||||
if (this->is_announcement_) {
|
||||
this->state = media_player::MEDIA_PLAYER_STATE_ANNOUNCING;
|
||||
}
|
||||
this->publish_state();
|
||||
}
|
||||
|
@ -202,6 +212,7 @@ void I2SAudioMediaPlayer::stop_() {
|
|||
this->high_freq_.stop();
|
||||
this->state = media_player::MEDIA_PLAYER_STATE_IDLE;
|
||||
this->publish_state();
|
||||
this->is_announcement_ = false;
|
||||
}
|
||||
|
||||
media_player::MediaPlayerTraits I2SAudioMediaPlayer::get_traits() {
|
||||
|
|
|
@ -78,7 +78,7 @@ class I2SAudioMediaPlayer : public Component, public media_player::MediaPlayer,
|
|||
HighFrequencyLoopRequester high_freq_;
|
||||
|
||||
optional<std::string> current_url_{};
|
||||
optional<bool> is_announcement_{};
|
||||
bool is_announcement_{false};
|
||||
};
|
||||
|
||||
} // namespace i2s_audio
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue