mirror of
https://github.com/syssi/esphome-votronic.git
synced 2025-07-22 20:20:35 +02:00
Compare commits
38 Commits
2.0.0
...
a6d882de4a
Author | SHA1 | Date | |
---|---|---|---|
|
a6d882de4a | ||
|
8cb0f29c7d | ||
|
79c0236050 | ||
|
f10cae1824 | ||
|
5b72c21170 | ||
|
f9d8ff54c0 | ||
|
57d74094eb | ||
|
db22f3a54a | ||
|
c6c75a19d0 | ||
|
c76f01eb1f | ||
|
aad083cd39 | ||
|
8bf35a2f6f | ||
|
5dc30e99fe | ||
|
eeede3e32e | ||
|
1fbbb5a9da | ||
|
1d705f4271 | ||
|
8e3c94a72a | ||
|
9aa26b3c9d | ||
|
91a7913e8b | ||
|
67d70a72ea | ||
|
98d0326f68 | ||
|
75f0a1da4b | ||
|
788a5855d5 | ||
|
ac7516f143 | ||
|
036ce247e3 | ||
|
9db23fcf4c | ||
|
98a0f0bf1e | ||
|
d1a917a1c7 | ||
|
0e50d94972 | ||
|
44f7d5794f | ||
|
8540227e75 | ||
|
6b6fc54783 | ||
|
0d0def1a81 | ||
|
961fcd0bdd | ||
|
afada45065 | ||
|
467af8f150 | ||
|
634964be80 | ||
|
cdec1318b6 |
1
.github/FUNDING.yml
vendored
Normal file
1
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1 @@
|
||||
buy_me_a_coffee: syssi
|
38
.github/actions/restore-python/action.yml
vendored
Normal file
38
.github/actions/restore-python/action.yml
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
name: Restore Python
|
||||
inputs:
|
||||
python-version:
|
||||
description: Python version to restore
|
||||
required: true
|
||||
type: string
|
||||
cache-key:
|
||||
description: Cache key to use
|
||||
required: true
|
||||
type: string
|
||||
outputs:
|
||||
python-version:
|
||||
description: Python version restored
|
||||
value: ${{ steps.python.outputs.python-version }}
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Set up Python ${{ inputs.python-version }}
|
||||
id: python
|
||||
uses: actions/setup-python@v5.6.0
|
||||
with:
|
||||
python-version: ${{ inputs.python-version }}
|
||||
- name: Restore Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache/restore@v4.2.3
|
||||
with:
|
||||
path: venv
|
||||
# yamllint disable-line rule:line-length
|
||||
key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-venv-${{ inputs.cache-key }}
|
||||
- name: Create Python virtual environment
|
||||
shell: bash
|
||||
run: |
|
||||
python -m venv venv
|
||||
source venv/bin/activate
|
||||
python --version
|
||||
cd esphome
|
||||
pip install -r requirements.txt -r requirements_test.txt
|
||||
pip install -e .
|
599
.github/workflows/ci.yaml
vendored
599
.github/workflows/ci.yaml
vendored
@@ -4,218 +4,465 @@ on: # yamllint disable-line rule:truthy
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
pull_request:
|
||||
schedule:
|
||||
- cron: 0 12 * * *
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
FORCE_COLOR: 1
|
||||
DEFAULT_PYTHON: "3.11"
|
||||
PYUPGRADE_TARGET: "--py311-plus"
|
||||
|
||||
concurrency:
|
||||
# yamllint disable-line rule:line-length
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
yamllint:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: yaml-lint
|
||||
uses: ibiqlik/action-yamllint@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Run yamllint
|
||||
uses: frenck/action-yamllint@v1
|
||||
with:
|
||||
config_file: .yamllint
|
||||
config: .yamllint
|
||||
|
||||
lint-clang-format:
|
||||
env:
|
||||
esphome_directory: esphome
|
||||
runs-on: ubuntu-latest
|
||||
# cpp lint job runs with esphome-lint docker image so that clang-format-*
|
||||
# doesn't have to be installed
|
||||
container: esphome/esphome-lint:latest
|
||||
bundle:
|
||||
name: Bundle external component and ESPHome
|
||||
runs-on: ubuntu-24.04
|
||||
outputs:
|
||||
repo-hash: ${{ github.sha }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
# Set up the pio project so that the cpp checks know how files are compiled
|
||||
# (build flags, libraries etc)
|
||||
- name: Check out this project
|
||||
uses: actions/checkout@v4.1.7
|
||||
|
||||
- name: 💣 Clone esphome project
|
||||
run: git clone https://github.com/esphome/esphome.git
|
||||
- name: 💣 Copy component into the esphome project
|
||||
- name: Check out code from ESPHome project
|
||||
uses: actions/checkout@v4.1.7
|
||||
with:
|
||||
repository: esphome/esphome
|
||||
ref: dev
|
||||
path: esphome
|
||||
|
||||
- name: Copy external component into the esphome project
|
||||
run: |
|
||||
cd esphome
|
||||
cp -r ../components/* esphome/components/
|
||||
git config user.name "ci"
|
||||
git config user.email "ci@github.com"
|
||||
git add .
|
||||
git commit -a -m "Add external component"
|
||||
working-directory: ${{ env.esphome_directory }}
|
||||
ln -sf ../venv venv
|
||||
|
||||
- name: Set up platformio environment
|
||||
run: pio init --ide atom
|
||||
working-directory: ${{ env.esphome_directory }}
|
||||
- name: Archive prepared repository
|
||||
uses: pyTooling/upload-artifact@v4
|
||||
with:
|
||||
name: bundle
|
||||
path: .
|
||||
include-hidden-files: true
|
||||
retention-days: 1
|
||||
|
||||
common:
|
||||
name: Create common environment
|
||||
runs-on: ubuntu-24.04
|
||||
needs: bundle
|
||||
outputs:
|
||||
cache-key: ${{ steps.cache-key.outputs.key }}
|
||||
steps:
|
||||
- name: Download prepared repository
|
||||
uses: pyTooling/download-artifact@v4
|
||||
with:
|
||||
name: bundle
|
||||
path: .
|
||||
- name: Update index to make "git diff-index" happy
|
||||
run: git update-index -q --really-refresh
|
||||
|
||||
- name: Generate cache-key
|
||||
id: cache-key
|
||||
run: echo key="${{ hashFiles('esphome/requirements.txt', 'esphome/requirements_test.txt') }}" >> $GITHUB_OUTPUT
|
||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||
id: python
|
||||
uses: actions/setup-python@v5.6.0
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
- name: Restore Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache@v4.2.3
|
||||
with:
|
||||
path: venv
|
||||
# yamllint disable-line rule:line-length
|
||||
key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-venv-${{ steps.cache-key.outputs.key }}
|
||||
|
||||
- name: Create Python virtual environment
|
||||
if: steps.cache-venv.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
python -m venv venv
|
||||
. venv/bin/activate
|
||||
python --version
|
||||
cd esphome
|
||||
pip install -r requirements.txt -r requirements_test.txt
|
||||
pip install -e .
|
||||
|
||||
ruff:
|
||||
name: Check ruff
|
||||
runs-on: ubuntu-24.04
|
||||
needs:
|
||||
- bundle
|
||||
- common
|
||||
defaults:
|
||||
run:
|
||||
working-directory: esphome
|
||||
steps:
|
||||
- name: Download prepared repository
|
||||
uses: pyTooling/download-artifact@v4
|
||||
with:
|
||||
name: bundle
|
||||
path: .
|
||||
|
||||
- name: Update index to make "git diff-index" happy
|
||||
run: git update-index -q --really-refresh
|
||||
|
||||
- name: Restore Python
|
||||
uses: ./.github/actions/restore-python
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
cache-key: ${{ needs.common.outputs.cache-key }}
|
||||
- name: Run Ruff
|
||||
run: |
|
||||
. venv/bin/activate
|
||||
ruff format esphome tests
|
||||
- name: Suggested changes
|
||||
run: script/ci-suggest-changes
|
||||
if: always()
|
||||
|
||||
flake8:
|
||||
name: Check flake8
|
||||
runs-on: ubuntu-24.04
|
||||
needs:
|
||||
- bundle
|
||||
- common
|
||||
defaults:
|
||||
run:
|
||||
working-directory: esphome
|
||||
steps:
|
||||
- name: Download prepared repository
|
||||
uses: pyTooling/download-artifact@v4
|
||||
with:
|
||||
name: bundle
|
||||
path: .
|
||||
- name: Update index to make "git diff-index" happy
|
||||
run: git update-index -q --really-refresh
|
||||
|
||||
- name: Restore Python
|
||||
uses: ./.github/actions/restore-python
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
cache-key: ${{ needs.common.outputs.cache-key }}
|
||||
- name: Run flake8
|
||||
run: |
|
||||
. venv/bin/activate
|
||||
flake8 esphome
|
||||
- name: Suggested changes
|
||||
run: script/ci-suggest-changes
|
||||
if: always()
|
||||
|
||||
pylint:
|
||||
name: Check pylint
|
||||
runs-on: ubuntu-24.04
|
||||
needs:
|
||||
- bundle
|
||||
- common
|
||||
defaults:
|
||||
run:
|
||||
working-directory: esphome
|
||||
steps:
|
||||
- name: Download prepared repository
|
||||
uses: pyTooling/download-artifact@v4
|
||||
with:
|
||||
name: bundle
|
||||
path: .
|
||||
- name: Update index to make "git diff-index" happy
|
||||
run: git update-index -q --really-refresh
|
||||
|
||||
- name: Restore Python
|
||||
uses: ./.github/actions/restore-python
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
cache-key: ${{ needs.common.outputs.cache-key }}
|
||||
- name: Run pylint
|
||||
run: |
|
||||
. venv/bin/activate
|
||||
pylint -f parseable --persistent=n esphome
|
||||
- name: Suggested changes
|
||||
run: script/ci-suggest-changes
|
||||
if: always()
|
||||
|
||||
pyupgrade:
|
||||
name: Check pyupgrade
|
||||
runs-on: ubuntu-24.04
|
||||
needs:
|
||||
- bundle
|
||||
- common
|
||||
defaults:
|
||||
run:
|
||||
working-directory: esphome
|
||||
steps:
|
||||
- name: Download prepared repository
|
||||
uses: pyTooling/download-artifact@v4
|
||||
with:
|
||||
name: bundle
|
||||
path: .
|
||||
- name: Update index to make "git diff-index" happy
|
||||
run: git update-index -q --really-refresh
|
||||
|
||||
- name: Restore Python
|
||||
uses: ./.github/actions/restore-python
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
cache-key: ${{ needs.common.outputs.cache-key }}
|
||||
- name: Run pyupgrade
|
||||
run: |
|
||||
. venv/bin/activate
|
||||
pyupgrade ${{ env.PYUPGRADE_TARGET }} `find esphome -name "*.py" -type f`
|
||||
- name: Suggested changes
|
||||
run: script/ci-suggest-changes
|
||||
if: always()
|
||||
|
||||
ci-custom:
|
||||
name: Run script/ci-custom
|
||||
runs-on: ubuntu-24.04
|
||||
needs:
|
||||
- bundle
|
||||
- common
|
||||
defaults:
|
||||
run:
|
||||
working-directory: esphome
|
||||
steps:
|
||||
- name: Download prepared repository
|
||||
uses: pyTooling/download-artifact@v4
|
||||
with:
|
||||
name: bundle
|
||||
path: .
|
||||
- name: Update index to make "git diff-index" happy
|
||||
run: git update-index -q --really-refresh
|
||||
|
||||
- name: Restore Python
|
||||
uses: ./.github/actions/restore-python
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
cache-key: ${{ needs.common.outputs.cache-key }}
|
||||
- name: Register matcher
|
||||
run: echo "::add-matcher::.github/workflows/matchers/ci-custom.json"
|
||||
|
||||
- name: Do not suggest to move consts
|
||||
run: |
|
||||
sed -i 's#if len(uses) < 3:#if len(uses) < 8:#' script/ci-custom.py
|
||||
git update-index --assume-unchanged script/ci-custom.py
|
||||
|
||||
- name: Run script/ci-custom
|
||||
run: |
|
||||
. ../venv/bin/activate
|
||||
script/ci-custom.py
|
||||
|
||||
clang-format:
|
||||
name: Check clang-format
|
||||
runs-on: ubuntu-24.04
|
||||
needs:
|
||||
- bundle
|
||||
- common
|
||||
defaults:
|
||||
run:
|
||||
working-directory: esphome
|
||||
steps:
|
||||
- name: Download prepared repository
|
||||
uses: pyTooling/download-artifact@v4
|
||||
with:
|
||||
name: bundle
|
||||
path: .
|
||||
- name: Update index to make "git diff-index" happy
|
||||
run: git update-index -q --really-refresh
|
||||
|
||||
- name: Restore Python
|
||||
uses: ./.github/actions/restore-python
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
cache-key: ${{ needs.common.outputs.cache-key }}
|
||||
- name: Install clang-format
|
||||
run: |
|
||||
. venv/bin/activate
|
||||
pip install clang-format -c requirements_dev.txt
|
||||
- name: Run clang-format
|
||||
run: script/clang-format -i
|
||||
working-directory: ${{ env.esphome_directory }}
|
||||
|
||||
- name: Suggest changes
|
||||
run: script/ci-suggest-changes
|
||||
working-directory: ${{ env.esphome_directory }}
|
||||
|
||||
lint-clang-tidy:
|
||||
env:
|
||||
esphome_directory: esphome
|
||||
runs-on: ubuntu-latest
|
||||
# cpp lint job runs with esphome-lint docker image so that clang-format-*
|
||||
# doesn't have to be installed
|
||||
container: esphome/esphome-lint:latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: 💣 Clone esphome project
|
||||
run: git clone https://github.com/esphome/esphome.git
|
||||
- name: 💣 Copy component into the esphome project
|
||||
run: |
|
||||
cp -r ../components/* esphome/components/
|
||||
git config user.name "ci"
|
||||
git config user.email "ci@github.com"
|
||||
git add .
|
||||
git commit -a -m "Add external component"
|
||||
working-directory: ${{ env.esphome_directory }}
|
||||
. venv/bin/activate
|
||||
script/clang-format -i
|
||||
- name: Suggested changes
|
||||
run: script/ci-suggest-changes
|
||||
if: always()
|
||||
|
||||
# Set up the pio project so that the cpp checks know how files are compiled
|
||||
# (build flags, libraries etc)
|
||||
- name: Set up platformio environment
|
||||
run: pio init --ide atom
|
||||
working-directory: ${{ env.esphome_directory }}
|
||||
clang-tidy:
|
||||
name: ${{ matrix.name }}
|
||||
runs-on: ubuntu-24.04
|
||||
needs:
|
||||
- bundle
|
||||
- common
|
||||
defaults:
|
||||
run:
|
||||
working-directory: esphome
|
||||
strategy:
|
||||
fail-fast: false
|
||||
max-parallel: 2
|
||||
matrix:
|
||||
include:
|
||||
- id: clang-tidy
|
||||
name: Run script/clang-tidy for ESP8266
|
||||
options: --environment esp8266-arduino-tidy --grep USE_ESP8266
|
||||
pio_cache_key: tidyesp8266
|
||||
- id: clang-tidy
|
||||
name: Run script/clang-tidy for ESP32 Arduino 1/4
|
||||
options: --environment esp32-arduino-tidy --split-num 4 --split-at 1
|
||||
pio_cache_key: tidyesp32
|
||||
- id: clang-tidy
|
||||
name: Run script/clang-tidy for ESP32 Arduino 2/4
|
||||
options: --environment esp32-arduino-tidy --split-num 4 --split-at 2
|
||||
pio_cache_key: tidyesp32
|
||||
- id: clang-tidy
|
||||
name: Run script/clang-tidy for ESP32 Arduino 3/4
|
||||
options: --environment esp32-arduino-tidy --split-num 4 --split-at 3
|
||||
pio_cache_key: tidyesp32
|
||||
- id: clang-tidy
|
||||
name: Run script/clang-tidy for ESP32 Arduino 4/4
|
||||
options: --environment esp32-arduino-tidy --split-num 4 --split-at 4
|
||||
pio_cache_key: tidyesp32
|
||||
- id: clang-tidy
|
||||
name: Run script/clang-tidy for ESP32 IDF
|
||||
options: --environment esp32-idf-tidy --grep USE_ESP_IDF
|
||||
pio_cache_key: tidyesp32-idf
|
||||
|
||||
steps:
|
||||
- name: Download prepared repository
|
||||
uses: pyTooling/download-artifact@v4
|
||||
with:
|
||||
name: bundle
|
||||
path: .
|
||||
- name: Update index to make "git diff-index" happy
|
||||
run: git update-index -q --really-refresh
|
||||
|
||||
- name: Restore Python
|
||||
uses: ./.github/actions/restore-python
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
cache-key: ${{ needs.common.outputs.cache-key }}
|
||||
|
||||
- name: Cache platformio
|
||||
if: github.ref == 'refs/heads/dev'
|
||||
uses: actions/cache@v4.2.3
|
||||
with:
|
||||
path: ~/.platformio
|
||||
key: platformio-${{ matrix.pio_cache_key }}
|
||||
|
||||
- name: Cache platformio
|
||||
if: github.ref != 'refs/heads/dev'
|
||||
uses: actions/cache/restore@v4.2.3
|
||||
with:
|
||||
path: ~/.platformio
|
||||
key: platformio-${{ matrix.pio_cache_key }}
|
||||
|
||||
- name: Register problem matchers
|
||||
run: |
|
||||
echo "::add-matcher::.github/workflows/matchers/clang-tidy.json"
|
||||
echo "::add-matcher::.github/workflows/matchers/gcc.json"
|
||||
# Can be removed as soon as esphome-lint container is fixed
|
||||
- name: Add missing pexpect
|
||||
run: pip install pexpect
|
||||
- name: Run lint-cpp
|
||||
run: script/lint-cpp -c
|
||||
working-directory: ${{ env.esphome_directory }}
|
||||
- name: Suggest changes
|
||||
echo "::add-matcher::.github/workflows/matchers/clang-tidy.json"
|
||||
|
||||
- name: Run 'pio run --list-targets -e esp32-idf-tidy'
|
||||
if: matrix.name == 'Run script/clang-tidy for ESP32 IDF'
|
||||
run: |
|
||||
. venv/bin/activate
|
||||
mkdir -p .temp
|
||||
pio run --list-targets -e esp32-idf-tidy
|
||||
|
||||
- name: Run clang-tidy
|
||||
run: |
|
||||
. venv/bin/activate
|
||||
script/clang-tidy --all-headers --fix ${{ matrix.options }} ../components
|
||||
env:
|
||||
# Also cache libdeps, store them in a ~/.platformio subfolder
|
||||
PLATFORMIO_LIBDEPS_DIR: ~/.platformio/libdeps
|
||||
|
||||
- name: Suggested changes
|
||||
run: script/ci-suggest-changes
|
||||
working-directory: ${{ env.esphome_directory }}
|
||||
|
||||
lint-python:
|
||||
env:
|
||||
esphome_directory: esphome
|
||||
# Don't use the esphome-lint docker image because it may contain outdated requirements.
|
||||
# This way, all dependencies are cached via the cache action.
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: '3.9'
|
||||
- name: Cache pip modules
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ~/.cache/pip
|
||||
key: esphome-pip-3.9-${{ hashFiles('setup.py') }}
|
||||
restore-keys: |
|
||||
esphome-pip-3.9-
|
||||
|
||||
- name: 💣Clone esphome project
|
||||
run: git clone https://github.com/esphome/esphome.git
|
||||
- name: 💣Copy component into the esphome project
|
||||
run: |
|
||||
cp -r ../components/* esphome/components/
|
||||
git config user.name "ci"
|
||||
git config user.email "ci@github.com"
|
||||
git add .
|
||||
git commit -a -m "Add external component"
|
||||
working-directory: ${{ env.esphome_directory }}
|
||||
|
||||
- name: Add missing requirements
|
||||
run: pip3 install setuptools wheel
|
||||
- name: Set up python environment
|
||||
run: VIRTUAL_ENV=false script/setup
|
||||
working-directory: ${{ env.esphome_directory }}
|
||||
|
||||
- name: Register problem matchers
|
||||
run: |
|
||||
echo "::add-matcher::.github/workflows/matchers/ci-custom.json"
|
||||
echo "::add-matcher::.github/workflows/matchers/lint-python.json"
|
||||
echo "::add-matcher::.github/workflows/matchers/python.json"
|
||||
|
||||
- name: Lint Custom
|
||||
run: script/ci-custom.py -c
|
||||
working-directory: ${{ env.esphome_directory }}
|
||||
- name: Lint Python
|
||||
run: script/lint-python -c
|
||||
working-directory: ${{ env.esphome_directory }}
|
||||
# yamllint disable-line rule:line-length
|
||||
if: always()
|
||||
|
||||
esphome-config:
|
||||
runs-on: ubuntu-latest
|
||||
name: Validate example configurations
|
||||
runs-on: ubuntu-24.04
|
||||
needs:
|
||||
- bundle
|
||||
- common
|
||||
steps:
|
||||
- name: ⤵️ Check out configuration from GitHub
|
||||
uses: actions/checkout@v2
|
||||
- name: Setup Python 3.9
|
||||
uses: actions/setup-python@v1
|
||||
- name: Download prepared repository
|
||||
uses: pyTooling/download-artifact@v4
|
||||
with:
|
||||
python-version: 3.9
|
||||
- name: Install dependencies
|
||||
name: bundle
|
||||
path: .
|
||||
|
||||
- name: Restore Python
|
||||
uses: ./.github/actions/restore-python
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
cache-key: ${{ needs.common.outputs.cache-key }}
|
||||
|
||||
- name: Register matcher
|
||||
run: echo "::add-matcher::.github/workflows/matchers/esphome-config.json"
|
||||
|
||||
- name: Validate example configurations
|
||||
run: |
|
||||
python -m pip install --upgrade pip setuptools wheel
|
||||
pip install esphome
|
||||
pip list
|
||||
esphome version
|
||||
- name: Write secrets.yaml
|
||||
shell: bash
|
||||
run: 'echo -e "wifi_ssid: ssid\nwifi_password: password\nmqtt_host: host\nmqtt_username: username\nmqtt_password: password" > secrets.yaml'
|
||||
- name: Write tests/secrets.yaml
|
||||
shell: bash
|
||||
run: 'echo -e "wifi_ssid: ssid\nwifi_password: password\nmqtt_host: host\nmqtt_username: username\nmqtt_password: password" > tests/secrets.yaml'
|
||||
- run: |
|
||||
esphome -s external_components_source components config esp32-ble-example-faker.yaml
|
||||
- run: |
|
||||
esphome -s external_components_source components config esp8266-solar-charger-example-faker.yaml
|
||||
esphome -s external_components_source components config esp8266-charger-example-faker.yaml
|
||||
esphome -s external_components_source components config esp8266-charging-converter-example-faker.yaml
|
||||
esphome -s external_components_source components config esp8266-smart-shunt-example-faker.yaml
|
||||
- run: |
|
||||
esphome -s external_components_source ../components config tests/esp8266-fake-charger.yaml
|
||||
esphome -s external_components_source ../components config tests/esp8266-fake-charging-converter.yaml
|
||||
esphome -s external_components_source ../components config tests/esp8266-fake-solar-charger.yaml
|
||||
. venv/bin/activate
|
||||
echo -e "wifi_ssid: ssid\nwifi_password: password\nmqtt_host: host\nmqtt_username: username\nmqtt_password: password" > secrets.yaml
|
||||
for YAML in esp*.yaml; do
|
||||
esphome -s external_components_source components config $YAML
|
||||
done
|
||||
|
||||
- name: Validate test configurations
|
||||
run: |
|
||||
. venv/bin/activate
|
||||
echo -e "wifi_ssid: ssid\nwifi_password: password\nmqtt_host: host\nmqtt_username: username\nmqtt_password: password" > tests/secrets.yaml
|
||||
for YAML in tests/esp*.yaml; do
|
||||
esphome -s external_components_source ../components config $YAML
|
||||
done
|
||||
|
||||
esphome-compile:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [esphome-config]
|
||||
name: Build example configurations
|
||||
runs-on: ubuntu-24.04
|
||||
needs:
|
||||
- bundle
|
||||
- common
|
||||
steps:
|
||||
- name: ⤵️ Check out configuration from GitHub
|
||||
uses: actions/checkout@v2
|
||||
- name: Cache .esphome
|
||||
uses: actions/cache@v2
|
||||
- name: Download prepared repository
|
||||
uses: pyTooling/download-artifact@v4
|
||||
with:
|
||||
path: .esphome
|
||||
key: esphome-compile-esphome-${{ hashFiles('*.yaml') }}
|
||||
restore-keys: esphome-compile-esphome-
|
||||
- name: Cache .pioenvs
|
||||
uses: actions/cache@v2
|
||||
name: bundle
|
||||
path: .
|
||||
|
||||
- name: Restore Python
|
||||
uses: ./.github/actions/restore-python
|
||||
with:
|
||||
path: .pioenvs
|
||||
key: esphome-compile-pioenvs-${{ hashFiles('*.yaml') }}
|
||||
restore-keys: esphome-compile-pioenvs-
|
||||
- name: Set up Python 3.9
|
||||
uses: actions/setup-python@v1
|
||||
with:
|
||||
python-version: 3.9
|
||||
- name: Install dependencies
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
cache-key: ${{ needs.common.outputs.cache-key }}
|
||||
|
||||
- name: Compile example configurations
|
||||
run: |
|
||||
python -m pip install --upgrade pip setuptools wheel
|
||||
pip install esphome
|
||||
pip list
|
||||
esphome version
|
||||
- name: Register problem matchers
|
||||
. venv/bin/activate
|
||||
echo -e "wifi_ssid: ssid\nwifi_password: password\nmqtt_host: host\nmqtt_username: username\nmqtt_password: password" > secrets.yaml
|
||||
for YAML in esp*faker.yaml; do
|
||||
esphome -s external_components_source components compile $YAML
|
||||
done
|
||||
|
||||
- name: Compile test configurations
|
||||
run: |
|
||||
echo "::add-matcher::.github/workflows/matchers/gcc.json"
|
||||
echo "::add-matcher::.github/workflows/matchers/python.json"
|
||||
- name: Write secrets.yaml
|
||||
shell: bash
|
||||
run: 'echo -e "wifi_ssid: ssid\nwifi_password: password\nmqtt_host: host\nmqtt_username: username\nmqtt_password: password" > secrets.yaml'
|
||||
- run: |
|
||||
esphome -s external_components_source components compile esp32-ble-example-faker.yaml
|
||||
- run: |
|
||||
esphome -s external_components_source components compile esp8266-solar-charger-example-faker.yaml
|
||||
esphome -s external_components_source components compile esp8266-charger-example-faker.yaml
|
||||
esphome -s external_components_source components compile esp8266-charging-converter-example-faker.yaml
|
||||
esphome -s external_components_source components compile esp8266-smart-shunt-example-faker.yaml
|
||||
. venv/bin/activate
|
||||
echo -e "wifi_ssid: ssid\nwifi_password: password\nmqtt_host: host\nmqtt_username: username\nmqtt_password: password" > tests/secrets.yaml
|
||||
esphome -s external_components_source ../components compile tests/esp32c6-compatibility-test.yaml
|
||||
|
14
.github/workflows/matchers/esphome-config.json
vendored
Normal file
14
.github/workflows/matchers/esphome-config.json
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"problemMatcher": [
|
||||
{
|
||||
"owner": "esphome-config",
|
||||
"severity": "warning",
|
||||
"pattern": [
|
||||
{
|
||||
"regexp": "^WARNING Using `([^`]+)` is deprecated and will be removed(.*)$",
|
||||
"message": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
@@ -1,28 +1,45 @@
|
||||
# See https://pre-commit.com for more information
|
||||
# See https://pre-commit.com/hooks.html for more hooks
|
||||
# See https://github.com/rytilahti/python-miio/blob/master/.pre-commit-config.yaml
|
||||
repos:
|
||||
- repo: https://github.com/ambv/black
|
||||
rev: 22.1.0
|
||||
- repo: https://github.com/pre-commit/mirrors-isort
|
||||
rev: v5.10.1
|
||||
hooks:
|
||||
- id: isort
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: v0.5.5
|
||||
hooks:
|
||||
# Run the linter.
|
||||
- id: ruff
|
||||
args: [--fix]
|
||||
# Run the formatter.
|
||||
- id: ruff-format
|
||||
- repo: https://github.com/psf/black-pre-commit-mirror
|
||||
rev: 24.4.2
|
||||
hooks:
|
||||
- id: black
|
||||
args:
|
||||
- --safe
|
||||
- --quiet
|
||||
files: ^((components|esphome|script|tests)/.+)?[^/]+\.py$
|
||||
- repo: https://gitlab.com/pycqa/flake8
|
||||
rev: 3.9.2
|
||||
- repo: https://github.com/PyCQA/flake8
|
||||
rev: 7.1.0
|
||||
hooks:
|
||||
- id: flake8
|
||||
additional_dependencies:
|
||||
- flake8-docstrings==1.5.0
|
||||
- pydocstyle==5.1.1
|
||||
files: ^(components|esphome|tests)/.+\.py$
|
||||
- repo: https://github.com/asottile/pyupgrade
|
||||
rev: v2.31.0
|
||||
rev: v3.19.1
|
||||
hooks:
|
||||
- id: pyupgrade
|
||||
args: [--py38-plus]
|
||||
- repo: https://github.com/pocc/pre-commit-hooks
|
||||
rev: v1.3.5
|
||||
args: [--py311-plus]
|
||||
- repo: https://github.com/adrienverge/yamllint.git
|
||||
rev: v1.35.1
|
||||
hooks:
|
||||
- id: yamllint
|
||||
- repo: https://github.com/pre-commit/mirrors-clang-format
|
||||
rev: v13.0.1
|
||||
hooks:
|
||||
- id: clang-format
|
||||
types_or: [c, c++]
|
||||
|
13
.yamllint
13
.yamllint
@@ -6,16 +6,9 @@ yaml-files:
|
||||
- '.yamllint'
|
||||
|
||||
ignore: |
|
||||
/.cache/
|
||||
esphome/**/*.pio*
|
||||
config/automations.yaml
|
||||
config/known_devices.yaml
|
||||
config/scenes.yaml
|
||||
config/google_calendars.yaml
|
||||
config/custom_components/scheduler
|
||||
config/custom_components/xiaomi_cloud_map_extractor
|
||||
config/custom_components/zha_map
|
||||
config/custom_components/hacs
|
||||
.clang-format
|
||||
.esphome/
|
||||
tests/.esphome/
|
||||
|
||||
rules:
|
||||
braces:
|
||||
|
@@ -1,153 +0,0 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import esp32_ble_tracker, esp32_ble_client
|
||||
from esphome.const import (
|
||||
CONF_CHARACTERISTIC_UUID,
|
||||
CONF_ID,
|
||||
CONF_MAC_ADDRESS,
|
||||
CONF_NAME,
|
||||
CONF_ON_CONNECT,
|
||||
CONF_ON_DISCONNECT,
|
||||
CONF_SERVICE_UUID,
|
||||
CONF_TRIGGER_ID,
|
||||
CONF_VALUE,
|
||||
)
|
||||
from esphome import automation
|
||||
|
||||
AUTO_LOAD = ["esp32_ble_client"]
|
||||
CODEOWNERS = ["@buxtronix"]
|
||||
DEPENDENCIES = ["esp32_ble_tracker"]
|
||||
|
||||
CONF_PIN_CODE = "pin_code"
|
||||
ble_client_ns = cg.esphome_ns.namespace("ble_client")
|
||||
BLEClient = ble_client_ns.class_("BLEClient", esp32_ble_client.BLEClientBase)
|
||||
BLEClientNode = ble_client_ns.class_("BLEClientNode")
|
||||
BLEClientNodeConstRef = BLEClientNode.operator("ref").operator("const")
|
||||
# Triggers
|
||||
BLEClientConnectTrigger = ble_client_ns.class_(
|
||||
"BLEClientConnectTrigger", automation.Trigger.template(BLEClientNodeConstRef)
|
||||
)
|
||||
BLEClientDisconnectTrigger = ble_client_ns.class_(
|
||||
"BLEClientDisconnectTrigger", automation.Trigger.template(BLEClientNodeConstRef)
|
||||
)
|
||||
# Actions
|
||||
BLEWriteAction = ble_client_ns.class_("BLEClientWriteAction", automation.Action)
|
||||
|
||||
# Espressif platformio framework is built with MAX_BLE_CONN to 3, so
|
||||
# enforce this in yaml checks.
|
||||
MULTI_CONF = 3
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(BLEClient),
|
||||
cv.Required(CONF_MAC_ADDRESS): cv.mac_address,
|
||||
cv.Optional(CONF_NAME): cv.string,
|
||||
cv.Optional(CONF_PIN_CODE, default=0): cv.int_range(min=0, max=999999),
|
||||
cv.Optional(CONF_ON_CONNECT): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
||||
BLEClientConnectTrigger
|
||||
),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_DISCONNECT): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
||||
BLEClientDisconnectTrigger
|
||||
),
|
||||
}
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
.extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA)
|
||||
)
|
||||
|
||||
CONF_BLE_CLIENT_ID = "ble_client_id"
|
||||
|
||||
BLE_CLIENT_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(CONF_BLE_CLIENT_ID): cv.use_id(BLEClient),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def register_ble_node(var, config):
|
||||
parent = await cg.get_variable(config[CONF_BLE_CLIENT_ID])
|
||||
cg.add(parent.register_ble_node(var))
|
||||
|
||||
|
||||
BLE_WRITE_ACTION_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(CONF_ID): cv.use_id(BLEClient),
|
||||
cv.Required(CONF_SERVICE_UUID): esp32_ble_tracker.bt_uuid,
|
||||
cv.Required(CONF_CHARACTERISTIC_UUID): esp32_ble_tracker.bt_uuid,
|
||||
cv.Required(CONF_VALUE): cv.templatable(cv.ensure_list(cv.hex_uint8_t)),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"ble_client.ble_write", BLEWriteAction, BLE_WRITE_ACTION_SCHEMA
|
||||
)
|
||||
async def ble_write_to_code(config, action_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
var = cg.new_Pvariable(action_id, template_arg, paren)
|
||||
|
||||
value = config[CONF_VALUE]
|
||||
if cg.is_template(value):
|
||||
templ = await cg.templatable(value, args, cg.std_vector.template(cg.uint8))
|
||||
cg.add(var.set_value_template(templ))
|
||||
else:
|
||||
cg.add(var.set_value_simple(value))
|
||||
|
||||
if len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid16_format):
|
||||
cg.add(
|
||||
var.set_service_uuid16(esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID]))
|
||||
)
|
||||
elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid32_format):
|
||||
cg.add(
|
||||
var.set_service_uuid32(esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID]))
|
||||
)
|
||||
elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid128_format):
|
||||
uuid128 = esp32_ble_tracker.as_reversed_hex_array(config[CONF_SERVICE_UUID])
|
||||
cg.add(var.set_service_uuid128(uuid128))
|
||||
|
||||
if len(config[CONF_CHARACTERISTIC_UUID]) == len(esp32_ble_tracker.bt_uuid16_format):
|
||||
cg.add(
|
||||
var.set_char_uuid16(
|
||||
esp32_ble_tracker.as_hex(config[CONF_CHARACTERISTIC_UUID])
|
||||
)
|
||||
)
|
||||
elif len(config[CONF_CHARACTERISTIC_UUID]) == len(
|
||||
esp32_ble_tracker.bt_uuid32_format
|
||||
):
|
||||
cg.add(
|
||||
var.set_char_uuid32(
|
||||
esp32_ble_tracker.as_hex(config[CONF_CHARACTERISTIC_UUID])
|
||||
)
|
||||
)
|
||||
elif len(config[CONF_CHARACTERISTIC_UUID]) == len(
|
||||
esp32_ble_tracker.bt_uuid128_format
|
||||
):
|
||||
uuid128 = esp32_ble_tracker.as_reversed_hex_array(
|
||||
config[CONF_CHARACTERISTIC_UUID]
|
||||
)
|
||||
cg.add(var.set_char_uuid128(uuid128))
|
||||
|
||||
return var
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await esp32_ble_tracker.register_client(var, config)
|
||||
cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex))
|
||||
cg.add(var.set_pin_code(config[CONF_PIN_CODE]))
|
||||
for conf in config.get(CONF_ON_CONNECT, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [], conf)
|
||||
for conf in config.get(CONF_ON_DISCONNECT, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [], conf)
|
@@ -1,75 +0,0 @@
|
||||
#include "automation.h"
|
||||
|
||||
#include <esp_bt_defs.h>
|
||||
#include <esp_gap_ble_api.h>
|
||||
#include <esp_gattc_api.h>
|
||||
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ble_client {
|
||||
static const char *const TAG = "ble_client.automation";
|
||||
|
||||
void BLEWriterClientNode::write(const std::vector<uint8_t> &value) {
|
||||
if (this->node_state != espbt::ClientState::ESTABLISHED) {
|
||||
ESP_LOGW(TAG, "Cannot write to BLE characteristic - not connected");
|
||||
return;
|
||||
} else if (this->ble_char_handle_ == 0) {
|
||||
ESP_LOGW(TAG, "Cannot write to BLE characteristic - characteristic not found");
|
||||
return;
|
||||
}
|
||||
esp_gatt_write_type_t write_type;
|
||||
if (this->char_props_ & ESP_GATT_CHAR_PROP_BIT_WRITE) {
|
||||
write_type = ESP_GATT_WRITE_TYPE_RSP;
|
||||
ESP_LOGD(TAG, "Write type: ESP_GATT_WRITE_TYPE_RSP");
|
||||
} else if (this->char_props_ & ESP_GATT_CHAR_PROP_BIT_WRITE_NR) {
|
||||
write_type = ESP_GATT_WRITE_TYPE_NO_RSP;
|
||||
ESP_LOGD(TAG, "Write type: ESP_GATT_WRITE_TYPE_NO_RSP");
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Characteristic %s does not allow writing", this->char_uuid_.to_string().c_str());
|
||||
return;
|
||||
}
|
||||
ESP_LOGVV(TAG, "Will write %d bytes: %s", value.size(), format_hex_pretty(value).c_str());
|
||||
esp_err_t err =
|
||||
esp_ble_gattc_write_char(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), this->ble_char_handle_,
|
||||
value.size(), const_cast<uint8_t *>(value.data()), write_type, ESP_GATT_AUTH_REQ_NONE);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Error writing to characteristic: %s!", esp_err_to_name(err));
|
||||
}
|
||||
}
|
||||
|
||||
void BLEWriterClientNode::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
||||
esp_ble_gattc_cb_param_t *param) {
|
||||
switch (event) {
|
||||
case ESP_GATTC_REG_EVT:
|
||||
break;
|
||||
case ESP_GATTC_OPEN_EVT:
|
||||
this->node_state = espbt::ClientState::ESTABLISHED;
|
||||
ESP_LOGD(TAG, "Connection established with %s", ble_client_->address_str().c_str());
|
||||
break;
|
||||
case ESP_GATTC_SEARCH_CMPL_EVT: {
|
||||
auto *chr = this->parent()->get_characteristic(this->service_uuid_, this->char_uuid_);
|
||||
if (chr == nullptr) {
|
||||
ESP_LOGW("ble_write_action", "Characteristic %s was not found in service %s",
|
||||
this->char_uuid_.to_string().c_str(), this->service_uuid_.to_string().c_str());
|
||||
break;
|
||||
}
|
||||
this->ble_char_handle_ = chr->handle;
|
||||
this->char_props_ = chr->properties;
|
||||
this->node_state = espbt::ClientState::ESTABLISHED;
|
||||
ESP_LOGD(TAG, "Found characteristic %s on device %s", this->char_uuid_.to_string().c_str(),
|
||||
ble_client_->address_str().c_str());
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_DISCONNECT_EVT:
|
||||
this->node_state = espbt::ClientState::IDLE;
|
||||
this->ble_char_handle_ = 0;
|
||||
ESP_LOGD(TAG, "Disconnected from %s", ble_client_->address_str().c_str());
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ble_client
|
||||
} // namespace esphome
|
@@ -1,100 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/components/ble_client/ble_client.h"
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
namespace esphome {
|
||||
namespace ble_client {
|
||||
class BLEClientConnectTrigger : public Trigger<>, public BLEClientNode {
|
||||
public:
|
||||
explicit BLEClientConnectTrigger(BLEClient *parent) { parent->register_ble_node(this); }
|
||||
void loop() override {}
|
||||
void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
||||
esp_ble_gattc_cb_param_t *param) override {
|
||||
if (event == ESP_GATTC_SEARCH_CMPL_EVT) {
|
||||
this->node_state = espbt::ClientState::ESTABLISHED;
|
||||
this->trigger();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class BLEClientDisconnectTrigger : public Trigger<>, public BLEClientNode {
|
||||
public:
|
||||
explicit BLEClientDisconnectTrigger(BLEClient *parent) { parent->register_ble_node(this); }
|
||||
void loop() override {}
|
||||
void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
||||
esp_ble_gattc_cb_param_t *param) override {
|
||||
if (event == ESP_GATTC_DISCONNECT_EVT &&
|
||||
memcmp(param->disconnect.remote_bda, this->parent_->get_remote_bda(), 6) == 0)
|
||||
this->trigger();
|
||||
if (event == ESP_GATTC_SEARCH_CMPL_EVT)
|
||||
this->node_state = espbt::ClientState::ESTABLISHED;
|
||||
}
|
||||
};
|
||||
|
||||
class BLEWriterClientNode : public BLEClientNode {
|
||||
public:
|
||||
BLEWriterClientNode(BLEClient *ble_client) {
|
||||
ble_client->register_ble_node(this);
|
||||
ble_client_ = ble_client;
|
||||
}
|
||||
|
||||
// Attempts to write the contents of value to char_uuid_.
|
||||
void write(const std::vector<uint8_t> &value);
|
||||
|
||||
void set_service_uuid16(uint16_t uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_uint16(uuid); }
|
||||
void set_service_uuid32(uint32_t uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_uint32(uuid); }
|
||||
void set_service_uuid128(uint8_t *uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_raw(uuid); }
|
||||
|
||||
void set_char_uuid16(uint16_t uuid) { this->char_uuid_ = espbt::ESPBTUUID::from_uint16(uuid); }
|
||||
void set_char_uuid32(uint32_t uuid) { this->char_uuid_ = espbt::ESPBTUUID::from_uint32(uuid); }
|
||||
void set_char_uuid128(uint8_t *uuid) { this->char_uuid_ = espbt::ESPBTUUID::from_raw(uuid); }
|
||||
|
||||
void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
||||
esp_ble_gattc_cb_param_t *param) override;
|
||||
|
||||
private:
|
||||
BLEClient *ble_client_;
|
||||
int ble_char_handle_ = 0;
|
||||
esp_gatt_char_prop_t char_props_;
|
||||
espbt::ESPBTUUID service_uuid_;
|
||||
espbt::ESPBTUUID char_uuid_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class BLEClientWriteAction : public Action<Ts...>, public BLEWriterClientNode {
|
||||
public:
|
||||
BLEClientWriteAction(BLEClient *ble_client) : BLEWriterClientNode(ble_client) {}
|
||||
|
||||
void play(Ts... x) override {
|
||||
if (has_simple_value_) {
|
||||
return write(this->value_simple_);
|
||||
} else {
|
||||
return write(this->value_template_(x...));
|
||||
}
|
||||
}
|
||||
|
||||
void set_value_template(std::function<std::vector<uint8_t>(Ts...)> func) {
|
||||
this->value_template_ = std::move(func);
|
||||
has_simple_value_ = false;
|
||||
}
|
||||
|
||||
void set_value_simple(const std::vector<uint8_t> &value) {
|
||||
this->value_simple_ = value;
|
||||
has_simple_value_ = true;
|
||||
}
|
||||
|
||||
private:
|
||||
bool has_simple_value_ = true;
|
||||
std::vector<uint8_t> value_simple_;
|
||||
std::function<std::vector<uint8_t>(Ts...)> value_template_{};
|
||||
};
|
||||
|
||||
} // namespace ble_client
|
||||
} // namespace esphome
|
||||
|
||||
#endif
|
@@ -1,95 +0,0 @@
|
||||
#include "ble_client.h"
|
||||
#include "esphome/components/esp32_ble_client/ble_client_base.h"
|
||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
namespace esphome {
|
||||
namespace ble_client {
|
||||
|
||||
static const char *const TAG = "ble_client";
|
||||
|
||||
void BLEClient::setup() {
|
||||
BLEClientBase::setup();
|
||||
this->enabled = true;
|
||||
}
|
||||
|
||||
void BLEClient::loop() {
|
||||
BLEClientBase::loop();
|
||||
for (auto *node : this->nodes_)
|
||||
node->loop();
|
||||
}
|
||||
|
||||
void BLEClient::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "BLE Client:");
|
||||
ESP_LOGCONFIG(TAG, " Address: %s", this->address_str().c_str());
|
||||
}
|
||||
|
||||
bool BLEClient::parse_device(const espbt::ESPBTDevice &device) {
|
||||
if (!this->enabled)
|
||||
return false;
|
||||
return BLEClientBase::parse_device(device);
|
||||
}
|
||||
|
||||
void BLEClient::set_enabled(bool enabled) {
|
||||
if (enabled == this->enabled)
|
||||
return;
|
||||
if (!enabled && this->state() != espbt::ClientState::IDLE) {
|
||||
ESP_LOGI(TAG, "[%s] Disabling BLE client.", this->address_str().c_str());
|
||||
auto ret = esp_ble_gattc_close(this->gattc_if_, this->conn_id_);
|
||||
if (ret) {
|
||||
ESP_LOGW(TAG, "esp_ble_gattc_close error, address=%s status=%d", this->address_str().c_str(), ret);
|
||||
}
|
||||
}
|
||||
this->enabled = enabled;
|
||||
}
|
||||
|
||||
bool BLEClient::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t esp_gattc_if,
|
||||
esp_ble_gattc_cb_param_t *param) {
|
||||
bool all_established = this->all_nodes_established_();
|
||||
|
||||
if (!BLEClientBase::gattc_event_handler(event, esp_gattc_if, param))
|
||||
return false;
|
||||
|
||||
for (auto *node : this->nodes_)
|
||||
node->gattc_event_handler(event, esp_gattc_if, param);
|
||||
|
||||
// Delete characteristics after clients have used them to save RAM.
|
||||
if (!all_established && this->all_nodes_established_()) {
|
||||
for (auto &svc : this->services_)
|
||||
delete svc; // NOLINT(cppcoreguidelines-owning-memory)
|
||||
this->services_.clear();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void BLEClient::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) {
|
||||
BLEClientBase::gap_event_handler(event, param);
|
||||
|
||||
for (auto *node : this->nodes_)
|
||||
node->gap_event_handler(event, param);
|
||||
}
|
||||
|
||||
void BLEClient::set_state(espbt::ClientState state) {
|
||||
BLEClientBase::set_state(state);
|
||||
for (auto &node : nodes_)
|
||||
node->node_state = state;
|
||||
}
|
||||
|
||||
bool BLEClient::all_nodes_established_() {
|
||||
if (this->state() != espbt::ClientState::ESTABLISHED)
|
||||
return false;
|
||||
for (auto &node : nodes_) {
|
||||
if (node->node_state != espbt::ClientState::ESTABLISHED)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace ble_client
|
||||
} // namespace esphome
|
||||
|
||||
#endif
|
@@ -1,81 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/esp32_ble_client/ble_client_base.h"
|
||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include <esp_bt_defs.h>
|
||||
#include <esp_gap_ble_api.h>
|
||||
#include <esp_gatt_common_api.h>
|
||||
#include <esp_gattc_api.h>
|
||||
#include <array>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace esphome {
|
||||
namespace ble_client {
|
||||
|
||||
namespace espbt = esphome::esp32_ble_tracker;
|
||||
|
||||
using namespace esp32_ble_client;
|
||||
|
||||
class BLEClient;
|
||||
|
||||
class BLEClientNode {
|
||||
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;
|
||||
virtual void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) {}
|
||||
virtual void loop() {}
|
||||
void set_address(uint64_t address) { address_ = address; }
|
||||
espbt::ESPBTClient *client;
|
||||
// This should be transitioned to Established once the node no longer needs
|
||||
// the services/descriptors/characteristics of the parent client. This will
|
||||
// allow some memory to be freed.
|
||||
espbt::ClientState node_state;
|
||||
|
||||
BLEClient *parent() { return this->parent_; }
|
||||
void set_ble_client_parent(BLEClient *parent) { this->parent_ = parent; }
|
||||
|
||||
protected:
|
||||
BLEClient *parent_;
|
||||
uint64_t address_;
|
||||
};
|
||||
|
||||
class BLEClient : public BLEClientBase {
|
||||
public:
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
void loop() override;
|
||||
|
||||
bool 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;
|
||||
bool parse_device(const espbt::ESPBTDevice &device) override;
|
||||
|
||||
void set_enabled(bool enabled);
|
||||
|
||||
void register_ble_node(BLEClientNode *node) {
|
||||
node->client = this;
|
||||
node->set_ble_client_parent(this);
|
||||
this->nodes_.push_back(node);
|
||||
}
|
||||
|
||||
bool enabled;
|
||||
|
||||
void set_state(espbt::ClientState state) override;
|
||||
|
||||
protected:
|
||||
bool all_nodes_established_();
|
||||
|
||||
std::vector<BLEClientNode *> nodes_;
|
||||
};
|
||||
|
||||
} // namespace ble_client
|
||||
} // namespace esphome
|
||||
|
||||
#endif
|
@@ -1,68 +0,0 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import ble_client, esp32_ble_tracker, output
|
||||
from esphome.const import CONF_CHARACTERISTIC_UUID, CONF_ID, CONF_SERVICE_UUID
|
||||
|
||||
from .. import ble_client_ns
|
||||
|
||||
DEPENDENCIES = ["ble_client"]
|
||||
|
||||
CONF_REQUIRE_RESPONSE = "require_response"
|
||||
|
||||
BLEBinaryOutput = ble_client_ns.class_(
|
||||
"BLEBinaryOutput", output.BinaryOutput, ble_client.BLEClientNode, cg.Component
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
output.BINARY_OUTPUT_SCHEMA.extend(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.declare_id(BLEBinaryOutput),
|
||||
cv.Required(CONF_SERVICE_UUID): esp32_ble_tracker.bt_uuid,
|
||||
cv.Required(CONF_CHARACTERISTIC_UUID): esp32_ble_tracker.bt_uuid,
|
||||
cv.Optional(CONF_REQUIRE_RESPONSE, default=False): cv.boolean,
|
||||
}
|
||||
)
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
.extend(ble_client.BLE_CLIENT_SCHEMA)
|
||||
)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
if len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid16_format):
|
||||
cg.add(
|
||||
var.set_service_uuid16(esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID]))
|
||||
)
|
||||
elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid32_format):
|
||||
cg.add(
|
||||
var.set_service_uuid32(esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID]))
|
||||
)
|
||||
elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid128_format):
|
||||
uuid128 = esp32_ble_tracker.as_reversed_hex_array(config[CONF_SERVICE_UUID])
|
||||
cg.add(var.set_service_uuid128(uuid128))
|
||||
|
||||
if len(config[CONF_CHARACTERISTIC_UUID]) == len(esp32_ble_tracker.bt_uuid16_format):
|
||||
cg.add(
|
||||
var.set_char_uuid16(
|
||||
esp32_ble_tracker.as_hex(config[CONF_CHARACTERISTIC_UUID])
|
||||
)
|
||||
)
|
||||
elif len(config[CONF_CHARACTERISTIC_UUID]) == len(
|
||||
esp32_ble_tracker.bt_uuid32_format
|
||||
):
|
||||
cg.add(
|
||||
var.set_char_uuid32(
|
||||
esp32_ble_tracker.as_hex(config[CONF_CHARACTERISTIC_UUID])
|
||||
)
|
||||
)
|
||||
elif len(config[CONF_CHARACTERISTIC_UUID]) == len(
|
||||
esp32_ble_tracker.bt_uuid128_format
|
||||
):
|
||||
uuid128 = esp32_ble_tracker.as_reversed_hex_array(
|
||||
config[CONF_CHARACTERISTIC_UUID]
|
||||
)
|
||||
cg.add(var.set_char_uuid128(uuid128))
|
||||
cg.add(var.set_require_response(config[CONF_REQUIRE_RESPONSE]))
|
||||
yield output.register_output(var, config)
|
||||
yield ble_client.register_ble_node(var, config)
|
||||
yield cg.register_component(var, config)
|
@@ -1,75 +0,0 @@
|
||||
#include "ble_binary_output.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
|
||||
|
||||
#ifdef USE_ESP32
|
||||
namespace esphome {
|
||||
namespace ble_client {
|
||||
|
||||
static const char *const TAG = "ble_binary_output";
|
||||
|
||||
void BLEBinaryOutput::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "BLE Binary Output:");
|
||||
ESP_LOGCONFIG(TAG, " MAC address : %s", this->parent_->address_str().c_str());
|
||||
ESP_LOGCONFIG(TAG, " Service UUID : %s", this->service_uuid_.to_string().c_str());
|
||||
ESP_LOGCONFIG(TAG, " Characteristic UUID: %s", this->char_uuid_.to_string().c_str());
|
||||
LOG_BINARY_OUTPUT(this);
|
||||
}
|
||||
|
||||
void BLEBinaryOutput::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
||||
esp_ble_gattc_cb_param_t *param) {
|
||||
switch (event) {
|
||||
case ESP_GATTC_OPEN_EVT:
|
||||
this->client_state_ = espbt::ClientState::ESTABLISHED;
|
||||
ESP_LOGW(TAG, "[%s] Connected successfully!", this->char_uuid_.to_string().c_str());
|
||||
break;
|
||||
case ESP_GATTC_DISCONNECT_EVT:
|
||||
ESP_LOGW(TAG, "[%s] Disconnected", this->char_uuid_.to_string().c_str());
|
||||
this->client_state_ = espbt::ClientState::IDLE;
|
||||
break;
|
||||
case ESP_GATTC_WRITE_CHAR_EVT: {
|
||||
if (param->write.status == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
auto *chr = this->parent()->get_characteristic(this->service_uuid_, this->char_uuid_);
|
||||
if (chr == nullptr) {
|
||||
ESP_LOGW(TAG, "[%s] Characteristic not found.", this->char_uuid_.to_string().c_str());
|
||||
break;
|
||||
}
|
||||
if (param->write.handle == chr->handle) {
|
||||
ESP_LOGW(TAG, "[%s] Write error, status=%d", this->char_uuid_.to_string().c_str(), param->write.status);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void BLEBinaryOutput::write_state(bool state) {
|
||||
if (this->client_state_ != espbt::ClientState::ESTABLISHED) {
|
||||
ESP_LOGW(TAG, "[%s] Not connected to BLE client. State update can not be written.",
|
||||
this->char_uuid_.to_string().c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
auto *chr = this->parent()->get_characteristic(this->service_uuid_, this->char_uuid_);
|
||||
if (chr == nullptr) {
|
||||
ESP_LOGW(TAG, "[%s] Characteristic not found. State update can not be written.",
|
||||
this->char_uuid_.to_string().c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t state_as_uint = (uint8_t) state;
|
||||
ESP_LOGV(TAG, "[%s] Write State: %d", this->char_uuid_.to_string().c_str(), state_as_uint);
|
||||
if (this->require_response_) {
|
||||
chr->write_value(&state_as_uint, sizeof(state_as_uint), ESP_GATT_WRITE_TYPE_RSP);
|
||||
} else {
|
||||
chr->write_value(&state_as_uint, sizeof(state_as_uint), ESP_GATT_WRITE_TYPE_NO_RSP);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ble_client
|
||||
} // namespace esphome
|
||||
#endif
|
@@ -1,41 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/ble_client/ble_client.h"
|
||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
|
||||
#include "esphome/components/output/binary_output.h"
|
||||
|
||||
#ifdef USE_ESP32
|
||||
#include <esp_gattc_api.h>
|
||||
namespace esphome {
|
||||
namespace ble_client {
|
||||
|
||||
namespace espbt = esphome::esp32_ble_tracker;
|
||||
|
||||
class BLEBinaryOutput : public output::BinaryOutput, public BLEClientNode, public Component {
|
||||
public:
|
||||
void dump_config() override;
|
||||
void loop() override {}
|
||||
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||
void set_service_uuid16(uint16_t uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_uint16(uuid); }
|
||||
void set_service_uuid32(uint32_t uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_uint32(uuid); }
|
||||
void set_service_uuid128(uint8_t *uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_raw(uuid); }
|
||||
void set_char_uuid16(uint16_t uuid) { this->char_uuid_ = espbt::ESPBTUUID::from_uint16(uuid); }
|
||||
void set_char_uuid32(uint32_t uuid) { this->char_uuid_ = espbt::ESPBTUUID::from_uint32(uuid); }
|
||||
void set_char_uuid128(uint8_t *uuid) { this->char_uuid_ = espbt::ESPBTUUID::from_raw(uuid); }
|
||||
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 set_require_response(bool response) { this->require_response_ = response; }
|
||||
|
||||
protected:
|
||||
void write_state(bool state) override;
|
||||
bool require_response_;
|
||||
espbt::ESPBTUUID service_uuid_;
|
||||
espbt::ESPBTUUID char_uuid_;
|
||||
espbt::ClientState client_state_;
|
||||
};
|
||||
|
||||
} // namespace ble_client
|
||||
} // namespace esphome
|
||||
|
||||
#endif
|
@@ -1,174 +0,0 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import sensor, ble_client, esp32_ble_tracker
|
||||
from esphome.const import (
|
||||
CONF_CHARACTERISTIC_UUID,
|
||||
CONF_LAMBDA,
|
||||
CONF_TRIGGER_ID,
|
||||
CONF_TYPE,
|
||||
CONF_SERVICE_UUID,
|
||||
DEVICE_CLASS_SIGNAL_STRENGTH,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_DECIBEL_MILLIWATT,
|
||||
)
|
||||
from esphome import automation
|
||||
from .. import ble_client_ns
|
||||
|
||||
DEPENDENCIES = ["ble_client"]
|
||||
|
||||
CONF_DESCRIPTOR_UUID = "descriptor_uuid"
|
||||
|
||||
CONF_NOTIFY = "notify"
|
||||
CONF_ON_NOTIFY = "on_notify"
|
||||
TYPE_CHARACTERISTIC = "characteristic"
|
||||
TYPE_RSSI = "rssi"
|
||||
|
||||
adv_data_t = cg.std_vector.template(cg.uint8)
|
||||
adv_data_t_const_ref = adv_data_t.operator("ref").operator("const")
|
||||
|
||||
BLESensor = ble_client_ns.class_(
|
||||
"BLESensor", sensor.Sensor, cg.PollingComponent, ble_client.BLEClientNode
|
||||
)
|
||||
BLESensorNotifyTrigger = ble_client_ns.class_(
|
||||
"BLESensorNotifyTrigger", automation.Trigger.template(cg.float_)
|
||||
)
|
||||
|
||||
BLEClientRssiSensor = ble_client_ns.class_(
|
||||
"BLEClientRSSISensor", sensor.Sensor, cg.PollingComponent, ble_client.BLEClientNode
|
||||
)
|
||||
|
||||
|
||||
def checkType(value):
|
||||
if CONF_TYPE not in value and CONF_SERVICE_UUID in value:
|
||||
raise cv.Invalid(
|
||||
"Looks like you're trying to create a ble characteristic sensor. Please add `type: characteristic` to your sensor config."
|
||||
)
|
||||
return value
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
checkType,
|
||||
cv.typed_schema(
|
||||
{
|
||||
TYPE_CHARACTERISTIC: sensor.sensor_schema(
|
||||
BLESensor,
|
||||
accuracy_decimals=0,
|
||||
)
|
||||
.extend(cv.polling_component_schema("60s"))
|
||||
.extend(ble_client.BLE_CLIENT_SCHEMA)
|
||||
.extend(
|
||||
{
|
||||
cv.Required(CONF_SERVICE_UUID): esp32_ble_tracker.bt_uuid,
|
||||
cv.Required(CONF_CHARACTERISTIC_UUID): esp32_ble_tracker.bt_uuid,
|
||||
cv.Optional(CONF_DESCRIPTOR_UUID): esp32_ble_tracker.bt_uuid,
|
||||
cv.Optional(CONF_LAMBDA): cv.returning_lambda,
|
||||
cv.Optional(CONF_NOTIFY, default=False): cv.boolean,
|
||||
cv.Optional(CONF_ON_NOTIFY): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
||||
BLESensorNotifyTrigger
|
||||
),
|
||||
}
|
||||
),
|
||||
}
|
||||
),
|
||||
TYPE_RSSI: sensor.sensor_schema(
|
||||
BLEClientRssiSensor,
|
||||
accuracy_decimals=0,
|
||||
unit_of_measurement=UNIT_DECIBEL_MILLIWATT,
|
||||
device_class=DEVICE_CLASS_SIGNAL_STRENGTH,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
)
|
||||
.extend(cv.polling_component_schema("60s"))
|
||||
.extend(ble_client.BLE_CLIENT_SCHEMA),
|
||||
},
|
||||
lower=True,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
async def rssi_sensor_to_code(config):
|
||||
var = await sensor.new_sensor(config)
|
||||
await cg.register_component(var, config)
|
||||
await ble_client.register_ble_node(var, config)
|
||||
|
||||
|
||||
async def characteristic_sensor_to_code(config):
|
||||
var = await sensor.new_sensor(config)
|
||||
if len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid16_format):
|
||||
cg.add(
|
||||
var.set_service_uuid16(esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID]))
|
||||
)
|
||||
elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid32_format):
|
||||
cg.add(
|
||||
var.set_service_uuid32(esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID]))
|
||||
)
|
||||
elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid128_format):
|
||||
uuid128 = esp32_ble_tracker.as_reversed_hex_array(config[CONF_SERVICE_UUID])
|
||||
cg.add(var.set_service_uuid128(uuid128))
|
||||
|
||||
if len(config[CONF_CHARACTERISTIC_UUID]) == len(esp32_ble_tracker.bt_uuid16_format):
|
||||
cg.add(
|
||||
var.set_char_uuid16(
|
||||
esp32_ble_tracker.as_hex(config[CONF_CHARACTERISTIC_UUID])
|
||||
)
|
||||
)
|
||||
elif len(config[CONF_CHARACTERISTIC_UUID]) == len(
|
||||
esp32_ble_tracker.bt_uuid32_format
|
||||
):
|
||||
cg.add(
|
||||
var.set_char_uuid32(
|
||||
esp32_ble_tracker.as_hex(config[CONF_CHARACTERISTIC_UUID])
|
||||
)
|
||||
)
|
||||
elif len(config[CONF_CHARACTERISTIC_UUID]) == len(
|
||||
esp32_ble_tracker.bt_uuid128_format
|
||||
):
|
||||
uuid128 = esp32_ble_tracker.as_reversed_hex_array(
|
||||
config[CONF_CHARACTERISTIC_UUID]
|
||||
)
|
||||
cg.add(var.set_char_uuid128(uuid128))
|
||||
|
||||
if CONF_DESCRIPTOR_UUID in config:
|
||||
if len(config[CONF_DESCRIPTOR_UUID]) == len(esp32_ble_tracker.bt_uuid16_format):
|
||||
cg.add(
|
||||
var.set_descr_uuid16(
|
||||
esp32_ble_tracker.as_hex(config[CONF_DESCRIPTOR_UUID])
|
||||
)
|
||||
)
|
||||
elif len(config[CONF_DESCRIPTOR_UUID]) == len(
|
||||
esp32_ble_tracker.bt_uuid32_format
|
||||
):
|
||||
cg.add(
|
||||
var.set_descr_uuid32(
|
||||
esp32_ble_tracker.as_hex(config[CONF_DESCRIPTOR_UUID])
|
||||
)
|
||||
)
|
||||
elif len(config[CONF_DESCRIPTOR_UUID]) == len(
|
||||
esp32_ble_tracker.bt_uuid128_format
|
||||
):
|
||||
uuid128 = esp32_ble_tracker.as_reversed_hex_array(
|
||||
config[CONF_DESCRIPTOR_UUID]
|
||||
)
|
||||
cg.add(var.set_descr_uuid128(uuid128))
|
||||
|
||||
if CONF_LAMBDA in config:
|
||||
lambda_ = await cg.process_lambda(
|
||||
config[CONF_LAMBDA], [(adv_data_t_const_ref, "x")], return_type=cg.float_
|
||||
)
|
||||
cg.add(var.set_data_to_value(lambda_))
|
||||
|
||||
await cg.register_component(var, config)
|
||||
await ble_client.register_ble_node(var, config)
|
||||
cg.add(var.set_enable_notify(config[CONF_NOTIFY]))
|
||||
for conf in config.get(CONF_ON_NOTIFY, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await ble_client.register_ble_node(trigger, config)
|
||||
await automation.build_automation(trigger, [(float, "x")], conf)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
if config[CONF_TYPE] == TYPE_RSSI:
|
||||
await rssi_sensor_to_code(config)
|
||||
elif config[CONF_TYPE] == TYPE_CHARACTERISTIC:
|
||||
await characteristic_sensor_to_code(config)
|
@@ -1,39 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/components/ble_client/sensor/ble_sensor.h"
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
namespace esphome {
|
||||
namespace ble_client {
|
||||
|
||||
class BLESensorNotifyTrigger : public Trigger<float>, public BLESensor {
|
||||
public:
|
||||
explicit BLESensorNotifyTrigger(BLESensor *sensor) { sensor_ = sensor; }
|
||||
void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
||||
esp_ble_gattc_cb_param_t *param) override {
|
||||
switch (event) {
|
||||
case ESP_GATTC_SEARCH_CMPL_EVT: {
|
||||
this->sensor_->node_state = espbt::ClientState::ESTABLISHED;
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_NOTIFY_EVT: {
|
||||
if (param->notify.conn_id != this->sensor_->parent()->get_conn_id() ||
|
||||
param->notify.handle != this->sensor_->handle)
|
||||
break;
|
||||
this->trigger(this->sensor_->parent()->parse_char_value(param->notify.value, param->notify.value_len));
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
BLESensor *sensor_;
|
||||
};
|
||||
|
||||
} // namespace ble_client
|
||||
} // namespace esphome
|
||||
|
||||
#endif
|
@@ -1,78 +0,0 @@
|
||||
#include "ble_rssi_sensor.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
namespace esphome {
|
||||
namespace ble_client {
|
||||
|
||||
static const char *const TAG = "ble_rssi_sensor";
|
||||
|
||||
void BLEClientRSSISensor::loop() {}
|
||||
|
||||
void BLEClientRSSISensor::dump_config() {
|
||||
LOG_SENSOR("", "BLE Client RSSI Sensor", this);
|
||||
ESP_LOGCONFIG(TAG, " MAC address : %s", this->parent()->address_str().c_str());
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
}
|
||||
|
||||
void BLEClientRSSISensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
||||
esp_ble_gattc_cb_param_t *param) {
|
||||
switch (event) {
|
||||
case ESP_GATTC_OPEN_EVT: {
|
||||
if (param->open.status == ESP_GATT_OK) {
|
||||
ESP_LOGI(TAG, "[%s] Connected successfully!", this->get_name().c_str());
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_DISCONNECT_EVT: {
|
||||
ESP_LOGW(TAG, "[%s] Disconnected!", this->get_name().c_str());
|
||||
this->status_set_warning();
|
||||
this->publish_state(NAN);
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_SEARCH_CMPL_EVT:
|
||||
this->node_state = espbt::ClientState::ESTABLISHED;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void BLEClientRSSISensor::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) {
|
||||
switch (event) {
|
||||
// server response on RSSI request:
|
||||
case ESP_GAP_BLE_READ_RSSI_COMPLETE_EVT:
|
||||
if (param->read_rssi_cmpl.status == ESP_BT_STATUS_SUCCESS) {
|
||||
int8_t rssi = param->read_rssi_cmpl.rssi;
|
||||
ESP_LOGI(TAG, "ESP_GAP_BLE_READ_RSSI_COMPLETE_EVT RSSI: %d", rssi);
|
||||
this->publish_state(rssi);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void BLEClientRSSISensor::update() {
|
||||
if (this->node_state != espbt::ClientState::ESTABLISHED) {
|
||||
ESP_LOGW(TAG, "[%s] Cannot poll, not connected", this->get_name().c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGV(TAG, "requesting rssi from %s", this->parent()->address_str().c_str());
|
||||
auto status = esp_ble_gap_read_rssi(this->parent()->get_remote_bda());
|
||||
if (status != ESP_OK) {
|
||||
ESP_LOGW(TAG, "esp_ble_gap_read_rssi error, address=%s, status=%d", this->parent()->address_str().c_str(), status);
|
||||
this->status_set_warning();
|
||||
this->publish_state(NAN);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ble_client
|
||||
} // namespace esphome
|
||||
#endif
|
@@ -1,31 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/ble_client/ble_client.h"
|
||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
|
||||
#ifdef USE_ESP32
|
||||
#include <esp_gattc_api.h>
|
||||
|
||||
namespace esphome {
|
||||
namespace ble_client {
|
||||
|
||||
namespace espbt = esphome::esp32_ble_tracker;
|
||||
|
||||
class BLEClientRSSISensor : public sensor::Sensor, public PollingComponent, public BLEClientNode {
|
||||
public:
|
||||
void loop() override;
|
||||
void update() override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||
|
||||
void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override;
|
||||
|
||||
void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
||||
esp_ble_gattc_cb_param_t *param) override;
|
||||
};
|
||||
|
||||
} // namespace ble_client
|
||||
} // namespace esphome
|
||||
#endif
|
@@ -1,136 +0,0 @@
|
||||
#include "ble_sensor.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
namespace esphome {
|
||||
namespace ble_client {
|
||||
|
||||
static const char *const TAG = "ble_sensor";
|
||||
|
||||
void BLESensor::loop() {}
|
||||
|
||||
void BLESensor::dump_config() {
|
||||
LOG_SENSOR("", "BLE Sensor", this);
|
||||
ESP_LOGCONFIG(TAG, " MAC address : %s", this->parent()->address_str().c_str());
|
||||
ESP_LOGCONFIG(TAG, " Service UUID : %s", this->service_uuid_.to_string().c_str());
|
||||
ESP_LOGCONFIG(TAG, " Characteristic UUID: %s", this->char_uuid_.to_string().c_str());
|
||||
ESP_LOGCONFIG(TAG, " Descriptor UUID : %s", this->descr_uuid_.to_string().c_str());
|
||||
ESP_LOGCONFIG(TAG, " Notifications : %s", YESNO(this->notify_));
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
}
|
||||
|
||||
void BLESensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
||||
esp_ble_gattc_cb_param_t *param) {
|
||||
switch (event) {
|
||||
case ESP_GATTC_OPEN_EVT: {
|
||||
if (param->open.status == ESP_GATT_OK) {
|
||||
ESP_LOGI(TAG, "[%s] Connected successfully!", this->get_name().c_str());
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_DISCONNECT_EVT: {
|
||||
ESP_LOGW(TAG, "[%s] Disconnected!", this->get_name().c_str());
|
||||
this->status_set_warning();
|
||||
this->publish_state(NAN);
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_SEARCH_CMPL_EVT: {
|
||||
this->handle = 0;
|
||||
auto *chr = this->parent()->get_characteristic(this->service_uuid_, this->char_uuid_);
|
||||
if (chr == nullptr) {
|
||||
this->status_set_warning();
|
||||
this->publish_state(NAN);
|
||||
ESP_LOGW(TAG, "No sensor characteristic found at service %s char %s", this->service_uuid_.to_string().c_str(),
|
||||
this->char_uuid_.to_string().c_str());
|
||||
break;
|
||||
}
|
||||
this->handle = chr->handle;
|
||||
if (this->descr_uuid_.get_uuid().len > 0) {
|
||||
auto *descr = chr->get_descriptor(this->descr_uuid_);
|
||||
if (descr == nullptr) {
|
||||
this->status_set_warning();
|
||||
this->publish_state(NAN);
|
||||
ESP_LOGW(TAG, "No sensor descriptor found at service %s char %s descr %s",
|
||||
this->service_uuid_.to_string().c_str(), this->char_uuid_.to_string().c_str(),
|
||||
this->descr_uuid_.to_string().c_str());
|
||||
break;
|
||||
}
|
||||
this->handle = descr->handle;
|
||||
}
|
||||
if (this->notify_) {
|
||||
auto status = esp_ble_gattc_register_for_notify(this->parent()->get_gattc_if(),
|
||||
this->parent()->get_remote_bda(), chr->handle);
|
||||
if (status) {
|
||||
ESP_LOGW(TAG, "esp_ble_gattc_register_for_notify failed, status=%d", status);
|
||||
}
|
||||
} else {
|
||||
this->node_state = espbt::ClientState::ESTABLISHED;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_READ_CHAR_EVT: {
|
||||
if (param->read.conn_id != this->parent()->get_conn_id())
|
||||
break;
|
||||
if (param->read.status != ESP_GATT_OK) {
|
||||
ESP_LOGW(TAG, "Error reading char at handle %d, status=%d", param->read.handle, param->read.status);
|
||||
break;
|
||||
}
|
||||
if (param->read.handle == this->handle) {
|
||||
this->status_clear_warning();
|
||||
this->publish_state(this->parse_data_(param->read.value, param->read.value_len));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_NOTIFY_EVT: {
|
||||
if (param->notify.conn_id != this->parent()->get_conn_id() || param->notify.handle != this->handle)
|
||||
break;
|
||||
ESP_LOGV(TAG, "[%s] ESP_GATTC_NOTIFY_EVT: handle=0x%x, value=0x%x", this->get_name().c_str(),
|
||||
param->notify.handle, param->notify.value[0]);
|
||||
this->publish_state(this->parse_data_(param->notify.value, param->notify.value_len));
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
|
||||
this->node_state = espbt::ClientState::ESTABLISHED;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
float BLESensor::parse_data_(uint8_t *value, uint16_t value_len) {
|
||||
if (this->data_to_value_func_.has_value()) {
|
||||
std::vector<uint8_t> data(value, value + value_len);
|
||||
return (*this->data_to_value_func_)(data);
|
||||
} else {
|
||||
return value[0];
|
||||
}
|
||||
}
|
||||
|
||||
void BLESensor::update() {
|
||||
if (this->node_state != espbt::ClientState::ESTABLISHED) {
|
||||
ESP_LOGW(TAG, "[%s] Cannot poll, not connected", this->get_name().c_str());
|
||||
return;
|
||||
}
|
||||
if (this->handle == 0) {
|
||||
ESP_LOGW(TAG, "[%s] Cannot poll, no service or characteristic found", this->get_name().c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
auto status = esp_ble_gattc_read_char(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), this->handle,
|
||||
ESP_GATT_AUTH_REQ_NONE);
|
||||
if (status) {
|
||||
this->status_set_warning();
|
||||
this->publish_state(NAN);
|
||||
ESP_LOGW(TAG, "[%s] Error sending read request for sensor, status=%d", this->get_name().c_str(), status);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ble_client
|
||||
} // namespace esphome
|
||||
#endif
|
@@ -1,52 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/ble_client/ble_client.h"
|
||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
#ifdef USE_ESP32
|
||||
#include <esp_gattc_api.h>
|
||||
|
||||
namespace esphome {
|
||||
namespace ble_client {
|
||||
|
||||
namespace espbt = esphome::esp32_ble_tracker;
|
||||
|
||||
using data_to_value_t = std::function<float(std::vector<uint8_t>)>;
|
||||
|
||||
class BLESensor : public sensor::Sensor, public PollingComponent, public BLEClientNode {
|
||||
public:
|
||||
void loop() override;
|
||||
void update() override;
|
||||
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 dump_config() override;
|
||||
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||
void set_service_uuid16(uint16_t uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_uint16(uuid); }
|
||||
void set_service_uuid32(uint32_t uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_uint32(uuid); }
|
||||
void set_service_uuid128(uint8_t *uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_raw(uuid); }
|
||||
void set_char_uuid16(uint16_t uuid) { this->char_uuid_ = espbt::ESPBTUUID::from_uint16(uuid); }
|
||||
void set_char_uuid32(uint32_t uuid) { this->char_uuid_ = espbt::ESPBTUUID::from_uint32(uuid); }
|
||||
void set_char_uuid128(uint8_t *uuid) { this->char_uuid_ = espbt::ESPBTUUID::from_raw(uuid); }
|
||||
void set_descr_uuid16(uint16_t uuid) { this->descr_uuid_ = espbt::ESPBTUUID::from_uint16(uuid); }
|
||||
void set_descr_uuid32(uint32_t uuid) { this->descr_uuid_ = espbt::ESPBTUUID::from_uint32(uuid); }
|
||||
void set_descr_uuid128(uint8_t *uuid) { this->descr_uuid_ = espbt::ESPBTUUID::from_raw(uuid); }
|
||||
void set_data_to_value(data_to_value_t &&lambda) { this->data_to_value_func_ = lambda; }
|
||||
void set_enable_notify(bool notify) { this->notify_ = notify; }
|
||||
uint16_t handle;
|
||||
|
||||
protected:
|
||||
float parse_data_(uint8_t *value, uint16_t value_len);
|
||||
optional<data_to_value_t> data_to_value_func_{};
|
||||
bool notify_;
|
||||
espbt::ESPBTUUID service_uuid_;
|
||||
espbt::ESPBTUUID char_uuid_;
|
||||
espbt::ESPBTUUID descr_uuid_;
|
||||
};
|
||||
|
||||
} // namespace ble_client
|
||||
} // namespace esphome
|
||||
#endif
|
@@ -1,21 +0,0 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import switch, ble_client
|
||||
from esphome.const import ICON_BLUETOOTH
|
||||
from .. import ble_client_ns
|
||||
|
||||
BLEClientSwitch = ble_client_ns.class_(
|
||||
"BLEClientSwitch", switch.Switch, cg.Component, ble_client.BLEClientNode
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
switch.switch_schema(BLEClientSwitch, icon=ICON_BLUETOOTH, block_inverted=True)
|
||||
.extend(ble_client.BLE_CLIENT_SCHEMA)
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = await switch.new_switch(config)
|
||||
await cg.register_component(var, config)
|
||||
await ble_client.register_ble_node(var, config)
|
@@ -1,39 +0,0 @@
|
||||
#include "ble_switch.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/application.h"
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
namespace esphome {
|
||||
namespace ble_client {
|
||||
|
||||
static const char *const TAG = "ble_switch";
|
||||
|
||||
void BLEClientSwitch::write_state(bool state) {
|
||||
this->parent_->set_enabled(state);
|
||||
this->publish_state(state);
|
||||
}
|
||||
|
||||
void BLEClientSwitch::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
||||
esp_ble_gattc_cb_param_t *param) {
|
||||
switch (event) {
|
||||
case ESP_GATTC_REG_EVT:
|
||||
this->publish_state(this->parent_->enabled);
|
||||
break;
|
||||
case ESP_GATTC_OPEN_EVT:
|
||||
this->node_state = espbt::ClientState::ESTABLISHED;
|
||||
break;
|
||||
case ESP_GATTC_DISCONNECT_EVT:
|
||||
this->node_state = espbt::ClientState::IDLE;
|
||||
this->publish_state(this->parent_->enabled);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void BLEClientSwitch::dump_config() { LOG_SWITCH("", "BLE Client Switch", this); }
|
||||
|
||||
} // namespace ble_client
|
||||
} // namespace esphome
|
||||
#endif
|
@@ -1,30 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/ble_client/ble_client.h"
|
||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
|
||||
#include "esphome/components/switch/switch.h"
|
||||
|
||||
#ifdef USE_ESP32
|
||||
#include <esp_gattc_api.h>
|
||||
|
||||
namespace esphome {
|
||||
namespace ble_client {
|
||||
|
||||
namespace espbt = esphome::esp32_ble_tracker;
|
||||
|
||||
class BLEClientSwitch : public switch_::Switch, public Component, public BLEClientNode {
|
||||
public:
|
||||
void dump_config() override;
|
||||
void loop() override {}
|
||||
void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
||||
esp_ble_gattc_cb_param_t *param) override;
|
||||
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||
|
||||
protected:
|
||||
void write_state(bool state) override;
|
||||
};
|
||||
|
||||
} // namespace ble_client
|
||||
} // namespace esphome
|
||||
#endif
|
@@ -1,121 +0,0 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import text_sensor, ble_client, esp32_ble_tracker
|
||||
from esphome.const import (
|
||||
CONF_CHARACTERISTIC_UUID,
|
||||
CONF_ID,
|
||||
CONF_TRIGGER_ID,
|
||||
CONF_SERVICE_UUID,
|
||||
)
|
||||
from esphome import automation
|
||||
from .. import ble_client_ns
|
||||
|
||||
DEPENDENCIES = ["ble_client"]
|
||||
|
||||
CONF_DESCRIPTOR_UUID = "descriptor_uuid"
|
||||
|
||||
CONF_NOTIFY = "notify"
|
||||
CONF_ON_NOTIFY = "on_notify"
|
||||
|
||||
adv_data_t = cg.std_vector.template(cg.uint8)
|
||||
adv_data_t_const_ref = adv_data_t.operator("ref").operator("const")
|
||||
|
||||
BLETextSensor = ble_client_ns.class_(
|
||||
"BLETextSensor",
|
||||
text_sensor.TextSensor,
|
||||
cg.PollingComponent,
|
||||
ble_client.BLEClientNode,
|
||||
)
|
||||
BLETextSensorNotifyTrigger = ble_client_ns.class_(
|
||||
"BLETextSensorNotifyTrigger", automation.Trigger.template(cg.std_string)
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
text_sensor.TEXT_SENSOR_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(BLETextSensor),
|
||||
cv.Required(CONF_SERVICE_UUID): esp32_ble_tracker.bt_uuid,
|
||||
cv.Required(CONF_CHARACTERISTIC_UUID): esp32_ble_tracker.bt_uuid,
|
||||
cv.Optional(CONF_DESCRIPTOR_UUID): esp32_ble_tracker.bt_uuid,
|
||||
cv.Optional(CONF_NOTIFY, default=False): cv.boolean,
|
||||
cv.Optional(CONF_ON_NOTIFY): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
||||
BLETextSensorNotifyTrigger
|
||||
),
|
||||
}
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(cv.polling_component_schema("60s"))
|
||||
.extend(ble_client.BLE_CLIENT_SCHEMA)
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
if len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid16_format):
|
||||
cg.add(
|
||||
var.set_service_uuid16(esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID]))
|
||||
)
|
||||
elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid32_format):
|
||||
cg.add(
|
||||
var.set_service_uuid32(esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID]))
|
||||
)
|
||||
elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid128_format):
|
||||
uuid128 = esp32_ble_tracker.as_reversed_hex_array(config[CONF_SERVICE_UUID])
|
||||
cg.add(var.set_service_uuid128(uuid128))
|
||||
|
||||
if len(config[CONF_CHARACTERISTIC_UUID]) == len(esp32_ble_tracker.bt_uuid16_format):
|
||||
cg.add(
|
||||
var.set_char_uuid16(
|
||||
esp32_ble_tracker.as_hex(config[CONF_CHARACTERISTIC_UUID])
|
||||
)
|
||||
)
|
||||
elif len(config[CONF_CHARACTERISTIC_UUID]) == len(
|
||||
esp32_ble_tracker.bt_uuid32_format
|
||||
):
|
||||
cg.add(
|
||||
var.set_char_uuid32(
|
||||
esp32_ble_tracker.as_hex(config[CONF_CHARACTERISTIC_UUID])
|
||||
)
|
||||
)
|
||||
elif len(config[CONF_CHARACTERISTIC_UUID]) == len(
|
||||
esp32_ble_tracker.bt_uuid128_format
|
||||
):
|
||||
uuid128 = esp32_ble_tracker.as_reversed_hex_array(
|
||||
config[CONF_CHARACTERISTIC_UUID]
|
||||
)
|
||||
cg.add(var.set_char_uuid128(uuid128))
|
||||
|
||||
if CONF_DESCRIPTOR_UUID in config:
|
||||
if len(config[CONF_DESCRIPTOR_UUID]) == len(esp32_ble_tracker.bt_uuid16_format):
|
||||
cg.add(
|
||||
var.set_descr_uuid16(
|
||||
esp32_ble_tracker.as_hex(config[CONF_DESCRIPTOR_UUID])
|
||||
)
|
||||
)
|
||||
elif len(config[CONF_DESCRIPTOR_UUID]) == len(
|
||||
esp32_ble_tracker.bt_uuid32_format
|
||||
):
|
||||
cg.add(
|
||||
var.set_descr_uuid32(
|
||||
esp32_ble_tracker.as_hex(config[CONF_DESCRIPTOR_UUID])
|
||||
)
|
||||
)
|
||||
elif len(config[CONF_DESCRIPTOR_UUID]) == len(
|
||||
esp32_ble_tracker.bt_uuid128_format
|
||||
):
|
||||
uuid128 = esp32_ble_tracker.as_reversed_hex_array(
|
||||
config[CONF_DESCRIPTOR_UUID]
|
||||
)
|
||||
cg.add(var.set_descr_uuid128(uuid128))
|
||||
|
||||
await cg.register_component(var, config)
|
||||
await ble_client.register_ble_node(var, config)
|
||||
cg.add(var.set_enable_notify(config[CONF_NOTIFY]))
|
||||
await text_sensor.register_text_sensor(var, config)
|
||||
for conf in config.get(CONF_ON_NOTIFY, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await ble_client.register_ble_node(trigger, config)
|
||||
await automation.build_automation(trigger, [(cg.std_string, "x")], conf)
|
@@ -1,39 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/components/ble_client/text_sensor/ble_text_sensor.h"
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
namespace esphome {
|
||||
namespace ble_client {
|
||||
|
||||
class BLETextSensorNotifyTrigger : public Trigger<std::string>, public BLETextSensor {
|
||||
public:
|
||||
explicit BLETextSensorNotifyTrigger(BLETextSensor *sensor) { sensor_ = sensor; }
|
||||
void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
||||
esp_ble_gattc_cb_param_t *param) override {
|
||||
switch (event) {
|
||||
case ESP_GATTC_SEARCH_CMPL_EVT: {
|
||||
this->sensor_->node_state = espbt::ClientState::ESTABLISHED;
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_NOTIFY_EVT: {
|
||||
if (param->notify.conn_id != this->sensor_->parent()->get_conn_id() ||
|
||||
param->notify.handle != this->sensor_->handle)
|
||||
break;
|
||||
this->trigger(this->sensor_->parse_data(param->notify.value, param->notify.value_len));
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
BLETextSensor *sensor_;
|
||||
};
|
||||
|
||||
} // namespace ble_client
|
||||
} // namespace esphome
|
||||
|
||||
#endif
|
@@ -1,135 +0,0 @@
|
||||
#include "ble_text_sensor.h"
|
||||
|
||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
namespace esphome {
|
||||
namespace ble_client {
|
||||
|
||||
static const char *const TAG = "ble_text_sensor";
|
||||
|
||||
static const std::string EMPTY = "";
|
||||
|
||||
void BLETextSensor::loop() {}
|
||||
|
||||
void BLETextSensor::dump_config() {
|
||||
LOG_TEXT_SENSOR("", "BLE Text Sensor", this);
|
||||
ESP_LOGCONFIG(TAG, " MAC address : %s", this->parent()->address_str().c_str());
|
||||
ESP_LOGCONFIG(TAG, " Service UUID : %s", this->service_uuid_.to_string().c_str());
|
||||
ESP_LOGCONFIG(TAG, " Characteristic UUID: %s", this->char_uuid_.to_string().c_str());
|
||||
ESP_LOGCONFIG(TAG, " Descriptor UUID : %s", this->descr_uuid_.to_string().c_str());
|
||||
ESP_LOGCONFIG(TAG, " Notifications : %s", YESNO(this->notify_));
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
}
|
||||
|
||||
void BLETextSensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
||||
esp_ble_gattc_cb_param_t *param) {
|
||||
switch (event) {
|
||||
case ESP_GATTC_OPEN_EVT: {
|
||||
if (param->open.status == ESP_GATT_OK) {
|
||||
ESP_LOGI(TAG, "[%s] Connected successfully!", this->get_name().c_str());
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_DISCONNECT_EVT: {
|
||||
ESP_LOGW(TAG, "[%s] Disconnected!", this->get_name().c_str());
|
||||
this->status_set_warning();
|
||||
this->publish_state(EMPTY);
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_SEARCH_CMPL_EVT: {
|
||||
this->handle = 0;
|
||||
auto *chr = this->parent()->get_characteristic(this->service_uuid_, this->char_uuid_);
|
||||
if (chr == nullptr) {
|
||||
this->status_set_warning();
|
||||
this->publish_state(EMPTY);
|
||||
ESP_LOGW(TAG, "No sensor characteristic found at service %s char %s", this->service_uuid_.to_string().c_str(),
|
||||
this->char_uuid_.to_string().c_str());
|
||||
break;
|
||||
}
|
||||
this->handle = chr->handle;
|
||||
if (this->descr_uuid_.get_uuid().len > 0) {
|
||||
auto *descr = chr->get_descriptor(this->descr_uuid_);
|
||||
if (descr == nullptr) {
|
||||
this->status_set_warning();
|
||||
this->publish_state(EMPTY);
|
||||
ESP_LOGW(TAG, "No sensor descriptor found at service %s char %s descr %s",
|
||||
this->service_uuid_.to_string().c_str(), this->char_uuid_.to_string().c_str(),
|
||||
this->descr_uuid_.to_string().c_str());
|
||||
break;
|
||||
}
|
||||
this->handle = descr->handle;
|
||||
}
|
||||
if (this->notify_) {
|
||||
auto status = esp_ble_gattc_register_for_notify(this->parent()->get_gattc_if(),
|
||||
this->parent()->get_remote_bda(), chr->handle);
|
||||
if (status) {
|
||||
ESP_LOGW(TAG, "esp_ble_gattc_register_for_notify failed, status=%d", status);
|
||||
}
|
||||
} else {
|
||||
this->node_state = espbt::ClientState::ESTABLISHED;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_READ_CHAR_EVT: {
|
||||
if (param->read.conn_id != this->parent()->get_conn_id())
|
||||
break;
|
||||
if (param->read.status != ESP_GATT_OK) {
|
||||
ESP_LOGW(TAG, "Error reading char at handle %d, status=%d", param->read.handle, param->read.status);
|
||||
break;
|
||||
}
|
||||
if (param->read.handle == this->handle) {
|
||||
this->status_clear_warning();
|
||||
this->publish_state(this->parse_data(param->read.value, param->read.value_len));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_NOTIFY_EVT: {
|
||||
if (param->notify.conn_id != this->parent()->get_conn_id() || param->notify.handle != this->handle)
|
||||
break;
|
||||
ESP_LOGV(TAG, "[%s] ESP_GATTC_NOTIFY_EVT: handle=0x%x, value=0x%x", this->get_name().c_str(),
|
||||
param->notify.handle, param->notify.value[0]);
|
||||
this->publish_state(this->parse_data(param->notify.value, param->notify.value_len));
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
|
||||
this->node_state = espbt::ClientState::ESTABLISHED;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
std::string BLETextSensor::parse_data(uint8_t *value, uint16_t value_len) {
|
||||
std::string text(value, value + value_len);
|
||||
return text;
|
||||
}
|
||||
|
||||
void BLETextSensor::update() {
|
||||
if (this->node_state != espbt::ClientState::ESTABLISHED) {
|
||||
ESP_LOGW(TAG, "[%s] Cannot poll, not connected", this->get_name().c_str());
|
||||
return;
|
||||
}
|
||||
if (this->handle == 0) {
|
||||
ESP_LOGW(TAG, "[%s] Cannot poll, no service or characteristic found", this->get_name().c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
auto status = esp_ble_gattc_read_char(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), this->handle,
|
||||
ESP_GATT_AUTH_REQ_NONE);
|
||||
if (status) {
|
||||
this->status_set_warning();
|
||||
this->publish_state(EMPTY);
|
||||
ESP_LOGW(TAG, "[%s] Error sending read request for sensor, status=%d", this->get_name().c_str(), status);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ble_client
|
||||
} // namespace esphome
|
||||
#endif
|
@@ -1,46 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/ble_client/ble_client.h"
|
||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
|
||||
#include "esphome/components/text_sensor/text_sensor.h"
|
||||
|
||||
#ifdef USE_ESP32
|
||||
#include <esp_gattc_api.h>
|
||||
|
||||
namespace esphome {
|
||||
namespace ble_client {
|
||||
|
||||
namespace espbt = esphome::esp32_ble_tracker;
|
||||
|
||||
class BLETextSensor : public text_sensor::TextSensor, public PollingComponent, public BLEClientNode {
|
||||
public:
|
||||
void loop() override;
|
||||
void update() override;
|
||||
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 dump_config() override;
|
||||
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||
void set_service_uuid16(uint16_t uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_uint16(uuid); }
|
||||
void set_service_uuid32(uint32_t uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_uint32(uuid); }
|
||||
void set_service_uuid128(uint8_t *uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_raw(uuid); }
|
||||
void set_char_uuid16(uint16_t uuid) { this->char_uuid_ = espbt::ESPBTUUID::from_uint16(uuid); }
|
||||
void set_char_uuid32(uint32_t uuid) { this->char_uuid_ = espbt::ESPBTUUID::from_uint32(uuid); }
|
||||
void set_char_uuid128(uint8_t *uuid) { this->char_uuid_ = espbt::ESPBTUUID::from_raw(uuid); }
|
||||
void set_descr_uuid16(uint16_t uuid) { this->descr_uuid_ = espbt::ESPBTUUID::from_uint16(uuid); }
|
||||
void set_descr_uuid32(uint32_t uuid) { this->descr_uuid_ = espbt::ESPBTUUID::from_uint32(uuid); }
|
||||
void set_descr_uuid128(uint8_t *uuid) { this->descr_uuid_ = espbt::ESPBTUUID::from_raw(uuid); }
|
||||
void set_enable_notify(bool notify) { this->notify_ = notify; }
|
||||
std::string parse_data(uint8_t *value, uint16_t value_len);
|
||||
uint16_t handle;
|
||||
|
||||
protected:
|
||||
bool notify_;
|
||||
espbt::ESPBTUUID service_uuid_;
|
||||
espbt::ESPBTUUID char_uuid_;
|
||||
espbt::ESPBTUUID descr_uuid_;
|
||||
};
|
||||
|
||||
} // namespace ble_client
|
||||
} // namespace esphome
|
||||
#endif
|
@@ -1,12 +0,0 @@
|
||||
import esphome.codegen as cg
|
||||
|
||||
from esphome.components import esp32_ble_tracker
|
||||
|
||||
AUTO_LOAD = ["esp32_ble_tracker"]
|
||||
CODEOWNERS = ["@jesserockz"]
|
||||
DEPENDENCIES = ["esp32"]
|
||||
|
||||
esp32_ble_client_ns = cg.esphome_ns.namespace("esp32_ble_client")
|
||||
BLEClientBase = esp32_ble_client_ns.class_(
|
||||
"BLEClientBase", esp32_ble_tracker.ESPBTClient, cg.Component
|
||||
)
|
@@ -1,99 +0,0 @@
|
||||
#include "ble_characteristic.h"
|
||||
#include "ble_client_base.h"
|
||||
#include "ble_service.h"
|
||||
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
namespace esphome {
|
||||
namespace esp32_ble_client {
|
||||
|
||||
static const char *const TAG = "esp32_ble_client";
|
||||
|
||||
BLECharacteristic::~BLECharacteristic() {
|
||||
for (auto &desc : this->descriptors)
|
||||
delete desc; // NOLINT(cppcoreguidelines-owning-memory)
|
||||
}
|
||||
|
||||
void BLECharacteristic::release_descriptors() {
|
||||
this->parsed = false;
|
||||
for (auto &desc : this->descriptors)
|
||||
delete desc; // NOLINT(cppcoreguidelines-owning-memory)
|
||||
this->descriptors.clear();
|
||||
}
|
||||
|
||||
void BLECharacteristic::parse_descriptors() {
|
||||
this->parsed = true;
|
||||
uint16_t offset = 0;
|
||||
esp_gattc_descr_elem_t result;
|
||||
|
||||
while (true) {
|
||||
uint16_t count = 1;
|
||||
esp_gatt_status_t status =
|
||||
esp_ble_gattc_get_all_descr(this->service->client->get_gattc_if(), this->service->client->get_conn_id(),
|
||||
this->handle, &result, &count, offset);
|
||||
if (status == ESP_GATT_INVALID_OFFSET || status == ESP_GATT_NOT_FOUND) {
|
||||
break;
|
||||
}
|
||||
if (status != ESP_GATT_OK) {
|
||||
ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_get_all_descr error, status=%d",
|
||||
this->service->client->get_connection_index(), this->service->client->address_str().c_str(), status);
|
||||
break;
|
||||
}
|
||||
if (count == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
BLEDescriptor *desc = new BLEDescriptor(); // NOLINT(cppcoreguidelines-owning-memory)
|
||||
desc->uuid = espbt::ESPBTUUID::from_uuid(result.uuid);
|
||||
desc->handle = result.handle;
|
||||
desc->characteristic = this;
|
||||
this->descriptors.push_back(desc);
|
||||
ESP_LOGV(TAG, "[%d] [%s] descriptor %s, handle 0x%x", this->service->client->get_connection_index(),
|
||||
this->service->client->address_str().c_str(), desc->uuid.to_string().c_str(), desc->handle);
|
||||
offset++;
|
||||
}
|
||||
}
|
||||
|
||||
BLEDescriptor *BLECharacteristic::get_descriptor(espbt::ESPBTUUID uuid) {
|
||||
if (!this->parsed)
|
||||
this->parse_descriptors();
|
||||
for (auto &desc : this->descriptors) {
|
||||
if (desc->uuid == uuid)
|
||||
return desc;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
BLEDescriptor *BLECharacteristic::get_descriptor(uint16_t uuid) {
|
||||
return this->get_descriptor(espbt::ESPBTUUID::from_uint16(uuid));
|
||||
}
|
||||
BLEDescriptor *BLECharacteristic::get_descriptor_by_handle(uint16_t handle) {
|
||||
if (!this->parsed)
|
||||
this->parse_descriptors();
|
||||
for (auto &desc : this->descriptors) {
|
||||
if (desc->handle == handle)
|
||||
return desc;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
esp_err_t BLECharacteristic::write_value(uint8_t *new_val, int16_t new_val_size, esp_gatt_write_type_t write_type) {
|
||||
auto *client = this->service->client;
|
||||
auto status = esp_ble_gattc_write_char(client->get_gattc_if(), client->get_conn_id(), this->handle, new_val_size,
|
||||
new_val, write_type, ESP_GATT_AUTH_REQ_NONE);
|
||||
if (status) {
|
||||
ESP_LOGW(TAG, "[%d] [%s] Error sending write value to BLE gattc server, status=%d",
|
||||
this->service->client->get_connection_index(), this->service->client->address_str().c_str(), status);
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
esp_err_t BLECharacteristic::write_value(uint8_t *new_val, int16_t new_val_size) {
|
||||
return write_value(new_val, new_val_size, ESP_GATT_WRITE_TYPE_NO_RSP);
|
||||
}
|
||||
|
||||
} // namespace esp32_ble_client
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ESP32
|
@@ -1,39 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
|
||||
|
||||
#include "ble_descriptor.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace esphome {
|
||||
namespace esp32_ble_client {
|
||||
|
||||
namespace espbt = esphome::esp32_ble_tracker;
|
||||
|
||||
class BLEService;
|
||||
|
||||
class BLECharacteristic {
|
||||
public:
|
||||
~BLECharacteristic();
|
||||
bool parsed = false;
|
||||
espbt::ESPBTUUID uuid;
|
||||
uint16_t handle;
|
||||
esp_gatt_char_prop_t properties;
|
||||
std::vector<BLEDescriptor *> descriptors;
|
||||
void parse_descriptors();
|
||||
void release_descriptors();
|
||||
BLEDescriptor *get_descriptor(espbt::ESPBTUUID uuid);
|
||||
BLEDescriptor *get_descriptor(uint16_t uuid);
|
||||
BLEDescriptor *get_descriptor_by_handle(uint16_t handle);
|
||||
esp_err_t write_value(uint8_t *new_val, int16_t new_val_size);
|
||||
esp_err_t write_value(uint8_t *new_val, int16_t new_val_size, esp_gatt_write_type_t write_type);
|
||||
BLEService *service;
|
||||
};
|
||||
|
||||
} // namespace esp32_ble_client
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ESP32
|
@@ -1,439 +0,0 @@
|
||||
#include "ble_client_base.h"
|
||||
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
namespace esphome {
|
||||
namespace esp32_ble_client {
|
||||
|
||||
static const char *const TAG = "esp32_ble_client";
|
||||
static const esp_bt_uuid_t NOTIFY_DESC_UUID = {
|
||||
.len = ESP_UUID_LEN_16,
|
||||
.uuid =
|
||||
{
|
||||
.uuid16 = ESP_GATT_UUID_CHAR_CLIENT_CONFIG,
|
||||
},
|
||||
};
|
||||
|
||||
void BLEClientBase::setup() {
|
||||
static uint8_t connection_index = 0;
|
||||
this->connection_index_ = connection_index++;
|
||||
|
||||
auto ret = esp_ble_gattc_app_register(this->app_id);
|
||||
if (ret) {
|
||||
ESP_LOGE(TAG, "gattc app register failed. app_id=%d code=%d", this->app_id, ret);
|
||||
this->mark_failed();
|
||||
}
|
||||
this->set_state(espbt::ClientState::IDLE);
|
||||
}
|
||||
|
||||
void BLEClientBase::loop() {
|
||||
// READY_TO_CONNECT means we have discovered the device
|
||||
// and the scanner has been stopped by the tracker.
|
||||
if (this->state_ == espbt::ClientState::READY_TO_CONNECT) {
|
||||
this->connect();
|
||||
}
|
||||
}
|
||||
|
||||
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_)
|
||||
return false;
|
||||
if (this->state_ != espbt::ClientState::IDLE && this->state_ != espbt::ClientState::SEARCHING)
|
||||
return false;
|
||||
|
||||
ESP_LOGD(TAG, "[%d] [%s] Found device", this->connection_index_, this->address_str_.c_str());
|
||||
this->set_state(espbt::ClientState::DISCOVERED);
|
||||
|
||||
auto addr = device.address_uint64();
|
||||
this->remote_bda_[0] = (addr >> 40) & 0xFF;
|
||||
this->remote_bda_[1] = (addr >> 32) & 0xFF;
|
||||
this->remote_bda_[2] = (addr >> 24) & 0xFF;
|
||||
this->remote_bda_[3] = (addr >> 16) & 0xFF;
|
||||
this->remote_bda_[4] = (addr >> 8) & 0xFF;
|
||||
this->remote_bda_[5] = (addr >> 0) & 0xFF;
|
||||
this->remote_addr_type_ = device.get_address_type();
|
||||
return true;
|
||||
}
|
||||
|
||||
void BLEClientBase::connect() {
|
||||
ESP_LOGI(TAG, "[%d] [%s] 0x%02x Attempting BLE connection", this->connection_index_, this->address_str_.c_str(),
|
||||
this->remote_addr_type_);
|
||||
auto ret = esp_ble_gattc_open(this->gattc_if_, this->remote_bda_, this->remote_addr_type_, true);
|
||||
if (ret) {
|
||||
ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_open error, status=%d", this->connection_index_, this->address_str_.c_str(),
|
||||
ret);
|
||||
this->set_state(espbt::ClientState::IDLE);
|
||||
} else {
|
||||
this->set_state(espbt::ClientState::CONNECTING);
|
||||
}
|
||||
}
|
||||
|
||||
void BLEClientBase::disconnect() {
|
||||
if (this->state_ == espbt::ClientState::IDLE || this->state_ == espbt::ClientState::DISCONNECTING)
|
||||
return;
|
||||
ESP_LOGI(TAG, "[%d] [%s] Disconnecting.", this->connection_index_, this->address_str_.c_str());
|
||||
auto err = esp_ble_gattc_close(this->gattc_if_, this->conn_id_);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_close error, err=%d", this->connection_index_, this->address_str_.c_str(),
|
||||
err);
|
||||
}
|
||||
|
||||
if (this->state_ == espbt::ClientState::SEARCHING || this->state_ == espbt::ClientState::READY_TO_CONNECT ||
|
||||
this->state_ == espbt::ClientState::DISCOVERED) {
|
||||
this->set_address(0);
|
||||
this->set_state(espbt::ClientState::IDLE);
|
||||
} else {
|
||||
this->set_state(espbt::ClientState::DISCONNECTING);
|
||||
}
|
||||
}
|
||||
|
||||
void BLEClientBase::release_services() {
|
||||
for (auto &svc : this->services_)
|
||||
delete svc; // NOLINT(cppcoreguidelines-owning-memory)
|
||||
this->services_.clear();
|
||||
#ifndef CONFIG_BT_GATTC_CACHE_NVS_FLASH
|
||||
esp_ble_gattc_cache_clean(this->remote_bda_);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t esp_gattc_if,
|
||||
esp_ble_gattc_cb_param_t *param) {
|
||||
if (event == ESP_GATTC_REG_EVT && this->app_id != param->reg.app_id)
|
||||
return false;
|
||||
if (event != ESP_GATTC_REG_EVT && esp_gattc_if != ESP_GATT_IF_NONE && esp_gattc_if != this->gattc_if_)
|
||||
return false;
|
||||
|
||||
ESP_LOGV(TAG, "[%d] [%s] gattc_event_handler: event=%d gattc_if=%d", this->connection_index_,
|
||||
this->address_str_.c_str(), event, esp_gattc_if);
|
||||
|
||||
switch (event) {
|
||||
case ESP_GATTC_REG_EVT: {
|
||||
if (param->reg.status == ESP_GATT_OK) {
|
||||
ESP_LOGV(TAG, "[%d] [%s] gattc registered app id %d", this->connection_index_, this->address_str_.c_str(),
|
||||
this->app_id);
|
||||
this->gattc_if_ = esp_gattc_if;
|
||||
} else {
|
||||
ESP_LOGE(TAG, "[%d] [%s] gattc app registration failed id=%d code=%d", this->connection_index_,
|
||||
this->address_str_.c_str(), param->reg.app_id, param->reg.status);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_OPEN_EVT: {
|
||||
ESP_LOGV(TAG, "[%d] [%s] ESP_GATTC_OPEN_EVT", this->connection_index_, this->address_str_.c_str());
|
||||
this->conn_id_ = param->open.conn_id;
|
||||
this->service_count_ = 0;
|
||||
if (param->open.status != ESP_GATT_OK && param->open.status != ESP_GATT_ALREADY_OPEN) {
|
||||
ESP_LOGW(TAG, "[%d] [%s] Connection failed, status=%d", this->connection_index_, this->address_str_.c_str(),
|
||||
param->open.status);
|
||||
this->set_state(espbt::ClientState::IDLE);
|
||||
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) {
|
||||
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;
|
||||
}
|
||||
case ESP_GATTC_CFG_MTU_EVT: {
|
||||
if (param->cfg_mtu.status != ESP_GATT_OK) {
|
||||
ESP_LOGW(TAG, "[%d] [%s] cfg_mtu failed, mtu %d, status %d", this->connection_index_,
|
||||
this->address_str_.c_str(), param->cfg_mtu.mtu, param->cfg_mtu.status);
|
||||
this->set_state(espbt::ClientState::IDLE);
|
||||
break;
|
||||
}
|
||||
ESP_LOGV(TAG, "[%d] [%s] cfg_mtu status %d, mtu %d", this->connection_index_, this->address_str_.c_str(),
|
||||
param->cfg_mtu.status, param->cfg_mtu.mtu);
|
||||
this->mtu_ = param->cfg_mtu.mtu;
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_DISCONNECT_EVT: {
|
||||
if (memcmp(param->disconnect.remote_bda, this->remote_bda_, 6) != 0)
|
||||
return false;
|
||||
ESP_LOGV(TAG, "[%d] [%s] ESP_GATTC_DISCONNECT_EVT, reason %d", this->connection_index_,
|
||||
this->address_str_.c_str(), param->disconnect.reason);
|
||||
this->release_services();
|
||||
this->set_state(espbt::ClientState::IDLE);
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_SEARCH_RES_EVT: {
|
||||
this->service_count_++;
|
||||
if (this->connection_type_ == espbt::ConnectionType::V3_WITHOUT_CACHE) {
|
||||
// V3 clients don't need services initialized since
|
||||
// they only request by handle after receiving the services.
|
||||
break;
|
||||
}
|
||||
BLEService *ble_service = new BLEService(); // NOLINT(cppcoreguidelines-owning-memory)
|
||||
ble_service->uuid = espbt::ESPBTUUID::from_uuid(param->search_res.srvc_id.uuid);
|
||||
ble_service->start_handle = param->search_res.start_handle;
|
||||
ble_service->end_handle = param->search_res.end_handle;
|
||||
ble_service->client = this;
|
||||
this->services_.push_back(ble_service);
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_SEARCH_CMPL_EVT: {
|
||||
ESP_LOGV(TAG, "[%d] [%s] ESP_GATTC_SEARCH_CMPL_EVT", this->connection_index_, this->address_str_.c_str());
|
||||
for (auto &svc : this->services_) {
|
||||
ESP_LOGV(TAG, "[%d] [%s] Service UUID: %s", this->connection_index_, this->address_str_.c_str(),
|
||||
svc->uuid.to_string().c_str());
|
||||
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);
|
||||
}
|
||||
this->set_state(espbt::ClientState::CONNECTED);
|
||||
this->state_ = espbt::ClientState::ESTABLISHED;
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
|
||||
if (this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE ||
|
||||
this->connection_type_ == espbt::ConnectionType::V3_WITHOUT_CACHE) {
|
||||
// Client is responsible for flipping the descriptor value
|
||||
// when using the cache
|
||||
break;
|
||||
}
|
||||
esp_gattc_descr_elem_t desc_result;
|
||||
uint16_t count = 1;
|
||||
esp_gatt_status_t descr_status =
|
||||
esp_ble_gattc_get_descr_by_char_handle(this->gattc_if_, this->connection_index_, param->reg_for_notify.handle,
|
||||
NOTIFY_DESC_UUID, &desc_result, &count);
|
||||
if (descr_status != ESP_GATT_OK) {
|
||||
ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_get_descr_by_char_handle error, status=%d", this->connection_index_,
|
||||
this->address_str_.c_str(), descr_status);
|
||||
break;
|
||||
}
|
||||
esp_gattc_char_elem_t char_result;
|
||||
esp_gatt_status_t char_status =
|
||||
esp_ble_gattc_get_all_char(this->gattc_if_, this->connection_index_, param->reg_for_notify.handle,
|
||||
param->reg_for_notify.handle, &char_result, &count, 0);
|
||||
if (char_status != ESP_GATT_OK) {
|
||||
ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_get_all_char error, status=%d", this->connection_index_,
|
||||
this->address_str_.c_str(), char_status);
|
||||
break;
|
||||
}
|
||||
|
||||
/*
|
||||
1 = notify
|
||||
2 = indicate
|
||||
*/
|
||||
uint16_t notify_en = char_result.properties & ESP_GATT_CHAR_PROP_BIT_NOTIFY ? 1 : 2;
|
||||
esp_err_t status =
|
||||
esp_ble_gattc_write_char_descr(this->gattc_if_, this->conn_id_, desc_result.handle, sizeof(notify_en),
|
||||
(uint8_t *) ¬ify_en, ESP_GATT_WRITE_TYPE_RSP, ESP_GATT_AUTH_REQ_NONE);
|
||||
if (status) {
|
||||
ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_write_char_descr error, status=%d", this->connection_index_,
|
||||
this->address_str_.c_str(), status);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void BLEClientBase::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) {
|
||||
esp_bd_addr_t bd_addr;
|
||||
memcpy(bd_addr, param->ble_security.auth_cmpl.bd_addr, sizeof(esp_bd_addr_t));
|
||||
if (memcmp(bd_addr, this->remote_bda_, sizeof(esp_bd_addr_t)) != 0)
|
||||
return;
|
||||
switch (event) {
|
||||
// This event is sent by the server when it requests security
|
||||
case ESP_GAP_BLE_SEC_REQ_EVT:
|
||||
ESP_LOGV(TAG, "[%d] [%s] ESP_GAP_BLE_SEC_REQ_EVT %x", this->connection_index_, this->address_str_.c_str(), event);
|
||||
esp_ble_gap_security_rsp(param->ble_security.ble_req.bd_addr, true);
|
||||
break;
|
||||
// This event is sent once authentication has completed
|
||||
case ESP_GAP_BLE_AUTH_CMPL_EVT:
|
||||
ESP_LOGV(TAG, "[%d] [%s] auth complete. remote BD_ADDR: %s", this->connection_index_, this->address_str_.c_str(),
|
||||
format_hex(bd_addr, 6).c_str());
|
||||
if (!param->ble_security.auth_cmpl.success) {
|
||||
ESP_LOGE(TAG, "[%d] [%s] auth fail reason = 0x%x", this->connection_index_, this->address_str_.c_str(),
|
||||
param->ble_security.auth_cmpl.fail_reason);
|
||||
} else {
|
||||
ESP_LOGI(TAG, "[%d] [%s] auth success. address type = %d auth mode = %d", this->connection_index_,
|
||||
this->address_str_.c_str(), param->ble_security.auth_cmpl.addr_type,
|
||||
param->ble_security.auth_cmpl.auth_mode);
|
||||
}
|
||||
break;
|
||||
case ESP_GAP_BLE_PASSKEY_REQ_EVT:
|
||||
// Call the following function to input the passkey which is displayed on the remote device
|
||||
ESP_LOGD(TAG, "[%d] [%s] ESP_GAP_BLE_PASSKEY_REQ_EVT: Authenticating with passkey", this->connection_index_,
|
||||
this->address_str_.c_str());
|
||||
esp_ble_passkey_reply(this->remote_bda_, true, this->pin_code_);
|
||||
break;
|
||||
case ESP_GAP_BLE_PASSKEY_NOTIF_EVT:
|
||||
// The app will receive this event when the IO has Output capability and the peer device IO has Input capability.
|
||||
// Show the passkey number to the user to input it in the peer device.
|
||||
ESP_LOGD(TAG, "[%d] [%s] ESP_GAP_BLE_PASSKEY_NOTIF_EVT: Passkey: %06d (0x%x)", this->connection_index_,
|
||||
this->address_str_.c_str(), param->ble_security.key_notif.passkey,
|
||||
param->ble_security.key_notif.passkey);
|
||||
break;
|
||||
case ESP_GAP_BLE_NC_REQ_EVT:
|
||||
// The app will receive this evt when the IO has DisplayYesNO capability and the peer device IO also has
|
||||
// DisplayYesNo capability. Show the passkey number to the user to confirm it with the number displayed by peer
|
||||
// device.
|
||||
ESP_LOGW(TAG, "[%d] [%s] ESP_GAP_BLE_NC_REQ_EVT: Passkey: %06d (0x%x) (Not implemented: esp_ble_confirm_reply)",
|
||||
this->connection_index_, this->address_str_.c_str(), param->ble_security.key_notif.passkey,
|
||||
param->ble_security.key_notif.passkey);
|
||||
// We probably want to something like this, however it has not yet been tested/verified.
|
||||
// esp_ble_confirm_reply(param->ble_security.ble_req.bd_addr, true);
|
||||
break;
|
||||
case ESP_GAP_BLE_OOB_REQ_EVT: {
|
||||
ESP_LOGW(TAG, "[%d] [%s] ESP_GAP_BLE_OOB_REQ_EVT (Not implemented: esp_ble_oob_req_reply)",
|
||||
this->connection_index_, this->address_str_.c_str());
|
||||
// We probably want to something like this, however it has not yet been tested/verified.
|
||||
// uint8_t tk[16] = {1}; // If you paired with OOB, both devices need to use the same tk
|
||||
// esp_ble_oob_req_reply(param->ble_security.ble_req.bd_addr, tk, sizeof(tk));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Parse GATT values into a float for a sensor.
|
||||
// Ref: https://www.bluetooth.com/specifications/assigned-numbers/format-types/
|
||||
float BLEClientBase::parse_char_value(uint8_t *value, uint16_t length) {
|
||||
// A length of one means a single octet value.
|
||||
if (length == 0)
|
||||
return 0;
|
||||
if (length == 1)
|
||||
return (float) ((uint8_t) value[0]);
|
||||
|
||||
switch (value[0]) {
|
||||
case 0x1: // boolean.
|
||||
case 0x2: // 2bit.
|
||||
case 0x3: // nibble.
|
||||
case 0x4: // uint8.
|
||||
return (float) ((uint8_t) value[1]);
|
||||
case 0x5: // uint12.
|
||||
case 0x6: // uint16.
|
||||
if (length > 2) {
|
||||
return (float) encode_uint16(value[1], value[2]);
|
||||
}
|
||||
// fall through
|
||||
case 0x7: // uint24.
|
||||
if (length > 3) {
|
||||
return (float) encode_uint24(value[1], value[2], value[3]);
|
||||
}
|
||||
// fall through
|
||||
case 0x8: // uint32.
|
||||
if (length > 4) {
|
||||
return (float) encode_uint32(value[1], value[2], value[3], value[4]);
|
||||
}
|
||||
// fall through
|
||||
case 0xC: // int8.
|
||||
return (float) ((int8_t) value[1]);
|
||||
case 0xD: // int12.
|
||||
case 0xE: // int16.
|
||||
if (length > 2) {
|
||||
return (float) ((int16_t) (value[1] << 8) + (int16_t) value[2]);
|
||||
}
|
||||
// fall through
|
||||
case 0xF: // int24.
|
||||
if (length > 3) {
|
||||
return (float) ((int32_t) (value[1] << 16) + (int32_t) (value[2] << 8) + (int32_t) (value[3]));
|
||||
}
|
||||
// fall through
|
||||
case 0x10: // int32.
|
||||
if (length > 4) {
|
||||
return (float) ((int32_t) (value[1] << 24) + (int32_t) (value[2] << 16) + (int32_t) (value[3] << 8) +
|
||||
(int32_t) (value[4]));
|
||||
}
|
||||
}
|
||||
ESP_LOGW(TAG, "[%d] [%s] Cannot parse characteristic value of type 0x%x length %d", this->connection_index_,
|
||||
this->address_str_.c_str(), value[0], length);
|
||||
return NAN;
|
||||
}
|
||||
|
||||
BLEService *BLEClientBase::get_service(espbt::ESPBTUUID uuid) {
|
||||
for (auto *svc : this->services_) {
|
||||
if (svc->uuid == uuid)
|
||||
return svc;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
BLEService *BLEClientBase::get_service(uint16_t uuid) { return this->get_service(espbt::ESPBTUUID::from_uint16(uuid)); }
|
||||
|
||||
BLECharacteristic *BLEClientBase::get_characteristic(espbt::ESPBTUUID service, espbt::ESPBTUUID chr) {
|
||||
auto *svc = this->get_service(service);
|
||||
if (svc == nullptr)
|
||||
return nullptr;
|
||||
return svc->get_characteristic(chr);
|
||||
}
|
||||
|
||||
BLECharacteristic *BLEClientBase::get_characteristic(uint16_t service, uint16_t chr) {
|
||||
return this->get_characteristic(espbt::ESPBTUUID::from_uint16(service), espbt::ESPBTUUID::from_uint16(chr));
|
||||
}
|
||||
|
||||
BLECharacteristic *BLEClientBase::get_characteristic(uint16_t handle) {
|
||||
for (auto *svc : this->services_) {
|
||||
if (!svc->parsed)
|
||||
svc->parse_characteristics();
|
||||
for (auto *chr : svc->characteristics) {
|
||||
if (chr->handle == handle)
|
||||
return chr;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
BLEDescriptor *BLEClientBase::get_config_descriptor(uint16_t handle) {
|
||||
auto *chr = this->get_characteristic(handle);
|
||||
if (chr != nullptr) {
|
||||
if (!chr->parsed)
|
||||
chr->parse_descriptors();
|
||||
for (auto &desc : chr->descriptors) {
|
||||
if (desc->uuid.get_uuid().uuid.uuid16 == ESP_GATT_UUID_CHAR_CLIENT_CONFIG)
|
||||
return desc;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
BLEDescriptor *BLEClientBase::get_descriptor(espbt::ESPBTUUID service, espbt::ESPBTUUID chr, espbt::ESPBTUUID descr) {
|
||||
auto *svc = this->get_service(service);
|
||||
if (svc == nullptr)
|
||||
return nullptr;
|
||||
auto *ch = svc->get_characteristic(chr);
|
||||
if (ch == nullptr)
|
||||
return nullptr;
|
||||
return ch->get_descriptor(descr);
|
||||
}
|
||||
|
||||
BLEDescriptor *BLEClientBase::get_descriptor(uint16_t service, uint16_t chr, uint16_t descr) {
|
||||
return this->get_descriptor(espbt::ESPBTUUID::from_uint16(service), espbt::ESPBTUUID::from_uint16(chr),
|
||||
espbt::ESPBTUUID::from_uint16(descr));
|
||||
}
|
||||
|
||||
BLEDescriptor *BLEClientBase::get_descriptor(uint16_t handle) {
|
||||
for (auto *svc : this->services_) {
|
||||
if (!svc->parsed)
|
||||
svc->parse_characteristics();
|
||||
for (auto *chr : svc->characteristics) {
|
||||
if (!chr->parsed)
|
||||
chr->parse_descriptors();
|
||||
for (auto *desc : chr->descriptors) {
|
||||
if (desc->handle == handle)
|
||||
return desc;
|
||||
}
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace esp32_ble_client
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ESP32
|
@@ -1,100 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
|
||||
#include "esphome/core/component.h"
|
||||
|
||||
#include "ble_service.h"
|
||||
|
||||
#include <array>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <esp_bt_defs.h>
|
||||
#include <esp_gap_ble_api.h>
|
||||
#include <esp_gatt_common_api.h>
|
||||
#include <esp_gattc_api.h>
|
||||
|
||||
namespace esphome {
|
||||
namespace esp32_ble_client {
|
||||
|
||||
namespace espbt = esphome::esp32_ble_tracker;
|
||||
|
||||
class BLEClientBase : public espbt::ESPBTClient, public Component {
|
||||
public:
|
||||
void setup() override;
|
||||
void loop() override;
|
||||
float get_setup_priority() const override;
|
||||
|
||||
bool parse_device(const espbt::ESPBTDevice &device) override;
|
||||
void on_scan_end() override {}
|
||||
bool 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;
|
||||
void connect() override;
|
||||
void disconnect();
|
||||
void release_services();
|
||||
|
||||
bool connected() { return this->state_ == espbt::ClientState::ESTABLISHED; }
|
||||
|
||||
void set_address(uint64_t address) {
|
||||
this->address_ = address;
|
||||
if (address == 0) {
|
||||
memset(this->remote_bda_, 0, sizeof(this->remote_bda_));
|
||||
this->address_str_ = "";
|
||||
} else {
|
||||
this->address_str_ =
|
||||
str_snprintf("%02X:%02X:%02X:%02X:%02X:%02X", 17, (uint8_t) (this->address_ >> 40) & 0xff,
|
||||
(uint8_t) (this->address_ >> 32) & 0xff, (uint8_t) (this->address_ >> 24) & 0xff,
|
||||
(uint8_t) (this->address_ >> 16) & 0xff, (uint8_t) (this->address_ >> 8) & 0xff,
|
||||
(uint8_t) (this->address_ >> 0) & 0xff);
|
||||
}
|
||||
}
|
||||
void set_pin_code(uint32_t pin_code) { pin_code_ = pin_code; }
|
||||
std::string address_str() const { return this->address_str_; }
|
||||
|
||||
BLEService *get_service(espbt::ESPBTUUID uuid);
|
||||
BLEService *get_service(uint16_t uuid);
|
||||
BLECharacteristic *get_characteristic(espbt::ESPBTUUID service, espbt::ESPBTUUID chr);
|
||||
BLECharacteristic *get_characteristic(uint16_t service, uint16_t chr);
|
||||
BLECharacteristic *get_characteristic(uint16_t handle);
|
||||
BLEDescriptor *get_descriptor(espbt::ESPBTUUID service, espbt::ESPBTUUID chr, espbt::ESPBTUUID descr);
|
||||
BLEDescriptor *get_descriptor(uint16_t service, uint16_t chr, uint16_t descr);
|
||||
BLEDescriptor *get_descriptor(uint16_t handle);
|
||||
// Get the configuration descriptor for the given characteristic handle.
|
||||
BLEDescriptor *get_config_descriptor(uint16_t handle);
|
||||
|
||||
float parse_char_value(uint8_t *value, uint16_t length);
|
||||
|
||||
int get_gattc_if() const { return this->gattc_if_; }
|
||||
uint8_t *get_remote_bda() { return this->remote_bda_; }
|
||||
esp_ble_addr_type_t get_remote_addr_type() const { return this->remote_addr_type_; }
|
||||
void set_remote_addr_type(esp_ble_addr_type_t address_type) { this->remote_addr_type_ = address_type; }
|
||||
uint16_t get_conn_id() const { return this->conn_id_; }
|
||||
uint64_t get_address() const { return this->address_; }
|
||||
|
||||
uint8_t get_connection_index() const { return this->connection_index_; }
|
||||
|
||||
virtual void set_connection_type(espbt::ConnectionType ct) { this->connection_type_ = ct; }
|
||||
|
||||
protected:
|
||||
int gattc_if_;
|
||||
esp_bd_addr_t remote_bda_;
|
||||
esp_ble_addr_type_t remote_addr_type_;
|
||||
uint16_t conn_id_{0xFFFF};
|
||||
uint64_t address_{0};
|
||||
std::string address_str_{};
|
||||
uint8_t connection_index_;
|
||||
int16_t service_count_{0};
|
||||
uint16_t mtu_{23};
|
||||
uint32_t pin_code_{0};
|
||||
espbt::ConnectionType connection_type_{espbt::ConnectionType::V1};
|
||||
|
||||
std::vector<BLEService *> services_;
|
||||
};
|
||||
|
||||
} // namespace esp32_ble_client
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ESP32
|
@@ -1,25 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace esp32_ble_client {
|
||||
|
||||
namespace espbt = esphome::esp32_ble_tracker;
|
||||
|
||||
class BLECharacteristic;
|
||||
|
||||
class BLEDescriptor {
|
||||
public:
|
||||
espbt::ESPBTUUID uuid;
|
||||
uint16_t handle;
|
||||
|
||||
BLECharacteristic *characteristic;
|
||||
};
|
||||
|
||||
} // namespace esp32_ble_client
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ESP32
|
@@ -1,77 +0,0 @@
|
||||
#include "ble_service.h"
|
||||
#include "ble_client_base.h"
|
||||
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
namespace esphome {
|
||||
namespace esp32_ble_client {
|
||||
|
||||
static const char *const TAG = "esp32_ble_client";
|
||||
|
||||
BLECharacteristic *BLEService::get_characteristic(espbt::ESPBTUUID uuid) {
|
||||
if (!this->parsed)
|
||||
this->parse_characteristics();
|
||||
for (auto &chr : this->characteristics) {
|
||||
if (chr->uuid == uuid)
|
||||
return chr;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
BLECharacteristic *BLEService::get_characteristic(uint16_t uuid) {
|
||||
return this->get_characteristic(espbt::ESPBTUUID::from_uint16(uuid));
|
||||
}
|
||||
|
||||
BLEService::~BLEService() {
|
||||
for (auto &chr : this->characteristics)
|
||||
delete chr; // NOLINT(cppcoreguidelines-owning-memory)
|
||||
}
|
||||
|
||||
void BLEService::release_characteristics() {
|
||||
this->parsed = false;
|
||||
for (auto &chr : this->characteristics)
|
||||
delete chr; // NOLINT(cppcoreguidelines-owning-memory)
|
||||
this->characteristics.clear();
|
||||
}
|
||||
|
||||
void BLEService::parse_characteristics() {
|
||||
this->parsed = true;
|
||||
uint16_t offset = 0;
|
||||
esp_gattc_char_elem_t result;
|
||||
|
||||
while (true) {
|
||||
uint16_t count = 1;
|
||||
esp_gatt_status_t status =
|
||||
esp_ble_gattc_get_all_char(this->client->get_gattc_if(), this->client->get_conn_id(), this->start_handle,
|
||||
this->end_handle, &result, &count, offset);
|
||||
if (status == ESP_GATT_INVALID_OFFSET || status == ESP_GATT_NOT_FOUND) {
|
||||
break;
|
||||
}
|
||||
if (status != ESP_GATT_OK) {
|
||||
ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_get_all_char error, status=%d", this->client->get_connection_index(),
|
||||
this->client->address_str().c_str(), status);
|
||||
break;
|
||||
}
|
||||
if (count == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
BLECharacteristic *characteristic = new BLECharacteristic(); // NOLINT(cppcoreguidelines-owning-memory)
|
||||
characteristic->uuid = espbt::ESPBTUUID::from_uuid(result.uuid);
|
||||
characteristic->properties = result.properties;
|
||||
characteristic->handle = result.char_handle;
|
||||
characteristic->service = this;
|
||||
this->characteristics.push_back(characteristic);
|
||||
ESP_LOGV(TAG, "[%d] [%s] characteristic %s, handle 0x%x, properties 0x%x", this->client->get_connection_index(),
|
||||
this->client->address_str().c_str(), characteristic->uuid.to_string().c_str(), characteristic->handle,
|
||||
characteristic->properties);
|
||||
offset++;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace esp32_ble_client
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ESP32
|
@@ -1,36 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
|
||||
|
||||
#include "ble_characteristic.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace esphome {
|
||||
namespace esp32_ble_client {
|
||||
|
||||
namespace espbt = esphome::esp32_ble_tracker;
|
||||
|
||||
class BLEClientBase;
|
||||
|
||||
class BLEService {
|
||||
public:
|
||||
~BLEService();
|
||||
bool parsed = false;
|
||||
espbt::ESPBTUUID uuid;
|
||||
uint16_t start_handle;
|
||||
uint16_t end_handle;
|
||||
std::vector<BLECharacteristic *> characteristics;
|
||||
BLEClientBase *client;
|
||||
void parse_characteristics();
|
||||
void release_characteristics();
|
||||
BLECharacteristic *get_characteristic(espbt::ESPBTUUID uuid);
|
||||
BLECharacteristic *get_characteristic(uint16_t uuid);
|
||||
};
|
||||
|
||||
} // namespace esp32_ble_client
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ESP32
|
@@ -1,320 +0,0 @@
|
||||
import re
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import automation
|
||||
from esphome.const import (
|
||||
CONF_ACTIVE,
|
||||
CONF_ID,
|
||||
CONF_INTERVAL,
|
||||
CONF_DURATION,
|
||||
CONF_TRIGGER_ID,
|
||||
CONF_MAC_ADDRESS,
|
||||
CONF_SERVICE_UUID,
|
||||
CONF_MANUFACTURER_ID,
|
||||
CONF_ON_BLE_ADVERTISE,
|
||||
CONF_ON_BLE_SERVICE_DATA_ADVERTISE,
|
||||
CONF_ON_BLE_MANUFACTURER_DATA_ADVERTISE,
|
||||
)
|
||||
from esphome.core import CORE
|
||||
from esphome.components.esp32 import add_idf_sdkconfig_option
|
||||
from esphome.components import esp32_ble
|
||||
|
||||
DEPENDENCIES = ["esp32"]
|
||||
|
||||
CONF_ESP32_BLE_ID = "esp32_ble_id"
|
||||
CONF_SCAN_PARAMETERS = "scan_parameters"
|
||||
CONF_WINDOW = "window"
|
||||
CONF_CONTINUOUS = "continuous"
|
||||
CONF_ON_SCAN_END = "on_scan_end"
|
||||
CONF_IO_CAPABILITY = "io_capability"
|
||||
esp32_ble_tracker_ns = cg.esphome_ns.namespace("esp32_ble_tracker")
|
||||
ESP32BLETracker = esp32_ble_tracker_ns.class_("ESP32BLETracker", cg.Component)
|
||||
ESPBTClient = esp32_ble_tracker_ns.class_("ESPBTClient")
|
||||
ESPBTDeviceListener = esp32_ble_tracker_ns.class_("ESPBTDeviceListener")
|
||||
ESPBTDevice = esp32_ble_tracker_ns.class_("ESPBTDevice")
|
||||
ESPBTDeviceConstRef = ESPBTDevice.operator("ref").operator("const")
|
||||
adv_data_t = cg.std_vector.template(cg.uint8)
|
||||
adv_data_t_const_ref = adv_data_t.operator("ref").operator("const")
|
||||
# Triggers
|
||||
ESPBTAdvertiseTrigger = esp32_ble_tracker_ns.class_(
|
||||
"ESPBTAdvertiseTrigger", automation.Trigger.template(ESPBTDeviceConstRef)
|
||||
)
|
||||
BLEServiceDataAdvertiseTrigger = esp32_ble_tracker_ns.class_(
|
||||
"BLEServiceDataAdvertiseTrigger", automation.Trigger.template(adv_data_t_const_ref)
|
||||
)
|
||||
BLEManufacturerDataAdvertiseTrigger = esp32_ble_tracker_ns.class_(
|
||||
"BLEManufacturerDataAdvertiseTrigger",
|
||||
automation.Trigger.template(adv_data_t_const_ref),
|
||||
)
|
||||
BLEEndOfScanTrigger = esp32_ble_tracker_ns.class_(
|
||||
"BLEEndOfScanTrigger", automation.Trigger.template()
|
||||
)
|
||||
# Actions
|
||||
ESP32BLEStartScanAction = esp32_ble_tracker_ns.class_(
|
||||
"ESP32BLEStartScanAction", automation.Action
|
||||
)
|
||||
ESP32BLEStopScanAction = esp32_ble_tracker_ns.class_(
|
||||
"ESP32BLEStopScanAction", automation.Action
|
||||
)
|
||||
|
||||
IoCapability = esp32_ble_tracker_ns.enum("IoCapability")
|
||||
IO_CAPABILITY = {
|
||||
"none": IoCapability.IO_CAP_NONE,
|
||||
"keyboard_only": IoCapability.IO_CAP_IN,
|
||||
"keyboard_display": IoCapability.IO_CAP_KBDISP,
|
||||
"display_only": IoCapability.IO_CAP_OUT,
|
||||
"display_yes_no": IoCapability.IO_CAP_IO,
|
||||
}
|
||||
|
||||
|
||||
def validate_scan_parameters(config):
|
||||
duration = config[CONF_DURATION]
|
||||
interval = config[CONF_INTERVAL]
|
||||
window = config[CONF_WINDOW]
|
||||
|
||||
if window > interval:
|
||||
raise cv.Invalid(
|
||||
f"Scan window ({window}) needs to be smaller than scan interval ({interval})"
|
||||
)
|
||||
|
||||
if interval.total_milliseconds * 3 > duration.total_milliseconds:
|
||||
raise cv.Invalid(
|
||||
"Scan duration needs to be at least three times the scan interval to"
|
||||
"cover all BLE channels."
|
||||
)
|
||||
|
||||
return config
|
||||
|
||||
|
||||
bt_uuid16_format = "XXXX"
|
||||
bt_uuid32_format = "XXXXXXXX"
|
||||
bt_uuid128_format = "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
|
||||
|
||||
|
||||
def bt_uuid(value):
|
||||
in_value = cv.string_strict(value)
|
||||
value = in_value.upper()
|
||||
|
||||
if len(value) == len(bt_uuid16_format):
|
||||
pattern = re.compile("^[A-F|0-9]{4,}$")
|
||||
if not pattern.match(value):
|
||||
raise cv.Invalid(
|
||||
f"Invalid hexadecimal value for 16 bit UUID format: '{in_value}'"
|
||||
)
|
||||
return value
|
||||
if len(value) == len(bt_uuid32_format):
|
||||
pattern = re.compile("^[A-F|0-9]{8,}$")
|
||||
if not pattern.match(value):
|
||||
raise cv.Invalid(
|
||||
f"Invalid hexadecimal value for 32 bit UUID format: '{in_value}'"
|
||||
)
|
||||
return value
|
||||
if len(value) == len(bt_uuid128_format):
|
||||
pattern = re.compile(
|
||||
"^[A-F|0-9]{8,}-[A-F|0-9]{4,}-[A-F|0-9]{4,}-[A-F|0-9]{4,}-[A-F|0-9]{12,}$"
|
||||
)
|
||||
if not pattern.match(value):
|
||||
raise cv.Invalid(
|
||||
f"Invalid hexadecimal value for 128 UUID format: '{in_value}'"
|
||||
)
|
||||
return value
|
||||
raise cv.Invalid(
|
||||
f"Service UUID must be in 16 bit '{bt_uuid16_format}', 32 bit '{bt_uuid32_format}', or 128 bit '{bt_uuid128_format}' format"
|
||||
)
|
||||
|
||||
|
||||
def as_hex(value):
|
||||
return cg.RawExpression(f"0x{value}ULL")
|
||||
|
||||
|
||||
def as_hex_array(value):
|
||||
value = value.replace("-", "")
|
||||
cpp_array = [
|
||||
f"0x{part}" for part in [value[i : i + 2] for i in range(0, len(value), 2)]
|
||||
]
|
||||
return cg.RawExpression(f"(uint8_t*)(const uint8_t[16]){{{','.join(cpp_array)}}}")
|
||||
|
||||
|
||||
def as_reversed_hex_array(value):
|
||||
value = value.replace("-", "")
|
||||
cpp_array = [
|
||||
f"0x{part}" for part in [value[i : i + 2] for i in range(0, len(value), 2)]
|
||||
]
|
||||
return cg.RawExpression(
|
||||
f"(uint8_t*)(const uint8_t[16]){{{','.join(reversed(cpp_array))}}}"
|
||||
)
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(ESP32BLETracker),
|
||||
cv.Optional(CONF_SCAN_PARAMETERS, default={}): cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Optional(
|
||||
CONF_DURATION, default="5min"
|
||||
): cv.positive_time_period_seconds,
|
||||
cv.Optional(
|
||||
CONF_INTERVAL, default="320ms"
|
||||
): cv.positive_time_period_milliseconds,
|
||||
cv.Optional(
|
||||
CONF_WINDOW, default="30ms"
|
||||
): cv.positive_time_period_milliseconds,
|
||||
cv.Optional(CONF_ACTIVE, default=True): cv.boolean,
|
||||
cv.Optional(CONF_CONTINUOUS, default=True): cv.boolean,
|
||||
}
|
||||
),
|
||||
validate_scan_parameters,
|
||||
),
|
||||
cv.Optional(CONF_IO_CAPABILITY, default="none"): cv.enum(
|
||||
IO_CAPABILITY, lower=True
|
||||
),
|
||||
cv.Optional(CONF_ON_BLE_ADVERTISE): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ESPBTAdvertiseTrigger),
|
||||
cv.Optional(CONF_MAC_ADDRESS): cv.mac_address,
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_BLE_SERVICE_DATA_ADVERTISE): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
||||
BLEServiceDataAdvertiseTrigger
|
||||
),
|
||||
cv.Optional(CONF_MAC_ADDRESS): cv.mac_address,
|
||||
cv.Required(CONF_SERVICE_UUID): bt_uuid,
|
||||
}
|
||||
),
|
||||
cv.Optional(
|
||||
CONF_ON_BLE_MANUFACTURER_DATA_ADVERTISE
|
||||
): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
||||
BLEManufacturerDataAdvertiseTrigger
|
||||
),
|
||||
cv.Optional(CONF_MAC_ADDRESS): cv.mac_address,
|
||||
cv.Required(CONF_MANUFACTURER_ID): bt_uuid,
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_SCAN_END): automation.validate_automation(
|
||||
{cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(BLEEndOfScanTrigger)}
|
||||
),
|
||||
}
|
||||
).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),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
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)))
|
||||
cg.add(var.set_scan_window(int(params[CONF_WINDOW].total_milliseconds / 0.625)))
|
||||
cg.add(var.set_scan_active(params[CONF_ACTIVE]))
|
||||
cg.add(var.set_scan_continuous(params[CONF_CONTINUOUS]))
|
||||
cg.add(var.set_io_capability(config[CONF_IO_CAPABILITY]))
|
||||
for conf in config.get(CONF_ON_BLE_ADVERTISE, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
if CONF_MAC_ADDRESS in conf:
|
||||
cg.add(trigger.set_address(conf[CONF_MAC_ADDRESS].as_hex))
|
||||
await automation.build_automation(trigger, [(ESPBTDeviceConstRef, "x")], conf)
|
||||
for conf in config.get(CONF_ON_BLE_SERVICE_DATA_ADVERTISE, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
if len(conf[CONF_SERVICE_UUID]) == len(bt_uuid16_format):
|
||||
cg.add(trigger.set_service_uuid16(as_hex(conf[CONF_SERVICE_UUID])))
|
||||
elif len(conf[CONF_SERVICE_UUID]) == len(bt_uuid32_format):
|
||||
cg.add(trigger.set_service_uuid32(as_hex(conf[CONF_SERVICE_UUID])))
|
||||
elif len(conf[CONF_SERVICE_UUID]) == len(bt_uuid128_format):
|
||||
uuid128 = as_reversed_hex_array(conf[CONF_SERVICE_UUID])
|
||||
cg.add(trigger.set_service_uuid128(uuid128))
|
||||
if CONF_MAC_ADDRESS in conf:
|
||||
cg.add(trigger.set_address(conf[CONF_MAC_ADDRESS].as_hex))
|
||||
await automation.build_automation(trigger, [(adv_data_t_const_ref, "x")], conf)
|
||||
for conf in config.get(CONF_ON_BLE_MANUFACTURER_DATA_ADVERTISE, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
if len(conf[CONF_MANUFACTURER_ID]) == len(bt_uuid16_format):
|
||||
cg.add(trigger.set_manufacturer_uuid16(as_hex(conf[CONF_MANUFACTURER_ID])))
|
||||
elif len(conf[CONF_MANUFACTURER_ID]) == len(bt_uuid32_format):
|
||||
cg.add(trigger.set_manufacturer_uuid32(as_hex(conf[CONF_MANUFACTURER_ID])))
|
||||
elif len(conf[CONF_MANUFACTURER_ID]) == len(bt_uuid128_format):
|
||||
uuid128 = as_reversed_hex_array(conf[CONF_MANUFACTURER_ID])
|
||||
cg.add(trigger.set_manufacturer_uuid128(uuid128))
|
||||
if CONF_MAC_ADDRESS in conf:
|
||||
cg.add(trigger.set_address(conf[CONF_MAC_ADDRESS].as_hex))
|
||||
await automation.build_automation(trigger, [(adv_data_t_const_ref, "x")], conf)
|
||||
for conf in config.get(CONF_ON_SCAN_END, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [], conf)
|
||||
|
||||
if CORE.using_esp_idf:
|
||||
add_idf_sdkconfig_option("CONFIG_BT_ENABLED", True)
|
||||
# https://github.com/espressif/esp-idf/issues/4101
|
||||
# https://github.com/espressif/esp-idf/issues/2503
|
||||
# Match arduino CONFIG_BTU_TASK_STACK_SIZE
|
||||
# https://github.com/espressif/arduino-esp32/blob/fd72cf46ad6fc1a6de99c1d83ba8eba17d80a4ee/tools/sdk/esp32/sdkconfig#L1866
|
||||
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
|
||||
|
||||
|
||||
ESP32_BLE_START_SCAN_ACTION_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(ESP32BLETracker),
|
||||
cv.Optional(CONF_CONTINUOUS, default=False): cv.templatable(cv.boolean),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"esp32_ble_tracker.start_scan",
|
||||
ESP32BLEStartScanAction,
|
||||
ESP32_BLE_START_SCAN_ACTION_SCHEMA,
|
||||
)
|
||||
async def esp32_ble_tracker_start_scan_action_to_code(
|
||||
config, action_id, template_arg, args
|
||||
):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
var = cg.new_Pvariable(action_id, template_arg, paren)
|
||||
cg.add(var.set_continuous(config[CONF_CONTINUOUS]))
|
||||
return var
|
||||
|
||||
|
||||
ESP32_BLE_STOP_SCAN_ACTION_SCHEMA = automation.maybe_simple_id(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(ESP32BLETracker),
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"esp32_ble_tracker.stop_scan",
|
||||
ESP32BLEStopScanAction,
|
||||
ESP32_BLE_STOP_SCAN_ACTION_SCHEMA,
|
||||
)
|
||||
async def esp32_ble_tracker_stop_scan_action_to_code(
|
||||
config, action_id, template_arg, args
|
||||
):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
return var
|
||||
|
||||
|
||||
async def register_ble_device(var, config):
|
||||
paren = await cg.get_variable(config[CONF_ESP32_BLE_ID])
|
||||
cg.add(paren.register_listener(var))
|
||||
return var
|
||||
|
||||
|
||||
async def register_client(var, config):
|
||||
paren = await cg.get_variable(config[CONF_ESP32_BLE_ID])
|
||||
cg.add(paren.register_client(var))
|
||||
return var
|
@@ -1,108 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
namespace esphome {
|
||||
namespace esp32_ble_tracker {
|
||||
class ESPBTAdvertiseTrigger : public Trigger<const ESPBTDevice &>, public ESPBTDeviceListener {
|
||||
public:
|
||||
explicit ESPBTAdvertiseTrigger(ESP32BLETracker *parent) { parent->register_listener(this); }
|
||||
void set_address(uint64_t address) { this->address_ = address; }
|
||||
|
||||
bool parse_device(const ESPBTDevice &device) override {
|
||||
if (this->address_ && device.address_uint64() != this->address_) {
|
||||
return false;
|
||||
}
|
||||
this->trigger(device);
|
||||
return true;
|
||||
}
|
||||
|
||||
protected:
|
||||
uint64_t address_ = 0;
|
||||
};
|
||||
|
||||
class BLEServiceDataAdvertiseTrigger : public Trigger<const adv_data_t &>, public ESPBTDeviceListener {
|
||||
public:
|
||||
explicit BLEServiceDataAdvertiseTrigger(ESP32BLETracker *parent) { parent->register_listener(this); }
|
||||
void set_address(uint64_t address) { this->address_ = address; }
|
||||
void set_service_uuid16(uint16_t uuid) { this->uuid_ = ESPBTUUID::from_uint16(uuid); }
|
||||
void set_service_uuid32(uint32_t uuid) { this->uuid_ = ESPBTUUID::from_uint32(uuid); }
|
||||
void set_service_uuid128(uint8_t *uuid) { this->uuid_ = ESPBTUUID::from_raw(uuid); }
|
||||
|
||||
bool parse_device(const ESPBTDevice &device) override {
|
||||
if (this->address_ && device.address_uint64() != this->address_) {
|
||||
return false;
|
||||
}
|
||||
for (auto &service_data : device.get_service_datas()) {
|
||||
if (service_data.uuid == this->uuid_) {
|
||||
this->trigger(service_data.data);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected:
|
||||
uint64_t address_ = 0;
|
||||
ESPBTUUID uuid_;
|
||||
};
|
||||
|
||||
class BLEManufacturerDataAdvertiseTrigger : public Trigger<const adv_data_t &>, public ESPBTDeviceListener {
|
||||
public:
|
||||
explicit BLEManufacturerDataAdvertiseTrigger(ESP32BLETracker *parent) { parent->register_listener(this); }
|
||||
void set_address(uint64_t address) { this->address_ = address; }
|
||||
void set_manufacturer_uuid16(uint16_t uuid) { this->uuid_ = ESPBTUUID::from_uint16(uuid); }
|
||||
void set_manufacturer_uuid32(uint32_t uuid) { this->uuid_ = ESPBTUUID::from_uint32(uuid); }
|
||||
void set_manufacturer_uuid128(uint8_t *uuid) { this->uuid_ = ESPBTUUID::from_raw(uuid); }
|
||||
|
||||
bool parse_device(const ESPBTDevice &device) override {
|
||||
if (this->address_ && device.address_uint64() != this->address_) {
|
||||
return false;
|
||||
}
|
||||
for (auto &manufacturer_data : device.get_manufacturer_datas()) {
|
||||
if (manufacturer_data.uuid == this->uuid_) {
|
||||
this->trigger(manufacturer_data.data);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected:
|
||||
uint64_t address_ = 0;
|
||||
ESPBTUUID uuid_;
|
||||
};
|
||||
|
||||
class BLEEndOfScanTrigger : public Trigger<>, public ESPBTDeviceListener {
|
||||
public:
|
||||
explicit BLEEndOfScanTrigger(ESP32BLETracker *parent) { parent->register_listener(this); }
|
||||
|
||||
bool parse_device(const ESPBTDevice &device) override { return false; }
|
||||
void on_scan_end() override { this->trigger(); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class ESP32BLEStartScanAction : public Action<Ts...> {
|
||||
public:
|
||||
ESP32BLEStartScanAction(ESP32BLETracker *parent) : parent_(parent) {}
|
||||
TEMPLATABLE_VALUE(bool, continuous)
|
||||
void play(Ts... x) override {
|
||||
this->parent_->set_scan_continuous(this->continuous_.value(x...));
|
||||
this->parent_->start_scan();
|
||||
}
|
||||
|
||||
protected:
|
||||
ESP32BLETracker *parent_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class ESP32BLEStopScanAction : public Action<Ts...>, public Parented<ESP32BLETracker> {
|
||||
public:
|
||||
void play(Ts... x) override { this->parent_->stop_scan(); }
|
||||
};
|
||||
|
||||
} // namespace esp32_ble_tracker
|
||||
} // namespace esphome
|
||||
|
||||
#endif
|
@@ -1,941 +0,0 @@
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include "esp32_ble_tracker.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#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>
|
||||
|
||||
#ifdef USE_OTA
|
||||
#include "esphome/components/ota/ota_component.h"
|
||||
#endif
|
||||
|
||||
#ifdef USE_ARDUINO
|
||||
#include <esp32-hal-bt.h>
|
||||
#endif
|
||||
|
||||
// bt_trace.h
|
||||
#undef TAG
|
||||
|
||||
namespace esphome {
|
||||
namespace esp32_ble_tracker {
|
||||
|
||||
static const char *const TAG = "esp32_ble_tracker";
|
||||
|
||||
ESP32BLETracker *global_esp32_ble_tracker = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
esp_ble_io_cap_t global_io_cap = ESP_IO_CAP_NONE; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
||||
uint64_t ble_addr_to_uint64(const esp_bd_addr_t address) {
|
||||
uint64_t u = 0;
|
||||
u |= uint64_t(address[0] & 0xFF) << 40;
|
||||
u |= uint64_t(address[1] & 0xFF) << 32;
|
||||
u |= uint64_t(address[2] & 0xFF) << 24;
|
||||
u |= uint64_t(address[3] & 0xFF) << 16;
|
||||
u |= uint64_t(address[4] & 0xFF) << 8;
|
||||
u |= uint64_t(address[5] & 0xFF) << 0;
|
||||
return u;
|
||||
}
|
||||
|
||||
float ESP32BLETracker::get_setup_priority() const { return setup_priority::BLUETOOTH; }
|
||||
|
||||
void ESP32BLETracker::setup() {
|
||||
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) {
|
||||
if (state == ota::OTA_STARTED) {
|
||||
this->stop_scan();
|
||||
}
|
||||
});
|
||||
#endif
|
||||
|
||||
if (this->scan_continuous_) {
|
||||
if (xSemaphoreTake(this->scan_end_lock_, 0L)) {
|
||||
this->start_scan_(true);
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Cannot start scan!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
int disconnecting = 0;
|
||||
for (auto *client : this->clients_) {
|
||||
switch (client->state()) {
|
||||
case ClientState::DISCONNECTING:
|
||||
disconnecting++;
|
||||
break;
|
||||
case ClientState::DISCOVERED:
|
||||
discovered++;
|
||||
break;
|
||||
case ClientState::SEARCHING:
|
||||
searching++;
|
||||
break;
|
||||
case ClientState::CONNECTING:
|
||||
case ClientState::READY_TO_CONNECT:
|
||||
connecting++;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
bool promote_to_connecting = discovered && !searching && !connecting;
|
||||
|
||||
if (!this->scanner_idle_) {
|
||||
if (this->scan_result_index_ && // if it looks like we have a scan result we will take the lock
|
||||
xSemaphoreTake(this->scan_result_lock_, 5L / portTICK_PERIOD_MS)) {
|
||||
uint32_t index = this->scan_result_index_;
|
||||
if (index) {
|
||||
if (index >= 16) {
|
||||
ESP_LOGW(TAG, "Too many BLE events to process. Some devices may not show up.");
|
||||
}
|
||||
for (size_t i = 0; i < index; i++) {
|
||||
ESPBTDevice device;
|
||||
device.parse_scan_rst(this->scan_result_buffer_[i]);
|
||||
|
||||
bool found = false;
|
||||
for (auto *listener : this->listeners_) {
|
||||
if (listener->parse_device(device))
|
||||
found = true;
|
||||
}
|
||||
|
||||
for (auto *client : this->clients_) {
|
||||
if (client->parse_device(device)) {
|
||||
found = true;
|
||||
if (!connecting && client->state() == ClientState::DISCOVERED) {
|
||||
promote_to_connecting = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!found && !this->scan_continuous_) {
|
||||
this->print_bt_device_info(device);
|
||||
}
|
||||
}
|
||||
this->scan_result_index_ = 0;
|
||||
}
|
||||
xSemaphoreGive(this->scan_result_lock_);
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
Avoid starting the scanner if:
|
||||
- we are already scanning
|
||||
- we are connecting to a device
|
||||
- we are disconnecting from a device
|
||||
|
||||
Otherwise the scanner could fail to ever start again
|
||||
and our only way to recover is to reboot.
|
||||
|
||||
https://github.com/espressif/esp-idf/issues/6688
|
||||
|
||||
*/
|
||||
if (!connecting && !disconnecting && xSemaphoreTake(this->scan_end_lock_, 0L)) {
|
||||
if (this->scan_continuous_) {
|
||||
if (!promote_to_connecting && !this->scan_start_failed_ && !this->scan_set_param_failed_) {
|
||||
this->start_scan_(false);
|
||||
} else {
|
||||
// We didn't start the scan, so we need to release the lock
|
||||
xSemaphoreGive(this->scan_end_lock_);
|
||||
}
|
||||
} else if (!this->scanner_idle_) {
|
||||
this->end_of_scan_();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (this->scan_start_failed_ || this->scan_set_param_failed_) {
|
||||
if (this->scan_start_fail_count_ == 255) {
|
||||
ESP_LOGE(TAG, "ESP-IDF BLE scan could not restart after 255 attempts, rebooting to restore BLE stack...");
|
||||
App.reboot();
|
||||
}
|
||||
if (xSemaphoreTake(this->scan_end_lock_, 0L)) {
|
||||
xSemaphoreGive(this->scan_end_lock_);
|
||||
} else {
|
||||
ESP_LOGD(TAG, "Stopping scan after failure...");
|
||||
esp_ble_gap_stop_scanning();
|
||||
this->cancel_timeout("scan");
|
||||
}
|
||||
if (this->scan_start_failed_) {
|
||||
ESP_LOGE(TAG, "Scan start failed: %d", this->scan_start_failed_);
|
||||
this->scan_start_failed_ = ESP_BT_STATUS_SUCCESS;
|
||||
}
|
||||
if (this->scan_set_param_failed_) {
|
||||
ESP_LOGE(TAG, "Scan set param failed: %d", this->scan_set_param_failed_);
|
||||
this->scan_set_param_failed_ = ESP_BT_STATUS_SUCCESS;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If there is a discovered client and no connecting
|
||||
// clients and no clients using the scanner to search for
|
||||
// devices, then stop scanning and promote the discovered
|
||||
// client to ready to connect.
|
||||
if (promote_to_connecting) {
|
||||
for (auto *client : this->clients_) {
|
||||
if (client->state() == ClientState::DISCOVERED) {
|
||||
if (xSemaphoreTake(this->scan_end_lock_, 0L)) {
|
||||
// Scanner is not running since we got the
|
||||
// lock, so we can promote the client.
|
||||
xSemaphoreGive(this->scan_end_lock_);
|
||||
// We only want to promote one client at a time.
|
||||
// once the scanner is fully stopped.
|
||||
client->set_state(ClientState::READY_TO_CONNECT);
|
||||
} else {
|
||||
ESP_LOGD(TAG, "Pausing scan to make connection...");
|
||||
esp_ble_gap_stop_scanning();
|
||||
this->cancel_timeout("scan");
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ESP32BLETracker::start_scan() {
|
||||
if (xSemaphoreTake(this->scan_end_lock_, 0L)) {
|
||||
this->start_scan_(true);
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Scan requested when a scan is already in progress. Ignoring.");
|
||||
}
|
||||
}
|
||||
|
||||
void ESP32BLETracker::stop_scan() {
|
||||
ESP_LOGD(TAG, "Stopping scan.");
|
||||
this->scan_continuous_ = false;
|
||||
esp_ble_gap_stop_scanning();
|
||||
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("");
|
||||
|
||||
err = esp_ble_gap_set_security_param(ESP_BLE_SM_IOCAP_MODE, &global_io_cap, 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)) {
|
||||
ESP_LOGE(TAG, "start_scan called without holding scan_end_lock_");
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, "Starting scan...");
|
||||
if (!first) {
|
||||
for (auto *listener : this->listeners_)
|
||||
listener->on_scan_end();
|
||||
}
|
||||
this->already_discovered_.clear();
|
||||
this->scanner_idle_ = false;
|
||||
this->scan_params_.scan_type = this->scan_active_ ? BLE_SCAN_TYPE_ACTIVE : BLE_SCAN_TYPE_PASSIVE;
|
||||
this->scan_params_.own_addr_type = BLE_ADDR_TYPE_PUBLIC;
|
||||
this->scan_params_.scan_filter_policy = BLE_SCAN_FILTER_ALLOW_ALL;
|
||||
this->scan_params_.scan_interval = this->scan_interval_;
|
||||
this->scan_params_.scan_window = this->scan_window_;
|
||||
|
||||
esp_ble_gap_set_scan_params(&this->scan_params_);
|
||||
esp_ble_gap_start_scanning(this->scan_duration_);
|
||||
|
||||
this->set_timeout("scan", this->scan_duration_ * 2000, []() {
|
||||
ESP_LOGE(TAG, "ESP-IDF BLE scan never terminated, rebooting to restore BLE stack...");
|
||||
App.reboot();
|
||||
});
|
||||
}
|
||||
|
||||
void ESP32BLETracker::end_of_scan_() {
|
||||
// The lock must be held when calling this function.
|
||||
if (xSemaphoreTake(this->scan_end_lock_, 0L)) {
|
||||
ESP_LOGE(TAG, "end_of_scan_ called without holding the scan_end_lock_");
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, "End of scan.");
|
||||
this->scanner_idle_ = true;
|
||||
this->already_discovered_.clear();
|
||||
xSemaphoreGive(this->scan_end_lock_);
|
||||
this->cancel_timeout("scan");
|
||||
|
||||
for (auto *listener : this->listeners_)
|
||||
listener->on_scan_end();
|
||||
}
|
||||
|
||||
void ESP32BLETracker::register_client(ESPBTClient *client) {
|
||||
client->app_id = ++this->app_id_;
|
||||
this->clients_.push_back(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);
|
||||
break;
|
||||
case ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT:
|
||||
this->gap_scan_set_param_complete_(param->scan_param_cmpl);
|
||||
break;
|
||||
case ESP_GAP_BLE_SCAN_START_COMPLETE_EVT:
|
||||
this->gap_scan_start_complete_(param->scan_start_cmpl);
|
||||
break;
|
||||
case ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT:
|
||||
this->gap_scan_stop_complete_(param->scan_stop_cmpl);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
for (auto *client : this->clients_) {
|
||||
client->gap_event_handler(event, param);
|
||||
}
|
||||
}
|
||||
|
||||
void ESP32BLETracker::gap_scan_set_param_complete_(const esp_ble_gap_cb_param_t::ble_scan_param_cmpl_evt_param ¶m) {
|
||||
this->scan_set_param_failed_ = param.status;
|
||||
}
|
||||
|
||||
void ESP32BLETracker::gap_scan_start_complete_(const esp_ble_gap_cb_param_t::ble_scan_start_cmpl_evt_param ¶m) {
|
||||
this->scan_start_failed_ = param.status;
|
||||
if (param.status == ESP_BT_STATUS_SUCCESS) {
|
||||
this->scan_start_fail_count_ = 0;
|
||||
} else {
|
||||
this->scan_start_fail_count_++;
|
||||
xSemaphoreGive(this->scan_end_lock_);
|
||||
}
|
||||
}
|
||||
|
||||
void ESP32BLETracker::gap_scan_stop_complete_(const esp_ble_gap_cb_param_t::ble_scan_stop_cmpl_evt_param ¶m) {
|
||||
xSemaphoreGive(this->scan_end_lock_);
|
||||
}
|
||||
|
||||
void ESP32BLETracker::gap_scan_result_(const esp_ble_gap_cb_param_t::ble_scan_result_evt_param ¶m) {
|
||||
if (param.search_evt == ESP_GAP_SEARCH_INQ_RES_EVT) {
|
||||
if (xSemaphoreTake(this->scan_result_lock_, 0L)) {
|
||||
if (this->scan_result_index_ < 16) {
|
||||
this->scan_result_buffer_[this->scan_result_index_++] = param;
|
||||
}
|
||||
xSemaphoreGive(this->scan_result_lock_);
|
||||
}
|
||||
} else if (param.search_evt == ESP_GAP_SEARCH_INQ_CMPL_EVT) {
|
||||
xSemaphoreGive(this->scan_end_lock_);
|
||||
}
|
||||
}
|
||||
|
||||
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 (uint8_t i = 0; i < 16; i++) {
|
||||
buf += str_snprintf("%02X", 2, this->uuid_.uuid.uuid128[i]);
|
||||
if (i == 3 || i == 5 || i == 7 || i == 9)
|
||||
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))
|
||||
return {};
|
||||
|
||||
if (data.data.size() != 23)
|
||||
return {};
|
||||
return ESPBLEiBeacon(data.data.data());
|
||||
}
|
||||
|
||||
void ESPBTDevice::parse_scan_rst(const esp_ble_gap_cb_param_t::ble_scan_result_evt_param ¶m) {
|
||||
this->scan_result_ = param;
|
||||
for (uint8_t i = 0; i < ESP_BD_ADDR_LEN; i++)
|
||||
this->address_[i] = param.bda[i];
|
||||
this->address_type_ = param.ble_addr_type;
|
||||
this->rssi_ = param.rssi;
|
||||
this->parse_adv_(param);
|
||||
|
||||
#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE
|
||||
ESP_LOGVV(TAG, "Parse Result:");
|
||||
const char *address_type = "";
|
||||
switch (this->address_type_) {
|
||||
case BLE_ADDR_TYPE_PUBLIC:
|
||||
address_type = "PUBLIC";
|
||||
break;
|
||||
case BLE_ADDR_TYPE_RANDOM:
|
||||
address_type = "RANDOM";
|
||||
break;
|
||||
case BLE_ADDR_TYPE_RPA_PUBLIC:
|
||||
address_type = "RPA_PUBLIC";
|
||||
break;
|
||||
case BLE_ADDR_TYPE_RPA_RANDOM:
|
||||
address_type = "RPA_RANDOM";
|
||||
break;
|
||||
}
|
||||
ESP_LOGVV(TAG, " Address: %02X:%02X:%02X:%02X:%02X:%02X (%s)", this->address_[0], this->address_[1],
|
||||
this->address_[2], this->address_[3], this->address_[4], this->address_[5], address_type);
|
||||
|
||||
ESP_LOGVV(TAG, " RSSI: %d", this->rssi_);
|
||||
ESP_LOGVV(TAG, " Name: '%s'", this->name_.c_str());
|
||||
for (auto &it : this->tx_powers_) {
|
||||
ESP_LOGVV(TAG, " TX Power: %d", it);
|
||||
}
|
||||
if (this->appearance_.has_value()) {
|
||||
ESP_LOGVV(TAG, " Appearance: %u", *this->appearance_);
|
||||
}
|
||||
if (this->ad_flag_.has_value()) {
|
||||
ESP_LOGVV(TAG, " Ad Flag: %u", *this->ad_flag_);
|
||||
}
|
||||
for (auto &uuid : this->service_uuids_) {
|
||||
ESP_LOGVV(TAG, " Service UUID: %s", uuid.to_string().c_str());
|
||||
}
|
||||
for (auto &data : this->manufacturer_datas_) {
|
||||
ESP_LOGVV(TAG, " Manufacturer data: %s", format_hex_pretty(data.data).c_str());
|
||||
if (this->get_ibeacon().has_value()) {
|
||||
auto ibeacon = this->get_ibeacon().value();
|
||||
ESP_LOGVV(TAG, " iBeacon data:");
|
||||
ESP_LOGVV(TAG, " UUID: %s", ibeacon.get_uuid().to_string().c_str());
|
||||
ESP_LOGVV(TAG, " Major: %u", ibeacon.get_major());
|
||||
ESP_LOGVV(TAG, " Minor: %u", ibeacon.get_minor());
|
||||
ESP_LOGVV(TAG, " TXPower: %d", ibeacon.get_signal_power());
|
||||
}
|
||||
}
|
||||
for (auto &data : this->service_datas_) {
|
||||
ESP_LOGVV(TAG, " Service data:");
|
||||
ESP_LOGVV(TAG, " UUID: %s", data.uuid.to_string().c_str());
|
||||
ESP_LOGVV(TAG, " Data: %s", format_hex_pretty(data.data).c_str());
|
||||
}
|
||||
|
||||
ESP_LOGVV(TAG, "Adv data: %s", format_hex_pretty(param.ble_adv, param.adv_data_len + param.scan_rsp_len).c_str());
|
||||
#endif
|
||||
}
|
||||
void ESPBTDevice::parse_adv_(const esp_ble_gap_cb_param_t::ble_scan_result_evt_param ¶m) {
|
||||
size_t offset = 0;
|
||||
const uint8_t *payload = param.ble_adv;
|
||||
uint8_t len = param.adv_data_len + param.scan_rsp_len;
|
||||
|
||||
while (offset + 2 < len) {
|
||||
const uint8_t field_length = payload[offset++]; // First byte is length of adv record
|
||||
if (field_length == 0) {
|
||||
continue; // Possible zero padded advertisement data
|
||||
}
|
||||
|
||||
// first byte of adv record is adv record type
|
||||
const uint8_t record_type = payload[offset++];
|
||||
const uint8_t *record = &payload[offset];
|
||||
const uint8_t record_length = field_length - 1;
|
||||
offset += record_length;
|
||||
|
||||
// See also Generic Access Profile Assigned Numbers:
|
||||
// https://www.bluetooth.com/specifications/assigned-numbers/generic-access-profile/ See also ADVERTISING AND SCAN
|
||||
// RESPONSE DATA FORMAT: https://www.bluetooth.com/specifications/bluetooth-core-specification/ (vol 3, part C, 11)
|
||||
// See also Core Specification Supplement: https://www.bluetooth.com/specifications/bluetooth-core-specification/
|
||||
// (called CSS here)
|
||||
|
||||
switch (record_type) {
|
||||
case ESP_BLE_AD_TYPE_NAME_SHORT:
|
||||
case ESP_BLE_AD_TYPE_NAME_CMPL: {
|
||||
// CSS 1.2 LOCAL NAME
|
||||
// "The Local Name data type shall be the same as, or a shortened version of, the local name assigned to the
|
||||
// device." CSS 1: Optional in this context; shall not appear more than once in a block.
|
||||
// SHORTENED LOCAL NAME
|
||||
// "The Shortened Local Name data type defines a shortened version of the Local Name data type. The Shortened
|
||||
// Local Name data type shall not be used to advertise a name that is longer than the Local Name data type."
|
||||
if (record_length > this->name_.length()) {
|
||||
this->name_ = std::string(reinterpret_cast<const char *>(record), record_length);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ESP_BLE_AD_TYPE_TX_PWR: {
|
||||
// CSS 1.5 TX POWER LEVEL
|
||||
// "The TX Power Level data type indicates the transmitted power level of the packet containing the data type."
|
||||
// CSS 1: Optional in this context (may appear more than once in a block).
|
||||
this->tx_powers_.push_back(*payload);
|
||||
break;
|
||||
}
|
||||
case ESP_BLE_AD_TYPE_APPEARANCE: {
|
||||
// CSS 1.12 APPEARANCE
|
||||
// "The Appearance data type defines the external appearance of the device."
|
||||
// See also https://www.bluetooth.com/specifications/gatt/characteristics/
|
||||
// CSS 1: Optional in this context; shall not appear more than once in a block and shall not appear in both
|
||||
// the AD and SRD of the same extended advertising interval.
|
||||
this->appearance_ = *reinterpret_cast<const uint16_t *>(record);
|
||||
break;
|
||||
}
|
||||
case ESP_BLE_AD_TYPE_FLAG: {
|
||||
// CSS 1.3 FLAGS
|
||||
// "The Flags data type contains one bit Boolean flags. The Flags data type shall be included when any of the
|
||||
// Flag bits are non-zero and the advertising packet is connectable, otherwise the Flags data type may be
|
||||
// omitted."
|
||||
// CSS 1: Optional in this context; shall not appear more than once in a block.
|
||||
this->ad_flag_ = *record;
|
||||
break;
|
||||
}
|
||||
// CSS 1.1 SERVICE UUID
|
||||
// The Service UUID data type is used to include a list of Service or Service Class UUIDs.
|
||||
// There are six data types defined for the three sizes of Service UUIDs that may be returned:
|
||||
// CSS 1: Optional in this context (may appear more than once in a block).
|
||||
case ESP_BLE_AD_TYPE_16SRV_CMPL:
|
||||
case ESP_BLE_AD_TYPE_16SRV_PART: {
|
||||
// • 16-bit Bluetooth Service UUIDs
|
||||
for (uint8_t i = 0; i < record_length / 2; i++) {
|
||||
this->service_uuids_.push_back(ESPBTUUID::from_uint16(*reinterpret_cast<const uint16_t *>(record + 2 * i)));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ESP_BLE_AD_TYPE_32SRV_CMPL:
|
||||
case ESP_BLE_AD_TYPE_32SRV_PART: {
|
||||
// • 32-bit Bluetooth Service UUIDs
|
||||
for (uint8_t i = 0; i < record_length / 4; i++) {
|
||||
this->service_uuids_.push_back(ESPBTUUID::from_uint32(*reinterpret_cast<const uint32_t *>(record + 4 * i)));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ESP_BLE_AD_TYPE_128SRV_CMPL:
|
||||
case ESP_BLE_AD_TYPE_128SRV_PART: {
|
||||
// • Global 128-bit Service UUIDs
|
||||
this->service_uuids_.push_back(ESPBTUUID::from_raw(record));
|
||||
break;
|
||||
}
|
||||
case ESP_BLE_AD_MANUFACTURER_SPECIFIC_TYPE: {
|
||||
// CSS 1.4 MANUFACTURER SPECIFIC DATA
|
||||
// "The Manufacturer Specific data type is used for manufacturer specific data. The first two data octets shall
|
||||
// contain a company identifier from Assigned Numbers. The interpretation of any other octets within the data
|
||||
// shall be defined by the manufacturer specified by the company identifier."
|
||||
// CSS 1: Optional in this context (may appear more than once in a block).
|
||||
if (record_length < 2) {
|
||||
ESP_LOGV(TAG, "Record length too small for ESP_BLE_AD_MANUFACTURER_SPECIFIC_TYPE");
|
||||
break;
|
||||
}
|
||||
ServiceData data{};
|
||||
data.uuid = ESPBTUUID::from_uint16(*reinterpret_cast<const uint16_t *>(record));
|
||||
data.data.assign(record + 2UL, record + record_length);
|
||||
this->manufacturer_datas_.push_back(data);
|
||||
break;
|
||||
}
|
||||
|
||||
// CSS 1.11 SERVICE DATA
|
||||
// "The Service Data data type consists of a service UUID with the data associated with that service."
|
||||
// CSS 1: Optional in this context (may appear more than once in a block).
|
||||
case ESP_BLE_AD_TYPE_SERVICE_DATA: {
|
||||
// «Service Data - 16 bit UUID»
|
||||
// Size: 2 or more octets
|
||||
// The first 2 octets contain the 16 bit Service UUID fol- lowed by additional service data
|
||||
if (record_length < 2) {
|
||||
ESP_LOGV(TAG, "Record length too small for ESP_BLE_AD_TYPE_SERVICE_DATA");
|
||||
break;
|
||||
}
|
||||
ServiceData data{};
|
||||
data.uuid = ESPBTUUID::from_uint16(*reinterpret_cast<const uint16_t *>(record));
|
||||
data.data.assign(record + 2UL, record + record_length);
|
||||
this->service_datas_.push_back(data);
|
||||
break;
|
||||
}
|
||||
case ESP_BLE_AD_TYPE_32SERVICE_DATA: {
|
||||
// «Service Data - 32 bit UUID»
|
||||
// Size: 4 or more octets
|
||||
// The first 4 octets contain the 32 bit Service UUID fol- lowed by additional service data
|
||||
if (record_length < 4) {
|
||||
ESP_LOGV(TAG, "Record length too small for ESP_BLE_AD_TYPE_32SERVICE_DATA");
|
||||
break;
|
||||
}
|
||||
ServiceData data{};
|
||||
data.uuid = ESPBTUUID::from_uint32(*reinterpret_cast<const uint32_t *>(record));
|
||||
data.data.assign(record + 4UL, record + record_length);
|
||||
this->service_datas_.push_back(data);
|
||||
break;
|
||||
}
|
||||
case ESP_BLE_AD_TYPE_128SERVICE_DATA: {
|
||||
// «Service Data - 128 bit UUID»
|
||||
// Size: 16 or more octets
|
||||
// The first 16 octets contain the 128 bit Service UUID followed by additional service data
|
||||
if (record_length < 16) {
|
||||
ESP_LOGV(TAG, "Record length too small for ESP_BLE_AD_TYPE_128SERVICE_DATA");
|
||||
break;
|
||||
}
|
||||
ServiceData data{};
|
||||
data.uuid = ESPBTUUID::from_raw(record);
|
||||
data.data.assign(record + 16UL, record + record_length);
|
||||
this->service_datas_.push_back(data);
|
||||
break;
|
||||
}
|
||||
case ESP_BLE_AD_TYPE_INT_RANGE:
|
||||
// Avoid logging this as it's very verbose
|
||||
break;
|
||||
default: {
|
||||
ESP_LOGV(TAG, "Unhandled type: advType: 0x%02x", record_type);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
std::string ESPBTDevice::address_str() const {
|
||||
char mac[24];
|
||||
snprintf(mac, sizeof(mac), "%02X:%02X:%02X:%02X:%02X:%02X", this->address_[0], this->address_[1], this->address_[2],
|
||||
this->address_[3], this->address_[4], this->address_[5]);
|
||||
return mac;
|
||||
}
|
||||
uint64_t ESPBTDevice::address_uint64() const { return ble_addr_to_uint64(this->address_); }
|
||||
|
||||
void ESP32BLETracker::dump_config() {
|
||||
const char *io_capability_s;
|
||||
switch (global_io_cap) {
|
||||
case ESP_IO_CAP_OUT:
|
||||
io_capability_s = "display_only";
|
||||
break;
|
||||
case ESP_IO_CAP_IO:
|
||||
io_capability_s = "display_yes_no";
|
||||
break;
|
||||
case ESP_IO_CAP_IN:
|
||||
io_capability_s = "keyboard_only";
|
||||
break;
|
||||
case ESP_IO_CAP_NONE:
|
||||
io_capability_s = "none";
|
||||
break;
|
||||
case ESP_IO_CAP_KBDISP:
|
||||
io_capability_s = "keyboard_display";
|
||||
break;
|
||||
default:
|
||||
io_capability_s = "invalid";
|
||||
break;
|
||||
}
|
||||
ESP_LOGCONFIG(TAG, "BLE Tracker:");
|
||||
ESP_LOGCONFIG(TAG, " Scan Duration: %u s", this->scan_duration_);
|
||||
ESP_LOGCONFIG(TAG, " Scan Interval: %.1f ms", this->scan_interval_ * 0.625f);
|
||||
ESP_LOGCONFIG(TAG, " Scan Window: %.1f ms", this->scan_window_ * 0.625f);
|
||||
ESP_LOGCONFIG(TAG, " Scan Type: %s", this->scan_active_ ? "ACTIVE" : "PASSIVE");
|
||||
ESP_LOGCONFIG(TAG, " Continuous Scanning: %s", this->scan_continuous_ ? "True" : "False");
|
||||
ESP_LOGCONFIG(TAG, " IO Capability: %s", io_capability_s);
|
||||
}
|
||||
|
||||
void ESP32BLETracker::print_bt_device_info(const ESPBTDevice &device) {
|
||||
const uint64_t address = device.address_uint64();
|
||||
for (auto &disc : this->already_discovered_) {
|
||||
if (disc == address)
|
||||
return;
|
||||
}
|
||||
this->already_discovered_.push_back(address);
|
||||
|
||||
ESP_LOGD(TAG, "Found device %s RSSI=%d", device.address_str().c_str(), device.get_rssi());
|
||||
|
||||
const char *address_type_s;
|
||||
switch (device.get_address_type()) {
|
||||
case BLE_ADDR_TYPE_PUBLIC:
|
||||
address_type_s = "PUBLIC";
|
||||
break;
|
||||
case BLE_ADDR_TYPE_RANDOM:
|
||||
address_type_s = "RANDOM";
|
||||
break;
|
||||
case BLE_ADDR_TYPE_RPA_PUBLIC:
|
||||
address_type_s = "RPA_PUBLIC";
|
||||
break;
|
||||
case BLE_ADDR_TYPE_RPA_RANDOM:
|
||||
address_type_s = "RPA_RANDOM";
|
||||
break;
|
||||
default:
|
||||
address_type_s = "UNKNOWN";
|
||||
break;
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, " Address Type: %s", address_type_s);
|
||||
if (!device.get_name().empty()) {
|
||||
ESP_LOGD(TAG, " Name: '%s'", device.get_name().c_str());
|
||||
}
|
||||
for (auto &tx_power : device.get_tx_powers()) {
|
||||
ESP_LOGD(TAG, " TX Power: %d", tx_power);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace esp32_ble_tracker
|
||||
} // namespace esphome
|
||||
|
||||
#endif
|
@@ -1,288 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "queue.h"
|
||||
|
||||
#include <array>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include <esp_gap_ble_api.h>
|
||||
#include <esp_gattc_api.h>
|
||||
#include <esp_bt_defs.h>
|
||||
|
||||
namespace esphome {
|
||||
namespace esp32_ble_tracker {
|
||||
|
||||
// NOLINTNEXTLINE
|
||||
extern esp_ble_io_cap_t global_io_cap;
|
||||
|
||||
enum IoCapability {
|
||||
IO_CAP_OUT = ESP_IO_CAP_OUT,
|
||||
IO_CAP_IO = ESP_IO_CAP_IO,
|
||||
IO_CAP_IN = ESP_IO_CAP_IN,
|
||||
IO_CAP_NONE = ESP_IO_CAP_NONE,
|
||||
IO_CAP_KBDISP = ESP_IO_CAP_KBDISP,
|
||||
};
|
||||
|
||||
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 adv_data_t = std::vector<uint8_t>;
|
||||
|
||||
struct ServiceData {
|
||||
ESPBTUUID uuid;
|
||||
adv_data_t data;
|
||||
};
|
||||
|
||||
class ESPBLEiBeacon {
|
||||
public:
|
||||
ESPBLEiBeacon() { memset(&this->beacon_data_, 0, sizeof(this->beacon_data_)); }
|
||||
ESPBLEiBeacon(const uint8_t *data);
|
||||
static optional<ESPBLEiBeacon> from_manufacturer_data(const ServiceData &data);
|
||||
|
||||
uint16_t get_major() { return ((this->beacon_data_.major & 0xFF) << 8) | (this->beacon_data_.major >> 8); }
|
||||
uint16_t get_minor() { return ((this->beacon_data_.minor & 0xFF) << 8) | (this->beacon_data_.minor >> 8); }
|
||||
int8_t get_signal_power() { return this->beacon_data_.signal_power; }
|
||||
ESPBTUUID get_uuid() { return ESPBTUUID::from_raw(this->beacon_data_.proximity_uuid); }
|
||||
|
||||
protected:
|
||||
struct {
|
||||
uint8_t sub_type;
|
||||
uint8_t length;
|
||||
uint8_t proximity_uuid[16];
|
||||
uint16_t major;
|
||||
uint16_t minor;
|
||||
int8_t signal_power;
|
||||
} PACKED beacon_data_;
|
||||
};
|
||||
|
||||
class ESPBTDevice {
|
||||
public:
|
||||
void parse_scan_rst(const esp_ble_gap_cb_param_t::ble_scan_result_evt_param ¶m);
|
||||
|
||||
std::string address_str() const;
|
||||
|
||||
uint64_t address_uint64() const;
|
||||
|
||||
const uint8_t *address() const { return address_; }
|
||||
|
||||
esp_ble_addr_type_t get_address_type() const { return this->address_type_; }
|
||||
int get_rssi() const { return rssi_; }
|
||||
const std::string &get_name() const { return this->name_; }
|
||||
|
||||
const std::vector<int8_t> &get_tx_powers() const { return tx_powers_; }
|
||||
|
||||
const optional<uint16_t> &get_appearance() const { return appearance_; }
|
||||
const optional<uint8_t> &get_ad_flag() const { return ad_flag_; }
|
||||
const std::vector<ESPBTUUID> &get_service_uuids() const { return service_uuids_; }
|
||||
|
||||
const std::vector<ServiceData> &get_manufacturer_datas() const { return manufacturer_datas_; }
|
||||
|
||||
const std::vector<ServiceData> &get_service_datas() const { return service_datas_; }
|
||||
|
||||
const esp_ble_gap_cb_param_t::ble_scan_result_evt_param &get_scan_result() const { return scan_result_; }
|
||||
|
||||
optional<ESPBLEiBeacon> get_ibeacon() const {
|
||||
for (auto &it : this->manufacturer_datas_) {
|
||||
auto res = ESPBLEiBeacon::from_manufacturer_data(it);
|
||||
if (res.has_value())
|
||||
return *res;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
protected:
|
||||
void parse_adv_(const esp_ble_gap_cb_param_t::ble_scan_result_evt_param ¶m);
|
||||
|
||||
esp_bd_addr_t address_{
|
||||
0,
|
||||
};
|
||||
esp_ble_addr_type_t address_type_{BLE_ADDR_TYPE_PUBLIC};
|
||||
int rssi_{0};
|
||||
std::string name_{};
|
||||
std::vector<int8_t> tx_powers_{};
|
||||
optional<uint16_t> appearance_{};
|
||||
optional<uint8_t> ad_flag_{};
|
||||
std::vector<ESPBTUUID> service_uuids_;
|
||||
std::vector<ServiceData> manufacturer_datas_{};
|
||||
std::vector<ServiceData> service_datas_{};
|
||||
esp_ble_gap_cb_param_t::ble_scan_result_evt_param scan_result_{};
|
||||
};
|
||||
|
||||
class ESP32BLETracker;
|
||||
|
||||
class ESPBTDeviceListener {
|
||||
public:
|
||||
virtual void on_scan_end() {}
|
||||
virtual bool parse_device(const ESPBTDevice &device) = 0;
|
||||
void set_parent(ESP32BLETracker *parent) { parent_ = parent; }
|
||||
|
||||
protected:
|
||||
ESP32BLETracker *parent_{nullptr};
|
||||
};
|
||||
|
||||
enum class ClientState {
|
||||
// Connection is allocated
|
||||
INIT,
|
||||
// Client is disconnecting
|
||||
DISCONNECTING,
|
||||
// Connection is idle, no device detected.
|
||||
IDLE,
|
||||
// Searching for device.
|
||||
SEARCHING,
|
||||
// Device advertisement found.
|
||||
DISCOVERED,
|
||||
// Device is discovered and the scanner is stopped
|
||||
READY_TO_CONNECT,
|
||||
// Connection in progress.
|
||||
CONNECTING,
|
||||
// Initial connection established.
|
||||
CONNECTED,
|
||||
// The client and sub-clients have completed setup.
|
||||
ESTABLISHED,
|
||||
};
|
||||
|
||||
enum class ConnectionType {
|
||||
// The default connection type, we hold all the services in ram
|
||||
// for the duration of the connection.
|
||||
V1,
|
||||
// The client has a cache of the services and mtu so we should not
|
||||
// fetch them again
|
||||
V3_WITH_CACHE,
|
||||
// The client does not need the services and mtu once we send them
|
||||
// so we should wipe them from memory as soon as we send them
|
||||
V3_WITHOUT_CACHE
|
||||
};
|
||||
|
||||
class ESPBTClient : public ESPBTDeviceListener {
|
||||
public:
|
||||
virtual bool gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
||||
esp_ble_gattc_cb_param_t *param) = 0;
|
||||
virtual void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) = 0;
|
||||
virtual void connect() = 0;
|
||||
virtual void set_state(ClientState st) { this->state_ = st; }
|
||||
ClientState state() const { return state_; }
|
||||
int app_id;
|
||||
|
||||
protected:
|
||||
ClientState state_;
|
||||
};
|
||||
|
||||
class ESP32BLETracker : public Component {
|
||||
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; }
|
||||
void set_scan_window(uint32_t scan_window) { scan_window_ = scan_window; }
|
||||
void set_scan_active(bool scan_active) { scan_active_ = scan_active; }
|
||||
void set_scan_continuous(bool scan_continuous) { scan_continuous_ = scan_continuous; }
|
||||
void set_io_capability(IoCapability io_capability) { global_io_cap = (esp_ble_io_cap_t) io_capability; }
|
||||
|
||||
/// Setup the FreeRTOS task and the Bluetooth stack.
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override;
|
||||
|
||||
void loop() override;
|
||||
|
||||
void register_listener(ESPBTDeviceListener *listener) {
|
||||
listener->set_parent(this);
|
||||
this->listeners_.push_back(listener);
|
||||
}
|
||||
|
||||
void register_client(ESPBTClient *client);
|
||||
|
||||
void print_bt_device_info(const ESPBTDevice &device);
|
||||
|
||||
void start_scan();
|
||||
void stop_scan();
|
||||
|
||||
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.
|
||||
void gap_scan_set_param_complete_(const esp_ble_gap_cb_param_t::ble_scan_param_cmpl_evt_param ¶m);
|
||||
/// Called when a `ESP_GAP_BLE_SCAN_START_COMPLETE_EVT` event is received.
|
||||
void gap_scan_start_complete_(const esp_ble_gap_cb_param_t::ble_scan_start_cmpl_evt_param ¶m);
|
||||
/// Called when a `ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT` event is received.
|
||||
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_;
|
||||
std::vector<ESPBTDeviceListener *> listeners_;
|
||||
/// Client parameters.
|
||||
std::vector<ESPBTClient *> clients_;
|
||||
/// A structure holding the ESP BLE scan parameters.
|
||||
esp_ble_scan_params_t scan_params_;
|
||||
/// The interval in seconds to perform scans.
|
||||
uint32_t scan_duration_;
|
||||
uint32_t scan_interval_;
|
||||
uint32_t scan_window_;
|
||||
uint8_t scan_start_fail_count_;
|
||||
bool scan_continuous_;
|
||||
bool scan_active_;
|
||||
bool scanner_idle_;
|
||||
SemaphoreHandle_t scan_result_lock_;
|
||||
SemaphoreHandle_t scan_end_lock_;
|
||||
size_t scan_result_index_{0};
|
||||
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
|
||||
extern ESP32BLETracker *global_esp32_ble_tracker;
|
||||
|
||||
} // namespace esp32_ble_tracker
|
||||
} // namespace esphome
|
||||
|
||||
#endif
|
@@ -1,109 +0,0 @@
|
||||
#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.
|
||||
*/
|
||||
|
||||
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().
|
||||
// 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;
|
||||
};
|
||||
|
||||
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 relevant event data.
|
||||
switch (e) {
|
||||
case ESP_GATTC_NOTIFY_EVT:
|
||||
this->data.assign(p->notify.value, p->notify.value + p->notify.value_len);
|
||||
this->event_.gattc.gattc_param.notify.value = this->data.data();
|
||||
break;
|
||||
case ESP_GATTC_READ_CHAR_EVT:
|
||||
case ESP_GATTC_READ_DESCR_EVT:
|
||||
this->data.assign(p->read.value, p->read.value + p->read.value_len);
|
||||
this->event_.gattc.gattc_param.read.value = this->data.data();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
this->type_ = 1;
|
||||
};
|
||||
|
||||
union {
|
||||
struct gap_event { // NOLINT(readability-identifier-naming)
|
||||
esp_gap_ble_cb_event_t gap_event;
|
||||
esp_ble_gap_cb_param_t gap_param;
|
||||
} gap;
|
||||
|
||||
struct gattc_event { // NOLINT(readability-identifier-naming)
|
||||
esp_gattc_cb_event_t gattc_event;
|
||||
esp_gatt_if_t gattc_if;
|
||||
esp_ble_gattc_cb_param_t gattc_param;
|
||||
} gattc;
|
||||
} event_;
|
||||
std::vector<uint8_t> data{};
|
||||
uint8_t type_; // 0=gap 1=gattc
|
||||
};
|
||||
|
||||
} // namespace esp32_ble_tracker
|
||||
} // namespace esphome
|
||||
|
||||
#endif
|
@@ -1,6 +1,6 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import uart
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ID, CONF_THROTTLE
|
||||
|
||||
CODEOWNERS = ["@syssi"]
|
||||
@@ -15,6 +15,12 @@ CONF_RX_TIMEOUT = "rx_timeout"
|
||||
votronic_ns = cg.esphome_ns.namespace("votronic")
|
||||
Votronic = votronic_ns.class_("Votronic", cg.PollingComponent, uart.UARTDevice)
|
||||
|
||||
VOTRONIC_COMPONENT_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(CONF_VOTRONIC_ID): cv.use_id(Votronic),
|
||||
}
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema(
|
||||
{
|
||||
|
@@ -1,9 +1,9 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import binary_sensor
|
||||
from esphome.const import CONF_ICON, CONF_ID
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ID
|
||||
|
||||
from . import CONF_VOTRONIC_ID, Votronic
|
||||
from . import CONF_VOTRONIC_ID, VOTRONIC_COMPONENT_SCHEMA
|
||||
|
||||
DEPENDENCIES = ["votronic"]
|
||||
|
||||
@@ -11,56 +11,97 @@ CODEOWNERS = ["@syssi"]
|
||||
|
||||
CONF_CHARGING = "charging"
|
||||
CONF_DISCHARGING = "discharging"
|
||||
CONF_CONTROLLER_ACTIVE = "controller_active"
|
||||
CONF_CURRENT_REDUCTION = "current_reduction"
|
||||
CONF_AES_ACTIVE = "aes_active"
|
||||
|
||||
ICON_CHARGING = "mdi:battery-charging"
|
||||
ICON_DISCHARGING = "mdi:power-plug"
|
||||
ICON_CONTROLLER_ACTIVE = "mdi:power"
|
||||
ICON_CURRENT_REDUCTION = "mdi:car-speed-limiter"
|
||||
ICON_AES_ACTIVE = "mdi:export"
|
||||
CONF_CHARGER_CHARGING = "charger_charging"
|
||||
CONF_CHARGER_DISCHARGING = "charger_discharging"
|
||||
CONF_CHARGER_CONTROLLER_ACTIVE = "charger_controller_active"
|
||||
CONF_CHARGER_CURRENT_REDUCTION = "charger_current_reduction"
|
||||
CONF_CHARGER_AES_ACTIVE = "charger_aes_active"
|
||||
|
||||
CONF_CHARGING_CONVERTER_CHARGING = "charging_converter_charging"
|
||||
CONF_CHARGING_CONVERTER_DISCHARGING = "charging_converter_discharging"
|
||||
CONF_CHARGING_CONVERTER_CONTROLLER_ACTIVE = "charging_converter_controller_active"
|
||||
CONF_CHARGING_CONVERTER_CURRENT_REDUCTION = "charging_converter_current_reduction"
|
||||
CONF_CHARGING_CONVERTER_AES_ACTIVE = "charging_converter_aes_active"
|
||||
|
||||
CONF_PV_CONTROLLER_ACTIVE = "pv_controller_active"
|
||||
CONF_PV_CURRENT_REDUCTION = "pv_current_reduction"
|
||||
CONF_PV_AES_ACTIVE = "pv_aes_active"
|
||||
|
||||
BINARY_SENSORS = [
|
||||
CONF_CHARGING,
|
||||
CONF_DISCHARGING,
|
||||
CONF_CONTROLLER_ACTIVE,
|
||||
CONF_CURRENT_REDUCTION,
|
||||
CONF_AES_ACTIVE,
|
||||
CONF_CHARGER_CHARGING,
|
||||
CONF_CHARGER_DISCHARGING,
|
||||
CONF_CHARGER_CONTROLLER_ACTIVE,
|
||||
CONF_CHARGER_CURRENT_REDUCTION,
|
||||
CONF_CHARGER_AES_ACTIVE,
|
||||
CONF_CHARGING_CONVERTER_CHARGING,
|
||||
CONF_CHARGING_CONVERTER_DISCHARGING,
|
||||
CONF_CHARGING_CONVERTER_CONTROLLER_ACTIVE,
|
||||
CONF_CHARGING_CONVERTER_CURRENT_REDUCTION,
|
||||
CONF_CHARGING_CONVERTER_AES_ACTIVE,
|
||||
CONF_PV_CONTROLLER_ACTIVE,
|
||||
CONF_PV_CURRENT_REDUCTION,
|
||||
CONF_PV_AES_ACTIVE,
|
||||
]
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
CONFIG_SCHEMA = VOTRONIC_COMPONENT_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(CONF_VOTRONIC_ID): cv.use_id(Votronic),
|
||||
cv.Optional(CONF_CHARGING): binary_sensor.BINARY_SENSOR_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(binary_sensor.BinarySensor),
|
||||
cv.Optional(CONF_ICON, default=ICON_CHARGING): cv.icon,
|
||||
}
|
||||
cv.Optional(CONF_CHARGING): binary_sensor.binary_sensor_schema(
|
||||
icon="mdi:battery-charging",
|
||||
),
|
||||
cv.Optional(CONF_DISCHARGING): binary_sensor.BINARY_SENSOR_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(binary_sensor.BinarySensor),
|
||||
cv.Optional(CONF_ICON, default=ICON_DISCHARGING): cv.icon,
|
||||
}
|
||||
cv.Optional(CONF_DISCHARGING): binary_sensor.binary_sensor_schema(
|
||||
icon="mdi:power-plug",
|
||||
),
|
||||
cv.Optional(CONF_CONTROLLER_ACTIVE): binary_sensor.BINARY_SENSOR_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(binary_sensor.BinarySensor),
|
||||
cv.Optional(CONF_ICON, default=ICON_CONTROLLER_ACTIVE): cv.icon,
|
||||
}
|
||||
cv.Optional(CONF_CHARGER_CHARGING): binary_sensor.binary_sensor_schema(
|
||||
icon="mdi:battery-charging",
|
||||
),
|
||||
cv.Optional(CONF_CURRENT_REDUCTION): binary_sensor.BINARY_SENSOR_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(binary_sensor.BinarySensor),
|
||||
cv.Optional(CONF_ICON, default=ICON_CURRENT_REDUCTION): cv.icon,
|
||||
}
|
||||
cv.Optional(CONF_CHARGER_DISCHARGING): binary_sensor.binary_sensor_schema(
|
||||
icon="mdi:power-plug",
|
||||
),
|
||||
cv.Optional(CONF_AES_ACTIVE): binary_sensor.BINARY_SENSOR_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(binary_sensor.BinarySensor),
|
||||
cv.Optional(CONF_ICON, default=ICON_AES_ACTIVE): cv.icon,
|
||||
}
|
||||
cv.Optional(CONF_CHARGER_CONTROLLER_ACTIVE): binary_sensor.binary_sensor_schema(
|
||||
icon="mdi:power",
|
||||
),
|
||||
cv.Optional(CONF_CHARGER_CURRENT_REDUCTION): binary_sensor.binary_sensor_schema(
|
||||
icon="mdi:car-speed-limiter",
|
||||
),
|
||||
cv.Optional(CONF_CHARGER_AES_ACTIVE): binary_sensor.binary_sensor_schema(
|
||||
icon="mdi:export",
|
||||
),
|
||||
cv.Optional(
|
||||
CONF_CHARGING_CONVERTER_CHARGING
|
||||
): binary_sensor.binary_sensor_schema(
|
||||
icon="mdi:battery-charging",
|
||||
),
|
||||
cv.Optional(
|
||||
CONF_CHARGING_CONVERTER_DISCHARGING
|
||||
): binary_sensor.binary_sensor_schema(
|
||||
icon="mdi:power-plug",
|
||||
),
|
||||
cv.Optional(
|
||||
CONF_CHARGING_CONVERTER_CONTROLLER_ACTIVE
|
||||
): binary_sensor.binary_sensor_schema(
|
||||
icon="mdi:power",
|
||||
),
|
||||
cv.Optional(
|
||||
CONF_CHARGING_CONVERTER_CURRENT_REDUCTION
|
||||
): binary_sensor.binary_sensor_schema(
|
||||
icon="mdi:car-speed-limiter",
|
||||
),
|
||||
cv.Optional(
|
||||
CONF_CHARGING_CONVERTER_AES_ACTIVE
|
||||
): binary_sensor.binary_sensor_schema(
|
||||
icon="mdi:export",
|
||||
),
|
||||
cv.Optional(CONF_PV_CONTROLLER_ACTIVE): binary_sensor.binary_sensor_schema(
|
||||
icon="mdi:power",
|
||||
),
|
||||
cv.Optional(CONF_PV_CURRENT_REDUCTION): binary_sensor.binary_sensor_schema(
|
||||
icon="mdi:car-speed-limiter",
|
||||
),
|
||||
cv.Optional(CONF_PV_AES_ACTIVE): binary_sensor.binary_sensor_schema(
|
||||
icon="mdi:export",
|
||||
),
|
||||
}
|
||||
)
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import sensor
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_BATTERY_VOLTAGE,
|
||||
CONF_CURRENT,
|
||||
@@ -21,7 +21,7 @@ from esphome.const import (
|
||||
UNIT_WATT,
|
||||
)
|
||||
|
||||
from . import CONF_VOTRONIC_ID, Votronic
|
||||
from . import CONF_VOTRONIC_ID, VOTRONIC_COMPONENT_SCHEMA
|
||||
|
||||
DEPENDENCIES = ["votronic"]
|
||||
|
||||
@@ -32,17 +32,45 @@ CONF_SECONDARY_BATTERY_VOLTAGE = "secondary_battery_voltage"
|
||||
CONF_BATTERY_CAPACITY_REMAINING = "battery_capacity_remaining"
|
||||
CONF_STATE_OF_CHARGE = "state_of_charge"
|
||||
# CONF_CURRENT = "current"
|
||||
# CONF_POWER = "power"
|
||||
CONF_BATTERY_NOMINAL_CAPACITY = "battery_nominal_capacity"
|
||||
CONF_PV_VOLTAGE = "pv_voltage"
|
||||
CONF_PV_CURRENT = "pv_current"
|
||||
CONF_BATTERY_STATUS_BITMASK = "battery_status_bitmask"
|
||||
CONF_CHARGING_CONTROLLER_STATUS_BITMASK = "charging_controller_status_bitmask"
|
||||
CONF_PV_CONTROLLER_STATUS_BITMASK = "pv_controller_status_bitmask"
|
||||
CONF_CHARGED_CAPACITY = "charged_capacity"
|
||||
CONF_CHARGED_ENERGY = "charged_energy"
|
||||
|
||||
CONF_PV_VOLTAGE = "pv_voltage"
|
||||
CONF_PV_CURRENT = "pv_current"
|
||||
CONF_PV_BATTERY_STATUS_BITMASK = "pv_battery_status_bitmask"
|
||||
CONF_PV_CONTROLLER_STATUS_BITMASK = "pv_controller_status_bitmask"
|
||||
CONF_PV_POWER = "pv_power"
|
||||
CONF_CHARGING_MODE_SETTING_ID = "charging_mode_setting_id"
|
||||
CONF_CONTROLLER_TEMPERATURE = "controller_temperature"
|
||||
CONF_PV_MODE_SETTING_ID = "pv_mode_setting_id"
|
||||
CONF_PV_CONTROLLER_TEMPERATURE = "pv_controller_temperature"
|
||||
|
||||
CONF_CHARGER_CURRENT = "charger_current"
|
||||
CONF_CHARGER_POWER = "charger_power"
|
||||
CONF_CHARGER_LOAD = "charger_load"
|
||||
CONF_CHARGER_BATTERY_STATUS_BITMASK = "charger_battery_status_bitmask"
|
||||
CONF_CHARGER_CONTROLLER_STATUS_BITMASK = "charger_controller_status_bitmask"
|
||||
CONF_CHARGER_MODE_SETTING_ID = "charger_mode_setting_id"
|
||||
CONF_CHARGER_CONTROLLER_TEMPERATURE = "charger_controller_temperature"
|
||||
|
||||
CONF_CHARGING_CONVERTER_BATTERY_VOLTAGE = "charging_converter_battery_voltage"
|
||||
CONF_CHARGING_CONVERTER_SECONDARY_BATTERY_VOLTAGE = (
|
||||
"charging_converter_secondary_battery_voltage"
|
||||
)
|
||||
CONF_CHARGING_CONVERTER_CURRENT = "charging_converter_current"
|
||||
CONF_CHARGING_CONVERTER_POWER = "charging_converter_power"
|
||||
CONF_CHARGING_CONVERTER_LOAD = "charging_converter_load"
|
||||
CONF_CHARGING_CONVERTER_CONTROLLER_TEMPERATURE = (
|
||||
"charging_converter_controller_temperature"
|
||||
)
|
||||
CONF_CHARGING_CONVERTER_MODE_SETTING_ID = "charging_converter_mode_setting_id"
|
||||
CONF_CHARGING_CONVERTER_BATTERY_STATUS_BITMASK = (
|
||||
"charging_converter_battery_status_bitmask"
|
||||
)
|
||||
CONF_CHARGING_CONVERTER_CONTROLLER_STATUS_BITMASK = (
|
||||
"charging_converter_controller_status_bitmask"
|
||||
)
|
||||
|
||||
ICON_BATTERY_CAPACITY_REMAINING = "mdi:battery-50"
|
||||
ICON_STATE_OF_CHARGE = "mdi:battery-50"
|
||||
@@ -50,9 +78,10 @@ ICON_CURRENT_DC = "mdi:current-dc"
|
||||
ICON_BATTERY_NOMINAL_CAPACITY = "mdi:battery"
|
||||
|
||||
ICON_BATTERY_STATUS_BITMASK = "mdi:alert-circle-outline"
|
||||
ICON_PV_BATTERY_STATUS_BITMASK = "mdi:alert-circle-outline"
|
||||
ICON_CHARGING_CONTROLLER_STATUS_BITMASK = "mdi:alert-circle-outline"
|
||||
ICON_PV_CONTROLLER_STATUS_BITMASK = "mdi:alert-circle-outline"
|
||||
ICON_CHARGING_MODE_SETTING_ID = "mdi:car-battery"
|
||||
ICON_MODE_SETTING_ID = "mdi:car-battery"
|
||||
|
||||
UNIT_AMPERE_HOURS = "Ah"
|
||||
|
||||
@@ -64,19 +93,34 @@ SENSORS = [
|
||||
CONF_CURRENT,
|
||||
CONF_POWER,
|
||||
CONF_BATTERY_NOMINAL_CAPACITY,
|
||||
CONF_BATTERY_STATUS_BITMASK,
|
||||
CONF_CHARGER_LOAD,
|
||||
CONF_CHARGER_BATTERY_STATUS_BITMASK,
|
||||
CONF_CHARGER_CONTROLLER_STATUS_BITMASK,
|
||||
CONF_CHARGER_MODE_SETTING_ID,
|
||||
CONF_CHARGER_CURRENT,
|
||||
CONF_CHARGER_POWER,
|
||||
CONF_CHARGER_CONTROLLER_TEMPERATURE,
|
||||
CONF_CHARGING_CONVERTER_BATTERY_VOLTAGE,
|
||||
CONF_CHARGING_CONVERTER_SECONDARY_BATTERY_VOLTAGE,
|
||||
CONF_CHARGING_CONVERTER_CURRENT,
|
||||
CONF_CHARGING_CONVERTER_POWER,
|
||||
CONF_CHARGING_CONVERTER_LOAD,
|
||||
CONF_CHARGING_CONVERTER_CONTROLLER_TEMPERATURE,
|
||||
CONF_CHARGING_CONVERTER_MODE_SETTING_ID,
|
||||
CONF_CHARGING_CONVERTER_BATTERY_STATUS_BITMASK,
|
||||
CONF_CHARGING_CONVERTER_CONTROLLER_STATUS_BITMASK,
|
||||
CONF_PV_VOLTAGE,
|
||||
CONF_PV_CURRENT,
|
||||
CONF_PV_POWER,
|
||||
CONF_BATTERY_STATUS_BITMASK,
|
||||
CONF_CHARGING_CONTROLLER_STATUS_BITMASK,
|
||||
CONF_PV_MODE_SETTING_ID,
|
||||
CONF_PV_BATTERY_STATUS_BITMASK,
|
||||
CONF_PV_CONTROLLER_STATUS_BITMASK,
|
||||
CONF_CHARGING_MODE_SETTING_ID,
|
||||
CONF_CONTROLLER_TEMPERATURE,
|
||||
CONF_PV_CONTROLLER_TEMPERATURE,
|
||||
]
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
CONFIG_SCHEMA = VOTRONIC_COMPONENT_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(CONF_VOTRONIC_ID): cv.use_id(Votronic),
|
||||
cv.Optional(CONF_BATTERY_VOLTAGE): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_VOLT,
|
||||
icon=ICON_EMPTY,
|
||||
@@ -126,6 +170,150 @@ CONFIG_SCHEMA = cv.Schema(
|
||||
device_class=DEVICE_CLASS_EMPTY,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_BATTERY_STATUS_BITMASK): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_EMPTY,
|
||||
icon=ICON_BATTERY_STATUS_BITMASK,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_EMPTY,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
cv.Optional(CONF_CHARGER_CURRENT): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_AMPERE,
|
||||
icon=ICON_CURRENT_DC,
|
||||
accuracy_decimals=3,
|
||||
device_class=DEVICE_CLASS_CURRENT,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_CHARGER_POWER): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_WATT,
|
||||
icon=ICON_EMPTY,
|
||||
accuracy_decimals=2,
|
||||
device_class=DEVICE_CLASS_POWER,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_CHARGER_LOAD): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_PERCENT,
|
||||
icon=ICON_STATE_OF_CHARGE,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_EMPTY,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_CHARGER_BATTERY_STATUS_BITMASK): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_EMPTY,
|
||||
icon=ICON_BATTERY_STATUS_BITMASK,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_EMPTY,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
cv.Optional(CONF_CHARGER_CONTROLLER_STATUS_BITMASK): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_EMPTY,
|
||||
icon=ICON_CHARGING_CONTROLLER_STATUS_BITMASK,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_EMPTY,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
cv.Optional(CONF_CHARGER_MODE_SETTING_ID): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_EMPTY,
|
||||
icon=ICON_MODE_SETTING_ID,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_EMPTY,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
cv.Optional(CONF_PV_CONTROLLER_STATUS_BITMASK): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_EMPTY,
|
||||
icon=ICON_PV_CONTROLLER_STATUS_BITMASK,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_EMPTY,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
cv.Optional(CONF_CHARGER_CONTROLLER_TEMPERATURE): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_CELSIUS,
|
||||
icon=ICON_EMPTY,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
cv.Optional(CONF_CHARGING_CONVERTER_BATTERY_VOLTAGE): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_VOLT,
|
||||
icon=ICON_EMPTY,
|
||||
accuracy_decimals=2,
|
||||
device_class=DEVICE_CLASS_VOLTAGE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(
|
||||
CONF_CHARGING_CONVERTER_SECONDARY_BATTERY_VOLTAGE
|
||||
): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_VOLT,
|
||||
icon=ICON_EMPTY,
|
||||
accuracy_decimals=2,
|
||||
device_class=DEVICE_CLASS_VOLTAGE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_CHARGING_CONVERTER_LOAD): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_PERCENT,
|
||||
icon=ICON_STATE_OF_CHARGE,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_EMPTY,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_CHARGING_CONVERTER_CURRENT): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_AMPERE,
|
||||
icon=ICON_CURRENT_DC,
|
||||
accuracy_decimals=3,
|
||||
device_class=DEVICE_CLASS_CURRENT,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_CHARGING_CONVERTER_POWER): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_WATT,
|
||||
icon=ICON_EMPTY,
|
||||
accuracy_decimals=2,
|
||||
device_class=DEVICE_CLASS_POWER,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(
|
||||
CONF_CHARGING_CONVERTER_BATTERY_STATUS_BITMASK
|
||||
): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_EMPTY,
|
||||
icon=ICON_BATTERY_STATUS_BITMASK,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_EMPTY,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
cv.Optional(
|
||||
CONF_CHARGING_CONVERTER_CONTROLLER_STATUS_BITMASK
|
||||
): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_EMPTY,
|
||||
icon=ICON_CHARGING_CONTROLLER_STATUS_BITMASK,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_EMPTY,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
cv.Optional(CONF_CHARGING_CONVERTER_MODE_SETTING_ID): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_EMPTY,
|
||||
icon=ICON_MODE_SETTING_ID,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_EMPTY,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
cv.Optional(
|
||||
CONF_CHARGING_CONVERTER_CONTROLLER_TEMPERATURE
|
||||
): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_CELSIUS,
|
||||
icon=ICON_EMPTY,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
cv.Optional(CONF_PV_VOLTAGE): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_VOLT,
|
||||
icon=ICON_EMPTY,
|
||||
@@ -147,39 +335,23 @@ CONFIG_SCHEMA = cv.Schema(
|
||||
device_class=DEVICE_CLASS_POWER,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_BATTERY_STATUS_BITMASK): sensor.sensor_schema(
|
||||
cv.Optional(CONF_PV_BATTERY_STATUS_BITMASK): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_EMPTY,
|
||||
icon=ICON_BATTERY_STATUS_BITMASK,
|
||||
icon=ICON_PV_BATTERY_STATUS_BITMASK,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_EMPTY,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
cv.Optional(CONF_CHARGING_CONTROLLER_STATUS_BITMASK): sensor.sensor_schema(
|
||||
cv.Optional(CONF_PV_MODE_SETTING_ID): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_EMPTY,
|
||||
icon=ICON_CHARGING_CONTROLLER_STATUS_BITMASK,
|
||||
icon=ICON_MODE_SETTING_ID,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_EMPTY,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
cv.Optional(CONF_PV_CONTROLLER_STATUS_BITMASK): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_EMPTY,
|
||||
icon=ICON_PV_CONTROLLER_STATUS_BITMASK,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_EMPTY,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
cv.Optional(CONF_CHARGING_MODE_SETTING_ID): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_EMPTY,
|
||||
icon=ICON_CHARGING_MODE_SETTING_ID,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_EMPTY,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
cv.Optional(CONF_CONTROLLER_TEMPERATURE): sensor.sensor_schema(
|
||||
cv.Optional(CONF_PV_CONTROLLER_TEMPERATURE): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_CELSIUS,
|
||||
icon=ICON_EMPTY,
|
||||
accuracy_decimals=0,
|
||||
|
@@ -1,61 +1,78 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import text_sensor
|
||||
from esphome.const import CONF_ICON, CONF_ID
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ID
|
||||
|
||||
from . import CONF_VOTRONIC_ID, Votronic
|
||||
from . import CONF_VOTRONIC_ID, VOTRONIC_COMPONENT_SCHEMA
|
||||
|
||||
DEPENDENCIES = ["votronic"]
|
||||
|
||||
CODEOWNERS = ["@syssi"]
|
||||
|
||||
CONF_BATTERY_STATUS = "battery_status"
|
||||
CONF_CHARGING_CONTROLLER_STATUS = "charging_controller_status"
|
||||
CONF_PV_CONTROLLER_STATUS = "pv_controller_status"
|
||||
CONF_CHARGING_MODE_SETTING = "charging_mode_setting"
|
||||
|
||||
ICON_BATTERY_STATUS = "mdi:alert-circle-outline"
|
||||
ICON_CHARGING_CONTROLLER_STATUS = "mdi:heart-pulse"
|
||||
ICON_PV_CONTROLLER_STATUS = "mdi:heart-pulse"
|
||||
ICON_CHARGING_MODE_SETTING = "mdi:car-battery"
|
||||
CONF_CHARGER_MODE_SETTING = "charger_mode_setting"
|
||||
CONF_CHARGER_CONTROLLER_STATUS = "charger_controller_status"
|
||||
CONF_CHARGER_BATTERY_STATUS = "charger_battery_status"
|
||||
|
||||
CONF_CHARGING_CONVERTER_MODE_SETTING = "charging_converter_mode_setting"
|
||||
CONF_CHARGING_CONVERTER_BATTERY_STATUS = "charging_converter_battery_status"
|
||||
CONF_CHARGING_CONVERTER_CONTROLLER_STATUS = "charging_converter_controller_status"
|
||||
|
||||
CONF_PV_MODE_SETTING = "pv_mode_setting"
|
||||
CONF_PV_BATTERY_STATUS = "pv_battery_status"
|
||||
CONF_PV_CONTROLLER_STATUS = "pv_controller_status"
|
||||
|
||||
TEXT_SENSORS = [
|
||||
CONF_BATTERY_STATUS,
|
||||
CONF_CHARGING_CONTROLLER_STATUS,
|
||||
CONF_CHARGER_MODE_SETTING,
|
||||
CONF_CHARGER_CONTROLLER_STATUS,
|
||||
CONF_CHARGER_BATTERY_STATUS,
|
||||
CONF_CHARGING_CONVERTER_MODE_SETTING,
|
||||
CONF_CHARGING_CONVERTER_BATTERY_STATUS,
|
||||
CONF_CHARGING_CONVERTER_CONTROLLER_STATUS,
|
||||
CONF_PV_MODE_SETTING,
|
||||
CONF_PV_BATTERY_STATUS,
|
||||
CONF_PV_CONTROLLER_STATUS,
|
||||
CONF_CHARGING_MODE_SETTING,
|
||||
]
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
CONFIG_SCHEMA = VOTRONIC_COMPONENT_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(CONF_VOTRONIC_ID): cv.use_id(Votronic),
|
||||
cv.Optional(CONF_BATTERY_STATUS): text_sensor.TEXT_SENSOR_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(text_sensor.TextSensor),
|
||||
cv.Optional(CONF_ICON, default=ICON_BATTERY_STATUS): cv.icon,
|
||||
}
|
||||
cv.Optional(CONF_BATTERY_STATUS): text_sensor.text_sensor_schema(
|
||||
text_sensor.TextSensor, icon="mdi:alert-circle-outline"
|
||||
),
|
||||
cv.Optional(CONF_CHARGER_MODE_SETTING): text_sensor.text_sensor_schema(
|
||||
text_sensor.TextSensor, icon="mdi:car-battery"
|
||||
),
|
||||
cv.Optional(CONF_CHARGER_CONTROLLER_STATUS): text_sensor.text_sensor_schema(
|
||||
text_sensor.TextSensor, icon="mdi:heart-pulse"
|
||||
),
|
||||
cv.Optional(CONF_CHARGER_BATTERY_STATUS): text_sensor.text_sensor_schema(
|
||||
text_sensor.TextSensor, icon="mdi:alert-circle-outline"
|
||||
),
|
||||
cv.Optional(
|
||||
CONF_CHARGING_CONTROLLER_STATUS
|
||||
): text_sensor.TEXT_SENSOR_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(text_sensor.TextSensor),
|
||||
cv.Optional(
|
||||
CONF_ICON, default=ICON_CHARGING_CONTROLLER_STATUS
|
||||
): cv.icon,
|
||||
}
|
||||
CONF_CHARGING_CONVERTER_BATTERY_STATUS
|
||||
): text_sensor.text_sensor_schema(
|
||||
text_sensor.TextSensor, icon="mdi:car-battery"
|
||||
),
|
||||
cv.Optional(CONF_PV_CONTROLLER_STATUS): text_sensor.TEXT_SENSOR_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(text_sensor.TextSensor),
|
||||
cv.Optional(CONF_ICON, default=ICON_PV_CONTROLLER_STATUS): cv.icon,
|
||||
}
|
||||
cv.Optional(
|
||||
CONF_CHARGING_CONVERTER_CONTROLLER_STATUS
|
||||
): text_sensor.text_sensor_schema(
|
||||
text_sensor.TextSensor, icon="mdi:heart-pulse"
|
||||
),
|
||||
cv.Optional(CONF_CHARGING_MODE_SETTING): text_sensor.TEXT_SENSOR_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(text_sensor.TextSensor),
|
||||
cv.Optional(CONF_ICON, default=ICON_CHARGING_MODE_SETTING): cv.icon,
|
||||
}
|
||||
cv.Optional(
|
||||
CONF_CHARGING_CONVERTER_MODE_SETTING
|
||||
): text_sensor.text_sensor_schema(
|
||||
text_sensor.TextSensor, icon="mdi:car-battery"
|
||||
),
|
||||
cv.Optional(CONF_PV_MODE_SETTING): text_sensor.text_sensor_schema(
|
||||
text_sensor.TextSensor, icon="mdi:car-battery"
|
||||
),
|
||||
cv.Optional(CONF_PV_BATTERY_STATUS): text_sensor.text_sensor_schema(
|
||||
text_sensor.TextSensor, icon="mdi:alert-circle-outline"
|
||||
),
|
||||
cv.Optional(CONF_PV_CONTROLLER_STATUS): text_sensor.text_sensor_schema(
|
||||
text_sensor.TextSensor, icon="mdi:heart-pulse"
|
||||
),
|
||||
}
|
||||
)
|
||||
|
@@ -23,6 +23,7 @@ static const uint8_t VOTRONIC_FRAME_TYPE_BATTERY_COMPUTER_INFO3 = 0xFA;
|
||||
|
||||
static const uint8_t VOTRONIC_FRAME_TYPE_CONTROL_CHARGER = 0x7A; // Incorrect protocol description?
|
||||
static const uint8_t VOTRONIC_FRAME_TYPE_CONTROL_CHARGING_CONVERTER = 0x3A; // Incorrect protocol description?
|
||||
static const uint8_t VOTRONIC_FRAME_TYPE_CONTROL_CHARGING_CONVERTER2 = 0x4A;
|
||||
static const uint8_t VOTRONIC_FRAME_TYPE_CONTROL_BATTERY_COMPUTER = 0xEA;
|
||||
|
||||
static const uint8_t BATTERY_STATUS_SIZE = 8;
|
||||
@@ -65,6 +66,8 @@ void Votronic::loop() {
|
||||
const uint32_t now = millis();
|
||||
|
||||
if (now - this->last_byte_ > this->rx_timeout_) {
|
||||
ESP_LOGVV(TAG, "Buffer cleared due to timeout: %s",
|
||||
format_hex_pretty(&this->rx_buffer_.front(), this->rx_buffer_.size()).c_str());
|
||||
this->rx_buffer_.clear();
|
||||
this->last_byte_ = now;
|
||||
}
|
||||
@@ -75,6 +78,8 @@ void Votronic::loop() {
|
||||
if (this->parse_votronic_byte_(byte)) {
|
||||
this->last_byte_ = now;
|
||||
} else {
|
||||
ESP_LOGVV(TAG, "Buffer cleared due to reset: %s",
|
||||
format_hex_pretty(&this->rx_buffer_.front(), this->rx_buffer_.size()).c_str());
|
||||
this->rx_buffer_.clear();
|
||||
}
|
||||
}
|
||||
@@ -88,11 +93,8 @@ bool Votronic::parse_votronic_byte_(uint8_t byte) {
|
||||
const uint8_t *raw = &this->rx_buffer_[0];
|
||||
const uint8_t frame_len = VOTRONIC_FRAME_LENGTH;
|
||||
|
||||
if (at == 0)
|
||||
return true;
|
||||
|
||||
if (raw[0] != VOTRONIC_FRAME_START) {
|
||||
ESP_LOGW(TAG, "Invalid header");
|
||||
if (at == 0 && raw[0] != VOTRONIC_FRAME_START) {
|
||||
ESP_LOGW(TAG, "Invalid header (0x%02X)", raw[0]);
|
||||
|
||||
// return false to reset buffer
|
||||
return false;
|
||||
@@ -131,8 +133,10 @@ void Votronic::on_votronic_data(const std::vector<uint8_t> &data) {
|
||||
this->decode_solar_charger_data_(data);
|
||||
break;
|
||||
case VOTRONIC_FRAME_TYPE_CHARGER:
|
||||
this->decode_charger_data_(data);
|
||||
break;
|
||||
case VOTRONIC_FRAME_TYPE_CHARGING_CONVERTER:
|
||||
this->decode_charger_data_(frame_type, data);
|
||||
this->decode_charging_converter_data_(data);
|
||||
break;
|
||||
case VOTRONIC_FRAME_TYPE_BATTERY_COMPUTER_INFO1:
|
||||
this->decode_battery_computer_info1_data_(data);
|
||||
@@ -143,6 +147,12 @@ void Votronic::on_votronic_data(const std::vector<uint8_t> &data) {
|
||||
case VOTRONIC_FRAME_TYPE_BATTERY_COMPUTER_INFO3:
|
||||
this->decode_battery_computer_info3_data_(data);
|
||||
break;
|
||||
case VOTRONIC_FRAME_TYPE_CONTROL_CHARGING_CONVERTER2:
|
||||
// Skip empty frame silently
|
||||
// 0xAA 0x4A 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x4A
|
||||
if (data[VOTRONIC_FRAME_LENGTH - 1] == VOTRONIC_FRAME_TYPE_CONTROL_CHARGING_CONVERTER2) {
|
||||
break;
|
||||
}
|
||||
default:
|
||||
ESP_LOGW(TAG, "Your device is probably not supported. Please create an issue here: "
|
||||
"https://github.com/syssi/esphome-votronic/issues");
|
||||
@@ -163,7 +173,7 @@ void Votronic::decode_solar_charger_data_(const std::vector<uint8_t> &data) {
|
||||
};
|
||||
|
||||
ESP_LOGI(TAG, "Solar charger data received");
|
||||
ESP_LOGVV(TAG, " %s", format_hex_pretty(&data.front(), data.size()).c_str());
|
||||
ESP_LOGD(TAG, " %s", format_hex_pretty(&data.front(), data.size()).c_str());
|
||||
|
||||
// Byte Len Payload Description Unit Precision
|
||||
// 0 1 0xAA Sync Byte
|
||||
@@ -181,23 +191,25 @@ void Votronic::decode_solar_charger_data_(const std::vector<uint8_t> &data) {
|
||||
// 9 1 0x00 Reserved
|
||||
// 10 1 0xA0 Reserved
|
||||
// 11 1 0x15 Reserved
|
||||
this->publish_state_(this->controller_temperature_sensor_, (float) data[11]);
|
||||
this->publish_state_(this->pv_controller_temperature_sensor_, (float) data[11]);
|
||||
// 12 1 0x03 Charging mode setting (dip switches)
|
||||
this->publish_state_(this->charging_mode_setting_id_sensor_, data[12]);
|
||||
this->publish_state_(this->charging_mode_setting_text_sensor_, this->charging_mode_setting_to_string_(data[12]));
|
||||
this->publish_state_(this->pv_mode_setting_id_sensor_, data[12]);
|
||||
this->publish_state_(this->pv_mode_setting_text_sensor_, this->charging_mode_setting_to_string_(data[12]));
|
||||
// 13 1 0x00 Battery Controller Status Bitmask
|
||||
this->publish_state_(this->battery_status_bitmask_sensor_, data[13]);
|
||||
this->publish_state_(this->battery_status_text_sensor_, this->battery_status_bitmask_to_string_(data[13]));
|
||||
ESP_LOGI(TAG, "PV - Battery Controller Status: 0x%02X", data[13]);
|
||||
this->publish_state_(this->pv_battery_status_bitmask_sensor_, data[13]);
|
||||
this->publish_state_(this->pv_battery_status_text_sensor_, this->battery_status_bitmask_to_string_(data[13]));
|
||||
// 14 1 0x00 PV Controller Status Bitmask
|
||||
ESP_LOGI(TAG, "PV - Controller Status: 0x%02X", data[14]);
|
||||
this->publish_state_(this->pv_controller_status_bitmask_sensor_, data[14]);
|
||||
this->publish_state_(this->pv_controller_status_text_sensor_,
|
||||
this->solar_charger_status_bitmask_to_string_(data[14]));
|
||||
this->publish_state_(this->controller_active_binary_sensor_, (data[14] & (1 << 3)));
|
||||
this->publish_state_(this->current_reduction_binary_sensor_, (data[14] & (1 << 4)));
|
||||
this->publish_state_(this->aes_active_binary_sensor_, (data[14] & (1 << 5)));
|
||||
this->publish_state_(this->pv_controller_active_binary_sensor_, (data[14] & (1 << 3)));
|
||||
this->publish_state_(this->pv_current_reduction_binary_sensor_, (data[14] & (1 << 4)));
|
||||
this->publish_state_(this->pv_aes_active_binary_sensor_, (data[14] & (1 << 5)));
|
||||
}
|
||||
|
||||
void Votronic::decode_charger_data_(const uint8_t &frame_type, const std::vector<uint8_t> &data) {
|
||||
void Votronic::decode_charger_data_(const std::vector<uint8_t> &data) {
|
||||
const uint32_t now = millis();
|
||||
if (now - this->last_charger_data_ < this->throttle_) {
|
||||
return;
|
||||
@@ -209,7 +221,7 @@ void Votronic::decode_charger_data_(const uint8_t &frame_type, const std::vector
|
||||
};
|
||||
|
||||
ESP_LOGI(TAG, "Charger data received");
|
||||
ESP_LOGVV(TAG, " %s", format_hex_pretty(&data.front(), data.size()).c_str());
|
||||
ESP_LOGD(TAG, " %s", format_hex_pretty(&data.front(), data.size()).c_str());
|
||||
|
||||
// Byte Len Payload Description Unit Precision
|
||||
// 0 1 0xAA Sync Byte
|
||||
@@ -221,29 +233,83 @@ void Votronic::decode_charger_data_(const uint8_t &frame_type, const std::vector
|
||||
this->publish_state_(this->secondary_battery_voltage_sensor_, votronic_get_16bit(4) * 0.01f);
|
||||
// 6 2 0x78 0x00 Charging Current A S16 100mA/Bit
|
||||
float current = (float) ((int16_t) votronic_get_16bit(6)) * 0.1f;
|
||||
this->publish_state_(this->current_sensor_, current);
|
||||
this->publish_state_(this->power_sensor_, current * battery_voltage);
|
||||
this->publish_state_(this->charging_binary_sensor_, (current > 0.0f));
|
||||
this->publish_state_(this->discharging_binary_sensor_, (current < 0.0f));
|
||||
this->publish_state_(this->charger_current_sensor_, current);
|
||||
this->publish_state_(this->charger_power_sensor_, current * battery_voltage);
|
||||
this->publish_state_(this->charger_charging_binary_sensor_, (current > 0.0f));
|
||||
this->publish_state_(this->charger_discharging_binary_sensor_, (current < 0.0f));
|
||||
// 8 1 0x00 Reserved
|
||||
// 9 1 0x00 Reserved
|
||||
// 10 1 0xA0 Charging Power % 0-100% 1%/Bit
|
||||
this->publish_state_(this->state_of_charge_sensor_, (float) data[10]);
|
||||
this->publish_state_(this->charger_load_sensor_, (float) data[10]);
|
||||
// 11 1 0x15 Controller temperature
|
||||
this->publish_state_(this->controller_temperature_sensor_, (float) data[11]);
|
||||
this->publish_state_(this->charger_controller_temperature_sensor_, data[11] * 0.1f);
|
||||
// 12 1 0x03 Charging mode setting (dip switches)
|
||||
this->publish_state_(this->charging_mode_setting_id_sensor_, data[12]);
|
||||
this->publish_state_(this->charging_mode_setting_text_sensor_, this->charging_mode_setting_to_string_(data[12]));
|
||||
this->publish_state_(this->charger_mode_setting_id_sensor_, data[12]);
|
||||
this->publish_state_(this->charger_mode_setting_text_sensor_, this->charging_mode_setting_to_string_(data[12]));
|
||||
// 13 1 0x00 Battery Controller Status Bitmask
|
||||
this->publish_state_(this->battery_status_bitmask_sensor_, data[13]);
|
||||
this->publish_state_(this->battery_status_text_sensor_, this->battery_status_bitmask_to_string_(data[13]));
|
||||
ESP_LOGI(TAG, "Charger - Battery Controller Status: 0x%02X", data[13]);
|
||||
this->publish_state_(this->charger_battery_status_bitmask_sensor_, data[13]);
|
||||
this->publish_state_(this->charger_battery_status_text_sensor_, this->battery_status_bitmask_to_string_(data[13]));
|
||||
// 14 1 0x00 Charging Controller Status Bitmask
|
||||
this->publish_state_(this->charging_controller_status_bitmask_sensor_, data[14]);
|
||||
this->publish_state_(this->charging_controller_status_text_sensor_,
|
||||
ESP_LOGI(TAG, "Charger - Controller Status: 0x%02X", data[14]);
|
||||
this->publish_state_(this->charger_controller_status_bitmask_sensor_, data[14]);
|
||||
this->publish_state_(this->charger_controller_status_text_sensor_, this->charger_status_bitmask_to_string_(data[14]));
|
||||
this->publish_state_(this->charger_controller_active_binary_sensor_, (data[14] & (1 << 3)));
|
||||
this->publish_state_(this->charger_current_reduction_binary_sensor_, (data[14] & (1 << 4)));
|
||||
this->publish_state_(this->charger_aes_active_binary_sensor_, (data[14] & (1 << 5)));
|
||||
}
|
||||
|
||||
void Votronic::decode_charging_converter_data_(const std::vector<uint8_t> &data) {
|
||||
const uint32_t now = millis();
|
||||
if (now - this->last_charger_data_ < this->throttle_) {
|
||||
return;
|
||||
}
|
||||
this->last_charger_data_ = now;
|
||||
|
||||
auto votronic_get_16bit = [&](size_t i) -> uint16_t {
|
||||
return (uint16_t(data[i + 1]) << 8) | (uint16_t(data[i + 0]) << 0);
|
||||
};
|
||||
|
||||
ESP_LOGI(TAG, "Charging converter data received");
|
||||
ESP_LOGD(TAG, " %s", format_hex_pretty(&data.front(), data.size()).c_str());
|
||||
|
||||
// Byte Len Payload Description Unit Precision
|
||||
// 0 1 0xAA Sync Byte
|
||||
// 1 1 0x3A Frame Type
|
||||
// 2 2 0xA0 0x05 Battery Voltage V U16 10mV/Bit
|
||||
float battery_voltage = votronic_get_16bit(2) * 0.01f;
|
||||
this->publish_state_(this->charging_converter_battery_voltage_sensor_, battery_voltage);
|
||||
// 4 2 0xA4 0x06 Second Battery Voltage V U16 10mV/Bit
|
||||
this->publish_state_(this->charging_converter_secondary_battery_voltage_sensor_, votronic_get_16bit(4) * 0.01f);
|
||||
// 6 2 0x78 0x00 Charging Current A S16 100mA/Bit
|
||||
float current = (float) ((int16_t) votronic_get_16bit(6)) * 0.1f;
|
||||
this->publish_state_(this->charging_converter_current_sensor_, current);
|
||||
this->publish_state_(this->charging_converter_power_sensor_, current * battery_voltage);
|
||||
this->publish_state_(this->charging_converter_charging_binary_sensor_, (current > 0.0f));
|
||||
this->publish_state_(this->charging_converter_discharging_binary_sensor_, (current < 0.0f));
|
||||
// 8 1 0x00 Reserved
|
||||
// 9 1 0x00 Reserved
|
||||
// 10 1 0xA0 Charging Power % 0-100% 1%/Bit
|
||||
this->publish_state_(this->charging_converter_load_sensor_, (float) data[10]);
|
||||
// 11 1 0x15 Controller temperature
|
||||
this->publish_state_(this->charging_converter_controller_temperature_sensor_, data[11] * 0.1f);
|
||||
// 12 1 0x03 Charging mode setting (dip switches)
|
||||
this->publish_state_(this->charging_converter_mode_setting_id_sensor_, data[12]);
|
||||
this->publish_state_(this->charging_converter_mode_setting_text_sensor_,
|
||||
this->charging_mode_setting_to_string_(data[12]));
|
||||
// 13 1 0x00 Battery Controller Status Bitmask
|
||||
ESP_LOGI(TAG, "Charging Converter - Battery Controller Status: 0x%02X", data[13]);
|
||||
this->publish_state_(this->charging_converter_battery_status_bitmask_sensor_, data[13]);
|
||||
this->publish_state_(this->charging_converter_battery_status_text_sensor_,
|
||||
this->battery_status_bitmask_to_string_(data[13]));
|
||||
// 14 1 0x00 Charging Controller Status Bitmask
|
||||
ESP_LOGI(TAG, "Charging Converter - Controller Status: 0x%02X", data[14]);
|
||||
this->publish_state_(this->charging_converter_controller_status_bitmask_sensor_, data[14]);
|
||||
this->publish_state_(this->charging_converter_controller_status_text_sensor_,
|
||||
this->charger_status_bitmask_to_string_(data[14]));
|
||||
this->publish_state_(this->controller_active_binary_sensor_, (data[14] & (1 << 3)));
|
||||
this->publish_state_(this->current_reduction_binary_sensor_, (data[14] & (1 << 4)));
|
||||
this->publish_state_(this->aes_active_binary_sensor_, (data[14] & (1 << 5)));
|
||||
this->publish_state_(this->charging_converter_controller_active_binary_sensor_, (data[14] & (1 << 3)));
|
||||
this->publish_state_(this->charging_converter_current_reduction_binary_sensor_, (data[14] & (1 << 4)));
|
||||
this->publish_state_(this->charging_converter_aes_active_binary_sensor_, (data[14] & (1 << 5)));
|
||||
}
|
||||
|
||||
void Votronic::decode_battery_computer_info1_data_(const std::vector<uint8_t> &data) {
|
||||
@@ -261,7 +327,7 @@ void Votronic::decode_battery_computer_info1_data_(const std::vector<uint8_t> &d
|
||||
// 0xAA 0xCA 0x03 0x05 0x0F 0x05 0xC7 0x01 0x20 0x00 0x63 0x00 0x7B 0xFE 0xFF 0x39
|
||||
|
||||
ESP_LOGI(TAG, "Battery computer info1 data received");
|
||||
ESP_LOGVV(TAG, " %s", format_hex_pretty(&data.front(), data.size()).c_str());
|
||||
ESP_LOGD(TAG, " %s", format_hex_pretty(&data.front(), data.size()).c_str());
|
||||
|
||||
// Byte Len Payload Description Unit Precision
|
||||
// 0 1 0xAA Sync Byte
|
||||
@@ -305,7 +371,7 @@ void Votronic::decode_battery_computer_info2_data_(const std::vector<uint8_t> &d
|
||||
// 0xAA 0xDA 0x00 0x00 0x00 0x00 0xF8 0x11 0x5E 0x07 0x00 0x00 0x2F 0x04 0x02 0x43
|
||||
|
||||
ESP_LOGI(TAG, "Battery computer info2 data received");
|
||||
ESP_LOGVV(TAG, " %s", format_hex_pretty(&data.front(), data.size()).c_str());
|
||||
ESP_LOGD(TAG, " %s", format_hex_pretty(&data.front(), data.size()).c_str());
|
||||
|
||||
// Byte Len Payload Description Unit Precision
|
||||
// 0 1 0xAA Sync Byte
|
||||
@@ -335,8 +401,8 @@ void Votronic::decode_battery_computer_info2_data_(const std::vector<uint8_t> &d
|
||||
// LiFePo4 14.4V 14.4 84 184
|
||||
// LiFePo4 14.6V 14.6 86 186
|
||||
// LiFePo4 14.8V 14.8 88 188
|
||||
this->publish_state_(this->charging_mode_setting_id_sensor_, data[12]);
|
||||
this->publish_state_(this->charging_mode_setting_text_sensor_, this->charging_mode_setting_to_string_(data[12]));
|
||||
// this->publish_state_(this->charging_mode_setting_id_sensor_, data[12]);
|
||||
// this->publish_state_(this->charging_mode_setting_text_sensor_, this->charging_mode_setting_to_string_(data[12]));
|
||||
// 13 1 0x04
|
||||
this->publish_state_(this->battery_status_bitmask_sensor_, data[13]);
|
||||
this->publish_state_(this->battery_status_text_sensor_, this->battery_status_bitmask_to_string_(data[13]));
|
||||
@@ -361,7 +427,7 @@ void Votronic::decode_battery_computer_info3_data_(const std::vector<uint8_t> &d
|
||||
// 0xAA 0xFA 0x2F 0x00 0x00 0x00 0xD2 0x02 0x00 0x0A 0x00 0x00 0x28 0xD0 0x00 0xF7
|
||||
|
||||
ESP_LOGI(TAG, "Battery computer info3 data received");
|
||||
ESP_LOGVV(TAG, " %s", format_hex_pretty(&data.front(), data.size()).c_str());
|
||||
ESP_LOGD(TAG, " %s", format_hex_pretty(&data.front(), data.size()).c_str());
|
||||
|
||||
// Byte Len Payload Description Unit Precision
|
||||
// 0 1 0xAA Sync Byte
|
||||
@@ -395,30 +461,72 @@ void Votronic::dump_config() {
|
||||
|
||||
LOG_BINARY_SENSOR("", "Charging", this->charging_binary_sensor_);
|
||||
LOG_BINARY_SENSOR("", "Discharging", this->discharging_binary_sensor_);
|
||||
LOG_BINARY_SENSOR("", "Controller active", this->controller_active_binary_sensor_);
|
||||
LOG_BINARY_SENSOR("", "Current reduction", this->current_reduction_binary_sensor_);
|
||||
LOG_BINARY_SENSOR("", "AES active", this->aes_active_binary_sensor_);
|
||||
|
||||
LOG_BINARY_SENSOR("", "Charger controller active", this->charger_controller_active_binary_sensor_);
|
||||
LOG_BINARY_SENSOR("", "Charger current reduction", this->charger_current_reduction_binary_sensor_);
|
||||
LOG_BINARY_SENSOR("", "Charger AES active", this->charger_aes_active_binary_sensor_);
|
||||
|
||||
LOG_BINARY_SENSOR("", "Charging converter charging", this->charging_converter_charging_binary_sensor_);
|
||||
LOG_BINARY_SENSOR("", "Charging converter discharging", this->charging_converter_discharging_binary_sensor_);
|
||||
LOG_BINARY_SENSOR("", "Charging converter controller active",
|
||||
this->charging_converter_controller_active_binary_sensor_);
|
||||
LOG_BINARY_SENSOR("", "Charging converter current reduction",
|
||||
this->charging_converter_current_reduction_binary_sensor_);
|
||||
LOG_BINARY_SENSOR("", "Charging converter AES active", this->charging_converter_aes_active_binary_sensor_);
|
||||
|
||||
LOG_BINARY_SENSOR("", "PV controller active", this->pv_controller_active_binary_sensor_);
|
||||
LOG_BINARY_SENSOR("", "PV current reduction", this->pv_current_reduction_binary_sensor_);
|
||||
LOG_BINARY_SENSOR("", "PV AES active", this->pv_aes_active_binary_sensor_);
|
||||
|
||||
LOG_SENSOR("", "Battery voltage", this->battery_voltage_sensor_);
|
||||
LOG_SENSOR("", "Secondary battery voltage", this->secondary_battery_voltage_sensor_);
|
||||
LOG_SENSOR("", "Battery capacity remaining", this->battery_capacity_remaining_sensor_);
|
||||
LOG_SENSOR("", "Battery nominal capacity", this->battery_nominal_capacity_sensor_);
|
||||
LOG_SENSOR("", "State of charge", this->state_of_charge_sensor_);
|
||||
LOG_SENSOR("", "Current", this->current_sensor_);
|
||||
LOG_SENSOR("", "Power", this->power_sensor_);
|
||||
LOG_SENSOR("", "Battery nominal capacity", this->battery_nominal_capacity_sensor_);
|
||||
LOG_SENSOR("", "Battery status bitmask", this->battery_status_bitmask_sensor_);
|
||||
|
||||
LOG_SENSOR("", "PV voltage", this->pv_voltage_sensor_);
|
||||
LOG_SENSOR("", "PV current", this->pv_current_sensor_);
|
||||
LOG_SENSOR("", "PV power", this->pv_power_sensor_);
|
||||
LOG_SENSOR("", "Battery status bitmask", this->battery_status_bitmask_sensor_);
|
||||
LOG_SENSOR("", "Charging Controller status bitmask", this->charging_controller_status_bitmask_sensor_);
|
||||
LOG_SENSOR("", "PV battery status bitmask", this->pv_battery_status_bitmask_sensor_);
|
||||
LOG_SENSOR("", "PV Controller status bitmask", this->pv_controller_status_bitmask_sensor_);
|
||||
LOG_SENSOR("", "Charging mode setting ID", this->charging_mode_setting_id_sensor_);
|
||||
LOG_SENSOR("", "Controller Temperature", this->controller_temperature_sensor_);
|
||||
LOG_SENSOR("", "PV controller temperature", this->pv_controller_temperature_sensor_);
|
||||
|
||||
LOG_SENSOR("", "Charger battery status bitmask", this->charger_battery_status_bitmask_sensor_);
|
||||
LOG_SENSOR("", "Charger Controller status bitmask", this->charger_controller_status_bitmask_sensor_);
|
||||
LOG_SENSOR("", "Charger mode setting ID", this->charger_mode_setting_id_sensor_);
|
||||
LOG_SENSOR("", "Charger controller temperature", this->charger_controller_temperature_sensor_);
|
||||
LOG_SENSOR("", "Charger load", this->charger_load_sensor_);
|
||||
|
||||
LOG_SENSOR("", "Charging converter battery voltage", this->charging_converter_battery_voltage_sensor_);
|
||||
LOG_SENSOR("", "Charging converter secondary battery voltage",
|
||||
this->charging_converter_secondary_battery_voltage_sensor_);
|
||||
LOG_SENSOR("", "Charging converter load", this->charging_converter_load_sensor_);
|
||||
LOG_SENSOR("", "Charging converter current", this->charging_converter_current_sensor_);
|
||||
LOG_SENSOR("", "Charging converter power", this->charging_converter_power_sensor_);
|
||||
LOG_SENSOR("", "Charging converter battery status bitmask", this->charging_converter_battery_status_bitmask_sensor_);
|
||||
LOG_SENSOR("", "Charging converter controller status bitmask",
|
||||
this->charging_converter_controller_status_bitmask_sensor_);
|
||||
LOG_SENSOR("", "Charging converter mode setting ID", this->charging_converter_mode_setting_id_sensor_);
|
||||
LOG_SENSOR("", "Charging converter controller temperature", this->charging_converter_controller_temperature_sensor_);
|
||||
LOG_SENSOR("", "Charging converter state of charge", this->charging_converter_state_of_charge_sensor_);
|
||||
LOG_SENSOR("", "PV Mode Setting ID", this->pv_mode_setting_id_sensor_);
|
||||
|
||||
LOG_TEXT_SENSOR("", "Battery status", this->battery_status_text_sensor_);
|
||||
LOG_TEXT_SENSOR("", "Charging controller status", this->charging_controller_status_text_sensor_);
|
||||
|
||||
LOG_TEXT_SENSOR("", "Charger battery status", this->charger_battery_status_text_sensor_);
|
||||
LOG_TEXT_SENSOR("", "Charger controller status", this->charger_controller_status_text_sensor_);
|
||||
LOG_TEXT_SENSOR("", "Charger mode setting", this->charger_mode_setting_text_sensor_);
|
||||
|
||||
LOG_TEXT_SENSOR("", "Charging converter battery status", this->charging_converter_battery_status_text_sensor_);
|
||||
LOG_TEXT_SENSOR("", "Charging converter controller status", this->charging_converter_controller_status_text_sensor_);
|
||||
LOG_TEXT_SENSOR("", "Charging converter mode setting", this->charging_converter_mode_setting_text_sensor_);
|
||||
|
||||
LOG_TEXT_SENSOR("", "PV battery status", this->pv_battery_status_text_sensor_);
|
||||
LOG_TEXT_SENSOR("", "PV controller status", this->pv_controller_status_text_sensor_);
|
||||
LOG_TEXT_SENSOR("", "Charging mode setting", this->charging_mode_setting_text_sensor_);
|
||||
LOG_TEXT_SENSOR("", "PV Mode Setting", this->pv_mode_setting_text_sensor_);
|
||||
|
||||
this->check_uart_settings(1000);
|
||||
}
|
||||
|
@@ -22,14 +22,51 @@ class Votronic : public uart::UARTDevice, public PollingComponent {
|
||||
void set_discharging_binary_sensor(binary_sensor::BinarySensor *discharging_binary_sensor) {
|
||||
discharging_binary_sensor_ = discharging_binary_sensor;
|
||||
}
|
||||
void set_controller_active_binary_sensor(binary_sensor::BinarySensor *controller_active_binary_sensor) {
|
||||
controller_active_binary_sensor_ = controller_active_binary_sensor;
|
||||
void set_charger_charging_binary_sensor(binary_sensor::BinarySensor *charger_charging_binary_sensor) {
|
||||
charger_charging_binary_sensor_ = charger_charging_binary_sensor;
|
||||
}
|
||||
void set_aes_active_binary_sensor(binary_sensor::BinarySensor *aes_active_binary_sensor) {
|
||||
aes_active_binary_sensor_ = aes_active_binary_sensor;
|
||||
void set_charger_discharging_binary_sensor(binary_sensor::BinarySensor *charger_discharging_binary_sensor) {
|
||||
charger_discharging_binary_sensor_ = charger_discharging_binary_sensor;
|
||||
}
|
||||
void set_current_reduction_binary_sensor(binary_sensor::BinarySensor *current_reduction_binary_sensor) {
|
||||
current_reduction_binary_sensor_ = current_reduction_binary_sensor;
|
||||
void set_charger_controller_active_binary_sensor(
|
||||
binary_sensor::BinarySensor *charger_controller_active_binary_sensor) {
|
||||
charger_controller_active_binary_sensor_ = charger_controller_active_binary_sensor;
|
||||
}
|
||||
void set_charger_aes_active_binary_sensor(binary_sensor::BinarySensor *charger_aes_active_binary_sensor) {
|
||||
charger_aes_active_binary_sensor_ = charger_aes_active_binary_sensor;
|
||||
}
|
||||
void set_charger_current_reduction_binary_sensor(
|
||||
binary_sensor::BinarySensor *charger_current_reduction_binary_sensor) {
|
||||
charger_current_reduction_binary_sensor_ = charger_current_reduction_binary_sensor;
|
||||
}
|
||||
void set_pv_controller_active_binary_sensor(binary_sensor::BinarySensor *pv_controller_active_binary_sensor) {
|
||||
pv_controller_active_binary_sensor_ = pv_controller_active_binary_sensor;
|
||||
}
|
||||
void set_pv_aes_active_binary_sensor(binary_sensor::BinarySensor *pv_aes_active_binary_sensor) {
|
||||
pv_aes_active_binary_sensor_ = pv_aes_active_binary_sensor;
|
||||
}
|
||||
void set_pv_current_reduction_binary_sensor(binary_sensor::BinarySensor *pv_current_reduction_binary_sensor) {
|
||||
pv_current_reduction_binary_sensor_ = pv_current_reduction_binary_sensor;
|
||||
}
|
||||
void set_charging_converter_charging_binary_sensor(
|
||||
binary_sensor::BinarySensor *charging_converter_charging_binary_sensor) {
|
||||
charging_converter_charging_binary_sensor_ = charging_converter_charging_binary_sensor;
|
||||
}
|
||||
void set_charging_converter_discharging_binary_sensor(
|
||||
binary_sensor::BinarySensor *charging_converter_discharging_binary_sensor) {
|
||||
charging_converter_discharging_binary_sensor_ = charging_converter_discharging_binary_sensor;
|
||||
}
|
||||
void set_charging_converter_controller_active_binary_sensor(
|
||||
binary_sensor::BinarySensor *charging_converter_controller_active_binary_sensor) {
|
||||
charging_converter_controller_active_binary_sensor_ = charging_converter_controller_active_binary_sensor;
|
||||
}
|
||||
void set_charging_converter_current_reduction_binary_sensor(
|
||||
binary_sensor::BinarySensor *charging_converter_current_reduction_binary_sensor) {
|
||||
charging_converter_current_reduction_binary_sensor_ = charging_converter_current_reduction_binary_sensor;
|
||||
}
|
||||
void set_charging_converter_aes_active_binary_sensor(
|
||||
binary_sensor::BinarySensor *charging_converter_aes_active_binary_sensor) {
|
||||
charging_converter_aes_active_binary_sensor_ = charging_converter_aes_active_binary_sensor;
|
||||
}
|
||||
|
||||
void set_battery_voltage_sensor(sensor::Sensor *battery_voltage_sensor) {
|
||||
@@ -43,23 +80,43 @@ class Votronic : public uart::UARTDevice, public PollingComponent {
|
||||
}
|
||||
void set_current_sensor(sensor::Sensor *current_sensor) { current_sensor_ = current_sensor; }
|
||||
void set_power_sensor(sensor::Sensor *power_sensor) { power_sensor_ = power_sensor; }
|
||||
void set_charger_current_sensor(sensor::Sensor *charger_current_sensor) {
|
||||
charger_current_sensor_ = charger_current_sensor;
|
||||
}
|
||||
void set_charger_power_sensor(sensor::Sensor *charger_power_sensor) { charger_power_sensor_ = charger_power_sensor; }
|
||||
void set_charger_load_sensor(sensor::Sensor *charger_load_sensor) { charger_load_sensor_ = charger_load_sensor; }
|
||||
void set_charger_battery_status_bitmask_sensor(sensor::Sensor *charger_battery_status_bitmask_sensor) {
|
||||
charger_battery_status_bitmask_sensor_ = charger_battery_status_bitmask_sensor;
|
||||
}
|
||||
void set_pv_voltage_sensor(sensor::Sensor *pv_voltage_sensor) { pv_voltage_sensor_ = pv_voltage_sensor; }
|
||||
void set_pv_current_sensor(sensor::Sensor *pv_current_sensor) { pv_current_sensor_ = pv_current_sensor; }
|
||||
void set_pv_power_sensor(sensor::Sensor *pv_power_sensor) { pv_power_sensor_ = pv_power_sensor; }
|
||||
void set_battery_status_bitmask_sensor(sensor::Sensor *battery_status_bitmask_sensor) {
|
||||
battery_status_bitmask_sensor_ = battery_status_bitmask_sensor;
|
||||
}
|
||||
void set_charging_controller_status_bitmask_sensor(sensor::Sensor *charging_controller_status_bitmask_sensor) {
|
||||
charging_controller_status_bitmask_sensor_ = charging_controller_status_bitmask_sensor;
|
||||
void set_pv_battery_status_bitmask_sensor(sensor::Sensor *pv_battery_status_bitmask_sensor) {
|
||||
pv_battery_status_bitmask_sensor_ = pv_battery_status_bitmask_sensor;
|
||||
}
|
||||
void set_charger_controller_status_bitmask_sensor(sensor::Sensor *charger_controller_status_bitmask_sensor) {
|
||||
charger_controller_status_bitmask_sensor_ = charger_controller_status_bitmask_sensor;
|
||||
}
|
||||
void set_pv_controller_status_bitmask_sensor(sensor::Sensor *pv_controller_status_bitmask_sensor) {
|
||||
pv_controller_status_bitmask_sensor_ = pv_controller_status_bitmask_sensor;
|
||||
}
|
||||
void set_charging_mode_setting_id_sensor(sensor::Sensor *charging_mode_setting_id_sensor) {
|
||||
charging_mode_setting_id_sensor_ = charging_mode_setting_id_sensor;
|
||||
void set_charger_mode_setting_id_sensor(sensor::Sensor *charger_mode_setting_id_sensor) {
|
||||
charger_mode_setting_id_sensor_ = charger_mode_setting_id_sensor;
|
||||
}
|
||||
void set_controller_temperature_sensor(sensor::Sensor *controller_temperature_sensor) {
|
||||
controller_temperature_sensor_ = controller_temperature_sensor;
|
||||
void set_charger_battery_status_text_sensor(text_sensor::TextSensor *charger_battery_status_text_sensor) {
|
||||
charger_battery_status_text_sensor_ = charger_battery_status_text_sensor;
|
||||
}
|
||||
void set_charger_controller_temperature_sensor(sensor::Sensor *charger_controller_temperature_sensor) {
|
||||
charger_controller_temperature_sensor_ = charger_controller_temperature_sensor;
|
||||
}
|
||||
void set_pv_controller_temperature_sensor(sensor::Sensor *pv_controller_temperature_sensor) {
|
||||
pv_controller_temperature_sensor_ = pv_controller_temperature_sensor;
|
||||
}
|
||||
void set_pv_mode_setting_id_sensor(sensor::Sensor *pv_mode_setting_id_sensor) {
|
||||
pv_mode_setting_id_sensor_ = pv_mode_setting_id_sensor;
|
||||
}
|
||||
void set_battery_capacity_remaining_sensor(sensor::Sensor *battery_capacity_remaining_sensor) {
|
||||
battery_capacity_remaining_sensor_ = battery_capacity_remaining_sensor;
|
||||
@@ -67,51 +124,142 @@ class Votronic : public uart::UARTDevice, public PollingComponent {
|
||||
void set_battery_nominal_capacity_sensor(sensor::Sensor *battery_nominal_capacity_sensor) {
|
||||
battery_nominal_capacity_sensor_ = battery_nominal_capacity_sensor;
|
||||
}
|
||||
void set_charging_converter_battery_voltage_sensor(sensor::Sensor *charging_converter_battery_voltage_sensor) {
|
||||
charging_converter_battery_voltage_sensor_ = charging_converter_battery_voltage_sensor;
|
||||
}
|
||||
void set_charging_converter_secondary_battery_voltage_sensor(
|
||||
sensor::Sensor *charging_converter_secondary_battery_voltage_sensor) {
|
||||
charging_converter_secondary_battery_voltage_sensor_ = charging_converter_secondary_battery_voltage_sensor;
|
||||
}
|
||||
void set_charging_converter_current_sensor(sensor::Sensor *charging_converter_current_sensor) {
|
||||
charging_converter_current_sensor_ = charging_converter_current_sensor;
|
||||
}
|
||||
void set_charging_converter_power_sensor(sensor::Sensor *charging_converter_power_sensor) {
|
||||
charging_converter_power_sensor_ = charging_converter_power_sensor;
|
||||
}
|
||||
void set_charging_converter_load_sensor(sensor::Sensor *charging_converter_load_sensor) {
|
||||
charging_converter_load_sensor_ = charging_converter_load_sensor;
|
||||
}
|
||||
void set_charging_converter_controller_temperature_sensor(
|
||||
sensor::Sensor *charging_converter_controller_temperature_sensor) {
|
||||
charging_converter_controller_temperature_sensor_ = charging_converter_controller_temperature_sensor;
|
||||
}
|
||||
void set_charging_converter_mode_setting_id_sensor(sensor::Sensor *charging_converter_mode_setting_id_sensor) {
|
||||
charging_converter_mode_setting_id_sensor_ = charging_converter_mode_setting_id_sensor;
|
||||
}
|
||||
void set_charging_converter_battery_status_bitmask_sensor(
|
||||
sensor::Sensor *charging_converter_battery_status_bitmask_sensor) {
|
||||
charging_converter_battery_status_bitmask_sensor_ = charging_converter_battery_status_bitmask_sensor;
|
||||
}
|
||||
void set_charging_converter_controller_status_bitmask_sensor(
|
||||
sensor::Sensor *charging_converter_controller_status_bitmask_sensor) {
|
||||
charging_converter_controller_status_bitmask_sensor_ = charging_converter_controller_status_bitmask_sensor;
|
||||
}
|
||||
|
||||
void set_battery_status_text_sensor(text_sensor::TextSensor *battery_status_text_sensor) {
|
||||
battery_status_text_sensor_ = battery_status_text_sensor;
|
||||
}
|
||||
void set_charging_controller_status_text_sensor(text_sensor::TextSensor *charging_controller_status_text_sensor) {
|
||||
charging_controller_status_text_sensor_ = charging_controller_status_text_sensor;
|
||||
void set_pv_mode_setting_text_sensor(text_sensor::TextSensor *pv_mode_setting_text_sensor) {
|
||||
pv_mode_setting_text_sensor_ = pv_mode_setting_text_sensor;
|
||||
}
|
||||
void set_pv_battery_status_text_sensor(text_sensor::TextSensor *pv_battery_status_text_sensor) {
|
||||
pv_battery_status_text_sensor_ = pv_battery_status_text_sensor;
|
||||
}
|
||||
void set_charger_controller_status_text_sensor(text_sensor::TextSensor *charger_controller_status_text_sensor) {
|
||||
charger_controller_status_text_sensor_ = charger_controller_status_text_sensor;
|
||||
}
|
||||
void set_pv_controller_status_text_sensor(text_sensor::TextSensor *pv_controller_status_text_sensor) {
|
||||
pv_controller_status_text_sensor_ = pv_controller_status_text_sensor;
|
||||
}
|
||||
void set_charging_mode_setting_text_sensor(text_sensor::TextSensor *charging_mode_setting_text_sensor) {
|
||||
charging_mode_setting_text_sensor_ = charging_mode_setting_text_sensor;
|
||||
void set_charger_mode_setting_text_sensor(text_sensor::TextSensor *charger_mode_setting_text_sensor) {
|
||||
charger_mode_setting_text_sensor_ = charger_mode_setting_text_sensor;
|
||||
}
|
||||
void set_charging_converter_mode_setting_text_sensor(
|
||||
text_sensor::TextSensor *charging_converter_mode_setting_text_sensor) {
|
||||
charging_converter_mode_setting_text_sensor_ = charging_converter_mode_setting_text_sensor;
|
||||
}
|
||||
void set_charging_converter_battery_status_text_sensor(
|
||||
text_sensor::TextSensor *charging_converter_battery_status_text_sensor) {
|
||||
charging_converter_battery_status_text_sensor_ = charging_converter_battery_status_text_sensor;
|
||||
}
|
||||
void set_charging_converter_controller_status_text_sensor(
|
||||
text_sensor::TextSensor *charging_converter_controller_status_text_sensor) {
|
||||
charging_converter_controller_status_text_sensor_ = charging_converter_controller_status_text_sensor;
|
||||
}
|
||||
|
||||
void on_votronic_data(const std::vector<uint8_t> &data);
|
||||
void set_throttle(uint16_t throttle) { this->throttle_ = throttle; }
|
||||
void set_throttle(uint32_t throttle) { this->throttle_ = throttle; }
|
||||
void set_rx_timeout(uint16_t rx_timeout) { rx_timeout_ = rx_timeout; }
|
||||
|
||||
protected:
|
||||
binary_sensor::BinarySensor *charging_binary_sensor_;
|
||||
binary_sensor::BinarySensor *discharging_binary_sensor_;
|
||||
binary_sensor::BinarySensor *controller_active_binary_sensor_;
|
||||
binary_sensor::BinarySensor *aes_active_binary_sensor_;
|
||||
binary_sensor::BinarySensor *current_reduction_binary_sensor_;
|
||||
|
||||
binary_sensor::BinarySensor *charger_charging_binary_sensor_;
|
||||
binary_sensor::BinarySensor *charger_discharging_binary_sensor_;
|
||||
binary_sensor::BinarySensor *charger_controller_active_binary_sensor_;
|
||||
binary_sensor::BinarySensor *charger_aes_active_binary_sensor_;
|
||||
binary_sensor::BinarySensor *charger_current_reduction_binary_sensor_;
|
||||
|
||||
binary_sensor::BinarySensor *charging_converter_charging_binary_sensor_;
|
||||
binary_sensor::BinarySensor *charging_converter_discharging_binary_sensor_;
|
||||
binary_sensor::BinarySensor *charging_converter_controller_active_binary_sensor_;
|
||||
binary_sensor::BinarySensor *charging_converter_current_reduction_binary_sensor_;
|
||||
binary_sensor::BinarySensor *charging_converter_aes_active_binary_sensor_;
|
||||
|
||||
binary_sensor::BinarySensor *pv_controller_active_binary_sensor_;
|
||||
binary_sensor::BinarySensor *pv_aes_active_binary_sensor_;
|
||||
binary_sensor::BinarySensor *pv_current_reduction_binary_sensor_;
|
||||
|
||||
sensor::Sensor *battery_voltage_sensor_;
|
||||
sensor::Sensor *secondary_battery_voltage_sensor_;
|
||||
sensor::Sensor *state_of_charge_sensor_;
|
||||
sensor::Sensor *current_sensor_;
|
||||
sensor::Sensor *power_sensor_;
|
||||
sensor::Sensor *pv_voltage_sensor_;
|
||||
sensor::Sensor *pv_current_sensor_;
|
||||
sensor::Sensor *pv_power_sensor_;
|
||||
sensor::Sensor *battery_status_bitmask_sensor_;
|
||||
sensor::Sensor *charging_controller_status_bitmask_sensor_;
|
||||
sensor::Sensor *pv_controller_status_bitmask_sensor_;
|
||||
sensor::Sensor *charging_mode_setting_id_sensor_;
|
||||
sensor::Sensor *controller_temperature_sensor_;
|
||||
sensor::Sensor *battery_capacity_remaining_sensor_;
|
||||
sensor::Sensor *battery_nominal_capacity_sensor_;
|
||||
|
||||
sensor::Sensor *charger_current_sensor_;
|
||||
sensor::Sensor *charger_power_sensor_;
|
||||
sensor::Sensor *charger_load_sensor_;
|
||||
sensor::Sensor *charger_controller_temperature_sensor_;
|
||||
sensor::Sensor *charger_mode_setting_id_sensor_;
|
||||
sensor::Sensor *charger_battery_status_bitmask_sensor_;
|
||||
sensor::Sensor *charger_controller_status_bitmask_sensor_;
|
||||
|
||||
sensor::Sensor *charging_converter_battery_voltage_sensor_;
|
||||
sensor::Sensor *charging_converter_secondary_battery_voltage_sensor_;
|
||||
sensor::Sensor *charging_converter_current_sensor_;
|
||||
sensor::Sensor *charging_converter_power_sensor_;
|
||||
sensor::Sensor *charging_converter_load_sensor_;
|
||||
sensor::Sensor *charging_converter_state_of_charge_sensor_;
|
||||
sensor::Sensor *charging_converter_controller_temperature_sensor_;
|
||||
sensor::Sensor *charging_converter_mode_setting_id_sensor_;
|
||||
sensor::Sensor *charging_converter_battery_status_bitmask_sensor_;
|
||||
sensor::Sensor *charging_converter_controller_status_bitmask_sensor_;
|
||||
|
||||
sensor::Sensor *pv_voltage_sensor_;
|
||||
sensor::Sensor *pv_current_sensor_;
|
||||
sensor::Sensor *pv_power_sensor_;
|
||||
sensor::Sensor *pv_battery_status_bitmask_sensor_;
|
||||
sensor::Sensor *pv_controller_status_bitmask_sensor_;
|
||||
sensor::Sensor *pv_controller_temperature_sensor_;
|
||||
sensor::Sensor *pv_mode_setting_id_sensor_;
|
||||
|
||||
text_sensor::TextSensor *battery_status_text_sensor_;
|
||||
text_sensor::TextSensor *charging_controller_status_text_sensor_;
|
||||
|
||||
text_sensor::TextSensor *charger_mode_setting_text_sensor_;
|
||||
text_sensor::TextSensor *charger_battery_status_text_sensor_;
|
||||
text_sensor::TextSensor *charger_controller_status_text_sensor_;
|
||||
|
||||
text_sensor::TextSensor *charging_converter_mode_setting_text_sensor_;
|
||||
text_sensor::TextSensor *charging_converter_battery_status_text_sensor_;
|
||||
text_sensor::TextSensor *charging_converter_controller_status_text_sensor_;
|
||||
|
||||
text_sensor::TextSensor *pv_mode_setting_text_sensor_;
|
||||
text_sensor::TextSensor *pv_battery_status_text_sensor_;
|
||||
text_sensor::TextSensor *pv_controller_status_text_sensor_;
|
||||
text_sensor::TextSensor *charging_mode_setting_text_sensor_;
|
||||
|
||||
std::vector<uint8_t> rx_buffer_;
|
||||
uint32_t last_byte_{0};
|
||||
@@ -120,11 +268,12 @@ class Votronic : public uart::UARTDevice, public PollingComponent {
|
||||
uint32_t last_battery_computer_info1_data_{0};
|
||||
uint32_t last_battery_computer_info2_data_{0};
|
||||
uint32_t last_battery_computer_info3_data_{0};
|
||||
uint16_t throttle_;
|
||||
uint32_t throttle_;
|
||||
uint16_t rx_timeout_{150};
|
||||
|
||||
void decode_solar_charger_data_(const std::vector<uint8_t> &data);
|
||||
void decode_charger_data_(const uint8_t &frame_type, const std::vector<uint8_t> &data);
|
||||
void decode_charger_data_(const std::vector<uint8_t> &data);
|
||||
void decode_charging_converter_data_(const std::vector<uint8_t> &data);
|
||||
void decode_battery_computer_info1_data_(const std::vector<uint8_t> &data);
|
||||
void decode_battery_computer_info2_data_(const std::vector<uint8_t> &data);
|
||||
void decode_battery_computer_info3_data_(const std::vector<uint8_t> &data);
|
||||
|
@@ -1,9 +1,9 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import ble_client
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ID, CONF_THROTTLE
|
||||
|
||||
AUTO_LOAD = ["sensor", "text_sensor"]
|
||||
AUTO_LOAD = ["binary_sensor", "sensor", "text_sensor"]
|
||||
CODEOWNERS = ["@syssi"]
|
||||
MULTI_CONF = True
|
||||
|
||||
@@ -27,10 +27,18 @@ CONFIG_SCHEMA = (
|
||||
.extend(cv.polling_component_schema("2s"))
|
||||
)
|
||||
|
||||
# Centralized schema for subcomponents
|
||||
VOTRONIC_BLE_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(CONF_VOTRONIC_BLE_ID): cv.use_id(VotronicBle),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await ble_client.register_ble_node(var, config)
|
||||
|
||||
cg.add_define("USE_ESP32_BLE_DEVICE")
|
||||
cg.add(var.set_throttle(config[CONF_THROTTLE]))
|
||||
|
@@ -1,9 +1,9 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import binary_sensor
|
||||
from esphome.const import CONF_ICON, CONF_ID
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ID
|
||||
|
||||
from . import CONF_VOTRONIC_BLE_ID, VotronicBle
|
||||
from . import CONF_VOTRONIC_BLE_ID, VOTRONIC_BLE_SCHEMA
|
||||
|
||||
DEPENDENCIES = ["votronic_ble"]
|
||||
|
||||
@@ -29,38 +29,22 @@ BINARY_SENSORS = [
|
||||
CONF_AES_ACTIVE,
|
||||
]
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
CONFIG_SCHEMA = VOTRONIC_BLE_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(CONF_VOTRONIC_BLE_ID): cv.use_id(VotronicBle),
|
||||
cv.Optional(CONF_CHARGING): binary_sensor.BINARY_SENSOR_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(binary_sensor.BinarySensor),
|
||||
cv.Optional(CONF_ICON, default=ICON_CHARGING): cv.icon,
|
||||
}
|
||||
cv.Optional(CONF_CHARGING): binary_sensor.binary_sensor_schema(
|
||||
binary_sensor.BinarySensor, icon=ICON_CHARGING
|
||||
),
|
||||
cv.Optional(CONF_DISCHARGING): binary_sensor.BINARY_SENSOR_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(binary_sensor.BinarySensor),
|
||||
cv.Optional(CONF_ICON, default=ICON_DISCHARGING): cv.icon,
|
||||
}
|
||||
cv.Optional(CONF_DISCHARGING): binary_sensor.binary_sensor_schema(
|
||||
binary_sensor.BinarySensor, icon=ICON_DISCHARGING
|
||||
),
|
||||
cv.Optional(CONF_CONTROLLER_ACTIVE): binary_sensor.BINARY_SENSOR_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(binary_sensor.BinarySensor),
|
||||
cv.Optional(CONF_ICON, default=ICON_CONTROLLER_ACTIVE): cv.icon,
|
||||
}
|
||||
cv.Optional(CONF_CONTROLLER_ACTIVE): binary_sensor.binary_sensor_schema(
|
||||
binary_sensor.BinarySensor, icon=ICON_CONTROLLER_ACTIVE
|
||||
),
|
||||
cv.Optional(CONF_CURRENT_REDUCTION): binary_sensor.BINARY_SENSOR_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(binary_sensor.BinarySensor),
|
||||
cv.Optional(CONF_ICON, default=ICON_CURRENT_REDUCTION): cv.icon,
|
||||
}
|
||||
cv.Optional(CONF_CURRENT_REDUCTION): binary_sensor.binary_sensor_schema(
|
||||
binary_sensor.BinarySensor, icon=ICON_CURRENT_REDUCTION
|
||||
),
|
||||
cv.Optional(CONF_AES_ACTIVE): binary_sensor.BINARY_SENSOR_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(binary_sensor.BinarySensor),
|
||||
cv.Optional(CONF_ICON, default=ICON_AES_ACTIVE): cv.icon,
|
||||
}
|
||||
cv.Optional(CONF_AES_ACTIVE): binary_sensor.binary_sensor_schema(
|
||||
binary_sensor.BinarySensor, icon=ICON_AES_ACTIVE
|
||||
),
|
||||
}
|
||||
)
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import sensor
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_BATTERY_VOLTAGE,
|
||||
CONF_CURRENT,
|
||||
@@ -20,7 +20,7 @@ from esphome.const import (
|
||||
UNIT_WATT_HOURS,
|
||||
)
|
||||
|
||||
from . import CONF_VOTRONIC_BLE_ID, VotronicBle
|
||||
from . import CONF_VOTRONIC_BLE_ID, VOTRONIC_BLE_SCHEMA
|
||||
|
||||
DEPENDENCIES = ["votronic_ble"]
|
||||
|
||||
@@ -71,9 +71,8 @@ SENSORS = [
|
||||
CONF_CHARGED_ENERGY,
|
||||
]
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
CONFIG_SCHEMA = VOTRONIC_BLE_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(CONF_VOTRONIC_BLE_ID): cv.use_id(VotronicBle),
|
||||
cv.Optional(CONF_BATTERY_VOLTAGE): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_VOLT,
|
||||
icon=ICON_EMPTY,
|
||||
|
@@ -1,9 +1,9 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import text_sensor
|
||||
from esphome.const import CONF_ICON, CONF_ID
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ID
|
||||
|
||||
from . import CONF_VOTRONIC_BLE_ID, VotronicBle
|
||||
from . import CONF_VOTRONIC_BLE_ID, VOTRONIC_BLE_SCHEMA
|
||||
|
||||
DEPENDENCIES = ["votronic_ble"]
|
||||
|
||||
@@ -20,20 +20,15 @@ TEXT_SENSORS = [
|
||||
CONF_PV_CONTROLLER_STATUS,
|
||||
]
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
CONFIG_SCHEMA = VOTRONIC_BLE_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(CONF_VOTRONIC_BLE_ID): cv.use_id(VotronicBle),
|
||||
cv.Optional(CONF_BATTERY_STATUS): text_sensor.TEXT_SENSOR_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(text_sensor.TextSensor),
|
||||
cv.Optional(CONF_ICON, default=ICON_BATTERY_STATUS): cv.icon,
|
||||
}
|
||||
cv.Optional(CONF_BATTERY_STATUS): text_sensor.text_sensor_schema(
|
||||
class_=text_sensor.TextSensor,
|
||||
icon=ICON_BATTERY_STATUS,
|
||||
),
|
||||
cv.Optional(CONF_PV_CONTROLLER_STATUS): text_sensor.TEXT_SENSOR_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(text_sensor.TextSensor),
|
||||
cv.Optional(CONF_ICON, default=ICON_PV_CONTROLLER_STATUS): cv.icon,
|
||||
}
|
||||
cv.Optional(CONF_PV_CONTROLLER_STATUS): text_sensor.text_sensor_schema(
|
||||
class_=text_sensor.TextSensor,
|
||||
icon=ICON_PV_CONTROLLER_STATUS,
|
||||
),
|
||||
}
|
||||
)
|
||||
|
@@ -37,6 +37,24 @@ void VotronicBle::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t
|
||||
this->publish_state_(this->charged_energy_sensor_, NAN);
|
||||
this->publish_state_(this->pv_power_sensor_, NAN);
|
||||
|
||||
if (this->char_battery_computer_handle_ != 0) {
|
||||
auto status = esp_ble_gattc_unregister_for_notify(
|
||||
this->parent()->get_gattc_if(), this->parent()->get_remote_bda(), this->char_battery_computer_handle_);
|
||||
if (status) {
|
||||
ESP_LOGW(TAG, "esp_ble_gattc_unregister_for_notify failed, status=%d", status);
|
||||
}
|
||||
}
|
||||
this->char_battery_computer_handle_ = 0;
|
||||
|
||||
if (this->char_solar_charger_handle_ != 0) {
|
||||
auto status = esp_ble_gattc_unregister_for_notify(
|
||||
this->parent()->get_gattc_if(), this->parent()->get_remote_bda(), this->char_solar_charger_handle_);
|
||||
if (status) {
|
||||
ESP_LOGW(TAG, "esp_ble_gattc_unregister_for_notify failed, status=%d", status);
|
||||
}
|
||||
}
|
||||
this->char_solar_charger_handle_ = 0;
|
||||
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_SEARCH_CMPL_EVT: {
|
||||
|
@@ -82,7 +82,7 @@ class VotronicBle : public esphome::ble_client::BLEClientNode, public PollingCom
|
||||
}
|
||||
|
||||
void on_votronic_ble_data(const uint8_t &handle, const std::vector<uint8_t> &data);
|
||||
void set_throttle(uint16_t throttle) { this->throttle_ = throttle; }
|
||||
void set_throttle(uint32_t throttle) { this->throttle_ = throttle; }
|
||||
|
||||
protected:
|
||||
binary_sensor::BinarySensor *charging_binary_sensor_;
|
||||
@@ -113,7 +113,7 @@ class VotronicBle : public esphome::ble_client::BLEClientNode, public PollingCom
|
||||
uint16_t char_solar_charger_handle_{0x25};
|
||||
uint32_t last_battery_computer_data_{0};
|
||||
uint32_t last_solar_charger_data_{0};
|
||||
uint16_t throttle_;
|
||||
uint32_t throttle_;
|
||||
|
||||
esp32_ble_tracker::ESPBTUUID service_bond_uuid_ =
|
||||
esp32_ble_tracker::ESPBTUUID::from_raw("70521e61-022d-f899-d046-4885a76acbd0");
|
||||
|
@@ -0,0 +1,126 @@
|
||||
AA.7A.30.05.08.05.00.00.00.00.00.96.52.28.00.AE
|
||||
AA.1A.30.05.37.07.0A.00.00.E0.96.0D.52.20.09.15
|
||||
AA.1A.30.05.9D.06.0A.00.00.E0.96.0D.52.20.09.BE
|
||||
AA.3A.30.05.08.05.00.00.00.01.00.96.52.28.00.EF
|
||||
AA.3A.30.05.08.05.00.00.00.01.00.96.52.28.00.EF
|
||||
AA.1A.30.05.0C.06.0A.00.00.E0.96.0D.52.20.09.2F
|
||||
AA.3A.30.05.08.05.00.00.00.01.00.96.52.28.00.EF
|
||||
AA.1A.30.05.2F.07.0B.00.00.E0.96.0D.52.20.09.0C
|
||||
AA.1A.30.05.C6.07.0B.00.00.E0.96.0D.52.20.09.E5
|
||||
AA.3A.30.05.08.05.00.00.00.01.00.96.52.28.00.EF
|
||||
AA.3A.30.05.08.05.00.00.00.01.00.96.52.28.00.EF
|
||||
AA.1A.2F.05.50.08.00.00.00.E0.96.0D.52.20.11.70
|
||||
AA.3A.30.05.08.05.0A.00.00.01.11.96.52.28.0C.F8
|
||||
AA.7A.37.05.11.05.00.00.00.00.00.96.52.28.00.B0
|
||||
AA.1A.38.05.64.08.00.00.00.E0.96.0D.52.20.11.53
|
||||
AA.1A.39.05.4C.08.00.00.00.E0.96.0E.52.20.11.79
|
||||
AA.3A.39.05.26.05.D4.00.00.01.61.96.52.28.0C.71
|
||||
AA.3A.3A.05.26.05.D5.00.00.01.63.96.52.28.0C.71
|
||||
AA.1A.3A.05.4A.08.00.00.00.E0.96.0E.52.20.11.7C
|
||||
AA.7A.3B.05.25.05.00.00.00.00.00.96.52.28.00.88
|
||||
AA.1A.3B.05.52.08.00.00.00.E0.96.0F.52.20.11.64
|
||||
AA.1A.3C.05.55.08.00.00.00.E0.96.0F.52.20.11.64
|
||||
AA.3A.3C.05.2E.05.D5.00.00.01.60.96.52.28.0C.7C
|
||||
AA.1A.3D.05.4F.08.00.00.00.E0.96.0F.52.20.11.7F
|
||||
AA.7A.3D.05.51.05.00.00.00.00.00.96.52.28.00.FA
|
||||
AA.3A.3E.05.52.05.D7.00.00.01.5E.96.52.28.0C.3E
|
||||
AA.1A.3D.05.56.08.00.00.00.E0.96.10.52.20.11.79
|
||||
AA.3A.3E.05.54.05.D7.00.00.01.5E.96.52.28.0C.38
|
||||
AA.1A.3E.05.5D.08.00.00.00.E0.96.10.52.20.11.71
|
||||
AA.1A.3F.05.6F.08.00.00.00.E0.96.11.52.20.11.43
|
||||
AA.7A.40.05.58.05.00.00.00.00.00.96.52.28.00.8E
|
||||
AA.1A.41.05.55.08.00.00.00.E0.96.12.52.20.11.04
|
||||
AA.3A.44.05.59.05.D3.00.00.01.61.96.52.28.0C.74
|
||||
AA.1A.44.05.44.08.00.00.00.E0.96.14.52.20.11.16
|
||||
AA.1A.44.05.3F.08.00.00.00.E0.96.14.52.20.11.6D
|
||||
AA.7A.45.05.59.05.00.00.00.00.00.96.52.28.00.8A
|
||||
AA.3A.45.05.58.05.D2.00.00.01.61.96.52.28.0C.75
|
||||
AA.1A.45.05.3E.08.00.00.00.E0.96.14.52.20.11.6D
|
||||
AA.7A.46.05.58.05.00.00.00.00.00.96.52.28.00.88
|
||||
AA.1A.46.05.3C.08.00.00.00.E0.96.15.52.20.11.6D
|
||||
AA.1A.46.05.3E.08.00.00.00.E0.96.15.52.20.11.6F
|
||||
AA.3A.46.05.58.05.D1.00.00.01.61.96.52.28.0C.75
|
||||
AA.1A.47.05.55.08.00.00.00.E0.96.15.52.20.11.05
|
||||
AA.3A.47.05.58.05.D0.00.00.01.61.96.52.28.0C.75
|
||||
AA.3A.47.05.58.05.D0.00.00.01.61.96.52.28.0C.75
|
||||
AA.1A.47.05.63.08.00.00.00.E0.96.16.52.20.11.30
|
||||
AA.1A.48.05.4D.08.00.00.00.E0.96.16.52.20.11.11
|
||||
AA.3A.48.05.58.05.CF.00.00.01.61.96.52.28.0C.65
|
||||
AA.7A.48.05.58.05.00.00.00.00.00.96.52.28.00.86
|
||||
AA.1A.48.05.48.08.00.00.00.E0.96.17.52.20.11.15
|
||||
AA.3A.4A.05.58.05.CE.00.00.01.61.96.52.28.0C.66
|
||||
AA.1A.4A.05.4F.08.00.00.00.E0.96.19.52.20.11.1E
|
||||
AA.1A.4B.05.95.08.00.00.00.E0.96.19.52.20.11.C5
|
||||
AA.3A.4B.05.57.05.CD.00.00.01.62.96.52.28.0C.68
|
||||
AA.7A.4B.05.58.05.00.00.00.00.00.96.52.28.00.85
|
||||
AA.1A.4B.05.AA.08.00.00.00.E0.96.19.52.20.11.FA
|
||||
AA.1A.4B.05.85.08.00.00.00.E0.96.19.52.20.11.D5
|
||||
AA.7A.4B.05.58.05.00.00.00.00.00.96.52.28.00.85
|
||||
AA.7A.4B.05.58.05.00.00.00.00.00.96.52.28.00.85
|
||||
AA.1A.4B.05.88.08.00.00.00.E0.96.19.52.20.11.D8
|
||||
AA.7A.4B.05.58.05.00.00.00.00.00.96.52.28.00.85
|
||||
AA.1A.4B.05.58.08.00.00.00.E0.96.1A.52.20.11.0B
|
||||
AA.1A.4B.05.83.08.00.00.00.E0.96.1A.52.20.11.D0
|
||||
AA.7A.4B.05.58.05.00.00.00.00.00.96.52.28.00.85
|
||||
AA.3A.4B.05.58.05.CC.00.00.01.61.96.52.28.0C.65
|
||||
AA.1A.4B.05.58.08.00.00.00.E0.96.1A.52.20.11.0B
|
||||
AA.7A.4C.05.58.05.00.00.00.00.00.96.52.28.00.82
|
||||
AA.1A.4C.05.45.08.00.00.00.E0.96.1A.52.20.11.11
|
||||
AA.1A.4C.05.A6.08.00.00.00.E0.96.1C.52.20.11.F4
|
||||
AA.7A.4C.05.58.05.00.00.00.00.00.96.52.28.00.82
|
||||
AA.7A.4D.05.58.05.00.00.00.00.00.96.52.28.00.83
|
||||
AA.1A.4D.05.9B.08.00.00.00.E0.96.1C.52.20.11.C8
|
||||
AA.3A.4D.05.58.05.CC.00.00.01.62.96.52.28.0C.60
|
||||
AA.1A.4D.05.A6.08.00.00.00.E0.96.1C.52.20.11.F5
|
||||
AA.7A.4D.05.58.05.00.00.00.00.00.96.52.28.00.83
|
||||
AA.1A.4D.05.7D.08.00.00.00.E0.96.1D.52.20.11.2F
|
||||
AA.3A.4D.05.58.05.CC.00.00.01.61.96.52.28.0C.63
|
||||
AA.1A.4D.05.8B.08.00.00.00.E0.96.1D.52.20.11.D9
|
||||
AA.7A.4D.05.58.05.00.00.00.00.00.96.52.28.00.83
|
||||
AA.1A.4D.05.A4.08.00.00.00.E0.96.1D.52.20.11.F6
|
||||
AA.1A.4D.05.63.08.00.00.00.E0.96.1D.52.20.11.31
|
||||
AA.7A.4D.05.58.05.00.00.00.00.00.96.52.28.00.83
|
||||
AA.7A.4D.05.58.05.00.00.00.00.00.96.52.28.00.83
|
||||
AA.1A.4D.05.64.08.00.00.00.E0.96.1E.52.20.11.35
|
||||
AA.3A.4E.05.58.05.CC.00.00.01.62.96.52.28.0C.63
|
||||
AA.1A.4D.05.5B.08.00.00.00.E0.96.1E.52.20.11.0A
|
||||
AA.3A.4D.05.58.05.CC.00.00.01.62.96.52.28.0C.60
|
||||
AA.7A.4E.05.59.05.00.00.00.00.00.96.52.28.00.81
|
||||
AA.1A.4E.05.55.08.00.00.00.E0.96.1F.52.20.11.06
|
||||
AA.7A.4E.05.59.05.00.00.00.00.00.96.52.28.00.81
|
||||
AA.1A.4E.05.52.08.00.00.00.E0.96.1F.52.20.11.01
|
||||
AA.1A.4E.05.4F.08.00.00.00.E0.96.1F.52.20.11.1C
|
||||
AA.7A.4E.05.59.05.00.00.00.00.00.96.52.28.00.81
|
||||
AA.1A.4E.05.59.08.00.00.00.E0.96.1F.52.20.11.0A
|
||||
AA.1A.4E.05.72.08.00.00.00.E0.96.20.52.20.11.1E
|
||||
AA.3A.4E.05.59.05.CC.00.00.01.63.96.52.28.0C.63
|
||||
AA.7A.4E.05.59.05.00.00.00.00.00.96.52.28.00.81
|
||||
AA.1A.4E.05.5E.08.00.00.00.E0.96.20.52.20.11.32
|
||||
AA.7A.4E.05.59.05.00.00.00.00.00.96.52.28.00.81
|
||||
AA.1A.4E.05.55.08.00.00.00.E0.96.20.52.20.11.39
|
||||
AA.3A.4E.05.59.05.CC.00.00.01.62.96.52.28.0C.62
|
||||
AA.1A.4E.05.59.08.00.00.00.E0.96.20.52.20.11.35
|
||||
AA.3A.4E.05.59.05.CC.00.00.01.63.96.52.28.0C.63
|
||||
AA.1A.4E.05.9F.08.00.00.00.E0.96.20.52.20.11.F3
|
||||
AA.7A.4E.05.59.05.00.00.00.00.00.96.52.28.00.81
|
||||
AA.3A.4E.05.59.05.CB.00.00.01.62.96.52.28.0C.65
|
||||
AA.1A.4E.05.4C.08.00.00.00.E0.96.22.52.20.11.22
|
||||
AA.3A.4E.05.59.05.CB.00.00.01.62.96.52.28.0C.65
|
||||
AA.3A.4E.05.59.05.CB.00.00.01.62.96.52.28.0C.65
|
||||
AA.1A.4E.05.4C.08.00.00.00.E0.96.22.52.20.11.22
|
||||
AA.3A.4E.05.59.05.CB.00.00.01.62.96.52.28.0C.65
|
||||
AA.1A.4E.05.4D.08.00.00.00.E0.96.23.52.20.11.22
|
||||
AA.7A.4E.05.59.05.00.00.00.00.00.96.52.28.00.81
|
||||
AA.1A.4E.05.61.08.00.00.00.E0.96.23.52.20.11.0E
|
||||
AA.3A.4E.05.59.05.CB.00.00.01.62.96.52.28.0C.65
|
||||
AA.1A.4E.05.7F.08.00.00.00.E0.96.23.52.20.11.10
|
||||
AA.3A.4E.05.59.05.CB.00.00.01.62.96.52.28.0C.65
|
||||
AA.1A.4E.05.8E.08.00.00.00.E0.96.23.52.20.11.E1
|
||||
AA.1A.4E.05.9F.08.00.00.00.E0.96.23.52.20.11.F0
|
||||
AA.3A.4E.05.5A.05.CB.00.00.01.62.96.52.28.0C.66
|
||||
AA.1A.4E.05.9E.08.00.00.00.E0.96.23.52.20.11.F1
|
||||
AA.3A.4F.05.59.05.CB.00.00.01.62.96.52.28.0C.64
|
||||
AA.7A.4F.05.59.05.00.00.00.00.00.96.52.28.00.80
|
||||
AA.1A.4E.05.99.08.00.00.00.E0.96.23.52.20.11.F6
|
||||
AA.1A.4E.05.74.08.00.00.00.E0.96.23.52.20.11.1B
|
||||
AA.3A.4E.05.59.05.CB.00.00.01.63.96.52.28.0C.64
|
@@ -0,0 +1,59 @@
|
||||
AA.1A.54.05.83.08.00.00.00.E0.97.32.52.20.01.F6
|
||||
AA.7A.54.05.4A.05.2B.01.00.00.63.97.52.28.0C.CC
|
||||
AA.1A.54.05.85.08.00.00.00.E0.97.32.52.20.01.F0
|
||||
AA.3A.54.05.49.05.00.00.00.01.00.97.52.28.00.CB
|
||||
AA.1A.54.05.85.08.00.00.00.E0.97.32.52.20.01.F0
|
||||
AA.7A.53.05.49.05.2B.01.00.00.64.97.52.28.0C.CF
|
||||
AA.1A.53.05.83.08.00.00.00.E0.97.33.52.20.01.F0
|
||||
AA.1A.54.05.85.08.00.00.00.E0.97.33.52.20.01.F1
|
||||
AA.3A.54.05.49.05.00.00.00.01.00.97.52.28.00.CB
|
||||
AA.1A.54.05.83.08.00.00.00.E0.97.33.52.20.01.F7
|
||||
AA.7A.54.05.49.05.2C.01.00.00.63.97.52.28.0C.C8
|
||||
AA.7A.53.05.49.05.2C.01.00.00.63.97.52.28.0C.CF
|
||||
AA.1A.54.05.7D.08.00.00.00.E0.97.33.52.20.01.09
|
||||
AA.1A.54.05.0D.08.00.00.00.E0.97.33.52.20.01.79
|
||||
AA.7A.55.05.4C.05.2B.01.00.00.64.97.52.28.0C.CC
|
||||
AA.1A.56.05.1B.08.00.00.00.E0.97.34.52.20.01.6A
|
||||
AA.7A.55.05.4C.05.2B.01.00.00.64.97.52.28.0C.CC
|
||||
AA.1A.56.05.5B.08.00.00.00.E0.97.34.52.20.01.2A
|
||||
AA.3A.56.05.4C.05.00.00.00.01.00.97.52.28.00.CC
|
||||
AA.7A.56.05.4A.05.2B.01.00.00.63.97.52.28.0C.CE
|
||||
AA.1A.56.05.7D.08.00.00.00.E0.97.34.52.20.01.0C
|
||||
AA.7A.56.05.4B.05.2B.01.00.00.63.97.52.28.0C.CF
|
||||
AA.1A.56.05.83.08.00.00.00.E0.97.34.52.20.01.F2
|
||||
AA.1A.56.05.87.08.00.00.00.E0.97.34.52.20.01.F6
|
||||
AA.3A.56.05.4B.05.00.00.00.01.00.97.52.28.00.CB
|
||||
AA.7A.56.05.4B.05.2B.01.00.00.63.97.52.28.0C.CF
|
||||
AA.1A.56.05.87.08.00.00.00.E0.97.34.52.20.01.F6
|
||||
AA.1A.56.05.88.08.00.00.00.E0.97.34.52.20.01.F9
|
||||
AA.7A.56.05.4A.05.2C.01.00.00.64.97.52.28.0C.CE
|
||||
AA.3A.56.05.49.05.00.00.00.01.00.97.52.28.00.C9
|
||||
AA.1A.56.05.8A.08.00.00.00.E0.97.34.52.20.01.FB
|
||||
AA.3A.56.05.49.05.00.00.00.01.00.97.52.28.00.C9
|
||||
AA.1A.56.05.8B.08.00.00.00.E0.97.34.52.20.01.FA
|
||||
AA.1A.56.05.8A.08.00.00.00.E0.97.34.52.20.01.FB
|
||||
AA.3A.56.05.49.05.00.00.00.01.00.97.52.28.00.C9
|
||||
AA.1A.56.05.88.08.00.00.00.E0.97.35.52.20.01.F8
|
||||
AA.3A.56.05.47.05.00.00.00.01.00.97.52.28.00.C7
|
||||
AA.1A.56.05.88.08.00.00.00.E0.97.35.52.20.01.F8
|
||||
AA.1A.56.05.85.08.00.00.00.E0.97.34.52.20.01.F4
|
||||
AA.7A.56.05.49.05.2B.01.00.00.64.97.52.28.0C.CA
|
||||
AA.1A.49.05.83.08.10.00.00.E0.97.35.52.20.09.F4
|
||||
AA.3A.49.05.35.05.00.00.00.01.00.97.52.28.00.AA
|
||||
AA.3A.46.05.30.05.00.00.00.01.00.97.52.28.00.A0
|
||||
AA.1A.46.05.BD.06.0A.00.00.E0.97.34.52.20.09.D0
|
||||
AA.1A.44.05.82.08.00.00.00.E0.97.32.52.20.11.F7
|
||||
AA.3A.44.05.2B.05.00.00.00.01.00.97.52.28.0C.B5
|
||||
AA.1A.43.05.80.08.00.00.00.E0.97.31.52.20.11.F1
|
||||
AA.7A.43.05.2A.05.00.00.00.00.00.97.52.28.00.FE
|
||||
AA.3A.46.05.2E.05.41.00.00.01.29.97.52.28.0C.DA
|
||||
AA.1A.46.05.82.08.00.00.00.E0.97.30.52.20.11.F7
|
||||
AA.3A.4B.05.34.05.C7.00.00.01.64.97.52.28.0C.06
|
||||
AA.1A.4C.05.82.08.00.00.00.E0.97.30.52.20.11.FD
|
||||
AA.7A.4C.05.33.05.00.00.00.00.00.97.52.28.00.E8
|
||||
AA.1A.4C.05.80.08.00.00.00.E0.97.2F.52.20.11.E0
|
||||
AA.1A.4D.05.80.08.00.00.00.E0.97.2F.52.20.11.E1
|
||||
AA.1A.4E.05.7F.08.00.00.00.E0.97.2F.52.20.11.1D
|
||||
AA.3A.4E.05.37.05.D1.00.00.01.64.97.52.28.0C.16
|
||||
AA.1A.4D.05.7F.08.00.00.00.E0.97.2F.52.20.11.1E
|
||||
AA.1A.4D.05.80.08.00.00.00.E0.97.2F.52.20.11.E1
|
@@ -7,9 +7,10 @@ substitutions:
|
||||
esphome:
|
||||
name: ${name}
|
||||
comment: ${device_description}
|
||||
min_version: 2024.6.0
|
||||
project:
|
||||
name: "syssi.esphome-votronic"
|
||||
version: 2.0.0
|
||||
version: 3.1.0
|
||||
|
||||
esp32:
|
||||
board: wemos_d1_mini32
|
||||
@@ -25,6 +26,7 @@ wifi:
|
||||
password: !secret wifi_password
|
||||
|
||||
ota:
|
||||
platform: esphome
|
||||
|
||||
logger:
|
||||
level: DEBUG
|
||||
@@ -39,26 +41,21 @@ mqtt:
|
||||
|
||||
# api:
|
||||
|
||||
esp32_ble_tracker:
|
||||
esp32_ble:
|
||||
io_capability: keyboard_only
|
||||
on_ble_advertise:
|
||||
then:
|
||||
- lambda: |-
|
||||
if (x.get_name().rfind("votronic", 0) == 0) {
|
||||
ESP_LOGI("ble_adv", "New Votronic BLE controller found");
|
||||
ESP_LOGI("ble_adv", " Name: %s", x.get_name().c_str());
|
||||
ESP_LOGI("ble_adv", " MAC address: %s", x.address_str().c_str());
|
||||
ESP_LOGD("ble_adv", " Advertised service UUIDs:");
|
||||
for (auto uuid : x.get_service_uuids()) {
|
||||
ESP_LOGD("ble_adv", " - %s", uuid.to_string().c_str());
|
||||
}
|
||||
}
|
||||
|
||||
esp32_ble_tracker:
|
||||
scan_parameters:
|
||||
active: false
|
||||
|
||||
ble_client:
|
||||
- mac_address: ${mac_address}
|
||||
id: client0
|
||||
pin_code: 173928
|
||||
on_passkey_request:
|
||||
then:
|
||||
- ble_client.passkey_reply:
|
||||
id: client0
|
||||
passkey: 173928
|
||||
|
||||
votronic_ble:
|
||||
- ble_client_id: client0
|
||||
|
@@ -5,9 +5,10 @@ substitutions:
|
||||
esphome:
|
||||
name: ${name}
|
||||
comment: ${device_description}
|
||||
min_version: 2024.6.0
|
||||
project:
|
||||
name: "syssi.esphome-votronic"
|
||||
version: 2.0.0
|
||||
version: 3.1.0
|
||||
|
||||
esp32:
|
||||
board: wemos_d1_mini32
|
||||
@@ -19,6 +20,7 @@ wifi:
|
||||
password: !secret wifi_password
|
||||
|
||||
ota:
|
||||
platform: esphome
|
||||
|
||||
logger:
|
||||
level: DEBUG
|
||||
@@ -29,6 +31,8 @@ api:
|
||||
reboot_timeout: 0s
|
||||
|
||||
esp32_ble_tracker:
|
||||
scan_parameters:
|
||||
active: true
|
||||
on_ble_advertise:
|
||||
then:
|
||||
- lambda: |-
|
||||
|
@@ -9,9 +9,10 @@ substitutions:
|
||||
esphome:
|
||||
name: ${name}
|
||||
comment: ${device_description}
|
||||
min_version: 2024.6.0
|
||||
project:
|
||||
name: "syssi.esphome-votronic"
|
||||
version: 2.0.0
|
||||
version: 3.1.0
|
||||
|
||||
esp8266:
|
||||
board: d1_mini
|
||||
@@ -25,6 +26,7 @@ wifi:
|
||||
password: !secret wifi_password
|
||||
|
||||
ota:
|
||||
platform: esphome
|
||||
|
||||
logger:
|
||||
level: DEBUG
|
||||
@@ -58,12 +60,17 @@ binary_sensor:
|
||||
name: "${name} charging"
|
||||
discharging:
|
||||
name: "${name} discharging"
|
||||
controller_active:
|
||||
name: "${name} controller active"
|
||||
current_reduction:
|
||||
name: "${name} current reduction"
|
||||
aes_active:
|
||||
name: "${name} aes active"
|
||||
|
||||
charger_charging:
|
||||
name: "${name} charger charging"
|
||||
charger_discharging:
|
||||
name: "${name} charger discharging"
|
||||
charger_controller_active:
|
||||
name: "${name} charger controller active"
|
||||
charger_current_reduction:
|
||||
name: "${name} charger current reduction"
|
||||
charger_aes_active:
|
||||
name: "${name} charger aes active"
|
||||
|
||||
sensor:
|
||||
- platform: votronic
|
||||
@@ -76,23 +83,28 @@ sensor:
|
||||
name: "${name} current"
|
||||
power:
|
||||
name: "${name} power"
|
||||
state_of_charge:
|
||||
name: "${name} state of charge"
|
||||
controller_temperature:
|
||||
name: "${name} controller temperature"
|
||||
charging_mode_setting_id:
|
||||
name: "${name} charging mode setting id"
|
||||
|
||||
charger_current:
|
||||
name: "${name} charger current"
|
||||
charger_power:
|
||||
name: "${name} charger power"
|
||||
charger_load:
|
||||
name: "${name} charger load"
|
||||
charger_controller_temperature:
|
||||
name: "${name} charger controller temperature"
|
||||
charger_mode_setting_id:
|
||||
name: "${name} charger mode setting id"
|
||||
battery_status_bitmask:
|
||||
name: "${name} battery status bitmask"
|
||||
charging_controller_status_bitmask:
|
||||
name: "${name} charging controller status bitmask"
|
||||
charger_controller_status_bitmask:
|
||||
name: "${name} charger controller status bitmask"
|
||||
|
||||
text_sensor:
|
||||
- platform: votronic
|
||||
votronic_id: votronic0
|
||||
charging_mode_setting:
|
||||
name: "${name} charging mode setting"
|
||||
charger_mode_setting:
|
||||
name: "${name} charger mode setting"
|
||||
battery_status:
|
||||
name: "${name} battery status"
|
||||
charging_controller_status:
|
||||
name: "${name} charging controller status"
|
||||
charger_controller_status:
|
||||
name: "${name} charger controller status"
|
||||
|
@@ -9,9 +9,10 @@ substitutions:
|
||||
esphome:
|
||||
name: ${name}
|
||||
comment: ${device_description}
|
||||
min_version: 2024.6.0
|
||||
project:
|
||||
name: "syssi.esphome-votronic"
|
||||
version: 2.0.0
|
||||
version: 3.1.0
|
||||
|
||||
esp8266:
|
||||
board: d1_mini
|
||||
@@ -25,6 +26,7 @@ wifi:
|
||||
password: !secret wifi_password
|
||||
|
||||
ota:
|
||||
platform: esphome
|
||||
|
||||
logger:
|
||||
level: DEBUG
|
||||
@@ -54,16 +56,16 @@ votronic:
|
||||
binary_sensor:
|
||||
- platform: votronic
|
||||
votronic_id: votronic0
|
||||
charging:
|
||||
name: "${name} charging"
|
||||
discharging:
|
||||
name: "${name} discharging"
|
||||
controller_active:
|
||||
name: "${name} controller active"
|
||||
current_reduction:
|
||||
name: "${name} current reduction"
|
||||
aes_active:
|
||||
name: "${name} aes active"
|
||||
charging_converter_charging:
|
||||
name: "${name} charging converter charging"
|
||||
charging_converter_discharging:
|
||||
name: "${name} charging converter discharging"
|
||||
charging_converter_controller_active:
|
||||
name: "${name} charging converter controller active"
|
||||
charging_converter_current_reduction:
|
||||
name: "${name} charging converter current reduction"
|
||||
charging_converter_aes_active:
|
||||
name: "${name} charging converter aes active"
|
||||
|
||||
sensor:
|
||||
- platform: votronic
|
||||
@@ -78,21 +80,32 @@ sensor:
|
||||
name: "${name} power"
|
||||
state_of_charge:
|
||||
name: "${name} state of charge"
|
||||
controller_temperature:
|
||||
name: "${name} controller temperature"
|
||||
charging_mode_setting_id:
|
||||
name: "${name} charging mode setting id"
|
||||
battery_status_bitmask:
|
||||
name: "${name} battery status bitmask"
|
||||
charging_controller_status_bitmask:
|
||||
name: "${name} charging controller status bitmask"
|
||||
|
||||
charging_converter_battery_voltage:
|
||||
name: "${name} charging converter battery voltage"
|
||||
charging_converter_secondary_battery_voltage:
|
||||
name: "${name} charging converter secondary battery voltage"
|
||||
charging_converter_current:
|
||||
name: "${name} charging converter current"
|
||||
charging_converter_power:
|
||||
name: "${name} charging converter power"
|
||||
charging_converter_load:
|
||||
name: "${name} charging converter load"
|
||||
charging_converter_controller_temperature:
|
||||
name: "${name} charging converter controller temperature"
|
||||
charging_converter_mode_setting_id:
|
||||
name: "${name} charging converter mode setting id"
|
||||
charging_converter_battery_status_bitmask:
|
||||
name: "${name} charging converter battery status bitmask"
|
||||
charging_converter_controller_status_bitmask:
|
||||
name: "${name} charging converter controller status bitmask"
|
||||
|
||||
text_sensor:
|
||||
- platform: votronic
|
||||
votronic_id: votronic0
|
||||
charging_mode_setting:
|
||||
name: "${name} charging mode setting"
|
||||
battery_status:
|
||||
name: "${name} battery status"
|
||||
charging_controller_status:
|
||||
name: "${name} charging controller status"
|
||||
charging_converter_mode_setting:
|
||||
name: "${name} charging converter mode setting"
|
||||
charging_converter_battery_status:
|
||||
name: "${name} charging converter battery status"
|
||||
charging_converter_controller_status:
|
||||
name: "${name} charging converter controller status"
|
||||
|
@@ -9,9 +9,10 @@ substitutions:
|
||||
esphome:
|
||||
name: ${name}
|
||||
comment: ${device_description}
|
||||
min_version: 2024.6.0
|
||||
project:
|
||||
name: "syssi.esphome-votronic"
|
||||
version: 2.0.0
|
||||
version: 3.1.0
|
||||
|
||||
esp8266:
|
||||
board: d1_mini
|
||||
@@ -25,6 +26,8 @@ wifi:
|
||||
password: !secret wifi_password
|
||||
|
||||
ota:
|
||||
platform: esphome
|
||||
|
||||
logger:
|
||||
|
||||
# If you use Home Assistant please remove this `mqtt` section and uncomment the native `api` component!
|
||||
@@ -73,15 +76,15 @@ sensor:
|
||||
name: "${name} battery nominal capacity"
|
||||
state_of_charge:
|
||||
name: "${name} state of charge"
|
||||
charging_mode_setting_id:
|
||||
name: "${name} charging mode setting id"
|
||||
charger_mode_setting_id:
|
||||
name: "${name} charger mode setting id"
|
||||
battery_status_bitmask:
|
||||
name: "${name} battery status bitmask"
|
||||
|
||||
text_sensor:
|
||||
- platform: votronic
|
||||
votronic_id: votronic0
|
||||
charging_mode_setting:
|
||||
name: "${name} charging mode setting"
|
||||
charger_mode_setting:
|
||||
name: "${name} charger mode setting"
|
||||
battery_status:
|
||||
name: "${name} battery status"
|
||||
|
@@ -9,9 +9,10 @@ substitutions:
|
||||
esphome:
|
||||
name: ${name}
|
||||
comment: ${device_description}
|
||||
min_version: 2024.6.0
|
||||
project:
|
||||
name: "syssi.esphome-votronic"
|
||||
version: 2.0.0
|
||||
version: 3.1.0
|
||||
|
||||
esp8266:
|
||||
board: d1_mini
|
||||
@@ -25,6 +26,7 @@ wifi:
|
||||
password: !secret wifi_password
|
||||
|
||||
ota:
|
||||
platform: esphome
|
||||
|
||||
logger:
|
||||
level: DEBUG
|
||||
@@ -58,12 +60,13 @@ binary_sensor:
|
||||
name: "${name} charging"
|
||||
discharging:
|
||||
name: "${name} discharging"
|
||||
controller_active:
|
||||
name: "${name} controller active"
|
||||
current_reduction:
|
||||
name: "${name} current reduction"
|
||||
aes_active:
|
||||
name: "${name} aes active"
|
||||
|
||||
pv_controller_active:
|
||||
name: "${name} pv controller active"
|
||||
pv_current_reduction:
|
||||
name: "${name} pv current reduction"
|
||||
pv_aes_active:
|
||||
name: "${name} pv aes active"
|
||||
|
||||
sensor:
|
||||
- platform: votronic
|
||||
@@ -76,21 +79,21 @@ sensor:
|
||||
name: "${name} pv current"
|
||||
pv_power:
|
||||
name: "${name} pv power"
|
||||
controller_temperature:
|
||||
name: "${name} controller temperature"
|
||||
charging_mode_setting_id:
|
||||
name: "${name} charging mode setting id"
|
||||
battery_status_bitmask:
|
||||
name: "${name} battery status bitmask"
|
||||
pv_controller_temperature:
|
||||
name: "${name} pv controller temperature"
|
||||
pv_mode_setting_id:
|
||||
name: "${name} pv mode setting id"
|
||||
pv_battery_status_bitmask:
|
||||
name: "${name} pv battery status bitmask"
|
||||
pv_controller_status_bitmask:
|
||||
name: "${name} pv controller status bitmask"
|
||||
|
||||
text_sensor:
|
||||
- platform: votronic
|
||||
votronic_id: votronic0
|
||||
charging_mode_setting:
|
||||
name: "${name} charging mode setting"
|
||||
battery_status:
|
||||
name: "${name} battery status"
|
||||
pv_mode_setting:
|
||||
name: "${name} pv mode setting"
|
||||
pv_battery_status:
|
||||
name: "${name} pv battery status"
|
||||
pv_controller_status:
|
||||
name: "${name} pv controller status"
|
||||
|
27
esp8266-triple-charger-example-debug.yaml
Normal file
27
esp8266-triple-charger-example-debug.yaml
Normal file
@@ -0,0 +1,27 @@
|
||||
<<: !include esp8266-triple-charger-example.yaml
|
||||
|
||||
logger:
|
||||
level: VERY_VERBOSE
|
||||
logs:
|
||||
votronic: VERY_VERBOSE
|
||||
component: DEBUG
|
||||
scheduler: INFO
|
||||
binary_sensor: DEBUG
|
||||
sensor: DEBUG
|
||||
text_sensor: DEBUG
|
||||
mqtt: INFO
|
||||
mqtt.idf: INFO
|
||||
mqtt.component: INFO
|
||||
mqtt.sensor: INFO
|
||||
mqtt.switch: INFO
|
||||
api.service: INFO
|
||||
api: INFO
|
||||
|
||||
uart:
|
||||
- id: uart_0
|
||||
baud_rate: 1000
|
||||
tx_pin: ${tx_pin}
|
||||
rx_pin: ${rx_pin}
|
||||
debug:
|
||||
direction: BOTH
|
||||
dummy_receiver: false
|
58
esp8266-triple-charger-example-faker.yaml
Normal file
58
esp8266-triple-charger-example-faker.yaml
Normal file
@@ -0,0 +1,58 @@
|
||||
<<: !include esp8266-triple-charger-example-debug.yaml
|
||||
|
||||
interval:
|
||||
- interval: 10s
|
||||
then:
|
||||
- lambda: |-
|
||||
id(votronic0).on_votronic_data({
|
||||
0xAA, 0x3A, 0xFD, 0x04, 0xF9, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x9A, 0x52, 0x28, 0x00, 0xDF
|
||||
});
|
||||
|
||||
- delay: 1s
|
||||
|
||||
- lambda: |-
|
||||
id(votronic0).on_votronic_data({
|
||||
0xAA, 0x4A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4A
|
||||
});
|
||||
|
||||
- delay: 1s
|
||||
|
||||
- lambda: |-
|
||||
id(votronic0).on_votronic_data({
|
||||
0xAA, 0x1A, 0xFD, 0x04, 0x94, 0x05, 0x01, 0x00, 0x00, 0xE0, 0x9A, 0x11, 0x52, 0x20, 0x09, 0x63
|
||||
});
|
||||
|
||||
- delay: 1s
|
||||
|
||||
- lambda: |-
|
||||
id(votronic0).on_votronic_data({
|
||||
0xAA, 0x7A, 0xFD, 0x04, 0xF9, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9A, 0x52, 0x28, 0x00, 0x9E
|
||||
});
|
||||
|
||||
- delay: 1s
|
||||
|
||||
- lambda: |-
|
||||
id(votronic0).on_votronic_data({
|
||||
0xAA, 0x3A, 0x09, 0x05, 0x02, 0x05, 0xC8, 0x00, 0x00, 0x01, 0x64, 0x9A, 0x52, 0x28, 0x0C, 0x70
|
||||
});
|
||||
|
||||
- delay: 1s
|
||||
|
||||
- lambda: |-
|
||||
id(votronic0).on_votronic_data({
|
||||
0xAA, 0x4A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4A
|
||||
});
|
||||
|
||||
- delay: 1s
|
||||
|
||||
- lambda: |-
|
||||
id(votronic0).on_votronic_data({
|
||||
0xAA, 0x1A, 0x0A, 0x05, 0xDD, 0x07, 0x00, 0x00, 0x00, 0xE0, 0x9A, 0x12, 0x52, 0x20, 0x11, 0xC4
|
||||
});
|
||||
|
||||
- delay: 1s
|
||||
|
||||
- lambda: |-
|
||||
id(votronic0).on_votronic_data({
|
||||
0xAA, 0x7A, 0x0C, 0x05, 0x19, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9A, 0x52, 0x28, 0x00, 0x8F
|
||||
});
|
177
esp8266-triple-charger-example.yaml
Normal file
177
esp8266-triple-charger-example.yaml
Normal file
@@ -0,0 +1,177 @@
|
||||
substitutions:
|
||||
name: votronic-solar-charger
|
||||
device_description: "Monitor a Votronic Solar Charger via the display link port (UART)"
|
||||
external_components_source: github://syssi/esphome-votronic@main
|
||||
tx_pin: GPIO4
|
||||
rx_pin: GPIO5
|
||||
rx_timeout: 600ms
|
||||
|
||||
esphome:
|
||||
name: ${name}
|
||||
comment: ${device_description}
|
||||
min_version: 2024.6.0
|
||||
project:
|
||||
name: "syssi.esphome-votronic"
|
||||
version: 3.1.0
|
||||
|
||||
esp8266:
|
||||
board: d1_mini
|
||||
|
||||
external_components:
|
||||
- source: ${external_components_source}
|
||||
refresh: 0s
|
||||
|
||||
wifi:
|
||||
ssid: !secret wifi_ssid
|
||||
password: !secret wifi_password
|
||||
|
||||
ota:
|
||||
platform: esphome
|
||||
|
||||
logger:
|
||||
level: DEBUG
|
||||
|
||||
# If you use Home Assistant please remove this `mqtt` section and uncomment the `api` component!
|
||||
# The native API has many advantages over MQTT: https://esphome.io/components/api.html#advantages-over-mqtt
|
||||
mqtt:
|
||||
broker: !secret mqtt_host
|
||||
username: !secret mqtt_username
|
||||
password: !secret mqtt_password
|
||||
id: mqtt_client
|
||||
|
||||
# api:
|
||||
|
||||
uart:
|
||||
- id: uart_0
|
||||
baud_rate: 1000
|
||||
tx_pin: ${tx_pin}
|
||||
rx_pin: ${rx_pin}
|
||||
|
||||
votronic:
|
||||
- id: votronic0
|
||||
uart_id: uart_0
|
||||
rx_timeout: ${rx_timeout}
|
||||
throttle: 2s
|
||||
|
||||
binary_sensor:
|
||||
- platform: votronic
|
||||
votronic_id: votronic0
|
||||
charging:
|
||||
name: "${name} charging"
|
||||
discharging:
|
||||
name: "${name} discharging"
|
||||
|
||||
charger_charging:
|
||||
name: "${name} charger charging"
|
||||
charger_discharging:
|
||||
name: "${name} charger discharging"
|
||||
charger_controller_active:
|
||||
name: "${name} charger controller active"
|
||||
charger_current_reduction:
|
||||
name: "${name} charger current reduction"
|
||||
charger_aes_active:
|
||||
name: "${name} charger aes active"
|
||||
|
||||
charging_converter_charging:
|
||||
name: "${name} charging converter charging"
|
||||
charging_converter_discharging:
|
||||
name: "${name} charging converter discharging"
|
||||
charging_converter_controller_active:
|
||||
name: "${name} charging converter controller active"
|
||||
charging_converter_current_reduction:
|
||||
name: "${name} charging converter current reduction"
|
||||
charging_converter_aes_active:
|
||||
name: "${name} charging converter aes active"
|
||||
|
||||
pv_controller_active:
|
||||
name: "${name} pv controller active"
|
||||
pv_current_reduction:
|
||||
name: "${name} pv current reduction"
|
||||
pv_aes_active:
|
||||
name: "${name} pv aes active"
|
||||
|
||||
sensor:
|
||||
- platform: votronic
|
||||
votronic_id: votronic0
|
||||
battery_voltage:
|
||||
name: "${name} battery voltage"
|
||||
current:
|
||||
name: "${name} current"
|
||||
power:
|
||||
name: "${name} power"
|
||||
battery_status_bitmask:
|
||||
name: "${name} battery status bitmask"
|
||||
|
||||
charger_current:
|
||||
name: "${name} charger current"
|
||||
charger_power:
|
||||
name: "${name} charger power"
|
||||
charger_load:
|
||||
name: "${name} charger load"
|
||||
charger_controller_temperature:
|
||||
name: "${name} charger controller temperature"
|
||||
charger_mode_setting_id:
|
||||
name: "${name} charger mode setting id"
|
||||
charger_battery_status_bitmask:
|
||||
name: "${name} charger battery status bitmask"
|
||||
charger_controller_status_bitmask:
|
||||
name: "${name} charger controller status bitmask"
|
||||
|
||||
charging_converter_battery_voltage:
|
||||
name: "${name} charging converter battery voltage"
|
||||
charging_converter_secondary_battery_voltage:
|
||||
name: "${name} charging converter secondary battery voltage"
|
||||
charging_converter_current:
|
||||
name: "${name} charging converter current"
|
||||
charging_converter_power:
|
||||
name: "${name} charging converter power"
|
||||
charging_converter_load:
|
||||
name: "${name} charging converter load"
|
||||
charging_converter_controller_temperature:
|
||||
name: "${name} charging converter controller temperature"
|
||||
charging_converter_mode_setting_id:
|
||||
name: "${name} charging converter mode setting id"
|
||||
charging_converter_battery_status_bitmask:
|
||||
name: "${name} charging converter battery status bitmask"
|
||||
charging_converter_controller_status_bitmask:
|
||||
name: "${name} charging converter controller status bitmask"
|
||||
|
||||
pv_controller_status_bitmask:
|
||||
name: "${name} pv controller status bitmask"
|
||||
pv_controller_temperature:
|
||||
name: "${name} pv controller temperature"
|
||||
pv_battery_status_bitmask:
|
||||
name: "${name} pv battery status bitmask"
|
||||
pv_voltage:
|
||||
name: "${name} pv voltage"
|
||||
pv_current:
|
||||
name: "${name} pv current"
|
||||
pv_power:
|
||||
name: "${name} pv power"
|
||||
|
||||
text_sensor:
|
||||
- platform: votronic
|
||||
votronic_id: votronic0
|
||||
battery_status:
|
||||
name: "${name} battery status"
|
||||
|
||||
charger_mode_setting:
|
||||
name: "${name} charger mode setting"
|
||||
charger_battery_status:
|
||||
name: "${name} charger battery status"
|
||||
charger_controller_status:
|
||||
name: "${name} charger controller status"
|
||||
|
||||
charging_converter_mode_setting:
|
||||
name: "${name} charging converter mode setting"
|
||||
charging_converter_battery_status:
|
||||
name: "${name} charging converter battery status"
|
||||
charging_converter_controller_status:
|
||||
name: "${name} charging converter controller status"
|
||||
|
||||
pv_mode_setting:
|
||||
name: "${name} pv mode setting"
|
||||
pv_controller_status:
|
||||
name: "${name} pv controller status"
|
||||
pv_battery_status:
|
||||
name: "${name} pv battery status"
|
58
setup.cfg
Normal file
58
setup.cfg
Normal file
@@ -0,0 +1,58 @@
|
||||
[flake8]
|
||||
max-line-length = 120
|
||||
# Following 4 for black compatibility
|
||||
# E501: line too long
|
||||
# W503: Line break occurred before a binary operator
|
||||
# E203: Whitespace before ':'
|
||||
# D202 No blank lines allowed after function docstring
|
||||
|
||||
# TODO fix flake8
|
||||
# D100 Missing docstring in public module
|
||||
# D101 Missing docstring in public class
|
||||
# D102 Missing docstring in public method
|
||||
# D103 Missing docstring in public function
|
||||
# D104 Missing docstring in public package
|
||||
# D105 Missing docstring in magic method
|
||||
# D107 Missing docstring in __init__
|
||||
# D200 One-line docstring should fit on one line with quotes
|
||||
# D205 1 blank line required between summary line and description
|
||||
# D209 Multi-line docstring closing quotes should be on a separate line
|
||||
# D400 First line should end with a period
|
||||
# D401 First line should be in imperative mood
|
||||
|
||||
ignore =
|
||||
E501,
|
||||
W503,
|
||||
E203,
|
||||
D202,
|
||||
|
||||
D100,
|
||||
D101,
|
||||
D102,
|
||||
D103,
|
||||
D104,
|
||||
D105,
|
||||
D107,
|
||||
D200,
|
||||
D205,
|
||||
D209,
|
||||
D400,
|
||||
D401,
|
||||
|
||||
[isort]
|
||||
# https://github.com/timothycrosley/isort
|
||||
# https://github.com/timothycrosley/isort/wiki/isort-Settings
|
||||
# splits long import on multiple lines indented by 4 spaces
|
||||
multi_line_output = 3
|
||||
include_trailing_comma=True
|
||||
force_grid_wrap=0
|
||||
use_parentheses=True
|
||||
line_length=88
|
||||
indent = " "
|
||||
# will group `import x` and `from x import` of the same module.
|
||||
force_sort_within_sections = true
|
||||
sections = FUTURE,STDLIB,THIRDPARTY,FIRSTPARTY,LOCALFOLDER
|
||||
default_section = THIRDPARTY
|
||||
known_first_party = custom_components,tests
|
||||
forced_separate = tests
|
||||
combine_as_imports = true
|
247
tests/esp32c6-compatibility-test.yaml
Normal file
247
tests/esp32c6-compatibility-test.yaml
Normal file
@@ -0,0 +1,247 @@
|
||||
substitutions:
|
||||
name: esp32c6-compatibility-test
|
||||
device0: device0
|
||||
device1: device1
|
||||
device_description: "Verify the project builds from source on ESP32C6"
|
||||
external_components_source: github://syssi/esphome-votronic@main
|
||||
mac_address: 60:A4:23:91:8F:55
|
||||
|
||||
esphome:
|
||||
name: ${name}
|
||||
comment: ${device_description}
|
||||
min_version: 2025.6.0
|
||||
|
||||
esp32:
|
||||
board: esp32-c6-devkitc-1
|
||||
variant: esp32c6
|
||||
framework:
|
||||
type: esp-idf
|
||||
|
||||
external_components:
|
||||
- source: ${external_components_source}
|
||||
refresh: 0s
|
||||
|
||||
wifi:
|
||||
ssid: !secret wifi_ssid
|
||||
password: !secret wifi_password
|
||||
|
||||
ota:
|
||||
platform: esphome
|
||||
|
||||
logger:
|
||||
level: VERY_VERBOSE
|
||||
|
||||
api:
|
||||
reboot_timeout: 0s
|
||||
|
||||
ble_client:
|
||||
- mac_address: ${mac_address}
|
||||
id: client0
|
||||
on_passkey_request:
|
||||
then:
|
||||
- ble_client.passkey_reply:
|
||||
id: client0
|
||||
passkey: 173928
|
||||
|
||||
votronic_ble:
|
||||
- ble_client_id: client0
|
||||
id: votronic0
|
||||
throttle: 5s
|
||||
|
||||
#
|
||||
#
|
||||
#
|
||||
|
||||
uart:
|
||||
- id: uart_0
|
||||
baud_rate: 1000
|
||||
tx_pin: GPIO16
|
||||
rx_pin: GPIO17
|
||||
|
||||
votronic:
|
||||
- id: votronic1
|
||||
uart_id: uart_0
|
||||
rx_timeout: 150ms
|
||||
throttle: 2s
|
||||
|
||||
#
|
||||
#
|
||||
#
|
||||
|
||||
|
||||
binary_sensor:
|
||||
- platform: votronic_ble
|
||||
votronic_ble_id: votronic0
|
||||
charging:
|
||||
name: "${device0} charging"
|
||||
discharging:
|
||||
name: "${device0} discharging"
|
||||
controller_active:
|
||||
name: "${device0} controller active"
|
||||
current_reduction:
|
||||
name: "${device0} current reduction"
|
||||
aes_active:
|
||||
name: "${device0} aes active"
|
||||
|
||||
- platform: votronic
|
||||
votronic_id: votronic1
|
||||
charging:
|
||||
name: "${device1} charging"
|
||||
discharging:
|
||||
name: "${device1} discharging"
|
||||
|
||||
charger_charging:
|
||||
name: "${device1} charger charging"
|
||||
charger_discharging:
|
||||
name: "${device1} charger discharging"
|
||||
charger_controller_active:
|
||||
name: "${device1} charger controller active"
|
||||
charger_current_reduction:
|
||||
name: "${device1} charger current reduction"
|
||||
charger_aes_active:
|
||||
name: "${device1} charger aes active"
|
||||
|
||||
charging_converter_charging:
|
||||
name: "${device1} charging converter charging"
|
||||
charging_converter_discharging:
|
||||
name: "${device1} charging converter discharging"
|
||||
charging_converter_controller_active:
|
||||
name: "${device1} charging converter controller active"
|
||||
charging_converter_current_reduction:
|
||||
name: "${device1} charging converter current reduction"
|
||||
charging_converter_aes_active:
|
||||
name: "${device1} charging converter aes active"
|
||||
|
||||
pv_controller_active:
|
||||
name: "${device1} pv controller active"
|
||||
pv_current_reduction:
|
||||
name: "${device1} pv current reduction"
|
||||
pv_aes_active:
|
||||
name: "${device1} pv aes active"
|
||||
|
||||
sensor:
|
||||
- platform: votronic_ble
|
||||
votronic_ble_id: votronic0
|
||||
# Battery computer
|
||||
battery_voltage:
|
||||
name: "${device0} battery voltage"
|
||||
secondary_battery_voltage:
|
||||
name: "${device0} secondary battery voltage"
|
||||
battery_capacity_remaining:
|
||||
name: "${device0} battery capacity remaining"
|
||||
state_of_charge:
|
||||
name: "${device0} state of charge"
|
||||
current:
|
||||
name: "${device0} current"
|
||||
power:
|
||||
name: "${device0} power"
|
||||
battery_nominal_capacity:
|
||||
name: "${device0} battery nominal capacity"
|
||||
|
||||
# Solar charger
|
||||
pv_voltage:
|
||||
name: "${device0} pv voltage"
|
||||
pv_current:
|
||||
name: "${device0} pv current"
|
||||
battery_status_bitmask:
|
||||
name: "${device0} battery status bitmask"
|
||||
pv_controller_status_bitmask:
|
||||
name: "${device0} pv controller status bitmask"
|
||||
charged_capacity:
|
||||
name: "${device0} charged capacity"
|
||||
charged_energy:
|
||||
name: "${device0} charged energy"
|
||||
pv_power:
|
||||
name: "${device0} pv power"
|
||||
|
||||
- platform: votronic
|
||||
votronic_id: votronic1
|
||||
battery_voltage:
|
||||
name: "${device1} battery voltage"
|
||||
current:
|
||||
name: "${device1} current"
|
||||
power:
|
||||
name: "${device1} power"
|
||||
battery_status_bitmask:
|
||||
name: "${device1} battery status bitmask"
|
||||
|
||||
charger_current:
|
||||
name: "${device1} charger current"
|
||||
charger_power:
|
||||
name: "${device1} charger power"
|
||||
charger_load:
|
||||
name: "${device1} charger load"
|
||||
charger_controller_temperature:
|
||||
name: "${device1} charger controller temperature"
|
||||
charger_mode_setting_id:
|
||||
name: "${device1} charger mode setting id"
|
||||
charger_battery_status_bitmask:
|
||||
name: "${device1} charger battery status bitmask"
|
||||
charger_controller_status_bitmask:
|
||||
name: "${device1} charger controller status bitmask"
|
||||
|
||||
charging_converter_battery_voltage:
|
||||
name: "${device1} charging converter battery voltage"
|
||||
charging_converter_secondary_battery_voltage:
|
||||
name: "${device1} charging converter secondary battery voltage"
|
||||
charging_converter_current:
|
||||
name: "${device1} charging converter current"
|
||||
charging_converter_power:
|
||||
name: "${device1} charging converter power"
|
||||
charging_converter_load:
|
||||
name: "${device1} charging converter load"
|
||||
charging_converter_controller_temperature:
|
||||
name: "${device1} charging converter controller temperature"
|
||||
charging_converter_mode_setting_id:
|
||||
name: "${device1} charging converter mode setting id"
|
||||
charging_converter_battery_status_bitmask:
|
||||
name: "${device1} charging converter battery status bitmask"
|
||||
charging_converter_controller_status_bitmask:
|
||||
name: "${device1} charging converter controller status bitmask"
|
||||
|
||||
pv_controller_status_bitmask:
|
||||
name: "${device1} pv controller status bitmask"
|
||||
pv_controller_temperature:
|
||||
name: "${device1} pv controller temperature"
|
||||
pv_battery_status_bitmask:
|
||||
name: "${device1} pv battery status bitmask"
|
||||
pv_voltage:
|
||||
name: "${device1} pv voltage"
|
||||
pv_current:
|
||||
name: "${device1} pv current"
|
||||
pv_power:
|
||||
name: "${device1} pv power"
|
||||
|
||||
text_sensor:
|
||||
- platform: votronic_ble
|
||||
votronic_ble_id: votronic0
|
||||
battery_status:
|
||||
name: "${device0} battery status"
|
||||
pv_controller_status:
|
||||
name: "${device0} pv controller status"
|
||||
|
||||
- platform: votronic
|
||||
votronic_id: votronic1
|
||||
battery_status:
|
||||
name: "${device1} battery status"
|
||||
|
||||
charger_mode_setting:
|
||||
name: "${device1} charger mode setting"
|
||||
charger_battery_status:
|
||||
name: "${device1} charger battery status"
|
||||
charger_controller_status:
|
||||
name: "${device1} charger controller status"
|
||||
|
||||
charging_converter_mode_setting:
|
||||
name: "${device1} charging converter mode setting"
|
||||
charging_converter_battery_status:
|
||||
name: "${device1} charging converter battery status"
|
||||
charging_converter_controller_status:
|
||||
name: "${device1} charging converter controller status"
|
||||
|
||||
pv_mode_setting:
|
||||
name: "${device1} pv mode setting"
|
||||
pv_controller_status:
|
||||
name: "${device1} pv controller status"
|
||||
pv_battery_status:
|
||||
name: "${device1} pv battery status"
|
61
tests/esp8266-fake-charger-vbcs-triple.yaml
Normal file
61
tests/esp8266-fake-charger-vbcs-triple.yaml
Normal file
@@ -0,0 +1,61 @@
|
||||
# Battery Charger VBCS 60/40/430 Triple-CI (3246)
|
||||
# See https://github.com/syssi/esphome-votronic/issues/41
|
||||
|
||||
substitutions:
|
||||
name: fake-charger
|
||||
device_description: "Emulate the Display-Link port traffic of a Votronic Charger"
|
||||
tx_pin: GPIO4
|
||||
rx_pin: GPIO5
|
||||
|
||||
esphome:
|
||||
name: ${name}
|
||||
comment: ${device_description}
|
||||
min_version: 2024.6.0
|
||||
project:
|
||||
name: "syssi.esphome-votronic"
|
||||
version: 3.1.0
|
||||
|
||||
esp8266:
|
||||
board: d1_mini
|
||||
|
||||
wifi:
|
||||
ssid: !secret wifi_ssid
|
||||
password: !secret wifi_password
|
||||
|
||||
ota:
|
||||
platform: esphome
|
||||
|
||||
logger:
|
||||
level: DEBUG
|
||||
|
||||
api:
|
||||
reboot_timeout: 0s
|
||||
|
||||
uart:
|
||||
baud_rate: 1000
|
||||
tx_pin: ${tx_pin}
|
||||
rx_pin: ${rx_pin}
|
||||
debug:
|
||||
direction: BOTH
|
||||
dummy_receiver: true
|
||||
|
||||
interval:
|
||||
- interval: 10s
|
||||
then:
|
||||
- uart.write: [0xAA, 0x3A, 0x67, 0x05, 0xDD, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0xA6, 0x56, 0x28, 0x00, 0x58]
|
||||
- uart.write: [0xAA, 0x7A, 0x68, 0x05, 0xDD, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0xA6, 0x56, 0x28, 0x00, 0x16]
|
||||
- uart.write: [0xAA, 0x1A, 0x68, 0x05, 0x74, 0x08, 0x00, 0x00, 0x00, 0xE0, 0xA6, 0x1F, 0x56, 0x22, 0x19, 0x3F]
|
||||
- uart.write: [0xAA, 0x4A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4A]
|
||||
- delay: 2s
|
||||
|
||||
- uart.write: [0xAA, 0x7A, 0x68, 0x05, 0xDE, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0xA6, 0x56, 0x28, 0x00, 0x15]
|
||||
- uart.write: [0xAA, 0x1A, 0x67, 0x05, 0x72, 0x08, 0x00, 0x00, 0x00, 0xE0, 0xA6, 0x1F, 0x56, 0x22, 0x19, 0x36]
|
||||
- uart.write: [0xAA, 0x3A, 0x68, 0x05, 0xDE, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0xA6, 0x56, 0x28, 0x00, 0x54]
|
||||
- uart.write: [0xAA, 0x4A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4A]
|
||||
- delay: 2s
|
||||
|
||||
- uart.write: [0xAA, 0x7A, 0x67, 0x05, 0xDD, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0xA6, 0x56, 0x28, 0x00, 0x19]
|
||||
- uart.write: [0xAA, 0x1A, 0x68, 0x05, 0x72, 0x08, 0x00, 0x00, 0x00, 0xE0, 0xA6, 0x1F, 0x56, 0x22, 0x19, 0x39]
|
||||
- uart.write: [0xAA, 0x3A, 0x67, 0x05, 0xDD, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0xA6, 0x56, 0x28, 0x00, 0x58]
|
||||
- uart.write: [0xAA, 0x4A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4A]
|
||||
- delay: 2s
|
@@ -7,9 +7,10 @@ substitutions:
|
||||
esphome:
|
||||
name: ${name}
|
||||
comment: ${device_description}
|
||||
min_version: 2024.6.0
|
||||
project:
|
||||
name: "syssi.esphome-votronic"
|
||||
version: 2.0.0
|
||||
version: 3.1.0
|
||||
|
||||
esp8266:
|
||||
board: d1_mini
|
||||
@@ -19,6 +20,7 @@ wifi:
|
||||
password: !secret wifi_password
|
||||
|
||||
ota:
|
||||
platform: esphome
|
||||
|
||||
logger:
|
||||
level: DEBUG
|
||||
|
@@ -7,9 +7,10 @@ substitutions:
|
||||
esphome:
|
||||
name: ${name}
|
||||
comment: ${device_description}
|
||||
min_version: 2024.6.0
|
||||
project:
|
||||
name: "syssi.esphome-votronic"
|
||||
version: 2.0.0
|
||||
version: 3.1.0
|
||||
|
||||
esp8266:
|
||||
board: d1_mini
|
||||
@@ -19,6 +20,7 @@ wifi:
|
||||
password: !secret wifi_password
|
||||
|
||||
ota:
|
||||
platform: esphome
|
||||
|
||||
logger:
|
||||
level: DEBUG
|
||||
|
@@ -7,9 +7,10 @@ substitutions:
|
||||
esphome:
|
||||
name: ${name}
|
||||
comment: ${device_description}
|
||||
min_version: 2024.6.0
|
||||
project:
|
||||
name: "syssi.esphome-votronic"
|
||||
version: 2.0.0
|
||||
version: 3.1.0
|
||||
|
||||
esp8266:
|
||||
board: d1_mini
|
||||
@@ -19,6 +20,7 @@ wifi:
|
||||
password: !secret wifi_password
|
||||
|
||||
ota:
|
||||
platform: esphome
|
||||
|
||||
logger:
|
||||
level: DEBUG
|
||||
|
@@ -23,7 +23,7 @@ ESPHome component to monitor votronic devices via BLE or Display Link
|
||||
|
||||
## Requirements
|
||||
|
||||
* [ESPHome 2022.12.0 or higher](https://github.com/esphome/esphome/releases).
|
||||
* [ESPHome 2024.6.0 or higher](https://github.com/esphome/esphome/releases).
|
||||
* Generic ESP32 board
|
||||
|
||||
## Installation
|
||||
|
@@ -22,7 +22,7 @@ ESPHome component to monitor votronic devices via Display Link
|
||||
|
||||
## Requirements
|
||||
|
||||
* [ESPHome 2022.12.0 or higher](https://github.com/esphome/esphome/releases).
|
||||
* [ESPHome 2024.6.0 or higher](https://github.com/esphome/esphome/releases).
|
||||
* A wire with RJ11 western connector
|
||||
* Generic ESP32 or ESP8266 board
|
||||
|
||||
|
Reference in New Issue
Block a user