mirror of
https://github.com/esphome/esphome.git
synced 2024-12-03 12:14:13 +01:00
Merge branch 'dev' into dev
This commit is contained in:
commit
82f2396743
1891 changed files with 4997 additions and 3352 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.0.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.0.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:
|
||||
|
|
10
.github/workflows/release.yml
vendored
10
.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:
|
||||
|
@ -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
|
||||
|
|
4
.github/workflows/sync-device-classes.yml
vendored
4
.github/workflows/sync-device-classes.yml
vendored
|
@ -13,10 +13,10 @@ jobs:
|
|||
if: github.repository == 'esphome/esphome'
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4.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
|
||||
|
|
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:
|
||||
|
|
|
@ -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
|
||||
|
@ -172,6 +174,7 @@ 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
|
||||
|
@ -269,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
|
||||
|
@ -316,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
|
||||
|
@ -410,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
|
||||
|
|
|
@ -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 \
|
||||
|
@ -101,7 +102,7 @@ RUN --mount=type=tmpfs,target=/root/.cargo if [ "$TARGETARCH$TARGETVARIANT" = "a
|
|||
&& /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 '/config/*'
|
||||
RUN git config --system --add safe.directory '*'
|
||||
|
||||
|
||||
# ======================= docker-type image =======================
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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) {}
|
||||
|
@ -1837,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;
|
||||
}
|
||||
|
|
|
@ -1287,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;
|
||||
|
|
|
@ -164,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
|
||||
|
|
|
@ -8376,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
|
||||
|
|
|
@ -2130,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
|
||||
|
|
|
@ -611,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: {
|
||||
|
@ -1106,6 +1124,17 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
|||
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;
|
||||
}
|
||||
|
@ -1434,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) {
|
||||
|
|
|
@ -306,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;
|
||||
|
@ -373,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
|
||||
|
@ -471,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;
|
||||
|
||||
|
|
|
@ -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_;
|
||||
|
|
|
@ -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}) {}
|
||||
|
|
|
@ -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]))
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -631,7 +631,7 @@ void EthernetComponent::write_phy_register_(esp_eth_mac_t *mac, PHYRegister regi
|
|||
ESPHL_ERROR_CHECK(err, "Writing PHY Register failed");
|
||||
|
||||
if (this->type_ == ETHERNET_TYPE_RTL8201 && register_data.page) {
|
||||
ESP_LOGD(TAG, "Select PHY Register Page 0x%02" PRIX32, 0x0);
|
||||
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");
|
||||
}
|
||||
|
|
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))
|
199
esphome/components/gpio/one_wire/gpio_one_wire.cpp
Normal file
199
esphome/components/gpio/one_wire/gpio_one_wire.cpp
Normal file
|
@ -0,0 +1,199 @@
|
|||
#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->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
|
|
@ -56,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);
|
||||
}
|
||||
|
@ -69,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();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -129,7 +128,7 @@ void HE60rCover::update_() {
|
|||
if (this->toggles_needed_ != 0) {
|
||||
if ((this->counter_++ & 0x3) == 0) {
|
||||
this->toggles_needed_--;
|
||||
ESP_LOGD(TAG, "Writing byte 0x30, still needed=%" PRIu32, 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);
|
||||
|
@ -235,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};
|
||||
|
|
|
@ -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 (body_len > 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
|
|
@ -2,92 +2,35 @@ import esphome.codegen as cg
|
|||
import esphome.config_validation as cv
|
||||
from esphome import automation
|
||||
from esphome.const import (
|
||||
CONF_ESP8266_DISABLE_SSL_SUPPORT,
|
||||
CONF_ID,
|
||||
CONF_PASSWORD,
|
||||
CONF_TIMEOUT,
|
||||
CONF_URL,
|
||||
CONF_USERNAME,
|
||||
)
|
||||
from esphome.components import esp32
|
||||
from esphome.components.ota import BASE_OTA_SCHEMA, ota_to_code, OTAComponent
|
||||
from esphome.core import CORE, coroutine_with_priority
|
||||
from .. import http_request_ns
|
||||
from esphome.core import coroutine_with_priority
|
||||
from .. import CONF_HTTP_REQUEST_ID, http_request_ns, HttpRequestComponent
|
||||
|
||||
CODEOWNERS = ["@oarcher"]
|
||||
|
||||
AUTO_LOAD = ["md5"]
|
||||
DEPENDENCIES = ["network"]
|
||||
DEPENDENCIES = ["network", "http_request"]
|
||||
|
||||
CONF_MD5 = "md5"
|
||||
CONF_MD5_URL = "md5_url"
|
||||
CONF_VERIFY_SSL = "verify_ssl"
|
||||
CONF_WATCHDOG_TIMEOUT = "watchdog_timeout"
|
||||
|
||||
OtaHttpRequestComponent = http_request_ns.class_(
|
||||
"OtaHttpRequestComponent", OTAComponent
|
||||
)
|
||||
OtaHttpRequestComponentArduino = http_request_ns.class_(
|
||||
"OtaHttpRequestComponentArduino", OtaHttpRequestComponent
|
||||
)
|
||||
OtaHttpRequestComponentIDF = http_request_ns.class_(
|
||||
"OtaHttpRequestComponentIDF", OtaHttpRequestComponent
|
||||
)
|
||||
OtaHttpRequestComponentFlashAction = http_request_ns.class_(
|
||||
"OtaHttpRequestComponentFlashAction", automation.Action
|
||||
)
|
||||
|
||||
|
||||
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 in Arduino"
|
||||
|
||||
if (
|
||||
CORE.is_esp8266
|
||||
and not config[CONF_ESP8266_DISABLE_SSL_SUPPORT]
|
||||
and config[CONF_VERIFY_SSL]
|
||||
):
|
||||
error_message = "ESPHome does not support certificate verification in Arduino"
|
||||
|
||||
if len(error_message) > 0:
|
||||
raise cv.Invalid(
|
||||
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(OtaHttpRequestComponentIDF)(value)
|
||||
|
||||
if CORE.is_esp8266 or CORE.is_esp32 or CORE.is_rp2040:
|
||||
return cv.declare_id(OtaHttpRequestComponentArduino)(value)
|
||||
return NotImplementedError
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): _declare_request_class,
|
||||
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_TIMEOUT, default="5min"
|
||||
): cv.positive_time_period_milliseconds,
|
||||
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,
|
||||
),
|
||||
cv.GenerateID(): cv.declare_id(OtaHttpRequestComponent),
|
||||
cv.GenerateID(CONF_HTTP_REQUEST_ID): cv.use_id(HttpRequestComponent),
|
||||
}
|
||||
)
|
||||
.extend(BASE_OTA_SCHEMA)
|
||||
|
@ -98,7 +41,6 @@ CONFIG_SCHEMA = cv.All(
|
|||
esp_idf=cv.Version(0, 0, 0),
|
||||
rp2040_arduino=cv.Version(0, 0, 0),
|
||||
),
|
||||
validate_ssl_verification,
|
||||
)
|
||||
|
||||
|
||||
|
@ -106,41 +48,8 @@ CONFIG_SCHEMA = cv.All(
|
|||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await ota_to_code(var, config)
|
||||
|
||||
cg.add(var.set_timeout(config[CONF_TIMEOUT]))
|
||||
|
||||
if timeout_ms := config.get(CONF_WATCHDOG_TIMEOUT):
|
||||
cg.add_define(
|
||||
"USE_HTTP_REQUEST_OTA_WATCHDOG_TIMEOUT",
|
||||
timeout_ms,
|
||||
)
|
||||
|
||||
if CORE.is_esp8266 and not config[CONF_ESP8266_DISABLE_SSL_SUPPORT]:
|
||||
cg.add_define("USE_HTTP_REQUEST_ESP8266_HTTPS")
|
||||
|
||||
if CORE.is_esp32:
|
||||
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)
|
||||
await cg.register_parented(var, config[CONF_HTTP_REQUEST_ID])
|
||||
|
||||
|
||||
OTA_HTTP_REQUEST_FLASH_ACTION_SCHEMA = cv.All(
|
||||
|
@ -148,7 +57,9 @@ OTA_HTTP_REQUEST_FLASH_ACTION_SCHEMA = cv.All(
|
|||
{
|
||||
cv.GenerateID(): cv.use_id(OtaHttpRequestComponent),
|
||||
cv.Optional(CONF_MD5_URL): cv.templatable(cv.url),
|
||||
cv.Optional(CONF_MD5): cv.templatable(cv.string),
|
||||
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),
|
||||
|
@ -159,7 +70,7 @@ OTA_HTTP_REQUEST_FLASH_ACTION_SCHEMA = cv.All(
|
|||
|
||||
|
||||
@automation.register_action(
|
||||
"ota_http_request.flash",
|
||||
"ota.http_request.flash",
|
||||
OtaHttpRequestComponentFlashAction,
|
||||
OTA_HTTP_REQUEST_FLASH_ACTION_SCHEMA,
|
||||
)
|
||||
|
|
|
@ -1,45 +1,29 @@
|
|||
#include "ota_http_request.h"
|
||||
#include "watchdog.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"
|
||||
#include "esphome/components/ota/ota_backend.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:");
|
||||
ESP_LOGCONFIG(TAG, " Timeout: %llus", this->timeout_ / 1000);
|
||||
#ifdef USE_ESP8266
|
||||
#ifdef USE_HTTP_REQUEST_ESP8266_HTTPS
|
||||
ESP_LOGCONFIG(TAG, " ESP8266 SSL support: No");
|
||||
#else
|
||||
ESP_LOGCONFIG(TAG, " ESP8266 SSL support: Yes");
|
||||
#endif
|
||||
#endif
|
||||
#ifdef CONFIG_MBEDTLS_CERTIFICATE_BUNDLE
|
||||
ESP_LOGCONFIG(TAG, " TLS server verification: Yes");
|
||||
#else
|
||||
ESP_LOGCONFIG(TAG, " TLS server verification: No");
|
||||
#endif
|
||||
#ifdef USE_HTTP_REQUEST_OTA_WATCHDOG_TIMEOUT
|
||||
ESP_LOGCONFIG(TAG, " Watchdog timeout: %ds", USE_HTTP_REQUEST_OTA_WATCHDOG_TIMEOUT / 1000);
|
||||
#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)) {
|
||||
|
@ -58,20 +42,6 @@ void OtaHttpRequestComponent::set_url(const std::string &url) {
|
|||
this->url_ = url;
|
||||
}
|
||||
|
||||
bool OtaHttpRequestComponent::check_status() {
|
||||
// status can be -1, or HTTP status code
|
||||
if (this->status_ < 100) {
|
||||
ESP_LOGE(TAG, "HTTP server did not respond (error %d)", this->status_);
|
||||
return false;
|
||||
}
|
||||
if (this->status_ >= 310) {
|
||||
ESP_LOGE(TAG, "HTTP error %d", this->status_);
|
||||
return false;
|
||||
}
|
||||
ESP_LOGV(TAG, "HTTP status %d", this->status_);
|
||||
return true;
|
||||
}
|
||||
|
||||
void OtaHttpRequestComponent::flash() {
|
||||
if (this->url_.empty()) {
|
||||
ESP_LOGE(TAG, "URL not set; cannot start update");
|
||||
|
@ -104,17 +74,18 @@ void OtaHttpRequestComponent::flash() {
|
|||
}
|
||||
}
|
||||
|
||||
void OtaHttpRequestComponent::cleanup_(std::unique_ptr<ota::OTABackend> backend) {
|
||||
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");
|
||||
this->http_end();
|
||||
container->end();
|
||||
};
|
||||
|
||||
uint8_t OtaHttpRequestComponent::do_ota_() {
|
||||
uint8_t buf[this->http_recv_buffer_ + 1];
|
||||
uint8_t buf[OtaHttpRequestComponent::HTTP_RECV_BUFFER + 1];
|
||||
uint32_t last_progress = 0;
|
||||
uint32_t update_start_time = millis();
|
||||
md5::MD5Digest md5_receive;
|
||||
|
@ -132,9 +103,10 @@ uint8_t OtaHttpRequestComponent::do_ota_() {
|
|||
}
|
||||
ESP_LOGVV(TAG, "url_with_auth: %s", url_with_auth.c_str());
|
||||
ESP_LOGI(TAG, "Connecting to: %s", this->url_.c_str());
|
||||
this->http_init(url_with_auth);
|
||||
if (!this->check_status()) {
|
||||
this->http_end();
|
||||
|
||||
auto container = this->parent_->get(url_with_auth);
|
||||
|
||||
if (container == nullptr) {
|
||||
return OTA_CONNECTION_ERROR;
|
||||
}
|
||||
|
||||
|
@ -144,18 +116,18 @@ uint8_t OtaHttpRequestComponent::do_ota_() {
|
|||
|
||||
ESP_LOGV(TAG, "OTA backend begin");
|
||||
auto backend = ota::make_ota_backend();
|
||||
auto error_code = backend->begin(this->body_length_);
|
||||
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));
|
||||
this->cleanup_(std::move(backend), container);
|
||||
return error_code;
|
||||
}
|
||||
|
||||
this->bytes_read_ = 0;
|
||||
while (this->bytes_read_ < this->body_length_) {
|
||||
while (container->get_bytes_read() < container->content_length) {
|
||||
// read a maximum of chunk_size bytes into buf. (real read size returned)
|
||||
int bufsize = this->http_read(buf, this->http_recv_buffer_);
|
||||
ESP_LOGVV(TAG, "bytes_read_ = %u, body_length_ = %u, bufsize = %i", this->bytes_read_, this->body_length_, bufsize);
|
||||
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();
|
||||
|
@ -163,9 +135,9 @@ uint8_t OtaHttpRequestComponent::do_ota_() {
|
|||
|
||||
if (bufsize < 0) {
|
||||
ESP_LOGE(TAG, "Stream closed");
|
||||
this->cleanup_(std::move(backend));
|
||||
this->cleanup_(std::move(backend), container);
|
||||
return OTA_CONNECTION_ERROR;
|
||||
} else if (bufsize > 0 && bufsize <= this->http_recv_buffer_) {
|
||||
} else if (bufsize > 0 && bufsize <= OtaHttpRequestComponent::HTTP_RECV_BUFFER) {
|
||||
// add read bytes to MD5
|
||||
md5_receive.add(buf, bufsize);
|
||||
|
||||
|
@ -176,16 +148,16 @@ uint8_t OtaHttpRequestComponent::do_ota_() {
|
|||
// 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,
|
||||
this->bytes_read_ - bufsize, this->body_length_);
|
||||
this->cleanup_(std::move(backend));
|
||||
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 (this->bytes_read_ == this->body_length_)) {
|
||||
if ((now - last_progress > 1000) or (container->get_bytes_read() == container->content_length)) {
|
||||
last_progress = now;
|
||||
float percentage = this->bytes_read_ * 100.0f / this->body_length_;
|
||||
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);
|
||||
|
@ -201,13 +173,13 @@ uint8_t OtaHttpRequestComponent::do_ota_() {
|
|||
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));
|
||||
this->cleanup_(std::move(backend), container);
|
||||
return ota::OTA_RESPONSE_ERROR_MD5_MISMATCH;
|
||||
} else {
|
||||
backend->set_update_md5(md5_receive_str.get());
|
||||
}
|
||||
|
||||
this->http_end();
|
||||
container->end();
|
||||
|
||||
// feed watchdog and give other tasks a chance to run
|
||||
App.feed_wdt();
|
||||
|
@ -217,7 +189,7 @@ uint8_t OtaHttpRequestComponent::do_ota_() {
|
|||
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));
|
||||
this->cleanup_(std::move(backend), container);
|
||||
return error_code;
|
||||
}
|
||||
|
||||
|
@ -256,28 +228,32 @@ bool OtaHttpRequestComponent::http_get_md5_() {
|
|||
|
||||
ESP_LOGVV(TAG, "url_with_auth: %s", url_with_auth.c_str());
|
||||
ESP_LOGI(TAG, "Connecting to: %s", this->md5_url_.c_str());
|
||||
this->http_init(url_with_auth);
|
||||
if (!this->check_status()) {
|
||||
this->http_end();
|
||||
auto container = this->parent_->get(url_with_auth);
|
||||
if (container == nullptr) {
|
||||
ESP_LOGE(TAG, "Failed to connect to MD5 URL");
|
||||
return false;
|
||||
}
|
||||
int length = this->body_length_;
|
||||
if (length < 0) {
|
||||
this->http_end();
|
||||
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,
|
||||
this->body_length_);
|
||||
this->http_end();
|
||||
ESP_LOGE(TAG, "MD5 file must be %u bytes; %u bytes reported by HTTP server. Aborting", MD5_SIZE, length);
|
||||
container->end();
|
||||
return false;
|
||||
}
|
||||
|
||||
this->bytes_read_ = 0;
|
||||
this->md5_expected_.resize(MD5_SIZE);
|
||||
auto read_len = this->http_read((uint8_t *) this->md5_expected_.data(), MD5_SIZE);
|
||||
this->http_end();
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,17 +1,19 @@
|
|||
#pragma once
|
||||
|
||||
#include "esphome/components/ota/ota_backend.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/components/ota/ota_backend.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "../http_request.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace http_request {
|
||||
|
||||
static const char *const TAG = "http_request.ota";
|
||||
static const uint8_t MD5_SIZE = 32;
|
||||
|
||||
enum OtaHttpRequestError : uint8_t {
|
||||
|
@ -20,7 +22,7 @@ enum OtaHttpRequestError : uint8_t {
|
|||
OTA_CONNECTION_ERROR = 0x12,
|
||||
};
|
||||
|
||||
class OtaHttpRequestComponent : public ota::OTAComponent {
|
||||
class OtaHttpRequestComponent : public ota::OTAComponent, public Parented<HttpRequestComponent> {
|
||||
public:
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
|
@ -29,27 +31,19 @@ class OtaHttpRequestComponent : public ota::OTAComponent {
|
|||
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_timeout(const uint64_t timeout) { this->timeout_ = timeout; }
|
||||
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_; }
|
||||
|
||||
bool check_status();
|
||||
|
||||
void flash();
|
||||
|
||||
virtual void http_init(const std::string &url){};
|
||||
virtual int http_read(uint8_t *buf, size_t len) { return 0; };
|
||||
virtual void http_end(){};
|
||||
|
||||
protected:
|
||||
void cleanup_(std::unique_ptr<ota::OTABackend> backend);
|
||||
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 secure_() { return this->url_.find("https:") != std::string::npos; };
|
||||
bool validate_url_(const std::string &url);
|
||||
|
||||
std::string md5_computed_{};
|
||||
|
@ -58,14 +52,9 @@ class OtaHttpRequestComponent : public ota::OTAComponent {
|
|||
std::string password_{};
|
||||
std::string username_{};
|
||||
std::string url_{};
|
||||
size_t body_length_ = 0;
|
||||
size_t bytes_read_ = 0;
|
||||
int status_ = -1;
|
||||
uint64_t timeout_ = 0;
|
||||
bool update_started_ = false;
|
||||
const uint16_t http_recv_buffer_ = 256; // the firmware GET chunk size
|
||||
const uint16_t max_http_recv_buffer_ = 512; // internal max http buffer size must be > HTTP_RECV_BUFFER_ (TLS
|
||||
// overhead) and must be a power of two from 512 to 4096
|
||||
static const uint16_t HTTP_RECV_BUFFER = 256; // the firmware GET chunk size
|
||||
};
|
||||
|
||||
} // namespace http_request
|
||||
|
|
|
@ -1,134 +0,0 @@
|
|||
#include "ota_http_request.h"
|
||||
#include "watchdog.h"
|
||||
|
||||
#ifdef USE_ARDUINO
|
||||
#include "ota_http_request_arduino.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/components/network/util.h"
|
||||
#include "esphome/components/md5/md5.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace http_request {
|
||||
|
||||
struct Header {
|
||||
const char *name;
|
||||
const char *value;
|
||||
};
|
||||
|
||||
void OtaHttpRequestComponentArduino::http_init(const std::string &url) {
|
||||
const char *header_keys[] = {"Content-Length", "Content-Type"};
|
||||
const size_t header_count = sizeof(header_keys) / sizeof(header_keys[0]);
|
||||
watchdog::WatchdogManager wdts;
|
||||
|
||||
#ifdef USE_ESP8266
|
||||
if (this->stream_ptr_ == nullptr && this->set_stream_ptr_()) {
|
||||
ESP_LOGE(TAG, "Unable to set client");
|
||||
return;
|
||||
}
|
||||
#endif // USE_ESP8266
|
||||
|
||||
#ifdef USE_RP2040
|
||||
this->client_.setInsecure();
|
||||
#endif
|
||||
|
||||
App.feed_wdt();
|
||||
|
||||
#if defined(USE_ESP32) || defined(USE_RP2040)
|
||||
this->status_ = this->client_.begin(url.c_str());
|
||||
#endif
|
||||
#ifdef USE_ESP8266
|
||||
this->client_.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
|
||||
this->status_ = this->client_.begin(*this->stream_ptr_, url.c_str());
|
||||
#endif
|
||||
|
||||
if (!this->status_) {
|
||||
this->client_.end();
|
||||
return;
|
||||
}
|
||||
|
||||
this->client_.setReuse(true);
|
||||
|
||||
// returned needed headers must be collected before the requests
|
||||
this->client_.collectHeaders(header_keys, header_count);
|
||||
|
||||
// HTTP GET
|
||||
this->status_ = this->client_.GET();
|
||||
|
||||
this->body_length_ = (size_t) this->client_.getSize();
|
||||
|
||||
#if defined(USE_ESP32) || defined(USE_RP2040)
|
||||
if (this->stream_ptr_ == nullptr) {
|
||||
this->set_stream_ptr_();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
int OtaHttpRequestComponentArduino::http_read(uint8_t *buf, const size_t max_len) {
|
||||
#ifdef USE_ESP8266
|
||||
#if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(3, 1, 0) // && USE_ARDUINO_VERSION_CODE < VERSION_CODE(?, ?, ?)
|
||||
if (!this->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
|
||||
#endif // USE_ESP8266
|
||||
|
||||
watchdog::WatchdogManager wdts;
|
||||
|
||||
// Since arduino8266 >= 3.1 using this->stream_ptr_ is broken (https://github.com/esp8266/Arduino/issues/9035)
|
||||
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((int) max_len, available_data);
|
||||
if (bufsize > 0) {
|
||||
stream_ptr->readBytes(buf, bufsize);
|
||||
this->bytes_read_ += bufsize;
|
||||
buf[bufsize] = '\0'; // not fed to ota
|
||||
}
|
||||
|
||||
return bufsize;
|
||||
}
|
||||
|
||||
void OtaHttpRequestComponentArduino::http_end() {
|
||||
watchdog::WatchdogManager wdts;
|
||||
this->client_.end();
|
||||
}
|
||||
|
||||
int OtaHttpRequestComponentArduino::set_stream_ptr_() {
|
||||
#ifdef USE_ESP8266
|
||||
#ifdef USE_HTTP_REQUEST_ESP8266_HTTPS
|
||||
if (this->secure_()) {
|
||||
ESP_LOGV(TAG, "ESP8266 HTTPS connection with WiFiClientSecure");
|
||||
this->stream_ptr_ = std::make_unique<WiFiClientSecure>();
|
||||
WiFiClientSecure *secure_client = static_cast<WiFiClientSecure *>(this->stream_ptr_.get());
|
||||
secure_client->setBufferSizes(this->max_http_recv_buffer_, 512);
|
||||
secure_client->setInsecure();
|
||||
} else {
|
||||
this->stream_ptr_ = std::make_unique<WiFiClient>();
|
||||
}
|
||||
#else
|
||||
ESP_LOGV(TAG, "ESP8266 HTTP connection with WiFiClient");
|
||||
if (this->secure_()) {
|
||||
ESP_LOGE(TAG, "Can't use HTTPS connection with esp8266_disable_ssl_support");
|
||||
return -1;
|
||||
}
|
||||
this->stream_ptr_ = std::make_unique<WiFiClient>();
|
||||
#endif // USE_HTTP_REQUEST_ESP8266_HTTPS
|
||||
#endif // USE_ESP8266
|
||||
|
||||
#if defined(USE_ESP32) || defined(USE_RP2040)
|
||||
this->stream_ptr_ = std::unique_ptr<WiFiClient>(this->client_.getStreamPtr());
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace http_request
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ARDUINO
|
|
@ -1,42 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "ota_http_request.h"
|
||||
|
||||
#ifdef USE_ARDUINO
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/defines.h"
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#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 OtaHttpRequestComponentArduino : public OtaHttpRequestComponent {
|
||||
public:
|
||||
void http_init(const std::string &url) override;
|
||||
int http_read(uint8_t *buf, size_t len) override;
|
||||
void http_end() override;
|
||||
|
||||
protected:
|
||||
int set_stream_ptr_();
|
||||
HTTPClient client_{};
|
||||
std::unique_ptr<WiFiClient> stream_ptr_;
|
||||
};
|
||||
|
||||
} // namespace http_request
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ARDUINO
|
|
@ -1,86 +0,0 @@
|
|||
#include "ota_http_request_idf.h"
|
||||
#include "watchdog.h"
|
||||
|
||||
#ifdef USE_ESP_IDF
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/components/md5/md5.h"
|
||||
#include "esphome/components/network/util.h"
|
||||
|
||||
#include "esp_event.h"
|
||||
#include "esp_http_client.h"
|
||||
#include "esp_idf_version.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_netif.h"
|
||||
#include "esp_system.h"
|
||||
#include "esp_task_wdt.h"
|
||||
#include "esp_tls.h"
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "nvs_flash.h"
|
||||
|
||||
#include <cctype>
|
||||
#include <cinttypes>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <sys/param.h>
|
||||
#if CONFIG_MBEDTLS_CERTIFICATE_BUNDLE
|
||||
#include "esp_crt_bundle.h"
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
namespace http_request {
|
||||
|
||||
void OtaHttpRequestComponentIDF::http_init(const std::string &url) {
|
||||
App.feed_wdt();
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wmissing-field-initializers"
|
||||
esp_http_client_config_t config = {nullptr};
|
||||
config.url = url.c_str();
|
||||
config.method = HTTP_METHOD_GET;
|
||||
config.timeout_ms = (int) this->timeout_;
|
||||
config.buffer_size = this->max_http_recv_buffer_;
|
||||
config.auth_type = HTTP_AUTH_TYPE_BASIC;
|
||||
config.max_authorization_retries = -1;
|
||||
#if CONFIG_MBEDTLS_CERTIFICATE_BUNDLE
|
||||
if (this->secure_()) {
|
||||
config.crt_bundle_attach = esp_crt_bundle_attach;
|
||||
}
|
||||
#endif
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
watchdog::WatchdogManager wdts;
|
||||
this->client_ = esp_http_client_init(&config);
|
||||
if ((this->status_ = esp_http_client_open(this->client_, 0)) == ESP_OK) {
|
||||
this->body_length_ = esp_http_client_fetch_headers(this->client_);
|
||||
this->status_ = esp_http_client_get_status_code(this->client_);
|
||||
}
|
||||
}
|
||||
|
||||
int OtaHttpRequestComponentIDF::http_read(uint8_t *buf, const size_t max_len) {
|
||||
watchdog::WatchdogManager wdts;
|
||||
int bufsize = std::min(max_len, this->body_length_ - this->bytes_read_);
|
||||
|
||||
App.feed_wdt();
|
||||
int read_len = esp_http_client_read(this->client_, (char *) buf, bufsize);
|
||||
if (read_len > 0) {
|
||||
this->bytes_read_ += bufsize;
|
||||
buf[bufsize] = '\0'; // not fed to ota
|
||||
}
|
||||
|
||||
return read_len;
|
||||
}
|
||||
|
||||
void OtaHttpRequestComponentIDF::http_end() {
|
||||
watchdog::WatchdogManager wdts;
|
||||
|
||||
esp_http_client_close(this->client_);
|
||||
esp_http_client_cleanup(this->client_);
|
||||
}
|
||||
|
||||
} // namespace http_request
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ESP_IDF
|
|
@ -1,24 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "ota_http_request.h"
|
||||
|
||||
#ifdef USE_ESP_IDF
|
||||
#include "esp_http_client.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace http_request {
|
||||
|
||||
class OtaHttpRequestComponentIDF : public OtaHttpRequestComponent {
|
||||
public:
|
||||
void http_init(const std::string &url) override;
|
||||
int http_read(uint8_t *buf, size_t len) override;
|
||||
void http_end() override;
|
||||
|
||||
protected:
|
||||
esp_http_client_handle_t client_{};
|
||||
};
|
||||
|
||||
} // namespace http_request
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ESP_IDF
|
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
|
|
@ -1,7 +1,5 @@
|
|||
#include "watchdog.h"
|
||||
|
||||
#ifdef USE_HTTP_REQUEST_OTA_WATCHDOG_TIMEOUT
|
||||
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
|
@ -20,14 +18,22 @@ namespace esphome {
|
|||
namespace http_request {
|
||||
namespace watchdog {
|
||||
|
||||
static const char *const TAG = "watchdog.http_request.ota";
|
||||
static const char *const TAG = "http_request.watchdog";
|
||||
|
||||
WatchdogManager::WatchdogManager() {
|
||||
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_(USE_HTTP_REQUEST_OTA_WATCHDOG_TIMEOUT);
|
||||
this->set_timeout_(timeout_ms);
|
||||
}
|
||||
|
||||
WatchdogManager::~WatchdogManager() { this->set_timeout_(this->saved_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);
|
||||
|
@ -68,4 +74,3 @@ uint32_t WatchdogManager::get_timeout_() {
|
|||
} // namespace watchdog
|
||||
} // namespace http_request
|
||||
} // namespace esphome
|
||||
#endif
|
|
@ -9,9 +9,8 @@ namespace http_request {
|
|||
namespace watchdog {
|
||||
|
||||
class WatchdogManager {
|
||||
#ifdef USE_HTTP_REQUEST_OTA_WATCHDOG_TIMEOUT
|
||||
public:
|
||||
WatchdogManager();
|
||||
WatchdogManager(uint32_t timeout_ms);
|
||||
~WatchdogManager();
|
||||
|
||||
private:
|
||||
|
@ -19,7 +18,7 @@ class WatchdogManager {
|
|||
void set_timeout_(uint32_t timeout_ms);
|
||||
|
||||
uint32_t saved_timeout_ms_{0};
|
||||
#endif
|
||||
uint32_t timeout_ms_{0};
|
||||
};
|
||||
|
||||
} // namespace watchdog
|
|
@ -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
|
||||
|
|
|
@ -38,15 +38,22 @@ void I2SAudioSpeaker::start() {
|
|||
ESP_LOGE(TAG, "Cannot start audio, speaker failed to setup");
|
||||
return;
|
||||
}
|
||||
if (this->task_created_) {
|
||||
ESP_LOGW(TAG, "Called start while task has been already created.");
|
||||
return;
|
||||
}
|
||||
this->state_ = speaker::STATE_STARTING;
|
||||
}
|
||||
void I2SAudioSpeaker::start_() {
|
||||
if (this->task_created_) {
|
||||
return;
|
||||
}
|
||||
if (!this->parent_->try_lock()) {
|
||||
return; // Waiting for another i2s component to return lock
|
||||
}
|
||||
this->state_ = speaker::STATE_RUNNING;
|
||||
|
||||
xTaskCreate(I2SAudioSpeaker::player_task, "speaker_task", 8192, (void *) this, 1, &this->player_task_handle_);
|
||||
this->task_created_ = true;
|
||||
}
|
||||
|
||||
void I2SAudioSpeaker::player_task(void *params) {
|
||||
|
@ -131,7 +138,16 @@ void I2SAudioSpeaker::player_task(void *params) {
|
|||
(10 / portTICK_PERIOD_MS));
|
||||
if (err != ESP_OK) {
|
||||
event = {.type = TaskEventType::WARNING, .err = err};
|
||||
xQueueSend(this_speaker->event_queue_, &event, portMAX_DELAY);
|
||||
if (xQueueSend(this_speaker->event_queue_, &event, 10 / portTICK_PERIOD_MS) != pdTRUE) {
|
||||
ESP_LOGW(TAG, "Failed to send WARNING event");
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (bytes_written != sizeof(sample)) {
|
||||
event = {.type = TaskEventType::WARNING, .err = ESP_FAIL};
|
||||
if (xQueueSend(this_speaker->event_queue_, &event, 10 / portTICK_PERIOD_MS) != pdTRUE) {
|
||||
ESP_LOGW(TAG, "Failed to send WARNING event");
|
||||
}
|
||||
continue;
|
||||
}
|
||||
remaining--;
|
||||
|
@ -139,18 +155,25 @@ void I2SAudioSpeaker::player_task(void *params) {
|
|||
}
|
||||
|
||||
event.type = TaskEventType::PLAYING;
|
||||
xQueueSend(this_speaker->event_queue_, &event, portMAX_DELAY);
|
||||
event.err = current;
|
||||
if (xQueueSend(this_speaker->event_queue_, &event, 10 / portTICK_PERIOD_MS) != pdTRUE) {
|
||||
ESP_LOGW(TAG, "Failed to send PLAYING event");
|
||||
}
|
||||
}
|
||||
|
||||
event.type = TaskEventType::STOPPING;
|
||||
if (xQueueSend(this_speaker->event_queue_, &event, 10 / portTICK_PERIOD_MS) != pdTRUE) {
|
||||
ESP_LOGW(TAG, "Failed to send STOPPING event");
|
||||
}
|
||||
|
||||
i2s_zero_dma_buffer(this_speaker->parent_->get_port());
|
||||
|
||||
event.type = TaskEventType::STOPPING;
|
||||
xQueueSend(this_speaker->event_queue_, &event, portMAX_DELAY);
|
||||
|
||||
i2s_driver_uninstall(this_speaker->parent_->get_port());
|
||||
|
||||
event.type = TaskEventType::STOPPED;
|
||||
xQueueSend(this_speaker->event_queue_, &event, portMAX_DELAY);
|
||||
if (xQueueSend(this_speaker->event_queue_, &event, 10 / portTICK_PERIOD_MS) != pdTRUE) {
|
||||
ESP_LOGW(TAG, "Failed to send STOPPED event");
|
||||
}
|
||||
|
||||
while (true) {
|
||||
delay(10);
|
||||
|
@ -181,6 +204,7 @@ void I2SAudioSpeaker::watch_() {
|
|||
break;
|
||||
case TaskEventType::STARTED:
|
||||
ESP_LOGD(TAG, "Started I2S Audio Speaker");
|
||||
this->state_ = speaker::STATE_RUNNING;
|
||||
break;
|
||||
case TaskEventType::STOPPING:
|
||||
ESP_LOGD(TAG, "Stopping I2S Audio Speaker");
|
||||
|
@ -191,6 +215,7 @@ void I2SAudioSpeaker::watch_() {
|
|||
case TaskEventType::STOPPED:
|
||||
this->state_ = speaker::STATE_STOPPED;
|
||||
vTaskDelete(this->player_task_handle_);
|
||||
this->task_created_ = false;
|
||||
this->player_task_handle_ = nullptr;
|
||||
this->parent_->unlock();
|
||||
xQueueReset(this->buffer_queue_);
|
||||
|
@ -208,7 +233,6 @@ void I2SAudioSpeaker::loop() {
|
|||
switch (this->state_) {
|
||||
case speaker::STATE_STARTING:
|
||||
this->start_();
|
||||
break;
|
||||
case speaker::STATE_RUNNING:
|
||||
case speaker::STATE_STOPPING:
|
||||
this->watch_();
|
||||
|
|
|
@ -60,7 +60,6 @@ class I2SAudioSpeaker : public Component, public speaker::Speaker, public I2SAud
|
|||
|
||||
protected:
|
||||
void start_();
|
||||
// void stop_();
|
||||
void watch_();
|
||||
|
||||
static void player_task(void *params);
|
||||
|
@ -70,6 +69,7 @@ class I2SAudioSpeaker : public Component, public speaker::Speaker, public I2SAud
|
|||
QueueHandle_t event_queue_;
|
||||
|
||||
uint8_t dout_pin_{0};
|
||||
bool task_created_{false};
|
||||
|
||||
#if SOC_I2S_SUPPORTS_DAC
|
||||
i2s_dac_mode_t internal_dac_mode_{I2S_DAC_CHANNEL_DISABLE};
|
||||
|
|
|
@ -69,6 +69,7 @@ MODELS = {
|
|||
"ILI9486": ili9xxx_ns.class_("ILI9XXXILI9486", ILI9XXXDisplay),
|
||||
"ILI9488": ili9xxx_ns.class_("ILI9XXXILI9488", ILI9XXXDisplay),
|
||||
"ILI9488_A": ili9xxx_ns.class_("ILI9XXXILI9488A", ILI9XXXDisplay),
|
||||
"ST7735": ili9xxx_ns.class_("ILI9XXXST7735", ILI9XXXDisplay),
|
||||
"ST7796": ili9xxx_ns.class_("ILI9XXXST7796", ILI9XXXDisplay),
|
||||
"ST7789V": ili9xxx_ns.class_("ILI9XXXST7789V", ILI9XXXDisplay),
|
||||
"S3BOX": ili9xxx_ns.class_("ILI9XXXS3Box", ILI9XXXDisplay),
|
||||
|
@ -134,6 +135,7 @@ def _validate(config):
|
|||
"ILI9341",
|
||||
"ILI9342",
|
||||
"ST7789V",
|
||||
"ST7735",
|
||||
]:
|
||||
raise cv.Invalid("Selected model can't run on ESP8266.")
|
||||
|
||||
|
|
|
@ -70,6 +70,7 @@ static const uint8_t ILI9XXX_PWCTR2 = 0xC1;
|
|||
static const uint8_t ILI9XXX_PWCTR3 = 0xC2;
|
||||
static const uint8_t ILI9XXX_PWCTR4 = 0xC3;
|
||||
static const uint8_t ILI9XXX_PWCTR5 = 0xC4;
|
||||
static const uint8_t ILI9XXX_PWCTR6 = 0xF6;
|
||||
static const uint8_t ILI9XXX_VMCTR1 = 0xC5;
|
||||
static const uint8_t ILI9XXX_IFCTR = 0xC6;
|
||||
static const uint8_t ILI9XXX_VMCTR2 = 0xC7;
|
||||
|
@ -91,6 +92,7 @@ static const uint8_t ILI9XXX_GMCTRN1 = 0xE1;
|
|||
|
||||
static const uint8_t ILI9XXX_CSCON = 0xF0;
|
||||
static const uint8_t ILI9XXX_ADJCTL3 = 0xF7;
|
||||
static const uint8_t ILI9XXX_DELAY = 0xFF; // followed by one byte of delay time in ms
|
||||
|
||||
} // namespace ili9xxx
|
||||
} // namespace esphome
|
||||
|
|
|
@ -34,8 +34,8 @@ void ILI9XXXDisplay::setup() {
|
|||
ESP_LOGD(TAG, "Setting up ILI9xxx");
|
||||
|
||||
this->setup_pins_();
|
||||
this->init_lcd_(this->init_sequence_);
|
||||
this->init_lcd_(this->extra_init_sequence_.data());
|
||||
this->init_lcd(this->init_sequence_);
|
||||
this->init_lcd(this->extra_init_sequence_.data());
|
||||
switch (this->pixel_mode_) {
|
||||
case PIXEL_MODE_16:
|
||||
if (this->is_18bitdisplay_) {
|
||||
|
@ -405,7 +405,29 @@ void ILI9XXXDisplay::reset_() {
|
|||
}
|
||||
}
|
||||
|
||||
void ILI9XXXDisplay::init_lcd_(const uint8_t *addr) {
|
||||
void ILI9XXXDisplay::init_lcd(const uint8_t *addr) {
|
||||
if (addr == nullptr)
|
||||
return;
|
||||
uint8_t cmd, x, num_args;
|
||||
while ((cmd = *addr++) != 0) {
|
||||
x = *addr++;
|
||||
if (cmd == ILI9XXX_DELAY) {
|
||||
ESP_LOGD(TAG, "Delay %dms", x);
|
||||
delay(x);
|
||||
} else {
|
||||
num_args = x & 0x7F;
|
||||
ESP_LOGD(TAG, "Command %02X, length %d, bits %02X", cmd, num_args, *addr);
|
||||
this->send_command(cmd, addr, num_args);
|
||||
addr += num_args;
|
||||
if (x & 0x80) {
|
||||
ESP_LOGD(TAG, "Delay 150ms");
|
||||
delay(150); // NOLINT
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ILI9XXXGC9A01A::init_lcd(const uint8_t *addr) {
|
||||
if (addr == nullptr)
|
||||
return;
|
||||
uint8_t cmd, x, num_args;
|
||||
|
|
|
@ -35,7 +35,6 @@ class ILI9XXXDisplay : public display::DisplayBuffer,
|
|||
while ((cmd = *addr++) != 0) {
|
||||
num_args = *addr++ & 0x7F;
|
||||
bits = *addr;
|
||||
esph_log_d(TAG, "Command %02X, length %d, bits %02X", cmd, num_args, bits);
|
||||
switch (cmd) {
|
||||
case ILI9XXX_MADCTL: {
|
||||
this->swap_xy_ = (bits & MADCTL_MV) != 0;
|
||||
|
@ -51,6 +50,9 @@ class ILI9XXXDisplay : public display::DisplayBuffer,
|
|||
break;
|
||||
}
|
||||
|
||||
case ILI9XXX_DELAY:
|
||||
continue; // no args to skip
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
@ -107,7 +109,7 @@ class ILI9XXXDisplay : public display::DisplayBuffer,
|
|||
|
||||
virtual void set_madctl();
|
||||
void display_();
|
||||
void init_lcd_(const uint8_t *addr);
|
||||
virtual void init_lcd(const uint8_t *addr);
|
||||
void set_addr_window_(uint16_t x, uint16_t y, uint16_t x2, uint16_t y2);
|
||||
void reset_();
|
||||
|
||||
|
@ -267,6 +269,13 @@ class ILI9XXXS3BoxLite : public ILI9XXXDisplay {
|
|||
class ILI9XXXGC9A01A : public ILI9XXXDisplay {
|
||||
public:
|
||||
ILI9XXXGC9A01A() : ILI9XXXDisplay(INITCMD_GC9A01A, 240, 240, true) {}
|
||||
void init_lcd(const uint8_t *addr) override;
|
||||
};
|
||||
|
||||
//----------- ILI9XXX_24_TFT display --------------
|
||||
class ILI9XXXST7735 : public ILI9XXXDisplay {
|
||||
public:
|
||||
ILI9XXXST7735() : ILI9XXXDisplay(INITCMD_ST7735, 128, 160, false) {}
|
||||
};
|
||||
|
||||
} // namespace ili9xxx
|
||||
|
|
|
@ -370,6 +370,57 @@ static const uint8_t PROGMEM INITCMD_GC9A01A[] = {
|
|||
0x00 // End of list
|
||||
};
|
||||
|
||||
static const uint8_t PROGMEM INITCMD_ST7735[] = {
|
||||
ILI9XXX_SWRESET, 0, // Soft reset, then delay 10ms
|
||||
ILI9XXX_DELAY, 10,
|
||||
ILI9XXX_SLPOUT , 0, // Exit Sleep, delay
|
||||
ILI9XXX_DELAY, 10,
|
||||
ILI9XXX_PIXFMT , 1, 0x05,
|
||||
ILI9XXX_FRMCTR1, 3, // 4: Frame rate control, 3 args + delay:
|
||||
0x01, 0x2C, 0x2D, // Rate = fosc/(1x2+40) * (LINE+2C+2D)
|
||||
ILI9XXX_FRMCTR2, 3, // 4: Framerate ctrl - idle mode, 3 args:
|
||||
0x01, 0x2C, 0x2D, // Rate = fosc/(1x2+40) * (LINE+2C+2D)
|
||||
ILI9XXX_FRMCTR3, 6, // 5: Framerate - partial mode, 6 args:
|
||||
0x01, 0x2C, 0x2D, // Dot inversion mode
|
||||
0x01, 0x2C, 0x2D, // Line inversion mode
|
||||
|
||||
ILI9XXX_INVCTR, 1, // 7: Display inversion control, 1 arg:
|
||||
0x7, // Line inversion
|
||||
ILI9XXX_PWCTR1, 3, // 7: Power control, 3 args, no delay:
|
||||
0xA2,
|
||||
0x02, // -4.6V
|
||||
0x84, // AUTO mode
|
||||
ILI9XXX_PWCTR2, 1, // 8: Power control, 1 arg, no delay:
|
||||
0xC5, // VGH25=2.4C VGSEL=-10 VGH=3 * AVDD
|
||||
ILI9XXX_PWCTR3, 2, // 9: Power control, 2 args, no delay:
|
||||
0x0A, // Opamp current small
|
||||
0x00, // Boost frequency
|
||||
ILI9XXX_PWCTR4, 2, // 10: Power control, 2 args, no delay:
|
||||
0x8A, // BCLK/2,
|
||||
0x2A, // opamp current small & medium low
|
||||
ILI9XXX_PWCTR5, 2, // 11: Power control, 2 args, no delay:
|
||||
0x8A, 0xEE,
|
||||
|
||||
ILI9XXX_VMCTR1, 1, // 11: Power control, 2 args + delay:
|
||||
0x0E,
|
||||
ILI9XXX_GMCTRP1, 16, // 13: Gamma Adjustments (pos. polarity), 16 args + delay:
|
||||
0x02, 0x1c, 0x07, 0x12, // (Not entirely necessary, but provides
|
||||
0x37, 0x32, 0x29, 0x2d, // accurate colors)
|
||||
0x29, 0x25, 0x2B, 0x39,
|
||||
0x00, 0x01, 0x03, 0x10,
|
||||
ILI9XXX_GMCTRN1, 16, // 14: Gamma Adjustments (neg. polarity), 16 args + delay:
|
||||
0x03, 0x1d, 0x07, 0x06, // (Not entirely necessary, but provides
|
||||
0x2E, 0x2C, 0x29, 0x2D, // accurate colors)
|
||||
0x2E, 0x2E, 0x37, 0x3F,
|
||||
0x00, 0x00, 0x02, 0x10,
|
||||
ILI9XXX_MADCTL , 1, 0x00, // Memory Access Control, BGR
|
||||
ILI9XXX_NORON , 0,
|
||||
ILI9XXX_DELAY, 10,
|
||||
ILI9XXX_DISPON , 0, // Display on
|
||||
ILI9XXX_DELAY, 10,
|
||||
00, // endo of list
|
||||
};
|
||||
|
||||
// clang-format on
|
||||
} // namespace ili9xxx
|
||||
} // namespace esphome
|
||||
|
|
|
@ -9,8 +9,6 @@ import re
|
|||
import requests
|
||||
from magic import Magic
|
||||
|
||||
from PIL import Image
|
||||
|
||||
from esphome import core
|
||||
from esphome.components import font
|
||||
from esphome import external_files
|
||||
|
@ -68,7 +66,7 @@ def _compute_local_icon_path(value: dict) -> Path:
|
|||
return base_dir / f"{value[CONF_ICON]}.svg"
|
||||
|
||||
|
||||
def _compute_local_image_path(value: dict) -> Path:
|
||||
def compute_local_image_path(value: dict) -> Path:
|
||||
url = value[CONF_URL]
|
||||
h = hashlib.new("sha256")
|
||||
h.update(url.encode())
|
||||
|
@ -117,7 +115,7 @@ def download_mdi(value):
|
|||
|
||||
def download_image(value):
|
||||
url = value[CONF_URL]
|
||||
path = _compute_local_image_path(value)
|
||||
path = compute_local_image_path(value)
|
||||
|
||||
download_content(url, path)
|
||||
|
||||
|
@ -267,6 +265,9 @@ CONFIG_SCHEMA = cv.All(font.validate_pillow_installed, IMAGE_SCHEMA)
|
|||
|
||||
|
||||
def load_svg_image(file: bytes, resize: tuple[int, int]):
|
||||
# Local import only to allow "validate_pillow_installed" to run *before* importing it
|
||||
from PIL import Image
|
||||
|
||||
# This import is only needed in case of SVG images; adding it
|
||||
# to the top would force configurations not using SVG to also have it
|
||||
# installed for no reason.
|
||||
|
@ -286,6 +287,9 @@ def load_svg_image(file: bytes, resize: tuple[int, int]):
|
|||
|
||||
|
||||
async def to_code(config):
|
||||
# Local import only to allow "validate_pillow_installed" to run *before* importing it
|
||||
from PIL import Image
|
||||
|
||||
conf_file = config[CONF_FILE]
|
||||
|
||||
if conf_file[CONF_SOURCE] == SOURCE_LOCAL:
|
||||
|
@ -295,7 +299,7 @@ async def to_code(config):
|
|||
path = _compute_local_icon_path(conf_file).as_posix()
|
||||
|
||||
elif conf_file[CONF_SOURCE] == SOURCE_WEB:
|
||||
path = _compute_local_image_path(conf_file).as_posix()
|
||||
path = compute_local_image_path(conf_file).as_posix()
|
||||
|
||||
try:
|
||||
with open(path, "rb") as f:
|
||||
|
|
|
@ -62,7 +62,7 @@ std::string build_json(const json_build_t &f) {
|
|||
}
|
||||
}
|
||||
|
||||
void parse_json(const std::string &data, const json_parse_t &f) {
|
||||
bool parse_json(const std::string &data, const json_parse_t &f) {
|
||||
// Here we are allocating 1.5 times the data size,
|
||||
// with the heap size minus 2kb to be safe if less than that
|
||||
// as we can not have a true dynamic sized document.
|
||||
|
@ -76,14 +76,13 @@ void parse_json(const std::string &data, const json_parse_t &f) {
|
|||
#elif defined(USE_LIBRETINY)
|
||||
const size_t free_heap = lt_heap_get_free();
|
||||
#endif
|
||||
bool pass = false;
|
||||
size_t request_size = std::min(free_heap, (size_t) (data.size() * 1.5));
|
||||
do {
|
||||
while (true) {
|
||||
DynamicJsonDocument json_document(request_size);
|
||||
if (json_document.capacity() == 0) {
|
||||
ESP_LOGE(TAG, "Could not allocate memory for JSON document! Requested %u bytes, free heap: %u", request_size,
|
||||
free_heap);
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
DeserializationError err = deserializeJson(json_document, data);
|
||||
json_document.shrinkToFit();
|
||||
|
@ -91,21 +90,21 @@ void parse_json(const std::string &data, const json_parse_t &f) {
|
|||
JsonObject root = json_document.as<JsonObject>();
|
||||
|
||||
if (err == DeserializationError::Ok) {
|
||||
pass = true;
|
||||
f(root);
|
||||
return f(root);
|
||||
} else if (err == DeserializationError::NoMemory) {
|
||||
if (request_size * 2 >= free_heap) {
|
||||
ESP_LOGE(TAG, "Can not allocate more memory for deserialization. Consider making source string smaller");
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
ESP_LOGV(TAG, "Increasing memory allocation.");
|
||||
request_size *= 2;
|
||||
continue;
|
||||
} else {
|
||||
ESP_LOGE(TAG, "JSON parse error: %s", err.c_str());
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
} while (!pass);
|
||||
};
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace json
|
||||
|
|
|
@ -14,7 +14,7 @@ namespace esphome {
|
|||
namespace json {
|
||||
|
||||
/// Callback function typedef for parsing JsonObjects.
|
||||
using json_parse_t = std::function<void(JsonObject)>;
|
||||
using json_parse_t = std::function<bool(JsonObject)>;
|
||||
|
||||
/// Callback function typedef for building JsonObjects.
|
||||
using json_build_t = std::function<void(JsonObject)>;
|
||||
|
@ -23,7 +23,7 @@ using json_build_t = std::function<void(JsonObject)>;
|
|||
std::string build_json(const json_build_t &f);
|
||||
|
||||
/// Parse a JSON string and run the provided json parse function if it's valid.
|
||||
void parse_json(const std::string &data, const json_parse_t &f);
|
||||
bool parse_json(const std::string &data, const json_parse_t &f);
|
||||
|
||||
} // namespace json
|
||||
} // namespace esphome
|
||||
|
|
|
@ -312,6 +312,7 @@ async def to_code(config):
|
|||
esp32.add_idf_component(
|
||||
name="esp-tflite-micro",
|
||||
repo="https://github.com/espressif/esp-tflite-micro",
|
||||
ref="v1.3.1",
|
||||
)
|
||||
|
||||
cg.add_build_flag("-DTF_LITE_STATIC_MEMORY")
|
||||
|
|
|
@ -126,6 +126,7 @@ MQTTSelectComponent = mqtt_ns.class_("MQTTSelectComponent", MQTTComponent)
|
|||
MQTTButtonComponent = mqtt_ns.class_("MQTTButtonComponent", MQTTComponent)
|
||||
MQTTLockComponent = mqtt_ns.class_("MQTTLockComponent", MQTTComponent)
|
||||
MQTTEventComponent = mqtt_ns.class_("MQTTEventComponent", MQTTComponent)
|
||||
MQTTUpdateComponent = mqtt_ns.class_("MQTTUpdateComponent", MQTTComponent)
|
||||
MQTTValveComponent = mqtt_ns.class_("MQTTValveComponent", MQTTComponent)
|
||||
|
||||
MQTTDiscoveryUniqueIdGenerator = mqtt_ns.enum("MQTTDiscoveryUniqueIdGenerator")
|
||||
|
|
|
@ -410,7 +410,10 @@ void MQTTClientComponent::subscribe(const std::string &topic, mqtt_callback_t ca
|
|||
|
||||
void MQTTClientComponent::subscribe_json(const std::string &topic, const mqtt_json_callback_t &callback, uint8_t qos) {
|
||||
auto f = [callback](const std::string &topic, const std::string &payload) {
|
||||
json::parse_json(payload, [topic, callback](JsonObject root) { callback(topic, root); });
|
||||
json::parse_json(payload, [topic, callback](JsonObject root) -> bool {
|
||||
callback(topic, root);
|
||||
return true;
|
||||
});
|
||||
};
|
||||
MQTTSubscription subscription{
|
||||
.topic = topic,
|
||||
|
|
|
@ -137,6 +137,7 @@ constexpr const char *const MQTT_PAYLOAD_CLOSE = "pl_cls";
|
|||
constexpr const char *const MQTT_PAYLOAD_DISARM = "pl_disarm";
|
||||
constexpr const char *const MQTT_PAYLOAD_HIGH_SPEED = "pl_hi_spd";
|
||||
constexpr const char *const MQTT_PAYLOAD_HOME = "pl_home";
|
||||
constexpr const char *const MQTT_PAYLOAD_INSTALL = "pl_inst";
|
||||
constexpr const char *const MQTT_PAYLOAD_LOCATE = "pl_loc";
|
||||
constexpr const char *const MQTT_PAYLOAD_LOCK = "pl_lock";
|
||||
constexpr const char *const MQTT_PAYLOAD_LOW_SPEED = "pl_lo_spd";
|
||||
|
@ -396,6 +397,7 @@ constexpr const char *const MQTT_PAYLOAD_CLOSE = "payload_close";
|
|||
constexpr const char *const MQTT_PAYLOAD_DISARM = "payload_disarm";
|
||||
constexpr const char *const MQTT_PAYLOAD_HIGH_SPEED = "payload_high_speed";
|
||||
constexpr const char *const MQTT_PAYLOAD_HOME = "payload_home";
|
||||
constexpr const char *const MQTT_PAYLOAD_INSTALL = "payload_install";
|
||||
constexpr const char *const MQTT_PAYLOAD_LOCATE = "payload_locate";
|
||||
constexpr const char *const MQTT_PAYLOAD_LOCK = "payload_lock";
|
||||
constexpr const char *const MQTT_PAYLOAD_LOW_SPEED = "payload_low_speed";
|
||||
|
|
|
@ -6,12 +6,12 @@
|
|||
#include "mqtt_const.h"
|
||||
|
||||
#ifdef USE_MQTT
|
||||
#ifdef USE_DATETIME_TIME
|
||||
#ifdef USE_DATETIME_DATETIME
|
||||
|
||||
namespace esphome {
|
||||
namespace mqtt {
|
||||
|
||||
static const char *const TAG = "mqtt.datetime.time";
|
||||
static const char *const TAG = "mqtt.datetime.datetime";
|
||||
|
||||
using namespace esphome::datetime;
|
||||
|
||||
|
@ -80,5 +80,5 @@ bool MQTTDateTimeComponent::publish_state(uint16_t year, uint8_t month, uint8_t
|
|||
} // namespace mqtt
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_DATETIME_TIME
|
||||
#endif // USE_DATETIME_DATETIME
|
||||
#endif // USE_MQTT
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
#include "esphome/core/defines.h"
|
||||
|
||||
#ifdef USE_MQTT
|
||||
#ifdef USE_DATETIME_TIME
|
||||
#ifdef USE_DATETIME_DATETIME
|
||||
|
||||
#include "esphome/components/datetime/datetime_entity.h"
|
||||
#include "mqtt_component.h"
|
||||
|
@ -17,7 +17,7 @@ class MQTTDateTimeComponent : public mqtt::MQTTComponent {
|
|||
*
|
||||
* @param time The time entity.
|
||||
*/
|
||||
explicit MQTTDateTimeComponent(datetime::DateTimeEntity *time);
|
||||
explicit MQTTDateTimeComponent(datetime::DateTimeEntity *datetime);
|
||||
|
||||
// ========== INTERNAL METHODS ==========
|
||||
// (In most use cases you won't need these)
|
||||
|
@ -41,5 +41,5 @@ class MQTTDateTimeComponent : public mqtt::MQTTComponent {
|
|||
} // namespace mqtt
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_DATETIME_DATE
|
||||
#endif // USE_DATETIME_DATETIME
|
||||
#endif // USE_MQTT
|
||||
|
|
62
esphome/components/mqtt/mqtt_update.cpp
Normal file
62
esphome/components/mqtt/mqtt_update.cpp
Normal file
|
@ -0,0 +1,62 @@
|
|||
#include "mqtt_update.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#include "mqtt_const.h"
|
||||
|
||||
#ifdef USE_MQTT
|
||||
#ifdef USE_UPDATE
|
||||
|
||||
namespace esphome {
|
||||
namespace mqtt {
|
||||
|
||||
static const char *const TAG = "mqtt.update";
|
||||
|
||||
using namespace esphome::update;
|
||||
|
||||
MQTTUpdateComponent::MQTTUpdateComponent(UpdateEntity *update) : update_(update) {}
|
||||
|
||||
void MQTTUpdateComponent::setup() {
|
||||
this->subscribe(this->get_command_topic_(), [this](const std::string &topic, const std::string &payload) {
|
||||
if (payload == "INSTALL") {
|
||||
this->update_->perform();
|
||||
} else {
|
||||
ESP_LOGW(TAG, "'%s': Received unknown update payload: %s", this->friendly_name().c_str(), payload.c_str());
|
||||
this->status_momentary_warning("state", 5000);
|
||||
}
|
||||
});
|
||||
|
||||
this->update_->add_on_state_callback([this]() { this->defer("send", [this]() { this->publish_state(); }); });
|
||||
}
|
||||
|
||||
bool MQTTUpdateComponent::publish_state() {
|
||||
return this->publish_json(this->get_state_topic_(), [this](JsonObject root) {
|
||||
root["installed_version"] = this->update_->update_info.current_version;
|
||||
root["latest_version"] = this->update_->update_info.latest_version;
|
||||
root["title"] = this->update_->update_info.title;
|
||||
if (!this->update_->update_info.summary.empty())
|
||||
root["release_summary"] = this->update_->update_info.summary;
|
||||
if (!this->update_->update_info.release_url.empty())
|
||||
root["release_url"] = this->update_->update_info.release_url;
|
||||
});
|
||||
}
|
||||
|
||||
void MQTTUpdateComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) {
|
||||
root["schema"] = "json";
|
||||
root[MQTT_PAYLOAD_INSTALL] = "INSTALL";
|
||||
}
|
||||
|
||||
bool MQTTUpdateComponent::send_initial_state() { return this->publish_state(); }
|
||||
|
||||
void MQTTUpdateComponent::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "MQTT Update '%s': ", this->update_->get_name().c_str());
|
||||
LOG_MQTT_COMPONENT(true, true);
|
||||
}
|
||||
|
||||
std::string MQTTUpdateComponent::component_type() const { return "update"; }
|
||||
const EntityBase *MQTTUpdateComponent::get_entity() const { return this->update_; }
|
||||
|
||||
} // namespace mqtt
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_UPDATE
|
||||
#endif // USE_MQTT
|
41
esphome/components/mqtt/mqtt_update.h
Normal file
41
esphome/components/mqtt/mqtt_update.h
Normal file
|
@ -0,0 +1,41 @@
|
|||
#pragma once
|
||||
|
||||
#include "esphome/core/defines.h"
|
||||
|
||||
#ifdef USE_MQTT
|
||||
#ifdef USE_UPDATE
|
||||
|
||||
#include "esphome/components/update/update_entity.h"
|
||||
#include "mqtt_component.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace mqtt {
|
||||
|
||||
class MQTTUpdateComponent : public mqtt::MQTTComponent {
|
||||
public:
|
||||
explicit MQTTUpdateComponent(update::UpdateEntity *update);
|
||||
|
||||
// ========== INTERNAL METHODS ==========
|
||||
// (In most use cases you won't need these)
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
|
||||
void send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) override;
|
||||
|
||||
bool send_initial_state() override;
|
||||
|
||||
bool publish_state();
|
||||
|
||||
protected:
|
||||
/// "update" component type.
|
||||
std::string component_type() const override;
|
||||
const EntityBase *get_entity() const override;
|
||||
|
||||
update::UpdateEntity *update_;
|
||||
};
|
||||
|
||||
} // namespace mqtt
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_UPDATE
|
||||
#endif // USE_MQTT
|
40
esphome/components/one_wire/__init__.py
Normal file
40
esphome/components/one_wire/__init__.py
Normal file
|
@ -0,0 +1,40 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ADDRESS
|
||||
|
||||
CODEOWNERS = ["@ssieb"]
|
||||
|
||||
IS_PLATFORM_COMPONENT = True
|
||||
|
||||
CONF_ONE_WIRE_ID = "one_wire_id"
|
||||
|
||||
one_wire_ns = cg.esphome_ns.namespace("one_wire")
|
||||
OneWireBus = one_wire_ns.class_("OneWireBus")
|
||||
OneWireDevice = one_wire_ns.class_("OneWireDevice")
|
||||
|
||||
|
||||
def one_wire_device_schema():
|
||||
"""Create a schema for a 1-wire device.
|
||||
|
||||
:return: The 1-wire device schema, `extend` this in your config schema.
|
||||
"""
|
||||
schema = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(CONF_ONE_WIRE_ID): cv.use_id(OneWireBus),
|
||||
cv.Optional(CONF_ADDRESS): cv.hex_uint64_t,
|
||||
}
|
||||
)
|
||||
return schema
|
||||
|
||||
|
||||
async def register_one_wire_device(var, config):
|
||||
"""Register an 1-wire device with the given config.
|
||||
|
||||
Sets the 1-wire bus to use and the 1-wire address.
|
||||
|
||||
This is a coroutine, you need to await it with a 'yield' expression!
|
||||
"""
|
||||
parent = await cg.get_variable(config[CONF_ONE_WIRE_ID])
|
||||
cg.add(var.set_one_wire_bus(parent))
|
||||
if (address := config.get(CONF_ADDRESS)) is not None:
|
||||
cg.add(var.set_address(address))
|
40
esphome/components/one_wire/one_wire.cpp
Normal file
40
esphome/components/one_wire/one_wire.cpp
Normal file
|
@ -0,0 +1,40 @@
|
|||
#include "one_wire.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace one_wire {
|
||||
|
||||
static const char *const TAG = "one_wire";
|
||||
|
||||
const std::string &OneWireDevice::get_address_name() {
|
||||
if (this->address_name_.empty())
|
||||
this->address_name_ = std::string("0x") + format_hex(this->address_);
|
||||
return this->address_name_;
|
||||
}
|
||||
|
||||
std::string OneWireDevice::unique_id() { return "dallas-" + str_lower_case(format_hex(this->address_)); }
|
||||
|
||||
bool OneWireDevice::send_command_(uint8_t cmd) {
|
||||
if (!this->bus_->select(this->address_))
|
||||
return false;
|
||||
this->bus_->write8(cmd);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OneWireDevice::check_address_() {
|
||||
if (this->address_ != 0)
|
||||
return true;
|
||||
auto devices = this->bus_->get_devices();
|
||||
if (devices.empty()) {
|
||||
ESP_LOGE(TAG, "No devices, can't auto-select address");
|
||||
return false;
|
||||
}
|
||||
if (devices.size() > 1) {
|
||||
ESP_LOGE(TAG, "More than one device, can't auto-select address");
|
||||
return false;
|
||||
}
|
||||
this->address_ = devices[0];
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace one_wire
|
||||
} // namespace esphome
|
44
esphome/components/one_wire/one_wire.h
Normal file
44
esphome/components/one_wire/one_wire.h
Normal file
|
@ -0,0 +1,44 @@
|
|||
#pragma once
|
||||
|
||||
#include "one_wire_bus.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace one_wire {
|
||||
|
||||
#define LOG_ONE_WIRE_DEVICE(this) \
|
||||
ESP_LOGCONFIG(TAG, " Address: %s (%s)", this->get_address_name().c_str(), \
|
||||
LOG_STR_ARG(this->bus_->get_model_str(this->address_ & 0xff)));
|
||||
|
||||
class OneWireDevice {
|
||||
public:
|
||||
/// @brief store the address of the device
|
||||
/// @param address of the device
|
||||
void set_address(uint64_t address) { this->address_ = address; }
|
||||
|
||||
/// @brief store the pointer to the OneWireBus to use
|
||||
/// @param bus pointer to the OneWireBus object
|
||||
void set_one_wire_bus(OneWireBus *bus) { this->bus_ = bus; }
|
||||
|
||||
/// Helper to create (and cache) the name for this sensor. For example "0xfe0000031f1eaf29".
|
||||
const std::string &get_address_name();
|
||||
|
||||
std::string unique_id();
|
||||
|
||||
protected:
|
||||
uint64_t address_{0};
|
||||
OneWireBus *bus_{nullptr}; ///< pointer to OneWireBus instance
|
||||
std::string address_name_;
|
||||
|
||||
/// @brief find an address if necessary
|
||||
/// should be called from setup
|
||||
bool check_address_();
|
||||
|
||||
/// @brief send command on the bus
|
||||
/// @param cmd command to send
|
||||
bool send_command_(uint8_t cmd);
|
||||
};
|
||||
|
||||
} // namespace one_wire
|
||||
} // namespace esphome
|
88
esphome/components/one_wire/one_wire_bus.cpp
Normal file
88
esphome/components/one_wire/one_wire_bus.cpp
Normal file
|
@ -0,0 +1,88 @@
|
|||
#include "one_wire_bus.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace one_wire {
|
||||
|
||||
static const char *const TAG = "one_wire";
|
||||
|
||||
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;
|
||||
|
||||
const uint8_t ONE_WIRE_ROM_SELECT = 0x55;
|
||||
const uint8_t ONE_WIRE_ROM_SEARCH = 0xF0;
|
||||
|
||||
const std::vector<uint64_t> &OneWireBus::get_devices() { return this->devices_; }
|
||||
|
||||
bool IRAM_ATTR OneWireBus::select(uint64_t address) {
|
||||
if (!this->reset())
|
||||
return false;
|
||||
this->write8(ONE_WIRE_ROM_SELECT);
|
||||
this->write64(address);
|
||||
return true;
|
||||
}
|
||||
|
||||
void OneWireBus::search() {
|
||||
this->devices_.clear();
|
||||
|
||||
this->reset_search();
|
||||
uint64_t address;
|
||||
while (true) {
|
||||
{
|
||||
InterruptLock lock;
|
||||
if (!this->reset()) {
|
||||
// Reset failed or no devices present
|
||||
return;
|
||||
}
|
||||
|
||||
this->write8(ONE_WIRE_ROM_SEARCH);
|
||||
address = this->search_int();
|
||||
}
|
||||
if (address == 0)
|
||||
break;
|
||||
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());
|
||||
} else {
|
||||
this->devices_.push_back(address);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OneWireBus::skip() {
|
||||
this->write8(0xCC); // skip ROM
|
||||
}
|
||||
|
||||
const LogString *OneWireBus::get_model_str(uint8_t model) {
|
||||
switch (model) {
|
||||
case DALLAS_MODEL_DS18S20:
|
||||
return LOG_STR("DS18S20");
|
||||
case DALLAS_MODEL_DS1822:
|
||||
return LOG_STR("DS1822");
|
||||
case DALLAS_MODEL_DS18B20:
|
||||
return LOG_STR("DS18B20");
|
||||
case DALLAS_MODEL_DS1825:
|
||||
return LOG_STR("DS1825");
|
||||
case DALLAS_MODEL_DS28EA00:
|
||||
return LOG_STR("DS28EA00");
|
||||
default:
|
||||
return LOG_STR("Unknown");
|
||||
}
|
||||
}
|
||||
|
||||
void OneWireBus::dump_devices_(const char *tag) {
|
||||
if (this->devices_.empty()) {
|
||||
ESP_LOGW(tag, " Found no devices!");
|
||||
} else {
|
||||
ESP_LOGCONFIG(tag, " Found devices:");
|
||||
for (auto &address : this->devices_) {
|
||||
ESP_LOGCONFIG(tag, " 0x%s (%s)", format_hex(address).c_str(), LOG_STR_ARG(get_model_str(address & 0xff)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace one_wire
|
||||
} // namespace esphome
|
61
esphome/components/one_wire/one_wire_bus.h
Normal file
61
esphome/components/one_wire/one_wire_bus.h
Normal file
|
@ -0,0 +1,61 @@
|
|||
#pragma once
|
||||
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include <vector>
|
||||
|
||||
namespace esphome {
|
||||
namespace one_wire {
|
||||
|
||||
class OneWireBus {
|
||||
public:
|
||||
/** Reset the bus, should be done before all write operations.
|
||||
*
|
||||
* Takes approximately 1ms.
|
||||
*
|
||||
* @return Whether the operation was successful.
|
||||
*/
|
||||
virtual bool reset() = 0;
|
||||
|
||||
/// Write a word to the bus. LSB first.
|
||||
virtual void write8(uint8_t val) = 0;
|
||||
|
||||
/// Write a 64 bit unsigned integer to the bus. LSB first.
|
||||
virtual void write64(uint64_t val) = 0;
|
||||
|
||||
/// Write a command to the bus that addresses all devices by skipping the ROM.
|
||||
void skip();
|
||||
|
||||
/// Read an 8 bit word from the bus.
|
||||
virtual uint8_t read8() = 0;
|
||||
|
||||
/// Read an 64-bit unsigned integer from the bus.
|
||||
virtual uint64_t read64() = 0;
|
||||
|
||||
/// Select a specific address on the bus for the following command.
|
||||
bool select(uint64_t address);
|
||||
|
||||
/// Return the list of found devices.
|
||||
const std::vector<uint64_t> &get_devices();
|
||||
|
||||
/// Search for 1-Wire devices on the bus.
|
||||
void search();
|
||||
|
||||
/// Get the description string for this model.
|
||||
const LogString *get_model_str(uint8_t model);
|
||||
|
||||
protected:
|
||||
std::vector<uint64_t> devices_;
|
||||
|
||||
/// log the found devices
|
||||
void dump_devices_(const char *tag);
|
||||
|
||||
/// Reset the device search.
|
||||
virtual void reset_search() = 0;
|
||||
|
||||
/// Search for a 1-Wire device on the bus. Returns 0 if all devices have been found.
|
||||
virtual uint64_t search_int() = 0;
|
||||
};
|
||||
|
||||
} // namespace one_wire
|
||||
} // namespace esphome
|
|
@ -47,10 +47,16 @@ def set_core_data(config):
|
|||
def get_download_types(storage_json):
|
||||
return [
|
||||
{
|
||||
"title": "UF2 format",
|
||||
"title": "UF2 factory format",
|
||||
"description": "For copying to RP2040 over USB.",
|
||||
"file": "firmware.uf2",
|
||||
"download": f"{storage_json.name}.uf2",
|
||||
"download": f"{storage_json.name}.factory.uf2",
|
||||
},
|
||||
{
|
||||
"title": "OTA format",
|
||||
"description": "For OTA updating a device.",
|
||||
"file": "firmware.ota.bin",
|
||||
"download": f"{storage_json.name}.ota.bin",
|
||||
},
|
||||
]
|
||||
|
||||
|
@ -160,6 +166,8 @@ async def to_code(config):
|
|||
cg.add_define("ESPHOME_BOARD", config[CONF_BOARD])
|
||||
cg.add_define("ESPHOME_VARIANT", "RP2040")
|
||||
|
||||
cg.add_platformio_option("extra_scripts", ["post:post_build.py"])
|
||||
|
||||
conf = config[CONF_FRAMEWORK]
|
||||
cg.add_platformio_option("framework", "arduino")
|
||||
cg.add_build_flag("-DUSE_ARDUINO")
|
||||
|
@ -225,4 +233,10 @@ def generate_pio_files() -> bool:
|
|||
|
||||
# Called by writer.py
|
||||
def copy_files() -> bool:
|
||||
dir = os.path.dirname(__file__)
|
||||
post_build_file = os.path.join(dir, "post_build.py.script")
|
||||
copy_file_if_changed(
|
||||
post_build_file,
|
||||
CORE.relative_build_path("post_build.py"),
|
||||
)
|
||||
return generate_pio_files()
|
||||
|
|
23
esphome/components/rp2040/post_build.py.script
Normal file
23
esphome/components/rp2040/post_build.py.script
Normal file
|
@ -0,0 +1,23 @@
|
|||
import shutil
|
||||
|
||||
# pylint: disable=E0602
|
||||
Import("env") # noqa
|
||||
|
||||
|
||||
def rp2040_copy_factory_uf2(source, target, env):
|
||||
firmware_name = env.subst("$BUILD_DIR/${PROGNAME}.uf2")
|
||||
new_file_name = env.subst("$BUILD_DIR/${PROGNAME}.factory.uf2")
|
||||
|
||||
shutil.copyfile(firmware_name, new_file_name)
|
||||
|
||||
|
||||
def rp2040_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", rp2040_copy_factory_uf2) # noqa
|
||||
env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", rp2040_copy_ota_bin) # noqa
|
|
@ -16,6 +16,7 @@ from esphome import automation
|
|||
|
||||
CODEOWNERS = ["@paulmonigatti", "@jsuanet", "@kbx81"]
|
||||
|
||||
CONF_BOOT_IS_GOOD_AFTER = "boot_is_good_after"
|
||||
CONF_ON_SAFE_MODE = "on_safe_mode"
|
||||
|
||||
safe_mode_ns = cg.esphome_ns.namespace("safe_mode")
|
||||
|
@ -34,6 +35,9 @@ CONFIG_SCHEMA = cv.All(
|
|||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(SafeModeComponent),
|
||||
cv.Optional(
|
||||
CONF_BOOT_IS_GOOD_AFTER, default="1min"
|
||||
): cv.positive_time_period_milliseconds,
|
||||
cv.Optional(CONF_DISABLED, default=False): cv.boolean,
|
||||
cv.Optional(CONF_NUM_ATTEMPTS, default="10"): cv.positive_not_null_int,
|
||||
cv.Optional(
|
||||
|
@ -63,7 +67,9 @@ async def to_code(config):
|
|||
await automation.build_automation(trigger, [], conf)
|
||||
|
||||
condition = var.should_enter_safe_mode(
|
||||
config[CONF_NUM_ATTEMPTS], config[CONF_REBOOT_TIMEOUT]
|
||||
config[CONF_NUM_ATTEMPTS],
|
||||
config[CONF_REBOOT_TIMEOUT],
|
||||
config[CONF_BOOT_IS_GOOD_AFTER],
|
||||
)
|
||||
cg.add(RawExpression(f"if ({condition}) return"))
|
||||
CORE.data[CONF_SAFE_MODE] = {}
|
||||
|
|
|
@ -16,6 +16,8 @@ static const char *const TAG = "safe_mode";
|
|||
|
||||
void SafeModeComponent::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "Safe Mode:");
|
||||
ESP_LOGCONFIG(TAG, " Boot considered successful after %" PRIu32 " seconds",
|
||||
this->safe_mode_boot_is_good_after_ / 1000); // because milliseconds
|
||||
ESP_LOGCONFIG(TAG, " Invoke after %u boot attempts", this->safe_mode_num_attempts_);
|
||||
ESP_LOGCONFIG(TAG, " Remain in safe mode for %" PRIu32 " seconds",
|
||||
this->safe_mode_enable_time_ / 1000); // because milliseconds
|
||||
|
@ -34,7 +36,7 @@ void SafeModeComponent::dump_config() {
|
|||
float SafeModeComponent::get_setup_priority() const { return setup_priority::AFTER_WIFI; }
|
||||
|
||||
void SafeModeComponent::loop() {
|
||||
if (!this->boot_successful_ && (millis() - this->safe_mode_start_time_) > this->safe_mode_enable_time_) {
|
||||
if (!this->boot_successful_ && (millis() - this->safe_mode_start_time_) > this->safe_mode_boot_is_good_after_) {
|
||||
// successful boot, reset counter
|
||||
ESP_LOGI(TAG, "Boot seems successful; resetting boot loop counter");
|
||||
this->clean_rtc();
|
||||
|
@ -60,9 +62,11 @@ bool SafeModeComponent::get_safe_mode_pending() {
|
|||
return this->read_rtc_() == SafeModeComponent::ENTER_SAFE_MODE_MAGIC;
|
||||
}
|
||||
|
||||
bool SafeModeComponent::should_enter_safe_mode(uint8_t num_attempts, uint32_t enable_time) {
|
||||
bool SafeModeComponent::should_enter_safe_mode(uint8_t num_attempts, uint32_t enable_time,
|
||||
uint32_t boot_is_good_after) {
|
||||
this->safe_mode_start_time_ = millis();
|
||||
this->safe_mode_enable_time_ = enable_time;
|
||||
this->safe_mode_boot_is_good_after_ = boot_is_good_after;
|
||||
this->safe_mode_num_attempts_ = num_attempts;
|
||||
this->rtc_ = global_preferences->make_preference<uint32_t>(233825507UL, false);
|
||||
this->safe_mode_rtc_value_ = this->read_rtc_();
|
||||
|
|
|
@ -11,7 +11,7 @@ namespace safe_mode {
|
|||
/// SafeModeComponent provides a safe way to recover from repeated boot failures
|
||||
class SafeModeComponent : public Component {
|
||||
public:
|
||||
bool should_enter_safe_mode(uint8_t num_attempts, uint32_t enable_time);
|
||||
bool should_enter_safe_mode(uint8_t num_attempts, uint32_t enable_time, uint32_t boot_is_good_after);
|
||||
|
||||
/// Set to true if the next startup will enter safe mode
|
||||
void set_safe_mode_pending(const bool &pending);
|
||||
|
@ -33,11 +33,12 @@ class SafeModeComponent : public Component {
|
|||
void write_rtc_(uint32_t val);
|
||||
uint32_t read_rtc_();
|
||||
|
||||
bool boot_successful_{false}; ///< set to true after boot is considered successful
|
||||
uint32_t safe_mode_start_time_; ///< stores when safe mode was enabled
|
||||
uint32_t safe_mode_enable_time_{60000}; ///< The time safe mode should remain active for
|
||||
uint32_t safe_mode_rtc_value_;
|
||||
uint8_t safe_mode_num_attempts_;
|
||||
bool boot_successful_{false}; ///< set to true after boot is considered successful
|
||||
uint32_t safe_mode_boot_is_good_after_{60000}; ///< The amount of time after which the boot is considered successful
|
||||
uint32_t safe_mode_enable_time_{60000}; ///< The time safe mode should remain active for
|
||||
uint32_t safe_mode_rtc_value_{0};
|
||||
uint32_t safe_mode_start_time_{0}; ///< stores when safe mode was enabled
|
||||
uint8_t safe_mode_num_attempts_{0};
|
||||
ESPPreferenceObject rtc_;
|
||||
CallbackManager<void()> safe_mode_callback_{};
|
||||
|
||||
|
|
1
esphome/components/sdl/__init__.py
Normal file
1
esphome/components/sdl/__init__.py
Normal file
|
@ -0,0 +1 @@
|
|||
CODEOWNERS = ["@clydebarrow"]
|
72
esphome/components/sdl/display.py
Normal file
72
esphome/components/sdl/display.py
Normal file
|
@ -0,0 +1,72 @@
|
|||
import subprocess
|
||||
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import display
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_DIMENSIONS,
|
||||
CONF_WIDTH,
|
||||
CONF_HEIGHT,
|
||||
CONF_LAMBDA,
|
||||
PLATFORM_HOST,
|
||||
)
|
||||
|
||||
sdl_ns = cg.esphome_ns.namespace("sdl")
|
||||
Sdl = sdl_ns.class_("Sdl", display.Display, cg.Component)
|
||||
|
||||
|
||||
CONF_SDL_OPTIONS = "sdl_options"
|
||||
CONF_SDL_ID = "sdl_id"
|
||||
|
||||
|
||||
def get_sdl_options(value):
|
||||
if value != "":
|
||||
return value
|
||||
try:
|
||||
return subprocess.check_output(["sdl2-config", "--cflags", "--libs"]).decode()
|
||||
except Exception as e:
|
||||
raise cv.Invalid("Unable to run sdl2-config - have you installed sdl2?") from e
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
display.FULL_DISPLAY_SCHEMA.extend(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(Sdl),
|
||||
cv.Optional(CONF_SDL_OPTIONS, default=""): get_sdl_options,
|
||||
cv.Required(CONF_DIMENSIONS): cv.Any(
|
||||
cv.dimensions,
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_WIDTH): cv.int_,
|
||||
cv.Required(CONF_HEIGHT): cv.int_,
|
||||
}
|
||||
),
|
||||
),
|
||||
}
|
||||
)
|
||||
),
|
||||
cv.only_on(PLATFORM_HOST),
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
for option in config[CONF_SDL_OPTIONS].split():
|
||||
cg.add_build_flag(option)
|
||||
cg.add_build_flag("-DSDL_BYTEORDER=4321")
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await display.register_display(var, config)
|
||||
|
||||
dimensions = config[CONF_DIMENSIONS]
|
||||
if isinstance(dimensions, dict):
|
||||
cg.add(var.set_dimensions(dimensions[CONF_WIDTH], dimensions[CONF_HEIGHT]))
|
||||
else:
|
||||
(width, height) = dimensions
|
||||
cg.add(var.set_dimensions(width, height))
|
||||
|
||||
if lamb := config.get(CONF_LAMBDA):
|
||||
lambda_ = await cg.process_lambda(
|
||||
lamb, [(display.DisplayRef, "it")], return_type=cg.void
|
||||
)
|
||||
cg.add(var.set_writer(lambda_))
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue