mirror of
https://github.com/esphome/esphome.git
synced 2024-12-26 17:17:46 +01:00
commit
d2913fe627
4
.github/workflows/ci.yml
vendored
4
.github/workflows/ci.yml
vendored
@ -52,6 +52,10 @@ jobs:
|
||||
file: tests/test6.yaml
|
||||
name: Test tests/test6.yaml
|
||||
pio_cache_key: test6
|
||||
- id: test
|
||||
file: tests/test7.yaml
|
||||
name: Test tests/test7.yaml
|
||||
pio_cache_key: test7
|
||||
- id: pytest
|
||||
name: Run pytest
|
||||
- id: clang-format
|
||||
|
117
.github/workflows/release.yml
vendored
117
.github/workflows/release.yml
vendored
@ -30,6 +30,10 @@ jobs:
|
||||
TAG=$(cat esphome/const.py | sed -n -E "s/^__version__\s+=\s+\"(.+)\"$/\1/p")
|
||||
today="$(date --utc '+%Y%m%d')"
|
||||
TAG="${TAG}${today}"
|
||||
BRANCH=${GITHUB_REF#refs/heads/}
|
||||
if [[ "$BRANCH" != "dev" ]]; then
|
||||
TAG="${TAG}-${BRANCH}"
|
||||
fi
|
||||
fi
|
||||
echo "tag=${TAG}" >> $GITHUB_OUTPUT
|
||||
# yamllint enable rule:line-length
|
||||
@ -57,17 +61,30 @@ jobs:
|
||||
run: twine upload dist/*
|
||||
|
||||
deploy-docker:
|
||||
name: Build and publish docker containers
|
||||
name: Build and publish ESPHome ${{ matrix.image.title}}
|
||||
if: github.repository == 'esphome/esphome'
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
runs-on: ubuntu-latest
|
||||
continue-on-error: ${{ matrix.image.title == 'lint' }}
|
||||
needs: [init]
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
arch: [amd64, armv7, aarch64]
|
||||
build_type: ["ha-addon", "docker", "lint"]
|
||||
image:
|
||||
- title: "ha-addon"
|
||||
suffix: "hassio"
|
||||
target: "hassio"
|
||||
baseimg: "hassio"
|
||||
- title: "docker"
|
||||
suffix: ""
|
||||
target: "docker"
|
||||
baseimg: "docker"
|
||||
- title: "lint"
|
||||
suffix: "lint"
|
||||
target: "lint"
|
||||
baseimg: "docker"
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Set up Python
|
||||
@ -92,69 +109,47 @@ jobs:
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Generate short tags
|
||||
id: tags
|
||||
run: |
|
||||
docker/generate_tags.py \
|
||||
--tag "${{ needs.init.outputs.tag }}" \
|
||||
--suffix "${{ matrix.image.suffix }}"
|
||||
|
||||
- name: Build and push
|
||||
run: |
|
||||
docker/build.py \
|
||||
--tag "${{ needs.init.outputs.tag }}" \
|
||||
--arch "${{ matrix.arch }}" \
|
||||
--build-type "${{ matrix.build_type }}" \
|
||||
build \
|
||||
--push
|
||||
|
||||
deploy-docker-manifest:
|
||||
if: github.repository == 'esphome/esphome'
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
runs-on: ubuntu-latest
|
||||
needs: [init, deploy-docker]
|
||||
strategy:
|
||||
matrix:
|
||||
build_type: ["ha-addon", "docker", "lint"]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
python-version: "3.9"
|
||||
- name: Enable experimental manifest support
|
||||
run: |
|
||||
mkdir -p ~/.docker
|
||||
echo "{\"experimental\": \"enabled\"}" > ~/.docker/config.json
|
||||
|
||||
- name: Log in to docker hub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USER }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
- name: Log in to the GitHub container registry
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Run manifest
|
||||
run: |
|
||||
docker/build.py \
|
||||
--tag "${{ needs.init.outputs.tag }}" \
|
||||
--build-type "${{ matrix.build_type }}" \
|
||||
manifest
|
||||
context: .
|
||||
file: ./docker/Dockerfile
|
||||
platforms: linux/amd64,linux/arm/v7,linux/arm64
|
||||
target: ${{ matrix.image.target }}
|
||||
push: true
|
||||
# yamllint disable rule:line-length
|
||||
cache-from: type=registry,ref=ghcr.io/${{ steps.tags.outputs.image }}:cache-${{ steps.tags.outputs.channel }}
|
||||
cache-to: type=registry,ref=ghcr.io/${{ steps.tags.outputs.image }}:cache-${{ steps.tags.outputs.channel }},mode=max
|
||||
# yamllint enable rule:line-length
|
||||
tags: ${{ steps.tags.outputs.tags }}
|
||||
build-args: |
|
||||
BASEIMGTYPE=${{ matrix.image.baseimg }}
|
||||
BUILD_VERSION=${{ needs.init.outputs.tag }}
|
||||
|
||||
deploy-ha-addon-repo:
|
||||
if: github.repository == 'esphome/esphome' && github.event_name == 'release'
|
||||
runs-on: ubuntu-latest
|
||||
needs: [deploy-docker]
|
||||
steps:
|
||||
- env:
|
||||
TOKEN: ${{ secrets.DEPLOY_HA_ADDON_REPO_TOKEN }}
|
||||
# yamllint disable rule:line-length
|
||||
run: |
|
||||
TAG="${GITHUB_REF#refs/tags/}"
|
||||
curl \
|
||||
-u ":$TOKEN" \
|
||||
-X POST \
|
||||
-H "Accept: application/vnd.github.v3+json" \
|
||||
https://api.github.com/repos/esphome/home-assistant-addon/actions/workflows/bump-version.yml/dispatches \
|
||||
-d "{\"ref\":\"main\",\"inputs\":{\"version\":\"$TAG\"}}"
|
||||
# yamllint enable rule:line-length
|
||||
- name: Trigger Workflow
|
||||
uses: actions/github-script@v6
|
||||
with:
|
||||
github-token: ${{ secrets.DEPLOY_HA_ADDON_REPO_TOKEN }}
|
||||
script: |
|
||||
github.rest.actions.createWorkflowDispatch({
|
||||
owner: "esphome",
|
||||
repo: "home-assistant-addon",
|
||||
workflow_id: "bump-version.yml",
|
||||
ref: "main",
|
||||
inputs: {
|
||||
version: "${{ github.event.release.tag_name }}",
|
||||
content: "${{ toJSON(github.event.release.body) }}"
|
||||
}
|
||||
})
|
||||
|
4
.github/workflows/stale.yml
vendored
4
.github/workflows/stale.yml
vendored
@ -18,7 +18,7 @@ jobs:
|
||||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/stale@v6
|
||||
- uses: actions/stale@v7
|
||||
with:
|
||||
days-before-pr-stale: 90
|
||||
days-before-pr-close: 7
|
||||
@ -38,7 +38,7 @@ jobs:
|
||||
close-issues:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/stale@v6
|
||||
- uses: actions/stale@v7
|
||||
with:
|
||||
days-before-pr-stale: -1
|
||||
days-before-pr-close: -1
|
||||
|
@ -3,7 +3,7 @@
|
||||
# See https://pre-commit.com/hooks.html for more hooks
|
||||
repos:
|
||||
- repo: https://github.com/ambv/black
|
||||
rev: 22.10.0
|
||||
rev: 22.12.0
|
||||
hooks:
|
||||
- id: black
|
||||
args:
|
||||
|
20
CODEOWNERS
20
CODEOWNERS
@ -41,6 +41,8 @@ esphome/components/ble_client/* @buxtronix
|
||||
esphome/components/bluetooth_proxy/* @jesserockz
|
||||
esphome/components/bme680_bsec/* @trvrnrth
|
||||
esphome/components/bmp3xx/* @martgras
|
||||
esphome/components/bp1658cj/* @Cossid
|
||||
esphome/components/bp5758d/* @Cossid
|
||||
esphome/components/button/* @esphome/core
|
||||
esphome/components/canbus/* @danielschramm @mvturnho
|
||||
esphome/components/cap1188/* @MrEditor97
|
||||
@ -69,6 +71,7 @@ esphome/components/display_menu_base/* @numo68
|
||||
esphome/components/dps310/* @kbx81
|
||||
esphome/components/ds1307/* @badbadc0ffee
|
||||
esphome/components/dsmr/* @glmnet @zuidwijk
|
||||
esphome/components/ee895/* @Stock-M
|
||||
esphome/components/ektf2232/* @jesserockz
|
||||
esphome/components/ens210/* @itn3rd77
|
||||
esphome/components/esp32/* @esphome/core
|
||||
@ -100,9 +103,11 @@ esphome/components/hitachi_ac424/* @sourabhjaiswal
|
||||
esphome/components/homeassistant/* @OttoWinter
|
||||
esphome/components/honeywellabp/* @RubyBailey
|
||||
esphome/components/hrxl_maxsonar_wr/* @netmikey
|
||||
esphome/components/hte501/* @Stock-M
|
||||
esphome/components/hydreon_rgxx/* @functionpointer
|
||||
esphome/components/i2c/* @esphome/core
|
||||
esphome/components/i2s_audio/* @jesserockz
|
||||
esphome/components/improv_base/* @esphome/core
|
||||
esphome/components/improv_serial/* @esphome/core
|
||||
esphome/components/ina260/* @MrEditor97
|
||||
esphome/components/inkbird_ibsth1_mini/* @fkirill
|
||||
@ -111,13 +116,17 @@ esphome/components/integration/* @OttoWinter
|
||||
esphome/components/interval/* @esphome/core
|
||||
esphome/components/json/* @OttoWinter
|
||||
esphome/components/kalman_combinator/* @Cat-Ion
|
||||
esphome/components/key_collector/* @ssieb
|
||||
esphome/components/key_provider/* @ssieb
|
||||
esphome/components/lcd_menu/* @numo68
|
||||
esphome/components/ld2410/* @sebcaps
|
||||
esphome/components/ledc/* @OttoWinter
|
||||
esphome/components/light/* @esphome/core
|
||||
esphome/components/lilygo_t5_47/touchscreen/* @jesserockz
|
||||
esphome/components/lock/* @esphome/core
|
||||
esphome/components/logger/* @esphome/core
|
||||
esphome/components/ltr390/* @sjtrny
|
||||
esphome/components/matrix_keypad/* @ssieb
|
||||
esphome/components/max31865/* @DAVe3283
|
||||
esphome/components/max44009/* @berfenger
|
||||
esphome/components/max7219digit/* @rspaargaren
|
||||
@ -138,6 +147,7 @@ esphome/components/mcp9808/* @k7hpn
|
||||
esphome/components/md5/* @esphome/core
|
||||
esphome/components/mdns/* @esphome/core
|
||||
esphome/components/media_player/* @jesserockz
|
||||
esphome/components/mics_4514/* @jesserockz
|
||||
esphome/components/midea/* @dudanov
|
||||
esphome/components/midea_ir/* @dudanov
|
||||
esphome/components/mitsubishi/* @RubyBailey
|
||||
@ -164,6 +174,8 @@ esphome/components/nfc/* @jesserockz
|
||||
esphome/components/number/* @esphome/core
|
||||
esphome/components/ota/* @esphome/core
|
||||
esphome/components/output/* @esphome/core
|
||||
esphome/components/pca9554/* @hwstar
|
||||
esphome/components/pcf85063/* @brogon
|
||||
esphome/components/pid/* @OttoWinter
|
||||
esphome/components/pipsolar/* @andreashergert1984
|
||||
esphome/components/pm1006/* @habbie
|
||||
@ -204,8 +216,12 @@ esphome/components/sgp4x/* @SenexCrenshaw @martgras
|
||||
esphome/components/shelly_dimmer/* @edge90 @rnauber
|
||||
esphome/components/sht4x/* @sjtrny
|
||||
esphome/components/shutdown/* @esphome/core @jsuanet
|
||||
esphome/components/sigma_delta_output/* @Cat-Ion
|
||||
esphome/components/sim800l/* @glmnet
|
||||
esphome/components/sm10bit_base/* @Cossid
|
||||
esphome/components/sm2135/* @BoukeHaarsma23
|
||||
esphome/components/sm2235/* @Cossid
|
||||
esphome/components/sm2335/* @Cossid
|
||||
esphome/components/sml/* @alengwenus
|
||||
esphome/components/smt100/* @piechade
|
||||
esphome/components/sn74hc165/* @jesserockz
|
||||
@ -234,6 +250,7 @@ esphome/components/switch/* @esphome/core
|
||||
esphome/components/t6615/* @tylermenezes
|
||||
esphome/components/tca9548a/* @andreashergert1984
|
||||
esphome/components/tcl112/* @glmnet
|
||||
esphome/components/tee501/* @Stock-M
|
||||
esphome/components/teleinfo/* @0hax
|
||||
esphome/components/thermostat/* @kbx81
|
||||
esphome/components/time/* @OttoWinter
|
||||
@ -258,12 +275,15 @@ esphome/components/uart/* @esphome/core
|
||||
esphome/components/ufire_ec/* @pvizeli
|
||||
esphome/components/ufire_ise/* @pvizeli
|
||||
esphome/components/ultrasonic/* @OttoWinter
|
||||
esphome/components/vbus/* @ssieb
|
||||
esphome/components/version/* @esphome/core
|
||||
esphome/components/wake_on_lan/* @willwill2will54
|
||||
esphome/components/web_server_base/* @OttoWinter
|
||||
esphome/components/whirlpool/* @glmnet
|
||||
esphome/components/whynter/* @aeonsablaze
|
||||
esphome/components/wiegand/* @ssieb
|
||||
esphome/components/wl_134/* @hobbypunk90
|
||||
esphome/components/x9c/* @EtienneMD
|
||||
esphome/components/xiaomi_lywsd03mmc/* @ahpohl
|
||||
esphome/components/xiaomi_mhoc303/* @drug123
|
||||
esphome/components/xiaomi_mhoc401/* @vevsvevs
|
||||
|
@ -6,12 +6,15 @@
|
||||
ARG BASEIMGTYPE=docker
|
||||
|
||||
# https://github.com/hassio-addons/addon-debian-base/releases
|
||||
FROM ghcr.io/hassio-addons/debian-base:6.1.3 AS base-hassio
|
||||
FROM ghcr.io/hassio-addons/debian-base:6.2.0 AS base-hassio
|
||||
# https://hub.docker.com/_/debian?tab=tags&page=1&name=bullseye
|
||||
FROM debian:bullseye-20221024-slim AS base-docker
|
||||
|
||||
FROM base-${BASEIMGTYPE} AS base
|
||||
|
||||
ARG TARGETARCH
|
||||
ARG TARGETVARIANT
|
||||
|
||||
RUN \
|
||||
apt-get update \
|
||||
# Use pinned versions so that we get updates with build caching
|
||||
@ -36,6 +39,14 @@ ENV \
|
||||
# Store globally installed pio libs in /piolibs
|
||||
PLATFORMIO_GLOBALLIB_DIR=/piolibs
|
||||
|
||||
# Support legacy binaries on Debian multiarch system. There is no "correct" way
|
||||
# to do this, other than using properly built toolchains...
|
||||
# See: https://unix.stackexchange.com/questions/553743/correct-way-to-add-lib-ld-linux-so-3-in-debian
|
||||
RUN \
|
||||
if [ "$TARGETARCH$TARGETVARIANT" = "armv7" ]; then \
|
||||
ln -s /lib/arm-linux-gnueabihf/ld-linux.so.3 /lib/ld-linux.so.3; \
|
||||
fi
|
||||
|
||||
RUN \
|
||||
# Ubuntu python3-pip is missing wheel
|
||||
pip3 install --no-cache-dir \
|
||||
@ -128,7 +139,7 @@ RUN \
|
||||
clang-tidy-11=1:11.0.1-2 \
|
||||
patch=2.7.6-7 \
|
||||
software-properties-common=0.96.20.2-2.1 \
|
||||
nano=5.4-2+deb11u1 \
|
||||
nano=5.4-2+deb11u2 \
|
||||
build-essential=12.9 \
|
||||
python3-dev=3.9.2-3 \
|
||||
&& rm -rf \
|
||||
|
@ -8,32 +8,49 @@ import re
|
||||
import sys
|
||||
|
||||
|
||||
CHANNEL_DEV = 'dev'
|
||||
CHANNEL_BETA = 'beta'
|
||||
CHANNEL_RELEASE = 'release'
|
||||
CHANNEL_DEV = "dev"
|
||||
CHANNEL_BETA = "beta"
|
||||
CHANNEL_RELEASE = "release"
|
||||
CHANNELS = [CHANNEL_DEV, CHANNEL_BETA, CHANNEL_RELEASE]
|
||||
|
||||
ARCH_AMD64 = 'amd64'
|
||||
ARCH_ARMV7 = 'armv7'
|
||||
ARCH_AARCH64 = 'aarch64'
|
||||
ARCH_AMD64 = "amd64"
|
||||
ARCH_ARMV7 = "armv7"
|
||||
ARCH_AARCH64 = "aarch64"
|
||||
ARCHS = [ARCH_AMD64, ARCH_ARMV7, ARCH_AARCH64]
|
||||
|
||||
TYPE_DOCKER = 'docker'
|
||||
TYPE_HA_ADDON = 'ha-addon'
|
||||
TYPE_LINT = 'lint'
|
||||
TYPE_DOCKER = "docker"
|
||||
TYPE_HA_ADDON = "ha-addon"
|
||||
TYPE_LINT = "lint"
|
||||
TYPES = [TYPE_DOCKER, TYPE_HA_ADDON, TYPE_LINT]
|
||||
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--tag", type=str, required=True, help="The main docker tag to push to. If a version number also adds latest and/or beta tag")
|
||||
parser.add_argument("--arch", choices=ARCHS, required=False, help="The architecture to build for")
|
||||
parser.add_argument("--build-type", choices=TYPES, required=True, help="The type of build to run")
|
||||
parser.add_argument("--dry-run", action="store_true", help="Don't run any commands, just print them")
|
||||
subparsers = parser.add_subparsers(help="Action to perform", dest="command", required=True)
|
||||
parser.add_argument(
|
||||
"--tag",
|
||||
type=str,
|
||||
required=True,
|
||||
help="The main docker tag to push to. If a version number also adds latest and/or beta tag",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--arch", choices=ARCHS, required=False, help="The architecture to build for"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--build-type", choices=TYPES, required=True, help="The type of build to run"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--dry-run", action="store_true", help="Don't run any commands, just print them"
|
||||
)
|
||||
subparsers = parser.add_subparsers(
|
||||
help="Action to perform", dest="command", required=True
|
||||
)
|
||||
build_parser = subparsers.add_parser("build", help="Build the image")
|
||||
build_parser.add_argument("--push", help="Also push the images", action="store_true")
|
||||
build_parser.add_argument("--load", help="Load the docker image locally", action="store_true")
|
||||
manifest_parser = subparsers.add_parser("manifest", help="Create a manifest from already pushed images")
|
||||
build_parser.add_argument(
|
||||
"--load", help="Load the docker image locally", action="store_true"
|
||||
)
|
||||
manifest_parser = subparsers.add_parser(
|
||||
"manifest", help="Create a manifest from already pushed images"
|
||||
)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
@ -49,7 +66,7 @@ class DockerParams:
|
||||
prefix = {
|
||||
TYPE_DOCKER: "esphome/esphome",
|
||||
TYPE_HA_ADDON: "esphome/esphome-hassio",
|
||||
TYPE_LINT: "esphome/esphome-lint"
|
||||
TYPE_LINT: "esphome/esphome-lint",
|
||||
}[build_type]
|
||||
build_to = f"{prefix}-{arch}"
|
||||
baseimgtype = {
|
||||
@ -128,13 +145,21 @@ def main():
|
||||
|
||||
# 3. build
|
||||
cmd = [
|
||||
"docker", "buildx", "build",
|
||||
"--build-arg", f"BASEIMGTYPE={params.baseimgtype}",
|
||||
"--build-arg", f"BUILD_VERSION={args.tag}",
|
||||
"--cache-from", f"type=registry,ref={cache_img}",
|
||||
"--file", "docker/Dockerfile",
|
||||
"--platform", params.platform,
|
||||
"--target", params.target,
|
||||
"docker",
|
||||
"buildx",
|
||||
"build",
|
||||
"--build-arg",
|
||||
f"BASEIMGTYPE={params.baseimgtype}",
|
||||
"--build-arg",
|
||||
f"BUILD_VERSION={args.tag}",
|
||||
"--cache-from",
|
||||
f"type=registry,ref={cache_img}",
|
||||
"--file",
|
||||
"docker/Dockerfile",
|
||||
"--platform",
|
||||
params.platform,
|
||||
"--target",
|
||||
params.target,
|
||||
]
|
||||
for img in imgs:
|
||||
cmd += ["--tag", img]
|
||||
@ -160,9 +185,7 @@ def main():
|
||||
run_command(*cmd)
|
||||
# 2. Push manifests
|
||||
for target in targets:
|
||||
run_command(
|
||||
"docker", "manifest", "push", target
|
||||
)
|
||||
run_command("docker", "manifest", "push", target)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
68
docker/generate_tags.py
Executable file
68
docker/generate_tags.py
Executable file
@ -0,0 +1,68 @@
|
||||
#!/usr/bin/env python3
|
||||
import re
|
||||
import os
|
||||
import argparse
|
||||
import json
|
||||
|
||||
CHANNEL_DEV = "dev"
|
||||
CHANNEL_BETA = "beta"
|
||||
CHANNEL_RELEASE = "release"
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument(
|
||||
"--tag",
|
||||
type=str,
|
||||
required=True,
|
||||
help="The main docker tag to push to. If a version number also adds latest and/or beta tag",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--suffix",
|
||||
type=str,
|
||||
required=True,
|
||||
help="The suffix of the tag.",
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
args = parser.parse_args()
|
||||
|
||||
# detect channel from tag
|
||||
match = re.match(r"^(\d+\.\d+)(?:\.\d+)?(b\d+)?$", args.tag)
|
||||
major_minor_version = None
|
||||
if match is None:
|
||||
channel = CHANNEL_DEV
|
||||
elif match.group(2) is None:
|
||||
major_minor_version = match.group(1)
|
||||
channel = CHANNEL_RELEASE
|
||||
else:
|
||||
channel = CHANNEL_BETA
|
||||
|
||||
tags_to_push = [args.tag]
|
||||
if channel == CHANNEL_DEV:
|
||||
tags_to_push.append("dev")
|
||||
elif channel == CHANNEL_BETA:
|
||||
tags_to_push.append("beta")
|
||||
elif channel == CHANNEL_RELEASE:
|
||||
# Additionally push to beta
|
||||
tags_to_push.append("beta")
|
||||
tags_to_push.append("latest")
|
||||
|
||||
if major_minor_version:
|
||||
tags_to_push.append("stable")
|
||||
tags_to_push.append(major_minor_version)
|
||||
|
||||
suffix = f"-{args.suffix}" if args.suffix else ""
|
||||
|
||||
with open(os.environ["GITHUB_OUTPUT"], "w") as f:
|
||||
print(f"channel={channel}", file=f)
|
||||
print(f"image=esphome/esphome{suffix}", file=f)
|
||||
full_tags = []
|
||||
|
||||
for tag in tags_to_push:
|
||||
full_tags += [f"ghcr.io/esphome/esphome{suffix}:{tag}"]
|
||||
full_tags += [f"esphome/esphome{suffix}:{tag}"]
|
||||
print(f"tags={','.join(full_tags)}", file=f)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@ -1,41 +0,0 @@
|
||||
#!/usr/bin/with-contenv bashio
|
||||
# ==============================================================================
|
||||
# Community Hass.io Add-ons: ESPHome
|
||||
# This files check if all user configuration requirements are met
|
||||
# ==============================================================================
|
||||
|
||||
# Check SSL requirements, if enabled
|
||||
if bashio::config.true 'ssl'; then
|
||||
if ! bashio::config.has_value 'certfile'; then
|
||||
bashio::log.fatal 'SSL is enabled, but no certfile was specified.'
|
||||
bashio::exit.nok
|
||||
fi
|
||||
|
||||
if ! bashio::config.has_value 'keyfile'; then
|
||||
bashio::log.fatal 'SSL is enabled, but no keyfile was specified'
|
||||
bashio::exit.nok
|
||||
fi
|
||||
|
||||
|
||||
certfile="/ssl/$(bashio::config 'certfile')"
|
||||
keyfile="/ssl/$(bashio::config 'keyfile')"
|
||||
|
||||
if ! bashio::fs.file_exists "${certfile}"; then
|
||||
if ! bashio::fs.file_exists "${keyfile}"; then
|
||||
# Both files are missing, let's print a friendlier error message
|
||||
bashio::log.fatal 'You enabled encrypted connections using the "ssl": true option.'
|
||||
bashio::log.fatal "However, the SSL files '${certfile}' and '${keyfile}'"
|
||||
bashio::log.fatal "were not found. If you're using Hass.io on your local network and don't want"
|
||||
bashio::log.fatal 'to encrypt connections to the ESPHome dashboard, you can manually disable'
|
||||
bashio::log.fatal 'SSL by setting "ssl" to false."'
|
||||
bashio::exit.nok
|
||||
fi
|
||||
bashio::log.fatal "The configured certfile '${certfile}' was not found."
|
||||
bashio::exit.nok
|
||||
fi
|
||||
|
||||
if ! bashio::fs.file_exists "/ssl/$(bashio::config 'keyfile')"; then
|
||||
bashio::log.fatal "The configured keyfile '${keyfile}' was not found."
|
||||
bashio::exit.nok
|
||||
fi
|
||||
fi
|
@ -1,34 +0,0 @@
|
||||
#!/usr/bin/with-contenv bashio
|
||||
# ==============================================================================
|
||||
# Community Hass.io Add-ons: ESPHome
|
||||
# Configures NGINX for use with ESPHome
|
||||
# ==============================================================================
|
||||
|
||||
declare certfile
|
||||
declare keyfile
|
||||
declare direct_port
|
||||
declare ingress_interface
|
||||
declare ingress_port
|
||||
|
||||
mkdir -p /var/log/nginx
|
||||
|
||||
direct_port=$(bashio::addon.port 6052)
|
||||
if bashio::var.has_value "${direct_port}"; then
|
||||
if bashio::config.true 'ssl'; then
|
||||
certfile=$(bashio::config 'certfile')
|
||||
keyfile=$(bashio::config 'keyfile')
|
||||
|
||||
mv /etc/nginx/servers/direct-ssl.disabled /etc/nginx/servers/direct.conf
|
||||
sed -i "s/%%certfile%%/${certfile}/g" /etc/nginx/servers/direct.conf
|
||||
sed -i "s/%%keyfile%%/${keyfile}/g" /etc/nginx/servers/direct.conf
|
||||
else
|
||||
mv /etc/nginx/servers/direct.disabled /etc/nginx/servers/direct.conf
|
||||
fi
|
||||
|
||||
sed -i "s/%%port%%/${direct_port}/g" /etc/nginx/servers/direct.conf
|
||||
fi
|
||||
|
||||
ingress_port=$(bashio::addon.ingress_port)
|
||||
ingress_interface=$(bashio::addon.ip_address)
|
||||
sed -i "s/%%port%%/${ingress_port}/g" /etc/nginx/servers/ingress.conf
|
||||
sed -i "s/%%interface%%/${ingress_interface}/g" /etc/nginx/servers/ingress.conf
|
@ -1,9 +0,0 @@
|
||||
#!/usr/bin/with-contenv bashio
|
||||
# ==============================================================================
|
||||
# Community Hass.io Add-ons: ESPHome
|
||||
# This files creates all directories used by esphome
|
||||
# ==============================================================================
|
||||
|
||||
pio_cache_base=/data/cache/platformio
|
||||
|
||||
mkdir -p "${pio_cache_base}"
|
@ -1,9 +1,9 @@
|
||||
proxy_http_version 1.1;
|
||||
proxy_ignore_client_abort off;
|
||||
proxy_read_timeout 86400s;
|
||||
proxy_redirect off;
|
||||
proxy_send_timeout 86400s;
|
||||
proxy_max_temp_file_size 0;
|
||||
proxy_http_version 1.1;
|
||||
proxy_ignore_client_abort off;
|
||||
proxy_read_timeout 86400s;
|
||||
proxy_redirect off;
|
||||
proxy_send_timeout 86400s;
|
||||
proxy_max_temp_file_size 0;
|
||||
|
||||
proxy_set_header Accept-Encoding "";
|
||||
proxy_set_header Connection $connection_upgrade;
|
||||
|
@ -1,5 +1,7 @@
|
||||
root /dev/null;
|
||||
server_name $hostname;
|
||||
root /dev/null;
|
||||
server_name $hostname;
|
||||
|
||||
client_max_body_size 512m;
|
||||
|
||||
add_header X-Content-Type-Options nosniff;
|
||||
add_header X-XSS-Protection "1; mode=block";
|
||||
|
@ -1,7 +1,6 @@
|
||||
ssl_protocols TLSv1.2;
|
||||
ssl_prefer_server_ciphers on;
|
||||
ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:DHE-RSA-AES256-SHA;
|
||||
ssl_ecdh_curve secp384r1;
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
ssl_prefer_server_ciphers off;
|
||||
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
|
||||
ssl_session_timeout 10m;
|
||||
ssl_session_cache shared:SSL:10m;
|
||||
ssl_session_tickets off;
|
||||
|
3
docker/ha-addon-rootfs/etc/nginx/includes/upstream.conf
Normal file
3
docker/ha-addon-rootfs/etc/nginx/includes/upstream.conf
Normal file
@ -0,0 +1,3 @@
|
||||
upstream esphome {
|
||||
server unix:/var/run/esphome.sock;
|
||||
}
|
@ -2,7 +2,6 @@ daemon off;
|
||||
user root;
|
||||
pid /var/run/nginx.pid;
|
||||
worker_processes 1;
|
||||
# Hass.io addon log
|
||||
error_log /proc/1/fd/1 error;
|
||||
events {
|
||||
worker_connections 1024;
|
||||
@ -10,24 +9,22 @@ events {
|
||||
|
||||
http {
|
||||
include /etc/nginx/includes/mime.types;
|
||||
access_log stdout;
|
||||
default_type application/octet-stream;
|
||||
gzip on;
|
||||
keepalive_timeout 65;
|
||||
sendfile on;
|
||||
server_tokens off;
|
||||
|
||||
access_log off;
|
||||
default_type application/octet-stream;
|
||||
gzip on;
|
||||
keepalive_timeout 65;
|
||||
sendfile on;
|
||||
server_tokens off;
|
||||
|
||||
tcp_nodelay on;
|
||||
tcp_nopush on;
|
||||
|
||||
map $http_upgrade $connection_upgrade {
|
||||
default upgrade;
|
||||
'' close;
|
||||
}
|
||||
|
||||
# Use Hass.io supervisor as resolver
|
||||
resolver 172.30.32.2;
|
||||
|
||||
upstream esphome {
|
||||
server unix:/var/run/esphome.sock;
|
||||
}
|
||||
|
||||
include /etc/nginx/includes/upstream.conf;
|
||||
include /etc/nginx/servers/*.conf;
|
||||
}
|
||||
|
1
docker/ha-addon-rootfs/etc/nginx/servers/.gitkeep
Normal file
1
docker/ha-addon-rootfs/etc/nginx/servers/.gitkeep
Normal file
@ -0,0 +1 @@
|
||||
Without requirements or design, programming is the art of adding bugs to an empty text file. (Louis Srygley)
|
@ -1,12 +0,0 @@
|
||||
server {
|
||||
listen %%port%% default_server;
|
||||
|
||||
include /etc/nginx/includes/server_params.conf;
|
||||
include /etc/nginx/includes/proxy_params.conf;
|
||||
# Clear Hass.io Ingress header
|
||||
proxy_set_header X-HA-Ingress "";
|
||||
|
||||
location / {
|
||||
proxy_pass http://esphome;
|
||||
}
|
||||
}
|
@ -1,20 +1,26 @@
|
||||
server {
|
||||
listen %%port%% default_server ssl http2;
|
||||
{{ if not .ssl }}
|
||||
listen 6052 default_server;
|
||||
{{ else }}
|
||||
listen 6052 default_server ssl http2;
|
||||
{{ end }}
|
||||
|
||||
include /etc/nginx/includes/server_params.conf;
|
||||
include /etc/nginx/includes/proxy_params.conf;
|
||||
|
||||
{{ if .ssl }}
|
||||
include /etc/nginx/includes/ssl_params.conf;
|
||||
|
||||
ssl on;
|
||||
ssl_certificate /ssl/%%certfile%%;
|
||||
ssl_certificate_key /ssl/%%keyfile%%;
|
||||
|
||||
# Clear Hass.io Ingress header
|
||||
proxy_set_header X-HA-Ingress "";
|
||||
ssl_certificate /ssl/{{ .certfile }};
|
||||
ssl_certificate_key /ssl/{{ .keyfile }};
|
||||
|
||||
# Redirect http requests to https on the same port.
|
||||
# https://rageagainstshell.com/2016/11/redirect-http-to-https-on-the-same-port-in-nginx/
|
||||
error_page 497 https://$http_host$request_uri;
|
||||
{{ end }}
|
||||
|
||||
# Clear Home Assistant Ingress header
|
||||
proxy_set_header X-HA-Ingress "";
|
||||
|
||||
location / {
|
||||
proxy_pass http://esphome;
|
@ -1,14 +1,16 @@
|
||||
server {
|
||||
listen %%interface%%:%%port%% default_server;
|
||||
listen 127.0.0.1:{{ .port }} default_server;
|
||||
listen {{ .interface }}:{{ .port }} default_server;
|
||||
|
||||
include /etc/nginx/includes/server_params.conf;
|
||||
include /etc/nginx/includes/proxy_params.conf;
|
||||
|
||||
# Set Home Assistant Ingress header
|
||||
proxy_set_header X-HA-Ingress "YES";
|
||||
|
||||
location / {
|
||||
# Only allow from Hass.io supervisor
|
||||
allow 172.30.32.2;
|
||||
allow 127.0.0.1;
|
||||
deny all;
|
||||
|
||||
proxy_pass http://esphome;
|
32
docker/ha-addon-rootfs/etc/s6-overlay/s6-rc.d/discovery/run
Executable file
32
docker/ha-addon-rootfs/etc/s6-overlay/s6-rc.d/discovery/run
Executable file
@ -0,0 +1,32 @@
|
||||
#!/command/with-contenv bashio
|
||||
# shellcheck shell=bash
|
||||
# ==============================================================================
|
||||
# Home Assistant Add-on: ESPHome
|
||||
# Sends discovery information to Home Assistant.
|
||||
# ==============================================================================
|
||||
declare config
|
||||
declare port
|
||||
|
||||
# We only disable it when disabled explicitly
|
||||
if bashio::config.false 'home_assistant_dashboard_integration';
|
||||
then
|
||||
bashio::log.info "Home Assistant discovery is disabled for this add-on."
|
||||
bashio::exit.ok
|
||||
fi
|
||||
|
||||
port=$(bashio::addon.ingress_port)
|
||||
|
||||
# Wait for NGINX to become available
|
||||
bashio::net.wait_for "${port}" "127.0.0.1" 300
|
||||
|
||||
config=$(\
|
||||
bashio::var.json \
|
||||
host "127.0.0.1" \
|
||||
port "^${port}" \
|
||||
)
|
||||
|
||||
if bashio::discovery "esphome" "${config}" > /dev/null; then
|
||||
bashio::log.info "Successfully send discovery information to Home Assistant."
|
||||
else
|
||||
bashio::log.error "Discovery message to Home Assistant failed!"
|
||||
fi
|
@ -0,0 +1 @@
|
||||
oneshot
|
@ -0,0 +1 @@
|
||||
/etc/s6-overlay/s6-rc.d/discovery/run
|
26
docker/ha-addon-rootfs/etc/s6-overlay/s6-rc.d/esphome/finish
Executable file
26
docker/ha-addon-rootfs/etc/s6-overlay/s6-rc.d/esphome/finish
Executable file
@ -0,0 +1,26 @@
|
||||
#!/command/with-contenv bashio
|
||||
# shellcheck shell=bash
|
||||
# ==============================================================================
|
||||
# Home Assistant Community Add-on: ESPHome
|
||||
# Take down the S6 supervision tree when ESPHome dashboard fails
|
||||
# ==============================================================================
|
||||
declare exit_code
|
||||
readonly exit_code_container=$(</run/s6-linux-init-container-results/exitcode)
|
||||
readonly exit_code_service="${1}"
|
||||
readonly exit_code_signal="${2}"
|
||||
|
||||
bashio::log.info \
|
||||
"Service ESPHome dashboard exited with code ${exit_code_service}" \
|
||||
"(by signal ${exit_code_signal})"
|
||||
|
||||
if [[ "${exit_code_service}" -eq 256 ]]; then
|
||||
if [[ "${exit_code_container}" -eq 0 ]]; then
|
||||
echo $((128 + $exit_code_signal)) > /run/s6-linux-init-container-results/exitcode
|
||||
fi
|
||||
[[ "${exit_code_signal}" -eq 15 ]] && exec /run/s6/basedir/bin/halt
|
||||
elif [[ "${exit_code_service}" -ne 0 ]]; then
|
||||
if [[ "${exit_code_container}" -eq 0 ]]; then
|
||||
echo "${exit_code_service}" > /run/s6-linux-init-container-results/exitcode
|
||||
fi
|
||||
exec /run/s6/basedir/bin/halt
|
||||
fi
|
@ -1,10 +1,19 @@
|
||||
#!/usr/bin/with-contenv bashio
|
||||
#!/command/with-contenv bashio
|
||||
# shellcheck shell=bash
|
||||
# ==============================================================================
|
||||
# Community Hass.io Add-ons: ESPHome
|
||||
# Runs the ESPHome dashboard
|
||||
# ==============================================================================
|
||||
readonly pio_cache_base=/data/cache/platformio
|
||||
|
||||
export ESPHOME_IS_HA_ADDON=true
|
||||
export PLATFORMIO_GLOBALLIB_DIR=/piolibs
|
||||
|
||||
# we can't set core_dir, because the settings file is stored in `core_dir/appstate.json`
|
||||
# setting `core_dir` would therefore prevent pio from accessing
|
||||
export PLATFORMIO_PLATFORMS_DIR="${pio_cache_base}/platforms"
|
||||
export PLATFORMIO_PACKAGES_DIR="${pio_cache_base}/packages"
|
||||
export PLATFORMIO_CACHE_DIR="${pio_cache_base}/cache"
|
||||
|
||||
if bashio::config.true 'leave_front_door_open'; then
|
||||
export DISABLE_HA_AUTHENTICATION=true
|
||||
@ -30,14 +39,7 @@ else
|
||||
fi
|
||||
fi
|
||||
|
||||
pio_cache_base=/data/cache/platformio
|
||||
# we can't set core_dir, because the settings file is stored in `core_dir/appstate.json`
|
||||
# setting `core_dir` would therefore prevent pio from accessing
|
||||
export PLATFORMIO_PLATFORMS_DIR="${pio_cache_base}/platforms"
|
||||
export PLATFORMIO_PACKAGES_DIR="${pio_cache_base}/packages"
|
||||
export PLATFORMIO_CACHE_DIR="${pio_cache_base}/cache"
|
||||
|
||||
export PLATFORMIO_GLOBALLIB_DIR=/piolibs
|
||||
mkdir -p "${pio_cache_base}"
|
||||
|
||||
bashio::log.info "Starting ESPHome dashboard..."
|
||||
exec esphome dashboard /config/esphome --socket /var/run/esphome.sock --ha-addon
|
@ -0,0 +1 @@
|
||||
longrun
|
27
docker/ha-addon-rootfs/etc/s6-overlay/s6-rc.d/init-nginx/run
Executable file
27
docker/ha-addon-rootfs/etc/s6-overlay/s6-rc.d/init-nginx/run
Executable file
@ -0,0 +1,27 @@
|
||||
#!/command/with-contenv bashio
|
||||
# shellcheck shell=bash
|
||||
# ==============================================================================
|
||||
# Community Hass.io Add-ons: ESPHome
|
||||
# Configures NGINX for use with ESPHome
|
||||
# ==============================================================================
|
||||
mkdir -p /var/log/nginx
|
||||
|
||||
# Generate Ingress configuration
|
||||
bashio::var.json \
|
||||
interface "$(bashio::addon.ip_address)" \
|
||||
port "^$(bashio::addon.ingress_port)" \
|
||||
| tempio \
|
||||
-template /etc/nginx/templates/ingress.gtpl \
|
||||
-out /etc/nginx/servers/ingress.conf
|
||||
|
||||
# Generate direct access configuration, if enabled.
|
||||
if bashio::var.has_value "$(bashio::addon.port 6052)"; then
|
||||
bashio::config.require.ssl
|
||||
bashio::var.json \
|
||||
certfile "$(bashio::config 'certfile')" \
|
||||
keyfile "$(bashio::config 'keyfile')" \
|
||||
ssl "^$(bashio::config 'ssl')" \
|
||||
| tempio \
|
||||
-template /etc/nginx/templates/direct.gtpl \
|
||||
-out /etc/nginx/servers/direct.conf
|
||||
fi
|
@ -0,0 +1 @@
|
||||
oneshot
|
@ -0,0 +1 @@
|
||||
/etc/s6-overlay/s6-rc.d/init-nginx/run
|
25
docker/ha-addon-rootfs/etc/s6-overlay/s6-rc.d/nginx/finish
Executable file
25
docker/ha-addon-rootfs/etc/s6-overlay/s6-rc.d/nginx/finish
Executable file
@ -0,0 +1,25 @@
|
||||
#!/command/with-contenv bashio
|
||||
# ==============================================================================
|
||||
# Community Hass.io Add-ons: ESPHome
|
||||
# Take down the S6 supervision tree when NGINX fails
|
||||
# ==============================================================================
|
||||
declare exit_code
|
||||
readonly exit_code_container=$(</run/s6-linux-init-container-results/exitcode)
|
||||
readonly exit_code_service="${1}"
|
||||
readonly exit_code_signal="${2}"
|
||||
|
||||
bashio::log.info \
|
||||
"Service NGINX exited with code ${exit_code_service}" \
|
||||
"(by signal ${exit_code_signal})"
|
||||
|
||||
if [[ "${exit_code_service}" -eq 256 ]]; then
|
||||
if [[ "${exit_code_container}" -eq 0 ]]; then
|
||||
echo $((128 + $exit_code_signal)) > /run/s6-linux-init-container-results/exitcode
|
||||
fi
|
||||
[[ "${exit_code_signal}" -eq 15 ]] && exec /run/s6/basedir/bin/halt
|
||||
elif [[ "${exit_code_service}" -ne 0 ]]; then
|
||||
if [[ "${exit_code_container}" -eq 0 ]]; then
|
||||
echo "${exit_code_service}" > /run/s6-linux-init-container-results/exitcode
|
||||
fi
|
||||
exec /run/s6/basedir/bin/halt
|
||||
fi
|
@ -1,10 +1,11 @@
|
||||
#!/usr/bin/with-contenv bashio
|
||||
#!/command/with-contenv bashio
|
||||
# shellcheck shell=bash
|
||||
# ==============================================================================
|
||||
# Community Hass.io Add-ons: ESPHome
|
||||
# Runs the NGINX proxy
|
||||
# ==============================================================================
|
||||
|
||||
bashio::log.info "Waiting for dashboard to come up..."
|
||||
bashio::log.info "Waiting for ESPHome dashboard to come up..."
|
||||
|
||||
while [[ ! -S /var/run/esphome.sock ]]; do
|
||||
sleep 0.5
|
1
docker/ha-addon-rootfs/etc/s6-overlay/s6-rc.d/nginx/type
Normal file
1
docker/ha-addon-rootfs/etc/s6-overlay/s6-rc.d/nginx/type
Normal file
@ -0,0 +1 @@
|
||||
longrun
|
@ -1,15 +0,0 @@
|
||||
#!/usr/bin/execlineb -S0
|
||||
# ==============================================================================
|
||||
# Community Hass.io Add-ons: ESPHome
|
||||
# Take down the S6 supervision tree when ESPHome fails
|
||||
# ==============================================================================
|
||||
|
||||
declare APP_EXIT_CODE=${1}
|
||||
|
||||
if [[ "${APP_EXIT_CODE}" -ne 0 ]] && [[ "${APP_EXIT_CODE}" -ne 256 ]]; then
|
||||
bashio::log.warning "Halt add-on with exit code ${APP_EXIT_CODE}"
|
||||
echo "${APP_EXIT_CODE}" > /run/s6-linux-init-container-results/exitcode
|
||||
exec /run/s6/basedir/bin/halt
|
||||
fi
|
||||
|
||||
bashio::log.info "Service restart after closing"
|
@ -1,15 +0,0 @@
|
||||
#!/usr/bin/execlineb -S0
|
||||
# ==============================================================================
|
||||
# Community Hass.io Add-ons: ESPHome
|
||||
# Take down the S6 supervision tree when NGINX fails
|
||||
# ==============================================================================
|
||||
|
||||
declare APP_EXIT_CODE=${1}
|
||||
|
||||
if [[ "${APP_EXIT_CODE}" -ne 0 ]] && [[ "${APP_EXIT_CODE}" -ne 256 ]]; then
|
||||
bashio::log.warning "Halt add-on with exit code ${APP_EXIT_CODE}"
|
||||
echo "${APP_EXIT_CODE}" > /run/s6-linux-init-container-results/exitcode
|
||||
exec /run/s6/basedir/bin/halt
|
||||
fi
|
||||
|
||||
bashio::log.info "Service restart after closing"
|
@ -339,7 +339,7 @@ def command_config(args, config):
|
||||
_LOGGER.info("Configuration is valid!")
|
||||
if not CORE.verbose:
|
||||
config = strip_default_ids(config)
|
||||
safe_print(yaml_util.dump(config))
|
||||
safe_print(yaml_util.dump(config, args.show_secrets))
|
||||
return 0
|
||||
|
||||
|
||||
@ -665,6 +665,9 @@ def parse_args(argv):
|
||||
parser_config.add_argument(
|
||||
"configuration", help="Your YAML configuration file(s).", nargs="+"
|
||||
)
|
||||
parser_config.add_argument(
|
||||
"--show-secrets", help="Show secrets in output.", action="store_true"
|
||||
)
|
||||
|
||||
parser_compile = subparsers.add_parser(
|
||||
"compile", help="Read the configuration and compile a program."
|
||||
|
@ -46,6 +46,7 @@ void A4988::loop() {
|
||||
return;
|
||||
|
||||
this->dir_pin_->digital_write(dir == 1);
|
||||
delayMicroseconds(50);
|
||||
this->step_pin_->digital_write(true);
|
||||
delayMicroseconds(5);
|
||||
this->step_pin_->digital_write(false);
|
||||
|
@ -9,7 +9,7 @@ static const char *const TAG = "ads1115";
|
||||
static const uint8_t ADS1115_REGISTER_CONVERSION = 0x00;
|
||||
static const uint8_t ADS1115_REGISTER_CONFIG = 0x01;
|
||||
|
||||
static const uint8_t ADS1115_DATA_RATE_860_SPS = 0b111;
|
||||
static const uint8_t ADS1115_DATA_RATE_860_SPS = 0b111; // 3300_SPS for ADS1015
|
||||
|
||||
void ADS1115Component::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up ADS1115...");
|
||||
@ -18,6 +18,9 @@ void ADS1115Component::setup() {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGCONFIG(TAG, "Configuring ADS1115...");
|
||||
|
||||
uint16_t config = 0;
|
||||
// Clear single-shot bit
|
||||
// 0b0xxxxxxxxxxxxxxx
|
||||
@ -77,6 +80,7 @@ void ADS1115Component::dump_config() {
|
||||
LOG_SENSOR(" ", "Sensor", sensor);
|
||||
ESP_LOGCONFIG(TAG, " Multiplexer: %u", sensor->get_multiplexer());
|
||||
ESP_LOGCONFIG(TAG, " Gain: %u", sensor->get_gain());
|
||||
ESP_LOGCONFIG(TAG, " Resolution: %u", sensor->get_resolution());
|
||||
}
|
||||
}
|
||||
float ADS1115Component::request_measurement(ADS1115Sensor *sensor) {
|
||||
@ -127,27 +131,45 @@ float ADS1115Component::request_measurement(ADS1115Sensor *sensor) {
|
||||
this->status_set_warning();
|
||||
return NAN;
|
||||
}
|
||||
|
||||
if (sensor->get_resolution() == ADS1015_12_BITS) {
|
||||
bool negative = (raw_conversion >> 15) == 1;
|
||||
|
||||
// shift raw_conversion as it's only 12-bits, left justified
|
||||
raw_conversion = raw_conversion >> (16 - ADS1015_12_BITS);
|
||||
|
||||
// check if number was negative in order to keep the sign
|
||||
if (negative) {
|
||||
// the number was negative
|
||||
// 1) set the negative bit back
|
||||
raw_conversion |= 0x8000;
|
||||
// 2) reset the former (shifted) negative bit
|
||||
raw_conversion &= 0xF7FF;
|
||||
}
|
||||
}
|
||||
|
||||
auto signed_conversion = static_cast<int16_t>(raw_conversion);
|
||||
|
||||
float millivolts;
|
||||
float divider = (sensor->get_resolution() == ADS1115_16_BITS) ? 32768.0f : 2048.0f;
|
||||
switch (sensor->get_gain()) {
|
||||
case ADS1115_GAIN_6P144:
|
||||
millivolts = signed_conversion * 0.187500f;
|
||||
millivolts = (signed_conversion * 6144) / divider;
|
||||
break;
|
||||
case ADS1115_GAIN_4P096:
|
||||
millivolts = signed_conversion * 0.125000f;
|
||||
millivolts = (signed_conversion * 4096) / divider;
|
||||
break;
|
||||
case ADS1115_GAIN_2P048:
|
||||
millivolts = signed_conversion * 0.062500f;
|
||||
millivolts = (signed_conversion * 2048) / divider;
|
||||
break;
|
||||
case ADS1115_GAIN_1P024:
|
||||
millivolts = signed_conversion * 0.031250f;
|
||||
millivolts = (signed_conversion * 1024) / divider;
|
||||
break;
|
||||
case ADS1115_GAIN_0P512:
|
||||
millivolts = signed_conversion * 0.015625f;
|
||||
millivolts = (signed_conversion * 512) / divider;
|
||||
break;
|
||||
case ADS1115_GAIN_0P256:
|
||||
millivolts = signed_conversion * 0.007813f;
|
||||
millivolts = (signed_conversion * 256) / divider;
|
||||
break;
|
||||
default:
|
||||
millivolts = NAN;
|
||||
|
@ -30,6 +30,11 @@ enum ADS1115Gain {
|
||||
ADS1115_GAIN_0P256 = 0b101,
|
||||
};
|
||||
|
||||
enum ADS1115Resolution {
|
||||
ADS1115_16_BITS = 16,
|
||||
ADS1015_12_BITS = 12,
|
||||
};
|
||||
|
||||
class ADS1115Sensor;
|
||||
|
||||
class ADS1115Component : public Component, public i2c::I2CDevice {
|
||||
@ -58,15 +63,17 @@ class ADS1115Sensor : public sensor::Sensor, public PollingComponent, public vol
|
||||
void update() override;
|
||||
void set_multiplexer(ADS1115Multiplexer multiplexer) { multiplexer_ = multiplexer; }
|
||||
void set_gain(ADS1115Gain gain) { gain_ = gain; }
|
||||
|
||||
void set_resolution(ADS1115Resolution resolution) { resolution_ = resolution; }
|
||||
float sample() override;
|
||||
uint8_t get_multiplexer() const { return multiplexer_; }
|
||||
uint8_t get_gain() const { return gain_; }
|
||||
uint8_t get_resolution() const { return resolution_; }
|
||||
|
||||
protected:
|
||||
ADS1115Component *parent_;
|
||||
ADS1115Multiplexer multiplexer_;
|
||||
ADS1115Gain gain_;
|
||||
ADS1115Resolution resolution_;
|
||||
};
|
||||
|
||||
} // namespace ads1115
|
||||
|
@ -4,6 +4,7 @@ from esphome.components import sensor, voltage_sampler
|
||||
from esphome.const import (
|
||||
CONF_GAIN,
|
||||
CONF_MULTIPLEXER,
|
||||
CONF_RESOLUTION,
|
||||
DEVICE_CLASS_VOLTAGE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_VOLT,
|
||||
@ -35,6 +36,12 @@ GAIN = {
|
||||
"0.256": ADS1115Gain.ADS1115_GAIN_0P256,
|
||||
}
|
||||
|
||||
ADS1115Resolution = ads1115_ns.enum("ADS1115Resolution")
|
||||
RESOLUTION = {
|
||||
"16_BITS": ADS1115Resolution.ADS1115_16_BITS,
|
||||
"12_BITS": ADS1115Resolution.ADS1015_12_BITS,
|
||||
}
|
||||
|
||||
|
||||
def validate_gain(value):
|
||||
if isinstance(value, float):
|
||||
@ -63,6 +70,9 @@ CONFIG_SCHEMA = (
|
||||
cv.GenerateID(CONF_ADS1115_ID): cv.use_id(ADS1115Component),
|
||||
cv.Required(CONF_MULTIPLEXER): cv.enum(MUX, upper=True, space="_"),
|
||||
cv.Required(CONF_GAIN): validate_gain,
|
||||
cv.Optional(CONF_RESOLUTION, default="16_BITS"): cv.enum(
|
||||
RESOLUTION, upper=True, space="_"
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(cv.polling_component_schema("60s"))
|
||||
@ -77,5 +87,6 @@ async def to_code(config):
|
||||
|
||||
cg.add(var.set_multiplexer(config[CONF_MULTIPLEXER]))
|
||||
cg.add(var.set_gain(config[CONF_GAIN]))
|
||||
cg.add(var.set_resolution(config[CONF_RESOLUTION]))
|
||||
|
||||
cg.add(paren.register_sensor(var))
|
||||
|
@ -117,7 +117,7 @@ async def to_code(config):
|
||||
data[pos] = rgb & 255
|
||||
pos += 1
|
||||
|
||||
elif config[CONF_TYPE] == "BINARY":
|
||||
elif config[CONF_TYPE] in ["BINARY", "TRANSPARENT_BINARY"]:
|
||||
width8 = ((width + 7) // 8) * 8
|
||||
data = [0 for _ in range((height * width8 // 8) * frames)]
|
||||
for frameIndex in range(frames):
|
||||
|
@ -23,7 +23,7 @@ void APDS9960::setup() {
|
||||
return;
|
||||
}
|
||||
|
||||
if (id != 0xAB && id != 0x9C) { // APDS9960 all should have one of these IDs
|
||||
if (id != 0xAB && id != 0x9C && id != 0xA8) { // APDS9960 all should have one of these IDs
|
||||
this->error_code_ = WRONG_ID;
|
||||
this->mark_failed();
|
||||
return;
|
||||
|
@ -206,6 +206,8 @@ message DeviceInfoResponse {
|
||||
uint32 bluetooth_proxy_version = 11;
|
||||
|
||||
string manufacturer = 12;
|
||||
|
||||
string friendly_name = 13;
|
||||
}
|
||||
|
||||
message ListEntitiesRequest {
|
||||
@ -785,6 +787,7 @@ enum ClimateFanMode {
|
||||
CLIMATE_FAN_MIDDLE = 6;
|
||||
CLIMATE_FAN_FOCUS = 7;
|
||||
CLIMATE_FAN_DIFFUSE = 8;
|
||||
CLIMATE_FAN_QUIET = 9;
|
||||
}
|
||||
enum ClimateSwingMode {
|
||||
CLIMATE_SWING_OFF = 0;
|
||||
|
@ -930,6 +930,7 @@ DeviceInfoResponse APIConnection::device_info(const DeviceInfoRequest &msg) {
|
||||
DeviceInfoResponse resp{};
|
||||
resp.uses_password = this->parent_->uses_password();
|
||||
resp.name = App.get_name();
|
||||
resp.friendly_name = App.get_friendly_name();
|
||||
resp.mac_address = get_mac_address_pretty();
|
||||
resp.esphome_version = ESPHOME_VERSION;
|
||||
resp.compilation_time = App.get_compilation_time();
|
||||
|
@ -616,6 +616,9 @@ APIError APINoiseFrameHelper::write_frame_(const uint8_t *data, size_t len) {
|
||||
struct iovec iov[2];
|
||||
iov[0].iov_base = header;
|
||||
iov[0].iov_len = 3;
|
||||
if (len == 0) {
|
||||
return write_raw_(iov, 1);
|
||||
}
|
||||
iov[1].iov_base = const_cast<uint8_t *>(data);
|
||||
iov[1].iov_len = len;
|
||||
|
||||
@ -913,6 +916,9 @@ APIError APIPlaintextFrameHelper::write_packet(uint16_t type, const uint8_t *pay
|
||||
struct iovec iov[2];
|
||||
iov[0].iov_base = &header[0];
|
||||
iov[0].iov_len = header.size();
|
||||
if (payload_len == 0) {
|
||||
return write_raw_(iov, 1);
|
||||
}
|
||||
iov[1].iov_base = const_cast<uint8_t *>(payload);
|
||||
iov[1].iov_len = payload_len;
|
||||
|
||||
|
@ -235,6 +235,8 @@ template<> const char *proto_enum_to_string<enums::ClimateFanMode>(enums::Climat
|
||||
return "CLIMATE_FAN_FOCUS";
|
||||
case enums::CLIMATE_FAN_DIFFUSE:
|
||||
return "CLIMATE_FAN_DIFFUSE";
|
||||
case enums::CLIMATE_FAN_QUIET:
|
||||
return "CLIMATE_FAN_QUIET";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
@ -628,6 +630,10 @@ bool DeviceInfoResponse::decode_length(uint32_t field_id, ProtoLengthDelimited v
|
||||
this->manufacturer = value.as_string();
|
||||
return true;
|
||||
}
|
||||
case 13: {
|
||||
this->friendly_name = value.as_string();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@ -645,6 +651,7 @@ void DeviceInfoResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_uint32(10, this->webserver_port);
|
||||
buffer.encode_uint32(11, this->bluetooth_proxy_version);
|
||||
buffer.encode_string(12, this->manufacturer);
|
||||
buffer.encode_string(13, this->friendly_name);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void DeviceInfoResponse::dump_to(std::string &out) const {
|
||||
@ -699,6 +706,10 @@ void DeviceInfoResponse::dump_to(std::string &out) const {
|
||||
out.append(" manufacturer: ");
|
||||
out.append("'").append(this->manufacturer).append("'");
|
||||
out.append("\n");
|
||||
|
||||
out.append(" friendly_name: ");
|
||||
out.append("'").append(this->friendly_name).append("'");
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
|
@ -99,6 +99,7 @@ enum ClimateFanMode : uint32_t {
|
||||
CLIMATE_FAN_MIDDLE = 6,
|
||||
CLIMATE_FAN_FOCUS = 7,
|
||||
CLIMATE_FAN_DIFFUSE = 8,
|
||||
CLIMATE_FAN_QUIET = 9,
|
||||
};
|
||||
enum ClimateSwingMode : uint32_t {
|
||||
CLIMATE_SWING_OFF = 0,
|
||||
@ -276,6 +277,7 @@ class DeviceInfoResponse : public ProtoMessage {
|
||||
uint32_t webserver_port{0};
|
||||
uint32_t bluetooth_proxy_version{0};
|
||||
std::string manufacturer{};
|
||||
std::string friendly_name{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
|
@ -37,9 +37,13 @@ void BedJetFan::control(const fan::FanCall &call) {
|
||||
|
||||
// ignore speed changes if not on or turning on
|
||||
if (this->state && call.get_speed().has_value()) {
|
||||
this->speed = *call.get_speed();
|
||||
this->parent_->set_fan_index(this->speed);
|
||||
did_change = true;
|
||||
auto speed = *call.get_speed();
|
||||
if (speed >= 1) {
|
||||
this->speed = speed;
|
||||
// Fan.speed is 1-20, but Bedjet expects 0-19, so subtract 1
|
||||
this->parent_->set_fan_index(this->speed - 1);
|
||||
did_change = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (did_change) {
|
||||
@ -57,8 +61,9 @@ void BedJetFan::on_status(const BedjetStatusPacket *data) {
|
||||
did_change = true;
|
||||
}
|
||||
|
||||
if (data->fan_step != this->speed) {
|
||||
this->speed = data->fan_step;
|
||||
// BedjetStatusPacket.fan_step is in range 0-19, but Fan.speed wants 1-20.
|
||||
if (data->fan_step + 1 != this->speed) {
|
||||
this->speed = data->fan_step + 1;
|
||||
did_change = true;
|
||||
}
|
||||
|
||||
|
@ -88,7 +88,10 @@ void BME280Component::setup() {
|
||||
|
||||
// Mark as not failed before initializing. Some devices will turn off sensors to save on batteries
|
||||
// and when they come back on, the COMPONENT_STATE_FAILED bit must be unset on the component.
|
||||
this->component_state_ &= ~COMPONENT_STATE_FAILED;
|
||||
if ((this->component_state_ & COMPONENT_STATE_MASK) == COMPONENT_STATE_FAILED) {
|
||||
this->component_state_ &= ~COMPONENT_STATE_MASK;
|
||||
this->component_state_ |= COMPONENT_STATE_CONSTRUCTION;
|
||||
}
|
||||
|
||||
if (!this->read_byte(BME280_REGISTER_CHIPID, &chip_id)) {
|
||||
this->error_code_ = COMMUNICATION_FAILED;
|
||||
|
@ -1,4 +1,5 @@
|
||||
#include "bmp280.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
@ -11,8 +12,11 @@ static const uint8_t BMP280_REGISTER_CONTROL = 0xF4;
|
||||
static const uint8_t BMP280_REGISTER_CONFIG = 0xF5;
|
||||
static const uint8_t BMP280_REGISTER_PRESSUREDATA = 0xF7;
|
||||
static const uint8_t BMP280_REGISTER_TEMPDATA = 0xFA;
|
||||
static const uint8_t BMP280_REGISTER_RESET = 0xE0;
|
||||
|
||||
static const uint8_t BMP280_MODE_FORCED = 0b01;
|
||||
static const uint8_t BMP280_SOFT_RESET = 0xB6;
|
||||
static const uint8_t BMP280_STATUS_IM_UPDATE = 0b01;
|
||||
|
||||
inline uint16_t combine_bytes(uint8_t msb, uint8_t lsb) { return ((msb & 0xFF) << 8) | (lsb & 0xFF); }
|
||||
|
||||
@ -66,6 +70,28 @@ void BMP280Component::setup() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Send a soft reset.
|
||||
if (!this->write_byte(BMP280_REGISTER_RESET, BMP280_SOFT_RESET)) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
// Wait until the NVM data has finished loading.
|
||||
uint8_t status;
|
||||
uint8_t retry = 5;
|
||||
do {
|
||||
delay(2);
|
||||
if (!this->read_byte(BMP280_REGISTER_STATUS, &status)) {
|
||||
ESP_LOGW(TAG, "Error reading status register.");
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
} while ((status & BMP280_STATUS_IM_UPDATE) && (--retry));
|
||||
if (status & BMP280_STATUS_IM_UPDATE) {
|
||||
ESP_LOGW(TAG, "Timeout loading NVM.");
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
// Read calibration
|
||||
this->calibration_.t1 = this->read_u16_le_(0x88);
|
||||
this->calibration_.t2 = this->read_s16_le_(0x8A);
|
||||
|
44
esphome/components/bp1658cj/__init__.py
Normal file
44
esphome/components/bp1658cj/__init__.py
Normal file
@ -0,0 +1,44 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import pins
|
||||
from esphome.const import (
|
||||
CONF_CLOCK_PIN,
|
||||
CONF_DATA_PIN,
|
||||
CONF_ID,
|
||||
)
|
||||
|
||||
CODEOWNERS = ["@Cossid"]
|
||||
MULTI_CONF = True
|
||||
|
||||
CONF_MAX_POWER_COLOR_CHANNELS = "max_power_color_channels"
|
||||
CONF_MAX_POWER_WHITE_CHANNELS = "max_power_white_channels"
|
||||
|
||||
AUTO_LOAD = ["output"]
|
||||
bp1658cj_ns = cg.esphome_ns.namespace("bp1658cj")
|
||||
BP1658CJ = bp1658cj_ns.class_("BP1658CJ", cg.Component)
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(BP1658CJ),
|
||||
cv.Required(CONF_DATA_PIN): pins.gpio_output_pin_schema,
|
||||
cv.Required(CONF_CLOCK_PIN): pins.gpio_output_pin_schema,
|
||||
cv.Optional(CONF_MAX_POWER_COLOR_CHANNELS, default=2): cv.int_range(
|
||||
min=0, max=15
|
||||
),
|
||||
cv.Optional(CONF_MAX_POWER_WHITE_CHANNELS, default=4): cv.int_range(
|
||||
min=0, max=15
|
||||
),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
|
||||
data = await cg.gpio_pin_expression(config[CONF_DATA_PIN])
|
||||
cg.add(var.set_data_pin(data))
|
||||
clock = await cg.gpio_pin_expression(config[CONF_CLOCK_PIN])
|
||||
cg.add(var.set_clock_pin(clock))
|
||||
cg.add(var.set_max_power_color_channels(config[CONF_MAX_POWER_COLOR_CHANNELS]))
|
||||
cg.add(var.set_max_power_white_channels(config[CONF_MAX_POWER_WHITE_CHANNELS]))
|
110
esphome/components/bp1658cj/bp1658cj.cpp
Normal file
110
esphome/components/bp1658cj/bp1658cj.cpp
Normal file
@ -0,0 +1,110 @@
|
||||
#include "bp1658cj.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace bp1658cj {
|
||||
|
||||
static const char *const TAG = "bp1658cj";
|
||||
|
||||
static const uint8_t BP1658CJ_MODEL_ID = 0x80;
|
||||
static const uint8_t BP1658CJ_ADDR_STANDBY = 0x0;
|
||||
static const uint8_t BP1658CJ_ADDR_START_3CH = 0x10;
|
||||
static const uint8_t BP1658CJ_ADDR_START_2CH = 0x20;
|
||||
static const uint8_t BP1658CJ_ADDR_START_5CH = 0x30;
|
||||
|
||||
void BP1658CJ::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up BP1658CJ Output Component...");
|
||||
this->data_pin_->setup();
|
||||
this->data_pin_->digital_write(false);
|
||||
this->clock_pin_->setup();
|
||||
this->clock_pin_->digital_write(false);
|
||||
this->pwm_amounts_.resize(5, 0);
|
||||
}
|
||||
void BP1658CJ::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "BP1658CJ:");
|
||||
LOG_PIN(" Data Pin: ", this->data_pin_);
|
||||
LOG_PIN(" Clock Pin: ", this->clock_pin_);
|
||||
ESP_LOGCONFIG(TAG, " Color Channels Max Power: %u", this->max_power_color_channels_);
|
||||
ESP_LOGCONFIG(TAG, " White Channels Max Power: %u", this->max_power_white_channels_);
|
||||
}
|
||||
|
||||
void BP1658CJ::loop() {
|
||||
if (!this->update_)
|
||||
return;
|
||||
|
||||
uint8_t data[12];
|
||||
if (this->pwm_amounts_[0] == 0 && this->pwm_amounts_[1] == 0 && this->pwm_amounts_[2] == 0 &&
|
||||
this->pwm_amounts_[3] == 0 && this->pwm_amounts_[4] == 0) {
|
||||
// Off / Sleep
|
||||
data[0] = BP1658CJ_MODEL_ID + BP1658CJ_ADDR_STANDBY;
|
||||
for (int i = 1; i < 12; i++)
|
||||
data[i] = 0;
|
||||
this->write_buffer_(data, 12);
|
||||
} else if (this->pwm_amounts_[0] == 0 && this->pwm_amounts_[1] == 0 && this->pwm_amounts_[2] == 0 &&
|
||||
(this->pwm_amounts_[3] > 0 || this->pwm_amounts_[4] > 0)) {
|
||||
// Only data on white channels
|
||||
data[0] = BP1658CJ_MODEL_ID + BP1658CJ_ADDR_START_2CH;
|
||||
data[1] = 0 << 4 | this->max_power_white_channels_;
|
||||
for (int i = 2, j = 0; i < 12; i += 2, j++) {
|
||||
data[i] = this->pwm_amounts_[j] & 0x1F;
|
||||
data[i + 1] = (this->pwm_amounts_[j] >> 5) & 0x1F;
|
||||
}
|
||||
this->write_buffer_(data, 12);
|
||||
} else if ((this->pwm_amounts_[0] > 0 || this->pwm_amounts_[1] > 0 || this->pwm_amounts_[2] > 0) &&
|
||||
this->pwm_amounts_[3] == 0 && this->pwm_amounts_[4] == 0) {
|
||||
// Only data on RGB channels
|
||||
data[0] = BP1658CJ_MODEL_ID + BP1658CJ_ADDR_START_3CH;
|
||||
data[1] = this->max_power_color_channels_ << 4 | 0;
|
||||
for (int i = 2, j = 0; i < 12; i += 2, j++) {
|
||||
data[i] = this->pwm_amounts_[j] & 0x1F;
|
||||
data[i + 1] = (this->pwm_amounts_[j] >> 5) & 0x1F;
|
||||
}
|
||||
this->write_buffer_(data, 12);
|
||||
} else {
|
||||
// All channels
|
||||
data[0] = BP1658CJ_MODEL_ID + BP1658CJ_ADDR_START_5CH;
|
||||
data[1] = this->max_power_color_channels_ << 4 | this->max_power_white_channels_;
|
||||
for (int i = 2, j = 0; i < 12; i += 2, j++) {
|
||||
data[i] = this->pwm_amounts_[j] & 0x1F;
|
||||
data[i + 1] = (this->pwm_amounts_[j] >> 5) & 0x1F;
|
||||
}
|
||||
this->write_buffer_(data, 12);
|
||||
}
|
||||
|
||||
this->update_ = false;
|
||||
}
|
||||
|
||||
void BP1658CJ::set_channel_value_(uint8_t channel, uint16_t value) {
|
||||
if (this->pwm_amounts_[channel] != value) {
|
||||
this->update_ = true;
|
||||
this->update_channel_ = channel;
|
||||
}
|
||||
this->pwm_amounts_[channel] = value;
|
||||
}
|
||||
void BP1658CJ::write_bit_(bool value) {
|
||||
this->clock_pin_->digital_write(false);
|
||||
this->data_pin_->digital_write(value);
|
||||
this->clock_pin_->digital_write(true);
|
||||
}
|
||||
|
||||
void BP1658CJ::write_byte_(uint8_t data) {
|
||||
for (uint8_t mask = 0x80; mask; mask >>= 1) {
|
||||
this->write_bit_(data & mask);
|
||||
}
|
||||
this->clock_pin_->digital_write(false);
|
||||
this->data_pin_->digital_write(true);
|
||||
this->clock_pin_->digital_write(true);
|
||||
}
|
||||
|
||||
void BP1658CJ::write_buffer_(uint8_t *buffer, uint8_t size) {
|
||||
this->data_pin_->digital_write(false);
|
||||
for (uint32_t i = 0; i < size; i++) {
|
||||
this->write_byte_(buffer[i]);
|
||||
}
|
||||
this->clock_pin_->digital_write(false);
|
||||
this->clock_pin_->digital_write(true);
|
||||
this->data_pin_->digital_write(true);
|
||||
}
|
||||
|
||||
} // namespace bp1658cj
|
||||
} // namespace esphome
|
64
esphome/components/bp1658cj/bp1658cj.h
Normal file
64
esphome/components/bp1658cj/bp1658cj.h
Normal file
@ -0,0 +1,64 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/components/output/float_output.h"
|
||||
#include <vector>
|
||||
|
||||
namespace esphome {
|
||||
namespace bp1658cj {
|
||||
|
||||
class BP1658CJ : public Component {
|
||||
public:
|
||||
class Channel;
|
||||
|
||||
void set_data_pin(GPIOPin *data_pin) { data_pin_ = data_pin; }
|
||||
void set_clock_pin(GPIOPin *clock_pin) { clock_pin_ = clock_pin; }
|
||||
void set_max_power_color_channels(uint8_t max_power_color_channels) {
|
||||
max_power_color_channels_ = max_power_color_channels;
|
||||
}
|
||||
void set_max_power_white_channels(uint8_t max_power_white_channels) {
|
||||
max_power_white_channels_ = max_power_white_channels;
|
||||
}
|
||||
|
||||
void setup() override;
|
||||
|
||||
void dump_config() override;
|
||||
|
||||
float get_setup_priority() const override { return setup_priority::HARDWARE; }
|
||||
|
||||
/// Send new values if they were updated.
|
||||
void loop() override;
|
||||
|
||||
class Channel : public output::FloatOutput {
|
||||
public:
|
||||
void set_parent(BP1658CJ *parent) { parent_ = parent; }
|
||||
void set_channel(uint8_t channel) { channel_ = channel; }
|
||||
|
||||
protected:
|
||||
void write_state(float state) override {
|
||||
auto amount = static_cast<uint16_t>(state * 0x3FF);
|
||||
this->parent_->set_channel_value_(this->channel_, amount);
|
||||
}
|
||||
|
||||
BP1658CJ *parent_;
|
||||
uint8_t channel_;
|
||||
};
|
||||
|
||||
protected:
|
||||
void set_channel_value_(uint8_t channel, uint16_t value);
|
||||
void write_bit_(bool value);
|
||||
void write_byte_(uint8_t data);
|
||||
void write_buffer_(uint8_t *buffer, uint8_t size);
|
||||
|
||||
GPIOPin *data_pin_;
|
||||
GPIOPin *clock_pin_;
|
||||
uint8_t max_power_color_channels_{4};
|
||||
uint8_t max_power_white_channels_{6};
|
||||
uint8_t update_channel_;
|
||||
std::vector<uint16_t> pwm_amounts_;
|
||||
bool update_{true};
|
||||
};
|
||||
|
||||
} // namespace bp1658cj
|
||||
} // namespace esphome
|
27
esphome/components/bp1658cj/output.py
Normal file
27
esphome/components/bp1658cj/output.py
Normal file
@ -0,0 +1,27 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import output
|
||||
from esphome.const import CONF_CHANNEL, CONF_ID
|
||||
from . import BP1658CJ
|
||||
|
||||
DEPENDENCIES = ["bp1658cj"]
|
||||
|
||||
Channel = BP1658CJ.class_("Channel", output.FloatOutput)
|
||||
|
||||
CONF_BP1658CJ_ID = "bp1658cj_id"
|
||||
CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(CONF_BP1658CJ_ID): cv.use_id(BP1658CJ),
|
||||
cv.Required(CONF_ID): cv.declare_id(Channel),
|
||||
cv.Required(CONF_CHANNEL): cv.int_range(min=0, max=65535),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await output.register_output(var, config)
|
||||
|
||||
parent = await cg.get_variable(config[CONF_BP1658CJ_ID])
|
||||
cg.add(var.set_parent(parent))
|
||||
cg.add(var.set_channel(config[CONF_CHANNEL]))
|
33
esphome/components/bp5758d/__init__.py
Normal file
33
esphome/components/bp5758d/__init__.py
Normal file
@ -0,0 +1,33 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import pins
|
||||
from esphome.const import (
|
||||
CONF_CLOCK_PIN,
|
||||
CONF_DATA_PIN,
|
||||
CONF_ID,
|
||||
)
|
||||
|
||||
CODEOWNERS = ["@Cossid"]
|
||||
MULTI_CONF = True
|
||||
|
||||
AUTO_LOAD = ["output"]
|
||||
bp5758d_ns = cg.esphome_ns.namespace("bp5758d")
|
||||
BP5758D = bp5758d_ns.class_("BP5758D", cg.Component)
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(BP5758D),
|
||||
cv.Required(CONF_DATA_PIN): pins.gpio_output_pin_schema,
|
||||
cv.Required(CONF_CLOCK_PIN): pins.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)
|
||||
|
||||
data = await cg.gpio_pin_expression(config[CONF_DATA_PIN])
|
||||
cg.add(var.set_data_pin(data))
|
||||
clock = await cg.gpio_pin_expression(config[CONF_CLOCK_PIN])
|
||||
cg.add(var.set_clock_pin(clock))
|
147
esphome/components/bp5758d/bp5758d.cpp
Normal file
147
esphome/components/bp5758d/bp5758d.cpp
Normal file
@ -0,0 +1,147 @@
|
||||
#include "bp5758d.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace bp5758d {
|
||||
|
||||
static const char *const TAG = "bp5758d";
|
||||
|
||||
static const uint8_t BP5758D_MODEL_ID = 0b10000000;
|
||||
static const uint8_t BP5758D_ADDR_STANDBY = 0b00000000;
|
||||
// Note, channel start address seems ambiguous and mis-translated.
|
||||
// Documentation states all are "invalid sleep"
|
||||
// All 3 values appear to activate all 5 channels. Using the mapping
|
||||
// from BP1658CJ ordering since it won't break anything.
|
||||
static const uint8_t BP5758D_ADDR_START_3CH = 0b00010000;
|
||||
static const uint8_t BP5758D_ADDR_START_2CH = 0b00100000;
|
||||
static const uint8_t BP5758D_ADDR_START_5CH = 0b00110000;
|
||||
static const uint8_t BP5758D_ALL_DATA_CHANNEL_ENABLEMENT = 0b00011111;
|
||||
|
||||
void BP5758D::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up BP5758D Output Component...");
|
||||
this->data_pin_->setup();
|
||||
this->data_pin_->digital_write(false);
|
||||
this->clock_pin_->setup();
|
||||
this->clock_pin_->digital_write(false);
|
||||
this->channel_current_.resize(5, 0);
|
||||
this->pwm_amounts_.resize(5, 0);
|
||||
}
|
||||
void BP5758D::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "BP5758D:");
|
||||
LOG_PIN(" Data Pin: ", this->data_pin_);
|
||||
LOG_PIN(" Clock Pin: ", this->clock_pin_);
|
||||
}
|
||||
|
||||
void BP5758D::loop() {
|
||||
if (!this->update_)
|
||||
return;
|
||||
|
||||
uint8_t data[17];
|
||||
if (this->pwm_amounts_[0] == 0 && this->pwm_amounts_[1] == 0 && this->pwm_amounts_[2] == 0 &&
|
||||
this->pwm_amounts_[3] == 0 && this->pwm_amounts_[4] == 0) {
|
||||
// Off / Sleep
|
||||
data[0] = BP5758D_MODEL_ID + BP5758D_ADDR_STANDBY;
|
||||
for (int i = 1; i < 16; i++)
|
||||
data[i] = 0;
|
||||
this->write_buffer_(data, 17);
|
||||
} else if (this->pwm_amounts_[0] == 0 && this->pwm_amounts_[1] == 0 && this->pwm_amounts_[2] == 0 &&
|
||||
(this->pwm_amounts_[3] > 0 || this->pwm_amounts_[4] > 0)) {
|
||||
// Only data on white channels
|
||||
data[0] = BP5758D_MODEL_ID + BP5758D_ADDR_START_2CH;
|
||||
data[1] = BP5758D_ALL_DATA_CHANNEL_ENABLEMENT;
|
||||
data[2] = 0;
|
||||
data[3] = 0;
|
||||
data[4] = 0;
|
||||
data[5] = this->pwm_amounts_[3] > 0 ? correct_current_level_bits_(this->channel_current_[3]) : 0;
|
||||
data[6] = this->pwm_amounts_[4] > 0 ? correct_current_level_bits_(this->channel_current_[4]) : 0;
|
||||
for (int i = 7, j = 0; i <= 15; i += 2, j++) {
|
||||
data[i] = this->pwm_amounts_[j] & 0x1F;
|
||||
data[i + 1] = (this->pwm_amounts_[j] >> 5) & 0x1F;
|
||||
}
|
||||
this->write_buffer_(data, 17);
|
||||
} else if ((this->pwm_amounts_[0] > 0 || this->pwm_amounts_[1] > 0 || this->pwm_amounts_[2] > 0) &&
|
||||
this->pwm_amounts_[3] == 0 && this->pwm_amounts_[4] == 0) {
|
||||
// Only data on RGB channels
|
||||
data[0] = BP5758D_MODEL_ID + BP5758D_ADDR_START_3CH;
|
||||
data[1] = BP5758D_ALL_DATA_CHANNEL_ENABLEMENT;
|
||||
data[2] = this->pwm_amounts_[0] > 0 ? correct_current_level_bits_(this->channel_current_[0]) : 0;
|
||||
data[3] = this->pwm_amounts_[1] > 0 ? correct_current_level_bits_(this->channel_current_[1]) : 0;
|
||||
data[4] = this->pwm_amounts_[2] > 0 ? correct_current_level_bits_(this->channel_current_[2]) : 0;
|
||||
data[5] = 0;
|
||||
data[6] = 0;
|
||||
for (int i = 7, j = 0; i <= 15; i += 2, j++) {
|
||||
data[i] = this->pwm_amounts_[j] & 0x1F;
|
||||
data[i + 1] = (this->pwm_amounts_[j] >> 5) & 0x1F;
|
||||
}
|
||||
this->write_buffer_(data, 17);
|
||||
} else {
|
||||
// All channels
|
||||
data[0] = BP5758D_MODEL_ID + BP5758D_ADDR_START_5CH;
|
||||
data[1] = BP5758D_ALL_DATA_CHANNEL_ENABLEMENT;
|
||||
data[2] = this->pwm_amounts_[0] > 0 ? correct_current_level_bits_(this->channel_current_[0]) : 0;
|
||||
data[3] = this->pwm_amounts_[1] > 0 ? correct_current_level_bits_(this->channel_current_[1]) : 0;
|
||||
data[4] = this->pwm_amounts_[2] > 0 ? correct_current_level_bits_(this->channel_current_[2]) : 0;
|
||||
data[5] = this->pwm_amounts_[3] > 0 ? correct_current_level_bits_(this->channel_current_[3]) : 0;
|
||||
data[6] = this->pwm_amounts_[4] > 0 ? correct_current_level_bits_(this->channel_current_[4]) : 0;
|
||||
for (int i = 7, j = 0; i <= 15; i += 2, j++) {
|
||||
data[i] = this->pwm_amounts_[j] & 0x1F;
|
||||
data[i + 1] = (this->pwm_amounts_[j] >> 5) & 0x1F;
|
||||
}
|
||||
this->write_buffer_(data, 17);
|
||||
}
|
||||
|
||||
this->update_ = false;
|
||||
}
|
||||
|
||||
uint8_t BP5758D::correct_current_level_bits_(uint8_t current) {
|
||||
// Anything below 64 uses normal bitmapping.
|
||||
if (current < 64) {
|
||||
return current;
|
||||
}
|
||||
|
||||
// Anything above 63 needs to be offset by +34 because the driver remaps bit 7 (normally 64) to 30.
|
||||
// (no idea why(!) but it is documented)
|
||||
// Example:
|
||||
// integer 64 would normally put out 0b01000000 but here 0b01000000 = 30 whereas everything lower
|
||||
// is normal, so we add 34 to the integer where
|
||||
// integer 98 = 0b01100010 which is 30 (7th bit adjusted) + 34 (1st-6th bits).
|
||||
return current + 34;
|
||||
}
|
||||
|
||||
void BP5758D::set_channel_value_(uint8_t channel, uint16_t value) {
|
||||
if (this->pwm_amounts_[channel] != value) {
|
||||
this->update_ = true;
|
||||
this->update_channel_ = channel;
|
||||
}
|
||||
this->pwm_amounts_[channel] = value;
|
||||
}
|
||||
|
||||
void BP5758D::set_channel_current_(uint8_t channel, uint8_t current) { this->channel_current_[channel] = current; }
|
||||
|
||||
void BP5758D::write_bit_(bool value) {
|
||||
this->clock_pin_->digital_write(false);
|
||||
this->data_pin_->digital_write(value);
|
||||
this->clock_pin_->digital_write(true);
|
||||
}
|
||||
|
||||
void BP5758D::write_byte_(uint8_t data) {
|
||||
for (uint8_t mask = 0x80; mask; mask >>= 1) {
|
||||
this->write_bit_(data & mask);
|
||||
}
|
||||
this->clock_pin_->digital_write(false);
|
||||
this->data_pin_->digital_write(true);
|
||||
this->clock_pin_->digital_write(true);
|
||||
}
|
||||
|
||||
void BP5758D::write_buffer_(uint8_t *buffer, uint8_t size) {
|
||||
this->data_pin_->digital_write(false);
|
||||
for (uint32_t i = 0; i < size; i++) {
|
||||
this->write_byte_(buffer[i]);
|
||||
}
|
||||
this->clock_pin_->digital_write(false);
|
||||
this->clock_pin_->digital_write(true);
|
||||
this->data_pin_->digital_write(true);
|
||||
}
|
||||
|
||||
} // namespace bp5758d
|
||||
} // namespace esphome
|
64
esphome/components/bp5758d/bp5758d.h
Normal file
64
esphome/components/bp5758d/bp5758d.h
Normal file
@ -0,0 +1,64 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/components/output/float_output.h"
|
||||
#include <vector>
|
||||
|
||||
namespace esphome {
|
||||
namespace bp5758d {
|
||||
|
||||
class BP5758D : public Component {
|
||||
public:
|
||||
class Channel;
|
||||
|
||||
void set_data_pin(GPIOPin *data_pin) { data_pin_ = data_pin; }
|
||||
void set_clock_pin(GPIOPin *clock_pin) { clock_pin_ = clock_pin; }
|
||||
|
||||
void setup() override;
|
||||
|
||||
void dump_config() override;
|
||||
|
||||
float get_setup_priority() const override { return setup_priority::HARDWARE; }
|
||||
|
||||
/// Send new values if they were updated.
|
||||
void loop() override;
|
||||
|
||||
class Channel : public output::FloatOutput {
|
||||
public:
|
||||
void set_parent(BP5758D *parent) { parent_ = parent; }
|
||||
void set_channel(uint8_t channel) { channel_ = channel; }
|
||||
void set_current(uint8_t current) { current_ = current; }
|
||||
|
||||
protected:
|
||||
void write_state(float state) override {
|
||||
auto amount = static_cast<uint16_t>(state * 0x3FF);
|
||||
// We're enforcing channels start at 1 to mach OUT1-OUT5, we must adjust
|
||||
// to our 0-based array internally here by subtracting 1.
|
||||
this->parent_->set_channel_value_(this->channel_ - 1, amount);
|
||||
this->parent_->set_channel_current_(this->channel_ - 1, this->current_);
|
||||
}
|
||||
|
||||
BP5758D *parent_;
|
||||
uint8_t channel_;
|
||||
uint8_t current_;
|
||||
};
|
||||
|
||||
protected:
|
||||
uint8_t correct_current_level_bits_(uint8_t current);
|
||||
void set_channel_value_(uint8_t channel, uint16_t value);
|
||||
void set_channel_current_(uint8_t channel, uint8_t current);
|
||||
void write_bit_(bool value);
|
||||
void write_byte_(uint8_t data);
|
||||
void write_buffer_(uint8_t *buffer, uint8_t size);
|
||||
|
||||
GPIOPin *data_pin_;
|
||||
GPIOPin *clock_pin_;
|
||||
uint8_t update_channel_;
|
||||
std::vector<uint8_t> channel_current_;
|
||||
std::vector<uint16_t> pwm_amounts_;
|
||||
bool update_{true};
|
||||
};
|
||||
|
||||
} // namespace bp5758d
|
||||
} // namespace esphome
|
29
esphome/components/bp5758d/output.py
Normal file
29
esphome/components/bp5758d/output.py
Normal file
@ -0,0 +1,29 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import output
|
||||
from esphome.const import CONF_CHANNEL, CONF_ID, CONF_CURRENT
|
||||
from . import BP5758D
|
||||
|
||||
DEPENDENCIES = ["bp5758d"]
|
||||
|
||||
Channel = BP5758D.class_("Channel", output.FloatOutput)
|
||||
|
||||
CONF_BP5758D_ID = "bp5758d_id"
|
||||
CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(CONF_BP5758D_ID): cv.use_id(BP5758D),
|
||||
cv.Required(CONF_ID): cv.declare_id(Channel),
|
||||
cv.Required(CONF_CHANNEL): cv.int_range(min=1, max=5),
|
||||
cv.Optional(CONF_CURRENT, default=10): cv.int_range(min=0, max=90),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await output.register_output(var, config)
|
||||
|
||||
parent = await cg.get_variable(config[CONF_BP5758D_ID])
|
||||
cg.add(var.set_parent(parent))
|
||||
cg.add(var.set_channel(config[CONF_CHANNEL]))
|
||||
cg.add(var.set_current(config[CONF_CURRENT]))
|
@ -22,6 +22,8 @@ from esphome.const import (
|
||||
CONF_MODE_STATE_TOPIC,
|
||||
CONF_ON_STATE,
|
||||
CONF_PRESET,
|
||||
CONF_PRESET_COMMAND_TOPIC,
|
||||
CONF_PRESET_STATE_TOPIC,
|
||||
CONF_SWING_MODE,
|
||||
CONF_SWING_MODE_COMMAND_TOPIC,
|
||||
CONF_SWING_MODE_STATE_TOPIC,
|
||||
@ -73,6 +75,7 @@ CLIMATE_FAN_MODES = {
|
||||
"MIDDLE": ClimateFanMode.CLIMATE_FAN_MIDDLE,
|
||||
"FOCUS": ClimateFanMode.CLIMATE_FAN_FOCUS,
|
||||
"DIFFUSE": ClimateFanMode.CLIMATE_FAN_DIFFUSE,
|
||||
"QUIET": ClimateFanMode.CLIMATE_FAN_QUIET,
|
||||
}
|
||||
|
||||
validate_climate_fan_mode = cv.enum(CLIMATE_FAN_MODES, upper=True)
|
||||
@ -142,6 +145,12 @@ CLIMATE_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).
|
||||
cv.Optional(CONF_MODE_STATE_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.publish_topic
|
||||
),
|
||||
cv.Optional(CONF_PRESET_COMMAND_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.publish_topic
|
||||
),
|
||||
cv.Optional(CONF_PRESET_STATE_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.publish_topic
|
||||
),
|
||||
cv.Optional(CONF_SWING_MODE_COMMAND_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.publish_topic
|
||||
),
|
||||
@ -216,7 +225,12 @@ async def setup_climate_core_(var, config):
|
||||
cg.add(mqtt_.set_custom_mode_command_topic(config[CONF_MODE_COMMAND_TOPIC]))
|
||||
if CONF_MODE_STATE_TOPIC in config:
|
||||
cg.add(mqtt_.set_custom_mode_state_topic(config[CONF_MODE_STATE_TOPIC]))
|
||||
|
||||
if CONF_PRESET_COMMAND_TOPIC in config:
|
||||
cg.add(
|
||||
mqtt_.set_custom_preset_command_topic(config[CONF_PRESET_COMMAND_TOPIC])
|
||||
)
|
||||
if CONF_PRESET_STATE_TOPIC in config:
|
||||
cg.add(mqtt_.set_custom_preset_state_topic(config[CONF_PRESET_STATE_TOPIC]))
|
||||
if CONF_SWING_MODE_COMMAND_TOPIC in config:
|
||||
cg.add(
|
||||
mqtt_.set_custom_swing_mode_command_topic(
|
||||
|
@ -174,6 +174,8 @@ ClimateCall &ClimateCall::set_fan_mode(const std::string &fan_mode) {
|
||||
this->set_fan_mode(CLIMATE_FAN_FOCUS);
|
||||
} else if (str_equals_case_insensitive(fan_mode, "DIFFUSE")) {
|
||||
this->set_fan_mode(CLIMATE_FAN_DIFFUSE);
|
||||
} else if (str_equals_case_insensitive(fan_mode, "QUIET")) {
|
||||
this->set_fan_mode(CLIMATE_FAN_QUIET);
|
||||
} else {
|
||||
if (this->parent_->get_traits().supports_custom_fan_mode(fan_mode)) {
|
||||
this->custom_fan_mode_ = fan_mode;
|
||||
|
@ -62,6 +62,8 @@ const LogString *climate_fan_mode_to_string(ClimateFanMode fan_mode) {
|
||||
return LOG_STR("FOCUS");
|
||||
case climate::CLIMATE_FAN_DIFFUSE:
|
||||
return LOG_STR("DIFFUSE");
|
||||
case climate::CLIMATE_FAN_QUIET:
|
||||
return LOG_STR("QUIET");
|
||||
default:
|
||||
return LOG_STR("UNKNOWN");
|
||||
}
|
||||
|
@ -62,6 +62,8 @@ enum ClimateFanMode : uint8_t {
|
||||
CLIMATE_FAN_FOCUS = 7,
|
||||
/// The fan mode is set to Diffuse
|
||||
CLIMATE_FAN_DIFFUSE = 8,
|
||||
/// The fan mode is set to Quiet
|
||||
CLIMATE_FAN_QUIET = 9,
|
||||
};
|
||||
|
||||
/// Enum for all modes a climate swing can be in
|
||||
|
@ -28,7 +28,7 @@ namespace climate {
|
||||
* - supports action - if the climate device supports reporting the active
|
||||
* current action of the device with the action property.
|
||||
* - supports fan modes - optionally, if it has a fan which can be configured in different ways:
|
||||
* - on, off, auto, high, medium, low, middle, focus, diffuse
|
||||
* - on, off, auto, high, medium, low, middle, focus, diffuse, quiet
|
||||
* - supports swing modes - optionally, if it has a swing which can be configured in different ways:
|
||||
* - off, both, vertical, horizontal
|
||||
*
|
||||
|
@ -1,14 +1,17 @@
|
||||
import base64
|
||||
import secrets
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
import requests
|
||||
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import git
|
||||
from esphome.components.packages import validate_source_shorthand
|
||||
from esphome.const import CONF_WIFI
|
||||
from esphome.const import CONF_REF, CONF_WIFI
|
||||
from esphome.wizard import wizard_file
|
||||
from esphome.yaml_util import dump
|
||||
from esphome import git
|
||||
|
||||
|
||||
dashboard_import_ns = cg.esphome_ns.namespace("dashboard_import")
|
||||
|
||||
@ -21,19 +24,32 @@ CODEOWNERS = ["@esphome/core"]
|
||||
def validate_import_url(value):
|
||||
value = cv.string_strict(value)
|
||||
value = cv.Length(max=255)(value)
|
||||
# ignore result, only check if it's a valid shorthand
|
||||
validate_source_shorthand(value)
|
||||
return value
|
||||
|
||||
|
||||
def validate_full_url(config):
|
||||
if not config[CONF_IMPORT_FULL_CONFIG]:
|
||||
return config
|
||||
source = validate_source_shorthand(config[CONF_PACKAGE_IMPORT_URL])
|
||||
if CONF_REF not in source:
|
||||
raise cv.Invalid(
|
||||
"Must specify a ref (branch or tag) to import from when importing full config"
|
||||
)
|
||||
return config
|
||||
|
||||
|
||||
CONF_PACKAGE_IMPORT_URL = "package_import_url"
|
||||
CONF_IMPORT_FULL_CONFIG = "import_full_config"
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_PACKAGE_IMPORT_URL): validate_import_url,
|
||||
cv.Optional(CONF_IMPORT_FULL_CONFIG, default=False): cv.boolean,
|
||||
}
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_PACKAGE_IMPORT_URL): validate_import_url,
|
||||
cv.Optional(CONF_IMPORT_FULL_CONFIG, default=False): cv.boolean,
|
||||
}
|
||||
),
|
||||
validate_full_url,
|
||||
)
|
||||
|
||||
WIFI_CONFIG = """
|
||||
@ -49,11 +65,17 @@ async def to_code(config):
|
||||
url = config[CONF_PACKAGE_IMPORT_URL]
|
||||
if config[CONF_IMPORT_FULL_CONFIG]:
|
||||
url += "?full_config"
|
||||
cg.add(dashboard_import_ns.set_package_import_url(config[CONF_PACKAGE_IMPORT_URL]))
|
||||
cg.add(dashboard_import_ns.set_package_import_url(url))
|
||||
|
||||
|
||||
def import_config(
|
||||
path: str, name: str, project_name: str, import_url: str, network: str = CONF_WIFI
|
||||
path: str,
|
||||
name: str,
|
||||
friendly_name: Optional[str],
|
||||
project_name: str,
|
||||
import_url: str,
|
||||
network: str = CONF_WIFI,
|
||||
encryption: bool = False,
|
||||
) -> None:
|
||||
p = Path(path)
|
||||
|
||||
@ -61,14 +83,21 @@ def import_config(
|
||||
raise FileExistsError
|
||||
|
||||
if project_name == "esphome.web":
|
||||
kwargs = {
|
||||
"name": name,
|
||||
"friendly_name": friendly_name,
|
||||
"platform": "ESP32" if "esp32" in import_url else "ESP8266",
|
||||
"board": "esp32dev" if "esp32" in import_url else "esp01_1m",
|
||||
"ssid": "!secret wifi_ssid",
|
||||
"psk": "!secret wifi_password",
|
||||
}
|
||||
if encryption:
|
||||
noise_psk = secrets.token_bytes(32)
|
||||
key = base64.b64encode(noise_psk).decode()
|
||||
kwargs["api_encryption_key"] = key
|
||||
|
||||
p.write_text(
|
||||
wizard_file(
|
||||
name=name,
|
||||
platform="ESP32" if "esp32" in import_url else "ESP8266",
|
||||
board="esp32dev" if "esp32" in import_url else "esp01_1m",
|
||||
ssid="!secret wifi_ssid",
|
||||
psk="!secret wifi_password",
|
||||
),
|
||||
wizard_file(**kwargs),
|
||||
encoding="utf8",
|
||||
)
|
||||
else:
|
||||
@ -85,14 +114,21 @@ def import_config(
|
||||
p.write_text(req.text, encoding="utf8")
|
||||
|
||||
else:
|
||||
substitutions = {"name": name}
|
||||
esphome_core = {"name": "${name}", "name_add_mac_suffix": False}
|
||||
if friendly_name:
|
||||
substitutions["friendly_name"] = friendly_name
|
||||
esphome_core["friendly_name"] = "${friendly_name}"
|
||||
config = {
|
||||
"substitutions": {"name": name},
|
||||
"substitutions": substitutions,
|
||||
"packages": {project_name: import_url},
|
||||
"esphome": {
|
||||
"name": "${name}",
|
||||
"name_add_mac_suffix": False,
|
||||
},
|
||||
"esphome": esphome_core,
|
||||
}
|
||||
if encryption:
|
||||
noise_psk = secrets.token_bytes(32)
|
||||
key = base64.b64encode(noise_psk).decode()
|
||||
config["api"] = {"encryption": {"key": key}}
|
||||
|
||||
output = dump(config)
|
||||
|
||||
if network == CONF_WIFI:
|
||||
|
@ -111,6 +111,7 @@ class DemoClimate : public climate::Climate, public Component {
|
||||
climate::CLIMATE_FAN_MIDDLE,
|
||||
climate::CLIMATE_FAN_FOCUS,
|
||||
climate::CLIMATE_FAN_DIFFUSE,
|
||||
climate::CLIMATE_FAN_QUIET,
|
||||
});
|
||||
traits.set_supported_custom_fan_modes({"Auto Low", "Auto High"});
|
||||
traits.set_supported_swing_modes({
|
||||
|
@ -452,7 +452,7 @@ int Font::match_next_glyph(const char *str, int *match_length) {
|
||||
}
|
||||
void Font::measure(const char *str, int *width, int *x_offset, int *baseline, int *height) {
|
||||
*baseline = this->baseline_;
|
||||
*height = this->bottom_;
|
||||
*height = this->height_;
|
||||
int i = 0;
|
||||
int min_x = 0;
|
||||
bool has_char = false;
|
||||
@ -483,7 +483,7 @@ void Font::measure(const char *str, int *width, int *x_offset, int *baseline, in
|
||||
*width = x - min_x;
|
||||
}
|
||||
const std::vector<Glyph> &Font::get_glyphs() const { return this->glyphs_; }
|
||||
Font::Font(const GlyphData *data, int data_nr, int baseline, int bottom) : baseline_(baseline), bottom_(bottom) {
|
||||
Font::Font(const GlyphData *data, int data_nr, int baseline, int height) : baseline_(baseline), height_(height) {
|
||||
for (int i = 0; i < data_nr; ++i)
|
||||
glyphs_.emplace_back(data + i);
|
||||
}
|
||||
@ -527,6 +527,7 @@ int Image::get_height() const { return this->height_; }
|
||||
ImageType Image::get_type() const { return this->type_; }
|
||||
Image::Image(const uint8_t *data_start, int width, int height, ImageType type)
|
||||
: width_(width), height_(height), type_(type), data_start_(data_start) {}
|
||||
int Image::get_current_frame() const { return 0; }
|
||||
|
||||
bool Animation::get_pixel(int x, int y) const {
|
||||
if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_)
|
||||
|
@ -448,18 +448,20 @@ class Font {
|
||||
* @param baseline The y-offset from the top of the text to the baseline.
|
||||
* @param bottom The y-offset from the top of the text to the bottom (i.e. height).
|
||||
*/
|
||||
Font(const GlyphData *data, int data_nr, int baseline, int bottom);
|
||||
Font(const GlyphData *data, int data_nr, int baseline, int height);
|
||||
|
||||
int match_next_glyph(const char *str, int *match_length);
|
||||
|
||||
void measure(const char *str, int *width, int *x_offset, int *baseline, int *height);
|
||||
inline int get_baseline() { return this->baseline_; }
|
||||
inline int get_height() { return this->height_; }
|
||||
|
||||
const std::vector<Glyph> &get_glyphs() const;
|
||||
|
||||
protected:
|
||||
std::vector<Glyph> glyphs_;
|
||||
int baseline_;
|
||||
int bottom_;
|
||||
int height_;
|
||||
};
|
||||
|
||||
class Image {
|
||||
@ -473,6 +475,8 @@ class Image {
|
||||
int get_height() const;
|
||||
ImageType get_type() const;
|
||||
|
||||
virtual int get_current_frame() const;
|
||||
|
||||
protected:
|
||||
int width_;
|
||||
int height_;
|
||||
@ -489,7 +493,7 @@ class Animation : public Image {
|
||||
Color get_grayscale_pixel(int x, int y) const override;
|
||||
|
||||
int get_animation_frame_count() const;
|
||||
int get_current_frame() const;
|
||||
int get_current_frame() const override;
|
||||
void next_frame();
|
||||
void prev_frame();
|
||||
|
||||
|
@ -8,6 +8,7 @@ from esphome.const import (
|
||||
CONF_TRIGGER_ID,
|
||||
CONF_ON_VALUE,
|
||||
CONF_COMMAND,
|
||||
CONF_CUSTOM,
|
||||
CONF_NUMBER,
|
||||
CONF_FORMAT,
|
||||
CONF_MODE,
|
||||
@ -32,7 +33,6 @@ CONF_BACK = "back"
|
||||
CONF_TEXT = "text"
|
||||
CONF_SELECT = "select"
|
||||
CONF_SWITCH = "switch"
|
||||
CONF_CUSTOM = "custom"
|
||||
CONF_ITEMS = "items"
|
||||
CONF_ON_TEXT = "on_text"
|
||||
CONF_OFF_TEXT = "off_text"
|
||||
|
@ -10,6 +10,8 @@ from esphome.const import (
|
||||
|
||||
CODEOWNERS = ["@glmnet", "@zuidwijk"]
|
||||
|
||||
MULTI_CONF = True
|
||||
|
||||
DEPENDENCIES = ["uart"]
|
||||
AUTO_LOAD = ["sensor", "text_sensor"]
|
||||
|
||||
|
0
esphome/components/ee895/__init__.py
Normal file
0
esphome/components/ee895/__init__.py
Normal file
115
esphome/components/ee895/ee895.cpp
Normal file
115
esphome/components/ee895/ee895.cpp
Normal file
@ -0,0 +1,115 @@
|
||||
#include "ee895.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ee895 {
|
||||
|
||||
static const char *const TAG = "ee895";
|
||||
|
||||
static const uint16_t CRC16_ONEWIRE_START = 0xFFFF;
|
||||
static const uint8_t FUNCTION_CODE_READ = 0x03;
|
||||
static const uint16_t SERIAL_NUMBER = 0x0000;
|
||||
static const uint16_t TEMPERATURE_ADDRESS = 0x03EA;
|
||||
static const uint16_t CO2_ADDRESS = 0x0424;
|
||||
static const uint16_t PRESSURE_ADDRESS = 0x04B0;
|
||||
|
||||
void EE895Component::setup() {
|
||||
uint16_t crc16_check = 0;
|
||||
ESP_LOGCONFIG(TAG, "Setting up EE895...");
|
||||
write_command_(SERIAL_NUMBER, 8);
|
||||
uint8_t serial_number[20];
|
||||
this->read(serial_number, 20);
|
||||
|
||||
crc16_check = (serial_number[19] << 8) + serial_number[18];
|
||||
if (crc16_check != calc_crc16_(serial_number, 19)) {
|
||||
this->error_code_ = CRC_CHECK_FAILED;
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
ESP_LOGV(TAG, " Serial Number: 0x%s", format_hex(serial_number + 2, 16).c_str());
|
||||
}
|
||||
|
||||
void EE895Component::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "EE895:");
|
||||
LOG_I2C_DEVICE(this);
|
||||
switch (this->error_code_) {
|
||||
case COMMUNICATION_FAILED:
|
||||
ESP_LOGE(TAG, "Communication with EE895 failed!");
|
||||
break;
|
||||
case CRC_CHECK_FAILED:
|
||||
ESP_LOGE(TAG, "The crc check failed");
|
||||
break;
|
||||
case NONE:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
|
||||
LOG_SENSOR(" ", "CO2", this->co2_sensor_);
|
||||
LOG_SENSOR(" ", "Pressure", this->pressure_sensor_);
|
||||
}
|
||||
|
||||
float EE895Component::get_setup_priority() const { return setup_priority::DATA; }
|
||||
|
||||
void EE895Component::update() {
|
||||
write_command_(TEMPERATURE_ADDRESS, 2);
|
||||
this->set_timeout(50, [this]() {
|
||||
float temperature = read_float_();
|
||||
|
||||
write_command_(CO2_ADDRESS, 2);
|
||||
float co2 = read_float_();
|
||||
|
||||
write_command_(PRESSURE_ADDRESS, 2);
|
||||
float pressure = read_float_();
|
||||
ESP_LOGD(TAG, "Got temperature=%.1f°C co2=%.0fppm pressure=%.1f%mbar", temperature, co2, pressure);
|
||||
if (this->temperature_sensor_ != nullptr)
|
||||
this->temperature_sensor_->publish_state(temperature);
|
||||
if (this->co2_sensor_ != nullptr)
|
||||
this->co2_sensor_->publish_state(co2);
|
||||
if (this->pressure_sensor_ != nullptr)
|
||||
this->pressure_sensor_->publish_state(pressure);
|
||||
this->status_clear_warning();
|
||||
});
|
||||
}
|
||||
|
||||
void EE895Component::write_command_(uint16_t addr, uint16_t reg_cnt) {
|
||||
uint8_t address[7];
|
||||
uint16_t crc16 = 0;
|
||||
address[0] = FUNCTION_CODE_READ;
|
||||
address[1] = (addr >> 8) & 0xFF;
|
||||
address[2] = addr & 0xFF;
|
||||
address[3] = (reg_cnt >> 8) & 0xFF;
|
||||
address[4] = reg_cnt & 0xFF;
|
||||
crc16 = calc_crc16_(address, 6);
|
||||
address[5] = crc16 & 0xFF;
|
||||
address[6] = (crc16 >> 8) & 0xFF;
|
||||
this->write(address, 7, true);
|
||||
}
|
||||
|
||||
float EE895Component::read_float_() {
|
||||
uint16_t crc16_check = 0;
|
||||
uint8_t i2c_response[8];
|
||||
this->read(i2c_response, 8);
|
||||
crc16_check = (i2c_response[7] << 8) + i2c_response[6];
|
||||
if (crc16_check != calc_crc16_(i2c_response, 7)) {
|
||||
this->error_code_ = CRC_CHECK_FAILED;
|
||||
this->status_set_warning();
|
||||
return 0;
|
||||
}
|
||||
uint32_t x = encode_uint32(i2c_response[4], i2c_response[5], i2c_response[2], i2c_response[3]);
|
||||
float value;
|
||||
memcpy(&value, &x, sizeof(value)); // convert uin32_t IEEE-754 format to float
|
||||
return value;
|
||||
}
|
||||
|
||||
uint16_t EE895Component::calc_crc16_(const uint8_t buf[], uint8_t len) {
|
||||
uint8_t crc_check_buf[22];
|
||||
for (int i = 0; i < len; i++) {
|
||||
crc_check_buf[i + 1] = buf[i];
|
||||
}
|
||||
crc_check_buf[0] = this->address_;
|
||||
return crc16(crc_check_buf, len);
|
||||
}
|
||||
} // namespace ee895
|
||||
} // namespace esphome
|
34
esphome/components/ee895/ee895.h
Normal file
34
esphome/components/ee895/ee895.h
Normal file
@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ee895 {
|
||||
|
||||
/// This class implements support for the ee895 of temperature i2c sensors.
|
||||
class EE895Component : public PollingComponent, public i2c::I2CDevice {
|
||||
public:
|
||||
void set_co2_sensor(sensor::Sensor *co2) { co2_sensor_ = co2; }
|
||||
void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; }
|
||||
void set_pressure_sensor(sensor::Sensor *pressure_sensor) { pressure_sensor_ = pressure_sensor; }
|
||||
|
||||
float get_setup_priority() const override;
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
void update() override;
|
||||
|
||||
protected:
|
||||
void write_command_(uint16_t addr, uint16_t reg_cnt);
|
||||
float read_float_();
|
||||
uint16_t calc_crc16_(const uint8_t buf[], uint8_t len);
|
||||
sensor::Sensor *co2_sensor_;
|
||||
sensor::Sensor *temperature_sensor_;
|
||||
sensor::Sensor *pressure_sensor_;
|
||||
|
||||
enum ErrorCode { NONE = 0, COMMUNICATION_FAILED, CRC_CHECK_FAILED } error_code_{NONE};
|
||||
};
|
||||
|
||||
} // namespace ee895
|
||||
} // namespace esphome
|
69
esphome/components/ee895/sensor.py
Normal file
69
esphome/components/ee895/sensor.py
Normal file
@ -0,0 +1,69 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import i2c, sensor
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_PRESSURE,
|
||||
CONF_TEMPERATURE,
|
||||
CONF_CO2,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
DEVICE_CLASS_PRESSURE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_HECTOPASCAL,
|
||||
UNIT_CELSIUS,
|
||||
ICON_MOLECULE_CO2,
|
||||
UNIT_PARTS_PER_MILLION,
|
||||
)
|
||||
|
||||
CODEOWNERS = ["@Stock-M"]
|
||||
|
||||
DEPENDENCIES = ["i2c"]
|
||||
|
||||
ee895_ns = cg.esphome_ns.namespace("ee895")
|
||||
EE895Component = ee895_ns.class_("EE895Component", cg.PollingComponent, i2c.I2CDevice)
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(EE895Component),
|
||||
cv.Required(CONF_TEMPERATURE): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_CELSIUS,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Required(CONF_CO2): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_PARTS_PER_MILLION,
|
||||
icon=ICON_MOLECULE_CO2,
|
||||
accuracy_decimals=0,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Required(CONF_PRESSURE): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_HECTOPASCAL,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_PRESSURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(cv.polling_component_schema("60s"))
|
||||
.extend(i2c.i2c_device_schema(0x5F))
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
|
||||
if CONF_TEMPERATURE in config:
|
||||
sens = await sensor.new_sensor(config[CONF_TEMPERATURE])
|
||||
cg.add(var.set_temperature_sensor(sens))
|
||||
|
||||
if CONF_CO2 in config:
|
||||
sens = await sensor.new_sensor(config[CONF_CO2])
|
||||
cg.add(var.set_co2_sensor(sens))
|
||||
|
||||
if CONF_PRESSURE in config:
|
||||
sens = await sensor.new_sensor(config[CONF_PRESSURE])
|
||||
cg.add(var.set_pressure_sensor(sens))
|
@ -356,9 +356,14 @@ async def to_code(config):
|
||||
|
||||
if conf[CONF_ADVANCED][CONF_IGNORE_EFUSE_MAC_CRC]:
|
||||
cg.add_define("USE_ESP32_IGNORE_EFUSE_MAC_CRC")
|
||||
add_idf_sdkconfig_option(
|
||||
"CONFIG_ESP32_PHY_CALIBRATION_AND_DATA_STORAGE", False
|
||||
)
|
||||
if (framework_ver.major, framework_ver.minor) >= (4, 4):
|
||||
add_idf_sdkconfig_option(
|
||||
"CONFIG_ESP_PHY_CALIBRATION_AND_DATA_STORAGE", False
|
||||
)
|
||||
else:
|
||||
add_idf_sdkconfig_option(
|
||||
"CONFIG_ESP32_PHY_CALIBRATION_AND_DATA_STORAGE", False
|
||||
)
|
||||
|
||||
cg.add_define(
|
||||
"USE_ESP_IDF_VERSION_CODE",
|
||||
|
@ -95,7 +95,7 @@ void ESP32InternalGPIOPin::pin_mode(gpio::Flags flags) {
|
||||
// can't call gpio_config here because that logs in esp-idf which may cause issues
|
||||
gpio_set_direction(pin_, flags_to_mode(flags));
|
||||
gpio_pull_mode_t pull_mode = GPIO_FLOATING;
|
||||
if (flags & (gpio::FLAG_PULLUP | gpio::FLAG_PULLDOWN)) {
|
||||
if ((flags & gpio::FLAG_PULLUP) && (flags & gpio::FLAG_PULLDOWN)) {
|
||||
pull_mode = GPIO_PULLUP_PULLDOWN;
|
||||
} else if (flags & gpio::FLAG_PULLUP) {
|
||||
pull_mode = GPIO_PULLUP_ONLY;
|
||||
@ -128,7 +128,7 @@ void IRAM_ATTR ISRInternalGPIOPin::pin_mode(gpio::Flags flags) {
|
||||
auto *arg = reinterpret_cast<ISRPinArg *>(arg_);
|
||||
gpio_set_direction(arg->pin, flags_to_mode(flags));
|
||||
gpio_pull_mode_t pull_mode = GPIO_FLOATING;
|
||||
if (flags & (gpio::FLAG_PULLUP | gpio::FLAG_PULLDOWN)) {
|
||||
if ((flags & gpio::FLAG_PULLUP) && (flags & gpio::FLAG_PULLDOWN)) {
|
||||
pull_mode = GPIO_PULLUP_PULLDOWN;
|
||||
} else if (flags & gpio::FLAG_PULLUP) {
|
||||
pull_mode = GPIO_PULLUP_ONLY;
|
||||
|
@ -123,11 +123,8 @@ def validate_gpio_pin(value):
|
||||
|
||||
def validate_supports(value):
|
||||
mode = value[CONF_MODE]
|
||||
is_input = mode[CONF_INPUT]
|
||||
is_output = mode[CONF_OUTPUT]
|
||||
is_open_drain = mode[CONF_OPEN_DRAIN]
|
||||
is_pullup = mode[CONF_PULLUP]
|
||||
is_pulldown = mode[CONF_PULLDOWN]
|
||||
variant = CORE.data[KEY_ESP32][KEY_VARIANT]
|
||||
if variant not in _esp32_validations:
|
||||
raise cv.Invalid(f"Unsupported ESP32 variant {variant}")
|
||||
@ -138,26 +135,6 @@ def validate_supports(value):
|
||||
)
|
||||
|
||||
value = _esp32_validations[variant].usage_validation(value)
|
||||
if CORE.using_arduino:
|
||||
# (input, output, open_drain, pullup, pulldown)
|
||||
supported_modes = {
|
||||
# INPUT
|
||||
(True, False, False, False, False),
|
||||
# OUTPUT
|
||||
(False, True, False, False, False),
|
||||
# INPUT_PULLUP
|
||||
(True, False, False, True, False),
|
||||
# INPUT_PULLDOWN
|
||||
(True, False, False, False, True),
|
||||
# OUTPUT_OPEN_DRAIN
|
||||
(False, True, True, False, False),
|
||||
}
|
||||
key = (is_input, is_output, is_open_drain, is_pullup, is_pulldown)
|
||||
if key not in supported_modes:
|
||||
raise cv.Invalid(
|
||||
"This pin mode is not supported on ESP32 for arduino frameworks",
|
||||
[CONF_MODE],
|
||||
)
|
||||
return value
|
||||
|
||||
|
||||
|
@ -2,15 +2,23 @@ import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ID
|
||||
from esphome.core import CORE
|
||||
from esphome.components.esp32 import add_idf_sdkconfig_option
|
||||
from esphome.components.esp32 import add_idf_sdkconfig_option, get_esp32_variant, const
|
||||
|
||||
DEPENDENCIES = ["esp32"]
|
||||
CODEOWNERS = ["@jesserockz"]
|
||||
CONFLICTS_WITH = ["esp32_ble_tracker", "esp32_ble_beacon"]
|
||||
CONFLICTS_WITH = ["esp32_ble_beacon"]
|
||||
|
||||
CONF_BLE_ID = "ble_id"
|
||||
|
||||
NO_BLUTOOTH_VARIANTS = [const.VARIANT_ESP32S2]
|
||||
|
||||
esp32_ble_ns = cg.esphome_ns.namespace("esp32_ble")
|
||||
ESP32BLE = esp32_ble_ns.class_("ESP32BLE", cg.Component)
|
||||
|
||||
GAPEventHandler = esp32_ble_ns.class_("GAPEventHandler")
|
||||
GATTcEventHandler = esp32_ble_ns.class_("GATTcEventHandler")
|
||||
GATTsEventHandler = esp32_ble_ns.class_("GATTsEventHandler")
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
@ -19,6 +27,15 @@ CONFIG_SCHEMA = cv.Schema(
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
|
||||
def validate_variant(_):
|
||||
variant = get_esp32_variant()
|
||||
if variant in NO_BLUTOOTH_VARIANTS:
|
||||
raise cv.Invalid(f"{variant} does not support Bluetooth")
|
||||
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = validate_variant
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
|
@ -4,13 +4,13 @@
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#include <nvs_flash.h>
|
||||
#include <freertos/FreeRTOSConfig.h>
|
||||
#include <esp_bt_main.h>
|
||||
#include <esp_bt.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/task.h>
|
||||
#include <esp_bt_main.h>
|
||||
#include <esp_gap_ble_api.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/FreeRTOSConfig.h>
|
||||
#include <freertos/task.h>
|
||||
#include <nvs_flash.h>
|
||||
|
||||
#ifdef USE_ARDUINO
|
||||
#include <esp32-hal-bt.h>
|
||||
@ -31,24 +31,17 @@ void ESP32BLE::setup() {
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef USE_ESP32_BLE_SERVER
|
||||
this->advertising_ = new BLEAdvertising(); // NOLINT(cppcoreguidelines-owning-memory)
|
||||
|
||||
this->advertising_->set_scan_response(true);
|
||||
this->advertising_->set_min_preferred_interval(0x06);
|
||||
this->advertising_->start();
|
||||
#endif // USE_ESP32_BLE_SERVER
|
||||
|
||||
ESP_LOGD(TAG, "BLE setup complete");
|
||||
}
|
||||
|
||||
void ESP32BLE::mark_failed() {
|
||||
Component::mark_failed();
|
||||
#ifdef USE_ESP32_BLE_SERVER
|
||||
if (this->server_ != nullptr) {
|
||||
this->server_->mark_failed();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
bool ESP32BLE::ble_setup_() {
|
||||
esp_err_t err = nvs_flash_init();
|
||||
if (err != ESP_OK) {
|
||||
@ -100,13 +93,16 @@ bool ESP32BLE::ble_setup_() {
|
||||
ESP_LOGE(TAG, "esp_bluedroid_enable failed: %d", err);
|
||||
return false;
|
||||
}
|
||||
err = esp_ble_gap_register_callback(ESP32BLE::gap_event_handler);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "esp_ble_gap_register_callback failed: %d", err);
|
||||
return false;
|
||||
|
||||
if (!this->gap_event_handlers_.empty()) {
|
||||
err = esp_ble_gap_register_callback(ESP32BLE::gap_event_handler);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "esp_ble_gap_register_callback failed: %d", err);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (this->has_server()) {
|
||||
if (!this->gatts_event_handlers_.empty()) {
|
||||
err = esp_ble_gatts_register_callback(ESP32BLE::gatts_event_handler);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "esp_ble_gatts_register_callback failed: %d", err);
|
||||
@ -114,7 +110,7 @@ bool ESP32BLE::ble_setup_() {
|
||||
}
|
||||
}
|
||||
|
||||
if (this->has_client()) {
|
||||
if (!this->gattc_event_handlers_.empty()) {
|
||||
err = esp_ble_gattc_register_callback(ESP32BLE::gattc_event_handler);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "esp_ble_gattc_register_callback failed: %d", err);
|
||||
@ -158,6 +154,10 @@ void ESP32BLE::loop() {
|
||||
this->real_gatts_event_handler_(ble_event->event_.gatts.gatts_event, ble_event->event_.gatts.gatts_if,
|
||||
&ble_event->event_.gatts.gatts_param);
|
||||
break;
|
||||
case BLEEvent::GATTC:
|
||||
this->real_gattc_event_handler_(ble_event->event_.gattc.gattc_event, ble_event->event_.gattc.gattc_if,
|
||||
&ble_event->event_.gattc.gattc_param);
|
||||
break;
|
||||
case BLEEvent::GAP:
|
||||
this->real_gap_event_handler_(ble_event->event_.gap.gap_event, &ble_event->event_.gap.gap_param);
|
||||
break;
|
||||
@ -176,9 +176,8 @@ void ESP32BLE::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_pa
|
||||
|
||||
void ESP32BLE::real_gap_event_handler_(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) {
|
||||
ESP_LOGV(TAG, "(BLE) gap_event_handler - %d", event);
|
||||
switch (event) {
|
||||
default:
|
||||
break;
|
||||
for (auto *gap_handler : this->gap_event_handlers_) {
|
||||
gap_handler->gap_event_handler(event, param);
|
||||
}
|
||||
}
|
||||
|
||||
@ -191,14 +190,23 @@ void ESP32BLE::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gat
|
||||
void ESP32BLE::real_gatts_event_handler_(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if,
|
||||
esp_ble_gatts_cb_param_t *param) {
|
||||
ESP_LOGV(TAG, "(BLE) gatts_event [esp_gatt_if: %d] - %d", gatts_if, event);
|
||||
#ifdef USE_ESP32_BLE_SERVER
|
||||
this->server_->gatts_event_handler(event, gatts_if, param);
|
||||
#endif
|
||||
for (auto *gatts_handler : this->gatts_event_handlers_) {
|
||||
gatts_handler->gatts_event_handler(event, gatts_if, param);
|
||||
}
|
||||
}
|
||||
|
||||
void ESP32BLE::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
||||
esp_ble_gattc_cb_param_t *param) {
|
||||
BLEEvent *new_event = new BLEEvent(event, gattc_if, param); // NOLINT(cppcoreguidelines-owning-memory)
|
||||
global_ble->ble_events_.push(new_event);
|
||||
} // NOLINT(clang-analyzer-cplusplus.NewDeleteLeaks)
|
||||
|
||||
void ESP32BLE::real_gattc_event_handler_(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
||||
esp_ble_gattc_cb_param_t *param) {
|
||||
// this->client_->gattc_event_handler(event, gattc_if, param);
|
||||
ESP_LOGV(TAG, "(BLE) gattc_event [esp_gatt_if: %d] - %d", gattc_if, event);
|
||||
for (auto *gattc_handler : this->gattc_event_handlers_) {
|
||||
gattc_handler->gattc_event_handler(event, gattc_if, param);
|
||||
}
|
||||
}
|
||||
|
||||
float ESP32BLE::get_setup_priority() const { return setup_priority::BLUETOOTH; }
|
||||
|
@ -5,17 +5,16 @@
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "queue.h"
|
||||
|
||||
#ifdef USE_ESP32_BLE_SERVER
|
||||
#include "esphome/components/esp32_ble_server/ble_server.h"
|
||||
#endif
|
||||
#include "queue.h"
|
||||
#include "ble_event.h"
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include <esp_gap_ble_api.h>
|
||||
#include <esp_gatts_api.h>
|
||||
#include <esp_gattc_api.h>
|
||||
|
||||
namespace esphome {
|
||||
namespace esp32_ble {
|
||||
|
||||
@ -26,28 +25,36 @@ typedef struct {
|
||||
uint16_t mtu;
|
||||
} conn_status_t;
|
||||
|
||||
class GAPEventHandler {
|
||||
public:
|
||||
virtual void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) = 0;
|
||||
};
|
||||
|
||||
class GATTcEventHandler {
|
||||
public:
|
||||
virtual void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
||||
esp_ble_gattc_cb_param_t *param) = 0;
|
||||
};
|
||||
|
||||
class GATTsEventHandler {
|
||||
public:
|
||||
virtual void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if,
|
||||
esp_ble_gatts_cb_param_t *param) = 0;
|
||||
};
|
||||
|
||||
class ESP32BLE : public Component {
|
||||
public:
|
||||
void setup() override;
|
||||
void loop() override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override;
|
||||
void mark_failed() override;
|
||||
|
||||
bool has_server() {
|
||||
#ifdef USE_ESP32_BLE_SERVER
|
||||
return this->server_ != nullptr;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
bool has_client() { return false; }
|
||||
|
||||
BLEAdvertising *get_advertising() { return this->advertising_; }
|
||||
|
||||
#ifdef USE_ESP32_BLE_SERVER
|
||||
void set_server(esp32_ble_server::BLEServer *server) { this->server_ = server; }
|
||||
#endif
|
||||
void register_gap_event_handler(GAPEventHandler *handler) { this->gap_event_handlers_.push_back(handler); }
|
||||
void register_gattc_event_handler(GATTcEventHandler *handler) { this->gattc_event_handlers_.push_back(handler); }
|
||||
void register_gatts_event_handler(GATTsEventHandler *handler) { this->gatts_event_handlers_.push_back(handler); }
|
||||
|
||||
protected:
|
||||
static void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param);
|
||||
static void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param);
|
||||
@ -59,9 +66,10 @@ class ESP32BLE : public Component {
|
||||
|
||||
bool ble_setup_();
|
||||
|
||||
#ifdef USE_ESP32_BLE_SERVER
|
||||
esp32_ble_server::BLEServer *server_{nullptr};
|
||||
#endif
|
||||
std::vector<GAPEventHandler *> gap_event_handlers_;
|
||||
std::vector<GATTcEventHandler *> gattc_event_handlers_;
|
||||
std::vector<GATTsEventHandler *> gatts_event_handlers_;
|
||||
|
||||
Queue<BLEEvent> ble_events_;
|
||||
BLEAdvertising *advertising_;
|
||||
};
|
||||
|
@ -1,69 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_ESP32
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
#include <vector>
|
||||
|
||||
#include <esp_gap_ble_api.h>
|
||||
#include <esp_gattc_api.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/semphr.h>
|
||||
|
||||
/*
|
||||
* BLE events come in from a separate Task (thread) in the ESP32 stack. Rather
|
||||
* than trying to deal with various locking strategies, all incoming GAP and GATT
|
||||
* events will simply be placed on a semaphore guarded queue. The next time the
|
||||
* component runs loop(), these events are popped off the queue and handed at
|
||||
* this safer time.
|
||||
*/
|
||||
#include <esp_gatts_api.h>
|
||||
|
||||
namespace esphome {
|
||||
namespace esp32_ble_tracker {
|
||||
|
||||
template<class T> class Queue {
|
||||
public:
|
||||
Queue() { m_ = xSemaphoreCreateMutex(); }
|
||||
|
||||
void push(T *element) {
|
||||
if (element == nullptr)
|
||||
return;
|
||||
if (xSemaphoreTake(m_, 5L / portTICK_PERIOD_MS)) {
|
||||
q_.push(element);
|
||||
xSemaphoreGive(m_);
|
||||
}
|
||||
}
|
||||
|
||||
T *pop() {
|
||||
T *element = nullptr;
|
||||
|
||||
if (xSemaphoreTake(m_, 5L / portTICK_PERIOD_MS)) {
|
||||
if (!q_.empty()) {
|
||||
element = q_.front();
|
||||
q_.pop();
|
||||
}
|
||||
xSemaphoreGive(m_);
|
||||
}
|
||||
return element;
|
||||
}
|
||||
|
||||
protected:
|
||||
std::queue<T *> q_;
|
||||
SemaphoreHandle_t m_;
|
||||
};
|
||||
|
||||
// Received GAP and GATTC events are only queued, and get processed in the main loop().
|
||||
namespace esp32_ble {
|
||||
// Received GAP, GATTC and GATTS events are only queued, and get processed in the main loop().
|
||||
// This class stores each event in a single type.
|
||||
class BLEEvent {
|
||||
public:
|
||||
BLEEvent(esp_gap_ble_cb_event_t e, esp_ble_gap_cb_param_t *p) {
|
||||
this->event_.gap.gap_event = e;
|
||||
memcpy(&this->event_.gap.gap_param, p, sizeof(esp_ble_gap_cb_param_t));
|
||||
this->type_ = 0;
|
||||
this->type_ = GAP;
|
||||
};
|
||||
|
||||
BLEEvent(esp_gattc_cb_event_t e, esp_gatt_if_t i, esp_ble_gattc_cb_param_t *p) {
|
||||
@ -84,26 +38,57 @@ class BLEEvent {
|
||||
default:
|
||||
break;
|
||||
}
|
||||
this->type_ = 1;
|
||||
this->type_ = GATTC;
|
||||
};
|
||||
|
||||
BLEEvent(esp_gatts_cb_event_t e, esp_gatt_if_t i, esp_ble_gatts_cb_param_t *p) {
|
||||
this->event_.gatts.gatts_event = e;
|
||||
this->event_.gatts.gatts_if = i;
|
||||
memcpy(&this->event_.gatts.gatts_param, p, sizeof(esp_ble_gatts_cb_param_t));
|
||||
// Need to also make a copy of relevant event data.
|
||||
switch (e) {
|
||||
case ESP_GATTS_WRITE_EVT:
|
||||
this->data.assign(p->write.value, p->write.value + p->write.len);
|
||||
this->event_.gatts.gatts_param.write.value = this->data.data();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
this->type_ = GATTS;
|
||||
};
|
||||
|
||||
union {
|
||||
struct gap_event { // NOLINT(readability-identifier-naming)
|
||||
// NOLINTNEXTLINE(readability-identifier-naming)
|
||||
struct gap_event {
|
||||
esp_gap_ble_cb_event_t gap_event;
|
||||
esp_ble_gap_cb_param_t gap_param;
|
||||
} gap;
|
||||
|
||||
struct gattc_event { // NOLINT(readability-identifier-naming)
|
||||
// NOLINTNEXTLINE(readability-identifier-naming)
|
||||
struct gattc_event {
|
||||
esp_gattc_cb_event_t gattc_event;
|
||||
esp_gatt_if_t gattc_if;
|
||||
esp_ble_gattc_cb_param_t gattc_param;
|
||||
} gattc;
|
||||
|
||||
// NOLINTNEXTLINE(readability-identifier-naming)
|
||||
struct gatts_event {
|
||||
esp_gatts_cb_event_t gatts_event;
|
||||
esp_gatt_if_t gatts_if;
|
||||
esp_ble_gatts_cb_param_t gatts_param;
|
||||
} gatts;
|
||||
} event_;
|
||||
|
||||
std::vector<uint8_t> data{};
|
||||
uint8_t type_; // 0=gap 1=gattc
|
||||
// NOLINTNEXTLINE(readability-identifier-naming)
|
||||
enum ble_event_t : uint8_t {
|
||||
GAP,
|
||||
GATTC,
|
||||
GATTS,
|
||||
} type_;
|
||||
};
|
||||
|
||||
} // namespace esp32_ble_tracker
|
||||
} // namespace esp32_ble
|
||||
} // namespace esphome
|
||||
|
||||
#endif
|
@ -27,8 +27,7 @@ ESPBTUUID ESPBTUUID::from_uint32(uint32_t uuid) {
|
||||
ESPBTUUID ESPBTUUID::from_raw(const uint8_t *data) {
|
||||
ESPBTUUID ret;
|
||||
ret.uuid_.len = ESP_UUID_LEN_128;
|
||||
for (size_t i = 0; i < ESP_UUID_LEN_128; i++)
|
||||
ret.uuid_.uuid.uuid128[i] = data[i];
|
||||
memcpy(ret.uuid_.uuid.uuid128, data, ESP_UUID_LEN_128);
|
||||
return ret;
|
||||
}
|
||||
ESPBTUUID ESPBTUUID::from_raw(const std::string &data) {
|
||||
@ -91,10 +90,13 @@ ESPBTUUID ESPBTUUID::from_raw(const std::string &data) {
|
||||
ESPBTUUID ESPBTUUID::from_uuid(esp_bt_uuid_t uuid) {
|
||||
ESPBTUUID ret;
|
||||
ret.uuid_.len = uuid.len;
|
||||
ret.uuid_.uuid.uuid16 = uuid.uuid.uuid16;
|
||||
ret.uuid_.uuid.uuid32 = uuid.uuid.uuid32;
|
||||
for (size_t i = 0; i < ESP_UUID_LEN_128; i++)
|
||||
ret.uuid_.uuid.uuid128[i] = uuid.uuid.uuid128[i];
|
||||
if (uuid.len == ESP_UUID_LEN_16) {
|
||||
ret.uuid_.uuid.uuid16 = uuid.uuid.uuid16;
|
||||
} else if (uuid.len == ESP_UUID_LEN_32) {
|
||||
ret.uuid_.uuid.uuid32 = uuid.uuid.uuid32;
|
||||
} else if (uuid.len == ESP_UUID_LEN_128) {
|
||||
memcpy(ret.uuid_.uuid.uuid128, uuid.uuid.uuid128, ESP_UUID_LEN_128);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
ESPBTUUID ESPBTUUID::as_128bit() const {
|
||||
@ -158,30 +160,26 @@ bool ESPBTUUID::operator==(const ESPBTUUID &uuid) const {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
esp_bt_uuid_t ESPBTUUID::get_uuid() { return this->uuid_; }
|
||||
std::string ESPBTUUID::to_string() {
|
||||
char sbuf[64];
|
||||
esp_bt_uuid_t ESPBTUUID::get_uuid() const { return this->uuid_; }
|
||||
std::string ESPBTUUID::to_string() const {
|
||||
switch (this->uuid_.len) {
|
||||
case ESP_UUID_LEN_16:
|
||||
sprintf(sbuf, "0x%02X%02X", this->uuid_.uuid.uuid16 >> 8, this->uuid_.uuid.uuid16 & 0xff);
|
||||
break;
|
||||
return str_snprintf("0x%02X%02X", 6, this->uuid_.uuid.uuid16 >> 8, this->uuid_.uuid.uuid16 & 0xff);
|
||||
case ESP_UUID_LEN_32:
|
||||
sprintf(sbuf, "0x%02X%02X%02X%02X", this->uuid_.uuid.uuid32 >> 24, (this->uuid_.uuid.uuid32 >> 16 & 0xff),
|
||||
(this->uuid_.uuid.uuid32 >> 8 & 0xff), this->uuid_.uuid.uuid32 & 0xff);
|
||||
break;
|
||||
return str_snprintf("0x%02X%02X%02X%02X", 10, this->uuid_.uuid.uuid32 >> 24,
|
||||
(this->uuid_.uuid.uuid32 >> 16 & 0xff), (this->uuid_.uuid.uuid32 >> 8 & 0xff),
|
||||
this->uuid_.uuid.uuid32 & 0xff);
|
||||
default:
|
||||
case ESP_UUID_LEN_128:
|
||||
char *bpos = sbuf;
|
||||
std::string buf;
|
||||
for (int8_t i = 15; i >= 0; i--) {
|
||||
sprintf(bpos, "%02X", this->uuid_.uuid.uuid128[i]);
|
||||
bpos += 2;
|
||||
buf += str_snprintf("%02X", 2, this->uuid_.uuid.uuid128[i]);
|
||||
if (i == 6 || i == 8 || i == 10 || i == 12)
|
||||
sprintf(bpos++, "-");
|
||||
buf += "-";
|
||||
}
|
||||
sbuf[47] = '\0';
|
||||
break;
|
||||
return buf;
|
||||
}
|
||||
return sbuf;
|
||||
return "";
|
||||
}
|
||||
|
||||
} // namespace esp32_ble
|
||||
|
@ -32,9 +32,9 @@ class ESPBTUUID {
|
||||
bool operator==(const ESPBTUUID &uuid) const;
|
||||
bool operator!=(const ESPBTUUID &uuid) const { return !(*this == uuid); }
|
||||
|
||||
esp_bt_uuid_t get_uuid();
|
||||
esp_bt_uuid_t get_uuid() const;
|
||||
|
||||
std::string to_string();
|
||||
std::string to_string() const;
|
||||
|
||||
protected:
|
||||
esp_bt_uuid_t uuid_;
|
||||
|
@ -2,16 +2,9 @@
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
#include <queue>
|
||||
#include <mutex>
|
||||
#include <cstring>
|
||||
#include <queue>
|
||||
|
||||
#include <esp_gap_ble_api.h>
|
||||
#include <esp_gatts_api.h>
|
||||
#include <esp_gattc_api.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/semphr.h>
|
||||
|
||||
@ -57,84 +50,6 @@ template<class T> class Queue {
|
||||
SemaphoreHandle_t m_;
|
||||
};
|
||||
|
||||
// Received GAP, GATTC and GATTS events are only queued, and get processed in the main loop().
|
||||
// This class stores each event in a single type.
|
||||
class BLEEvent {
|
||||
public:
|
||||
BLEEvent(esp_gap_ble_cb_event_t e, esp_ble_gap_cb_param_t *p) {
|
||||
this->event_.gap.gap_event = e;
|
||||
memcpy(&this->event_.gap.gap_param, p, sizeof(esp_ble_gap_cb_param_t));
|
||||
this->type_ = GAP;
|
||||
};
|
||||
|
||||
BLEEvent(esp_gattc_cb_event_t e, esp_gatt_if_t i, esp_ble_gattc_cb_param_t *p) {
|
||||
this->event_.gattc.gattc_event = e;
|
||||
this->event_.gattc.gattc_if = i;
|
||||
memcpy(&this->event_.gattc.gattc_param, p, sizeof(esp_ble_gattc_cb_param_t));
|
||||
// Need to also make a copy of notify event data.
|
||||
switch (e) {
|
||||
case ESP_GATTC_NOTIFY_EVT:
|
||||
memcpy(this->event_.gattc.data, p->notify.value, p->notify.value_len);
|
||||
this->event_.gattc.gattc_param.notify.value = this->event_.gattc.data;
|
||||
break;
|
||||
case ESP_GATTC_READ_CHAR_EVT:
|
||||
case ESP_GATTC_READ_DESCR_EVT:
|
||||
memcpy(this->event_.gattc.data, p->read.value, p->read.value_len);
|
||||
this->event_.gattc.gattc_param.read.value = this->event_.gattc.data;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
this->type_ = GATTC;
|
||||
};
|
||||
|
||||
BLEEvent(esp_gatts_cb_event_t e, esp_gatt_if_t i, esp_ble_gatts_cb_param_t *p) {
|
||||
this->event_.gatts.gatts_event = e;
|
||||
this->event_.gatts.gatts_if = i;
|
||||
memcpy(&this->event_.gatts.gatts_param, p, sizeof(esp_ble_gatts_cb_param_t));
|
||||
// Need to also make a copy of write data.
|
||||
switch (e) {
|
||||
case ESP_GATTS_WRITE_EVT:
|
||||
memcpy(this->event_.gatts.data, p->write.value, p->write.len);
|
||||
this->event_.gatts.gatts_param.write.value = this->event_.gatts.data;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
this->type_ = GATTS;
|
||||
};
|
||||
|
||||
union {
|
||||
// NOLINTNEXTLINE(readability-identifier-naming)
|
||||
struct gap_event {
|
||||
esp_gap_ble_cb_event_t gap_event;
|
||||
esp_ble_gap_cb_param_t gap_param;
|
||||
} gap;
|
||||
|
||||
// NOLINTNEXTLINE(readability-identifier-naming)
|
||||
struct gattc_event {
|
||||
esp_gattc_cb_event_t gattc_event;
|
||||
esp_gatt_if_t gattc_if;
|
||||
esp_ble_gattc_cb_param_t gattc_param;
|
||||
uint8_t data[64];
|
||||
} gattc;
|
||||
|
||||
// NOLINTNEXTLINE(readability-identifier-naming)
|
||||
struct gatts_event {
|
||||
esp_gatts_cb_event_t gatts_event;
|
||||
esp_gatt_if_t gatts_if;
|
||||
esp_ble_gatts_cb_param_t gatts_param;
|
||||
uint8_t data[64];
|
||||
} gatts;
|
||||
} event_;
|
||||
// NOLINTNEXTLINE(readability-identifier-naming)
|
||||
enum ble_event_t : uint8_t {
|
||||
GAP,
|
||||
GATTC,
|
||||
GATTS,
|
||||
} type_;
|
||||
};
|
||||
|
||||
} // namespace esp32_ble
|
||||
} // namespace esphome
|
||||
|
||||
|
@ -3,6 +3,7 @@ import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ID, CONF_TYPE, CONF_UUID, CONF_TX_POWER
|
||||
from esphome.core import CORE, TimePeriod
|
||||
from esphome.components.esp32 import add_idf_sdkconfig_option
|
||||
from esphome.components import esp32_ble
|
||||
|
||||
DEPENDENCIES = ["esp32"]
|
||||
CONFLICTS_WITH = ["esp32_ble_tracker"]
|
||||
@ -54,6 +55,8 @@ CONFIG_SCHEMA = cv.All(
|
||||
validate_config,
|
||||
)
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = esp32_ble.validate_variant
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
uuid = config[CONF_UUID].hex
|
||||
|
@ -40,7 +40,7 @@ void BLEClientBase::loop() {
|
||||
float BLEClientBase::get_setup_priority() const { return setup_priority::AFTER_BLUETOOTH; }
|
||||
|
||||
bool BLEClientBase::parse_device(const espbt::ESPBTDevice &device) {
|
||||
if (device.address_uint64() != this->address_)
|
||||
if (this->address_ == 0 || device.address_uint64() != this->address_)
|
||||
return false;
|
||||
if (this->state_ != espbt::ClientState::IDLE && this->state_ != espbt::ClientState::SEARCHING)
|
||||
return false;
|
||||
@ -132,16 +132,17 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
|
||||
this->set_state(espbt::ClientState::IDLE);
|
||||
break;
|
||||
}
|
||||
if (this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE) {
|
||||
this->set_state(espbt::ClientState::CONNECTED);
|
||||
this->state_ = espbt::ClientState::ESTABLISHED;
|
||||
break;
|
||||
}
|
||||
auto ret = esp_ble_gattc_send_mtu_req(this->gattc_if_, param->open.conn_id);
|
||||
if (ret) {
|
||||
ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_send_mtu_req failed, status=%x", this->connection_index_,
|
||||
this->address_str_.c_str(), ret);
|
||||
}
|
||||
if (this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE) {
|
||||
ESP_LOGI(TAG, "[%d] [%s] Connected", this->connection_index_, this->address_str_.c_str());
|
||||
this->set_state(espbt::ClientState::CONNECTED);
|
||||
this->state_ = espbt::ClientState::ESTABLISHED;
|
||||
break;
|
||||
}
|
||||
esp_ble_gattc_search_service(esp_gattc_if, param->cfg_mtu.conn_id, nullptr);
|
||||
break;
|
||||
}
|
||||
@ -189,6 +190,7 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
|
||||
ESP_LOGV(TAG, "[%d] [%s] start_handle: 0x%x end_handle: 0x%x", this->connection_index_,
|
||||
this->address_str_.c_str(), svc->start_handle, svc->end_handle);
|
||||
}
|
||||
ESP_LOGI(TAG, "[%d] [%s] Connected", this->connection_index_, this->address_str_.c_str());
|
||||
this->set_state(espbt::ClientState::CONNECTED);
|
||||
this->state_ = espbt::ClientState::ESTABLISHED;
|
||||
break;
|
||||
|
@ -7,21 +7,25 @@ from esphome.components.esp32 import add_idf_sdkconfig_option
|
||||
|
||||
AUTO_LOAD = ["esp32_ble"]
|
||||
CODEOWNERS = ["@jesserockz"]
|
||||
CONFLICTS_WITH = ["esp32_ble_tracker", "esp32_ble_beacon"]
|
||||
CONFLICTS_WITH = ["esp32_ble_beacon"]
|
||||
DEPENDENCIES = ["esp32"]
|
||||
|
||||
CONF_MANUFACTURER = "manufacturer"
|
||||
CONF_BLE_ID = "ble_id"
|
||||
|
||||
esp32_ble_server_ns = cg.esphome_ns.namespace("esp32_ble_server")
|
||||
BLEServer = esp32_ble_server_ns.class_("BLEServer", cg.Component)
|
||||
BLEServer = esp32_ble_server_ns.class_(
|
||||
"BLEServer",
|
||||
cg.Component,
|
||||
esp32_ble.GATTsEventHandler,
|
||||
cg.Parented.template(esp32_ble.ESP32BLE),
|
||||
)
|
||||
BLEServiceComponent = esp32_ble_server_ns.class_("BLEServiceComponent")
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(BLEServer),
|
||||
cv.GenerateID(CONF_BLE_ID): cv.use_id(esp32_ble.ESP32BLE),
|
||||
cv.GenerateID(esp32_ble.CONF_BLE_ID): cv.use_id(esp32_ble.ESP32BLE),
|
||||
cv.Optional(CONF_MANUFACTURER, default="ESPHome"): cv.string,
|
||||
cv.Optional(CONF_MODEL): cv.string,
|
||||
}
|
||||
@ -29,16 +33,18 @@ CONFIG_SCHEMA = cv.Schema(
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
parent = await cg.get_variable(config[CONF_BLE_ID])
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
|
||||
await cg.register_component(var, config)
|
||||
|
||||
parent = await cg.get_variable(config[esp32_ble.CONF_BLE_ID])
|
||||
cg.add(parent.register_gatts_event_handler(var))
|
||||
cg.add(var.set_parent(parent))
|
||||
|
||||
cg.add(var.set_manufacturer(config[CONF_MANUFACTURER]))
|
||||
if CONF_MODEL in config:
|
||||
cg.add(var.set_model(config[CONF_MODEL]))
|
||||
cg.add_define("USE_ESP32_BLE_SERVER")
|
||||
|
||||
cg.add(parent.set_server(var))
|
||||
|
||||
if CORE.using_esp_idf:
|
||||
add_idf_sdkconfig_option("CONFIG_BT_ENABLED", True)
|
||||
|
@ -25,7 +25,8 @@ static const uint16_t VERSION_UUID = 0x2A26;
|
||||
static const uint16_t MANUFACTURER_UUID = 0x2A29;
|
||||
|
||||
void BLEServer::setup() {
|
||||
if (this->is_failed()) {
|
||||
if (this->parent_->is_failed()) {
|
||||
this->mark_failed();
|
||||
ESP_LOGE(TAG, "BLE Server was marked failed by ESP32BLE");
|
||||
return;
|
||||
}
|
||||
|
@ -3,6 +3,7 @@
|
||||
#include "ble_service.h"
|
||||
#include "ble_characteristic.h"
|
||||
|
||||
#include "esphome/components/esp32_ble/ble.h"
|
||||
#include "esphome/components/esp32_ble/ble_advertising.h"
|
||||
#include "esphome/components/esp32_ble/ble_uuid.h"
|
||||
#include "esphome/components/esp32_ble/queue.h"
|
||||
@ -32,7 +33,7 @@ class BLEServiceComponent {
|
||||
virtual void stop();
|
||||
};
|
||||
|
||||
class BLEServer : public Component {
|
||||
class BLEServer : public Component, public GATTsEventHandler, public Parented<ESP32BLE> {
|
||||
public:
|
||||
void setup() override;
|
||||
void loop() override;
|
||||
@ -55,7 +56,8 @@ class BLEServer : public Component {
|
||||
uint32_t get_connected_client_count() { return this->connected_clients_; }
|
||||
const std::map<uint16_t, void *> &get_clients() { return this->clients_; }
|
||||
|
||||
void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param);
|
||||
void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if,
|
||||
esp_ble_gatts_cb_param_t *param) override;
|
||||
|
||||
void register_service_component(BLEServiceComponent *component) { this->service_components_.push_back(component); }
|
||||
|
||||
|
@ -15,9 +15,11 @@ from esphome.const import (
|
||||
CONF_ON_BLE_SERVICE_DATA_ADVERTISE,
|
||||
CONF_ON_BLE_MANUFACTURER_DATA_ADVERTISE,
|
||||
)
|
||||
from esphome.components import esp32_ble
|
||||
from esphome.core import CORE
|
||||
from esphome.components.esp32 import add_idf_sdkconfig_option
|
||||
|
||||
AUTO_LOAD = ["esp32_ble"]
|
||||
DEPENDENCIES = ["esp32"]
|
||||
|
||||
CONF_ESP32_BLE_ID = "esp32_ble_id"
|
||||
@ -26,7 +28,13 @@ CONF_WINDOW = "window"
|
||||
CONF_CONTINUOUS = "continuous"
|
||||
CONF_ON_SCAN_END = "on_scan_end"
|
||||
esp32_ble_tracker_ns = cg.esphome_ns.namespace("esp32_ble_tracker")
|
||||
ESP32BLETracker = esp32_ble_tracker_ns.class_("ESP32BLETracker", cg.Component)
|
||||
ESP32BLETracker = esp32_ble_tracker_ns.class_(
|
||||
"ESP32BLETracker",
|
||||
cg.Component,
|
||||
esp32_ble.GAPEventHandler,
|
||||
esp32_ble.GATTcEventHandler,
|
||||
cg.Parented.template(esp32_ble.ESP32BLE),
|
||||
)
|
||||
ESPBTClient = esp32_ble_tracker_ns.class_("ESPBTClient")
|
||||
ESPBTDeviceListener = esp32_ble_tracker_ns.class_("ESPBTDeviceListener")
|
||||
ESPBTDevice = esp32_ble_tracker_ns.class_("ESPBTDevice")
|
||||
@ -137,6 +145,7 @@ def as_reversed_hex_array(value):
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(ESP32BLETracker),
|
||||
cv.GenerateID(esp32_ble.CONF_BLE_ID): cv.use_id(esp32_ble.ESP32BLE),
|
||||
cv.Optional(CONF_SCAN_PARAMETERS, default={}): cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
@ -187,6 +196,8 @@ CONFIG_SCHEMA = cv.Schema(
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = esp32_ble.validate_variant
|
||||
|
||||
ESP_BLE_DEVICE_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(CONF_ESP32_BLE_ID): cv.use_id(ESP32BLETracker),
|
||||
@ -197,6 +208,12 @@ ESP_BLE_DEVICE_SCHEMA = cv.Schema(
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
|
||||
parent = await cg.get_variable(config[esp32_ble.CONF_BLE_ID])
|
||||
cg.add(parent.register_gap_event_handler(var))
|
||||
cg.add(parent.register_gattc_event_handler(var))
|
||||
cg.add(var.set_parent(parent))
|
||||
|
||||
params = config[CONF_SCAN_PARAMETERS]
|
||||
cg.add(var.set_scan_duration(params[CONF_DURATION]))
|
||||
cg.add(var.set_scan_interval(int(params[CONF_INTERVAL].total_milliseconds / 0.625)))
|
||||
@ -245,6 +262,7 @@ async def to_code(config):
|
||||
add_idf_sdkconfig_option("CONFIG_BTU_TASK_STACK_SIZE", 8192)
|
||||
|
||||
cg.add_define("USE_OTA_STATE_CALLBACK") # To be notified when an OTA update starts
|
||||
cg.add_define("USE_ESP32_BLE_CLIENT")
|
||||
|
||||
|
||||
ESP32_BLE_START_SCAN_ACTION_SCHEMA = cv.Schema(
|
||||
|
@ -7,14 +7,14 @@
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#include <nvs_flash.h>
|
||||
#include <freertos/FreeRTOSConfig.h>
|
||||
#include <esp_bt_main.h>
|
||||
#include <esp_bt.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/task.h>
|
||||
#include <esp_gap_ble_api.h>
|
||||
#include <esp_bt_defs.h>
|
||||
#include <esp_bt_main.h>
|
||||
#include <esp_gap_ble_api.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/FreeRTOSConfig.h>
|
||||
#include <freertos/task.h>
|
||||
#include <nvs_flash.h>
|
||||
|
||||
#ifdef USE_OTA
|
||||
#include "esphome/components/ota/ota_component.h"
|
||||
@ -45,17 +45,19 @@ uint64_t ble_addr_to_uint64(const esp_bd_addr_t address) {
|
||||
return u;
|
||||
}
|
||||
|
||||
float ESP32BLETracker::get_setup_priority() const { return setup_priority::BLUETOOTH; }
|
||||
float ESP32BLETracker::get_setup_priority() const { return setup_priority::AFTER_BLUETOOTH; }
|
||||
|
||||
void ESP32BLETracker::setup() {
|
||||
if (this->parent_->is_failed()) {
|
||||
this->mark_failed();
|
||||
ESP_LOGE(TAG, "BLE Tracker was marked failed by ESP32BLE");
|
||||
return;
|
||||
}
|
||||
|
||||
global_esp32_ble_tracker = this;
|
||||
this->scan_result_lock_ = xSemaphoreCreateMutex();
|
||||
this->scan_end_lock_ = xSemaphoreCreateMutex();
|
||||
this->scanner_idle_ = true;
|
||||
if (!ESP32BLETracker::ble_setup()) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef USE_OTA
|
||||
ota::global_ota_component->add_on_state_callback([this](ota::OTAState state, float progress, uint8_t error) {
|
||||
@ -75,18 +77,6 @@ void ESP32BLETracker::setup() {
|
||||
}
|
||||
|
||||
void ESP32BLETracker::loop() {
|
||||
BLEEvent *ble_event = this->ble_events_.pop();
|
||||
while (ble_event != nullptr) {
|
||||
if (ble_event->type_) {
|
||||
this->real_gattc_event_handler_(ble_event->event_.gattc.gattc_event, ble_event->event_.gattc.gattc_if,
|
||||
&ble_event->event_.gattc.gattc_param);
|
||||
} else {
|
||||
this->real_gap_event_handler_(ble_event->event_.gap.gap_event, &ble_event->event_.gap.gap_param);
|
||||
}
|
||||
delete ble_event; // NOLINT(cppcoreguidelines-owning-memory)
|
||||
ble_event = this->ble_events_.pop();
|
||||
}
|
||||
|
||||
int connecting = 0;
|
||||
int discovered = 0;
|
||||
int searching = 0;
|
||||
@ -238,85 +228,6 @@ void ESP32BLETracker::stop_scan() {
|
||||
this->cancel_timeout("scan");
|
||||
}
|
||||
|
||||
bool ESP32BLETracker::ble_setup() {
|
||||
// Initialize non-volatile storage for the bluetooth controller
|
||||
esp_err_t err = nvs_flash_init();
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "nvs_flash_init failed: %d", err);
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef USE_ARDUINO
|
||||
if (!btStart()) {
|
||||
ESP_LOGE(TAG, "btStart failed: %d", esp_bt_controller_get_status());
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
if (esp_bt_controller_get_status() != ESP_BT_CONTROLLER_STATUS_ENABLED) {
|
||||
// start bt controller
|
||||
if (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_IDLE) {
|
||||
esp_bt_controller_config_t cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
|
||||
err = esp_bt_controller_init(&cfg);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "esp_bt_controller_init failed: %s", esp_err_to_name(err));
|
||||
return false;
|
||||
}
|
||||
while (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_IDLE)
|
||||
;
|
||||
}
|
||||
if (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_INITED) {
|
||||
err = esp_bt_controller_enable(ESP_BT_MODE_BLE);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "esp_bt_controller_enable failed: %s", esp_err_to_name(err));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (esp_bt_controller_get_status() != ESP_BT_CONTROLLER_STATUS_ENABLED) {
|
||||
ESP_LOGE(TAG, "esp bt controller enable failed");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT);
|
||||
|
||||
err = esp_bluedroid_init();
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "esp_bluedroid_init failed: %d", err);
|
||||
return false;
|
||||
}
|
||||
err = esp_bluedroid_enable();
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "esp_bluedroid_enable failed: %d", err);
|
||||
return false;
|
||||
}
|
||||
err = esp_ble_gap_register_callback(ESP32BLETracker::gap_event_handler);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "esp_ble_gap_register_callback failed: %d", err);
|
||||
return false;
|
||||
}
|
||||
err = esp_ble_gattc_register_callback(ESP32BLETracker::gattc_event_handler);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "esp_ble_gattc_register_callback failed: %d", err);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Empty name
|
||||
esp_ble_gap_set_device_name("");
|
||||
|
||||
esp_ble_io_cap_t iocap = ESP_IO_CAP_NONE;
|
||||
err = esp_ble_gap_set_security_param(ESP_BLE_SM_IOCAP_MODE, &iocap, sizeof(uint8_t));
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "esp_ble_gap_set_security_param failed: %d", err);
|
||||
return false;
|
||||
}
|
||||
|
||||
// BLE takes some time to be fully set up, 200ms should be more than enough
|
||||
delay(200); // NOLINT
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ESP32BLETracker::start_scan_(bool first) {
|
||||
// The lock must be held when calling this function.
|
||||
if (xSemaphoreTake(this->scan_end_lock_, 0L)) {
|
||||
@ -369,11 +280,6 @@ void ESP32BLETracker::register_client(ESPBTClient *client) {
|
||||
}
|
||||
|
||||
void ESP32BLETracker::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) {
|
||||
BLEEvent *gap_event = new BLEEvent(event, param); // NOLINT(cppcoreguidelines-owning-memory)
|
||||
global_esp32_ble_tracker->ble_events_.push(gap_event);
|
||||
} // NOLINT(clang-analyzer-cplusplus.NewDeleteLeaks)
|
||||
|
||||
void ESP32BLETracker::real_gap_event_handler_(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) {
|
||||
switch (event) {
|
||||
case ESP_GAP_BLE_SCAN_RESULT_EVT:
|
||||
this->gap_scan_result_(param->scan_rst);
|
||||
@ -428,204 +334,11 @@ void ESP32BLETracker::gap_scan_result_(const esp_ble_gap_cb_param_t::ble_scan_re
|
||||
|
||||
void ESP32BLETracker::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
||||
esp_ble_gattc_cb_param_t *param) {
|
||||
BLEEvent *gattc_event = new BLEEvent(event, gattc_if, param); // NOLINT(cppcoreguidelines-owning-memory)
|
||||
global_esp32_ble_tracker->ble_events_.push(gattc_event);
|
||||
} // NOLINT(clang-analyzer-cplusplus.NewDeleteLeaks)
|
||||
|
||||
void ESP32BLETracker::real_gattc_event_handler_(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
||||
esp_ble_gattc_cb_param_t *param) {
|
||||
for (auto *client : this->clients_) {
|
||||
client->gattc_event_handler(event, gattc_if, param);
|
||||
}
|
||||
}
|
||||
|
||||
ESPBTUUID::ESPBTUUID() : uuid_() {}
|
||||
ESPBTUUID ESPBTUUID::from_uint16(uint16_t uuid) {
|
||||
ESPBTUUID ret;
|
||||
ret.uuid_.len = ESP_UUID_LEN_16;
|
||||
ret.uuid_.uuid.uuid16 = uuid;
|
||||
return ret;
|
||||
}
|
||||
ESPBTUUID ESPBTUUID::from_uint32(uint32_t uuid) {
|
||||
ESPBTUUID ret;
|
||||
ret.uuid_.len = ESP_UUID_LEN_32;
|
||||
ret.uuid_.uuid.uuid32 = uuid;
|
||||
return ret;
|
||||
}
|
||||
ESPBTUUID ESPBTUUID::from_raw(const uint8_t *data) {
|
||||
ESPBTUUID ret;
|
||||
ret.uuid_.len = ESP_UUID_LEN_128;
|
||||
for (size_t i = 0; i < ESP_UUID_LEN_128; i++)
|
||||
ret.uuid_.uuid.uuid128[i] = data[i];
|
||||
return ret;
|
||||
}
|
||||
ESPBTUUID ESPBTUUID::from_raw(const std::string &data) {
|
||||
ESPBTUUID ret;
|
||||
if (data.length() == 4) {
|
||||
ret.uuid_.len = ESP_UUID_LEN_16;
|
||||
ret.uuid_.uuid.uuid16 = 0;
|
||||
for (int i = 0; i < data.length();) {
|
||||
uint8_t msb = data.c_str()[i];
|
||||
uint8_t lsb = data.c_str()[i + 1];
|
||||
|
||||
if (msb > '9')
|
||||
msb -= 7;
|
||||
if (lsb > '9')
|
||||
lsb -= 7;
|
||||
ret.uuid_.uuid.uuid16 += (((msb & 0x0F) << 4) | (lsb & 0x0F)) << (2 - i) * 4;
|
||||
i += 2;
|
||||
}
|
||||
} else if (data.length() == 8) {
|
||||
ret.uuid_.len = ESP_UUID_LEN_32;
|
||||
ret.uuid_.uuid.uuid32 = 0;
|
||||
for (int i = 0; i < data.length();) {
|
||||
uint8_t msb = data.c_str()[i];
|
||||
uint8_t lsb = data.c_str()[i + 1];
|
||||
|
||||
if (msb > '9')
|
||||
msb -= 7;
|
||||
if (lsb > '9')
|
||||
lsb -= 7;
|
||||
ret.uuid_.uuid.uuid32 += (((msb & 0x0F) << 4) | (lsb & 0x0F)) << (6 - i) * 4;
|
||||
i += 2;
|
||||
}
|
||||
} else if (data.length() == 16) { // how we can have 16 byte length string reprezenting 128 bit uuid??? needs to be
|
||||
// investigated (lack of time)
|
||||
ret.uuid_.len = ESP_UUID_LEN_128;
|
||||
memcpy(ret.uuid_.uuid.uuid128, (uint8_t *) data.data(), 16);
|
||||
} else if (data.length() == 36) {
|
||||
// If the length of the string is 36 bytes then we will assume it is a long hex string in
|
||||
// UUID format.
|
||||
ret.uuid_.len = ESP_UUID_LEN_128;
|
||||
int n = 0;
|
||||
for (int i = 0; i < data.length();) {
|
||||
if (data.c_str()[i] == '-')
|
||||
i++;
|
||||
uint8_t msb = data.c_str()[i];
|
||||
uint8_t lsb = data.c_str()[i + 1];
|
||||
|
||||
if (msb > '9')
|
||||
msb -= 7;
|
||||
if (lsb > '9')
|
||||
lsb -= 7;
|
||||
ret.uuid_.uuid.uuid128[15 - n++] = ((msb & 0x0F) << 4) | (lsb & 0x0F);
|
||||
i += 2;
|
||||
}
|
||||
} else {
|
||||
ESP_LOGE(TAG, "ERROR: UUID value not 2, 4, 16 or 36 bytes - %s", data.c_str());
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
ESPBTUUID ESPBTUUID::from_uuid(esp_bt_uuid_t uuid) {
|
||||
ESPBTUUID ret;
|
||||
ret.uuid_.len = uuid.len;
|
||||
if (uuid.len == ESP_UUID_LEN_16) {
|
||||
ret.uuid_.uuid.uuid16 = uuid.uuid.uuid16;
|
||||
} else if (uuid.len == ESP_UUID_LEN_32) {
|
||||
ret.uuid_.uuid.uuid32 = uuid.uuid.uuid32;
|
||||
} else if (uuid.len == ESP_UUID_LEN_128) {
|
||||
memcpy(ret.uuid_.uuid.uuid128, uuid.uuid.uuid128, ESP_UUID_LEN_128);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
ESPBTUUID ESPBTUUID::as_128bit() const {
|
||||
if (this->uuid_.len == ESP_UUID_LEN_128) {
|
||||
return *this;
|
||||
}
|
||||
uint8_t data[] = {0xFB, 0x34, 0x9B, 0x5F, 0x80, 0x00, 0x00, 0x80, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
uint32_t uuid32;
|
||||
if (this->uuid_.len == ESP_UUID_LEN_32) {
|
||||
uuid32 = this->uuid_.uuid.uuid32;
|
||||
} else {
|
||||
uuid32 = this->uuid_.uuid.uuid16;
|
||||
}
|
||||
for (uint8_t i = 0; i < this->uuid_.len; i++) {
|
||||
data[12 + i] = ((uuid32 >> i * 8) & 0xFF);
|
||||
}
|
||||
return ESPBTUUID::from_raw(data);
|
||||
}
|
||||
bool ESPBTUUID::contains(uint8_t data1, uint8_t data2) const {
|
||||
if (this->uuid_.len == ESP_UUID_LEN_16) {
|
||||
return (this->uuid_.uuid.uuid16 >> 8) == data2 && (this->uuid_.uuid.uuid16 & 0xFF) == data1;
|
||||
} else if (this->uuid_.len == ESP_UUID_LEN_32) {
|
||||
for (uint8_t i = 0; i < 3; i++) {
|
||||
bool a = ((this->uuid_.uuid.uuid32 >> i * 8) & 0xFF) == data1;
|
||||
bool b = ((this->uuid_.uuid.uuid32 >> (i + 1) * 8) & 0xFF) == data2;
|
||||
if (a && b)
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
for (uint8_t i = 0; i < 15; i++) {
|
||||
if (this->uuid_.uuid.uuid128[i] == data1 && this->uuid_.uuid.uuid128[i + 1] == data2)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
bool ESPBTUUID::operator==(const ESPBTUUID &uuid) const {
|
||||
if (this->uuid_.len == uuid.uuid_.len) {
|
||||
switch (this->uuid_.len) {
|
||||
case ESP_UUID_LEN_16:
|
||||
if (uuid.uuid_.uuid.uuid16 == this->uuid_.uuid.uuid16) {
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case ESP_UUID_LEN_32:
|
||||
if (uuid.uuid_.uuid.uuid32 == this->uuid_.uuid.uuid32) {
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case ESP_UUID_LEN_128:
|
||||
for (int i = 0; i < ESP_UUID_LEN_128; i++) {
|
||||
if (uuid.uuid_.uuid.uuid128[i] != this->uuid_.uuid.uuid128[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
return this->as_128bit() == uuid.as_128bit();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
esp_bt_uuid_t ESPBTUUID::get_uuid() const { return this->uuid_; }
|
||||
std::string ESPBTUUID::to_string() const {
|
||||
switch (this->uuid_.len) {
|
||||
case ESP_UUID_LEN_16:
|
||||
return str_snprintf("0x%02X%02X", 6, this->uuid_.uuid.uuid16 >> 8, this->uuid_.uuid.uuid16 & 0xff);
|
||||
case ESP_UUID_LEN_32:
|
||||
return str_snprintf("0x%02X%02X%02X%02X", 10, this->uuid_.uuid.uuid32 >> 24,
|
||||
(this->uuid_.uuid.uuid32 >> 16 & 0xff), (this->uuid_.uuid.uuid32 >> 8 & 0xff),
|
||||
this->uuid_.uuid.uuid32 & 0xff);
|
||||
default:
|
||||
case ESP_UUID_LEN_128:
|
||||
std::string buf;
|
||||
for (int8_t i = 15; i >= 0; i--) {
|
||||
buf += str_snprintf("%02X", 2, this->uuid_.uuid.uuid128[i]);
|
||||
if (i == 6 || i == 8 || i == 10 || i == 12)
|
||||
buf += "-";
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
uint64_t ESPBTUUID::get_128bit_high() const {
|
||||
esp_bt_uuid_t uuid = this->as_128bit().get_uuid();
|
||||
return ((uint64_t) uuid.uuid.uuid128[15] << 56) | ((uint64_t) uuid.uuid.uuid128[14] << 48) |
|
||||
((uint64_t) uuid.uuid.uuid128[13] << 40) | ((uint64_t) uuid.uuid.uuid128[12] << 32) |
|
||||
((uint64_t) uuid.uuid.uuid128[11] << 24) | ((uint64_t) uuid.uuid.uuid128[10] << 16) |
|
||||
((uint64_t) uuid.uuid.uuid128[9] << 8) | ((uint64_t) uuid.uuid.uuid128[8]);
|
||||
}
|
||||
uint64_t ESPBTUUID::get_128bit_low() const {
|
||||
esp_bt_uuid_t uuid = this->as_128bit().get_uuid();
|
||||
return ((uint64_t) uuid.uuid.uuid128[7] << 56) | ((uint64_t) uuid.uuid.uuid128[6] << 48) |
|
||||
((uint64_t) uuid.uuid.uuid128[5] << 40) | ((uint64_t) uuid.uuid.uuid128[4] << 32) |
|
||||
((uint64_t) uuid.uuid.uuid128[3] << 24) | ((uint64_t) uuid.uuid.uuid128[2] << 16) |
|
||||
((uint64_t) uuid.uuid.uuid128[1] << 8) | ((uint64_t) uuid.uuid.uuid128[0]);
|
||||
}
|
||||
|
||||
ESPBLEiBeacon::ESPBLEiBeacon(const uint8_t *data) { memcpy(&this->beacon_data_, data, sizeof(beacon_data_)); }
|
||||
optional<ESPBLEiBeacon> ESPBLEiBeacon::from_manufacturer_data(const ServiceData &data) {
|
||||
if (!data.uuid.contains(0x4C, 0x00))
|
||||
@ -706,11 +419,7 @@ void ESPBTDevice::parse_adv_(const esp_ble_gap_cb_param_t::ble_scan_result_evt_p
|
||||
while (offset + 2 < len) {
|
||||
const uint8_t field_length = payload[offset++]; // First byte is length of adv record
|
||||
if (field_length == 0) {
|
||||
if (offset < param.adv_data_len && param.scan_rsp_len > 0) { // Zero padded advertisement data
|
||||
offset = param.adv_data_len;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
continue; // Possible zero padded advertisement data
|
||||
}
|
||||
|
||||
// first byte of adv record is adv record type
|
||||
|
@ -1,9 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "queue.h"
|
||||
|
||||
#include <array>
|
||||
#include <string>
|
||||
@ -15,40 +14,16 @@
|
||||
#include <esp_gattc_api.h>
|
||||
#include <esp_bt_defs.h>
|
||||
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/semphr.h>
|
||||
|
||||
#include "esphome/components/esp32_ble/ble.h"
|
||||
#include "esphome/components/esp32_ble/ble_uuid.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace esp32_ble_tracker {
|
||||
|
||||
class ESPBTUUID {
|
||||
public:
|
||||
ESPBTUUID();
|
||||
|
||||
static ESPBTUUID from_uint16(uint16_t uuid);
|
||||
|
||||
static ESPBTUUID from_uint32(uint32_t uuid);
|
||||
|
||||
static ESPBTUUID from_raw(const uint8_t *data);
|
||||
|
||||
static ESPBTUUID from_raw(const std::string &data);
|
||||
|
||||
static ESPBTUUID from_uuid(esp_bt_uuid_t uuid);
|
||||
|
||||
ESPBTUUID as_128bit() const;
|
||||
|
||||
bool contains(uint8_t data1, uint8_t data2) const;
|
||||
|
||||
bool operator==(const ESPBTUUID &uuid) const;
|
||||
bool operator!=(const ESPBTUUID &uuid) const { return !(*this == uuid); }
|
||||
|
||||
esp_bt_uuid_t get_uuid() const;
|
||||
|
||||
std::string to_string() const;
|
||||
|
||||
uint64_t get_128bit_high() const;
|
||||
uint64_t get_128bit_low() const;
|
||||
|
||||
protected:
|
||||
esp_bt_uuid_t uuid_;
|
||||
};
|
||||
using namespace esp32_ble;
|
||||
|
||||
using adv_data_t = std::vector<uint8_t>;
|
||||
|
||||
@ -191,7 +166,7 @@ class ESPBTClient : public ESPBTDeviceListener {
|
||||
ClientState state_;
|
||||
};
|
||||
|
||||
class ESP32BLETracker : public Component {
|
||||
class ESP32BLETracker : public Component, public GAPEventHandler, public GATTcEventHandler, public Parented<ESP32BLE> {
|
||||
public:
|
||||
void set_scan_duration(uint32_t scan_duration) { scan_duration_ = scan_duration; }
|
||||
void set_scan_interval(uint32_t scan_interval) { scan_interval_ = scan_interval; }
|
||||
@ -218,16 +193,15 @@ class ESP32BLETracker : public Component {
|
||||
void start_scan();
|
||||
void stop_scan();
|
||||
|
||||
void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
||||
esp_ble_gattc_cb_param_t *param) override;
|
||||
void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override;
|
||||
|
||||
protected:
|
||||
/// The FreeRTOS task managing the bluetooth interface.
|
||||
static bool ble_setup();
|
||||
/// Start a single scan by setting up the parameters and doing some esp-idf calls.
|
||||
void start_scan_(bool first);
|
||||
/// Called when a scan ends
|
||||
void end_of_scan_();
|
||||
/// Callback that will handle all GAP events and redistribute them to other callbacks.
|
||||
static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param);
|
||||
void real_gap_event_handler_(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param);
|
||||
/// Called when a `ESP_GAP_BLE_SCAN_RESULT_EVT` event is received.
|
||||
void gap_scan_result_(const esp_ble_gap_cb_param_t::ble_scan_result_evt_param ¶m);
|
||||
/// Called when a `ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT` event is received.
|
||||
@ -238,9 +212,6 @@ class ESP32BLETracker : public Component {
|
||||
void gap_scan_stop_complete_(const esp_ble_gap_cb_param_t::ble_scan_stop_cmpl_evt_param ¶m);
|
||||
|
||||
int app_id_;
|
||||
/// Callback that will handle all GATTC events and redistribute them to other callbacks.
|
||||
static void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param);
|
||||
void real_gattc_event_handler_(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param);
|
||||
|
||||
/// Vector of addresses that have already been printed in print_bt_device_info
|
||||
std::vector<uint64_t> already_discovered_;
|
||||
@ -263,8 +234,6 @@ class ESP32BLETracker : public Component {
|
||||
esp_ble_gap_cb_param_t::ble_scan_result_evt_param scan_result_buffer_[16];
|
||||
esp_bt_status_t scan_start_failed_{ESP_BT_STATUS_SUCCESS};
|
||||
esp_bt_status_t scan_set_param_failed_{ESP_BT_STATUS_SUCCESS};
|
||||
|
||||
Queue<BLEEvent> ble_events_;
|
||||
};
|
||||
|
||||
// NOLINTNEXTLINE
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user