1
0
mirror of https://github.com/esphome/esphome.git synced 2025-11-03 08:31:47 +00:00

Compare commits

..

118 Commits

Author SHA1 Message Date
Jesse Hills
3792823c24 Add conn_id to verbose log message for ble gattc events 2022-11-07 08:00:28 +13:00
Jesse Hills
1554c5700e rp2040: Set watchdog to reboot properly (#3991) 2022-11-03 20:47:19 +13:00
dependabot[bot]
5cf257b251 Bump aioesphomeapi from 10.13.0 to 11.4.2 (#3987)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-03 20:30:23 +13:00
mbardeen
04883e14f6 Allow the use of multiple RDM6300 devices (#3989) 2022-11-02 22:25:09 -07:00
Jesse Hills
0a649c184f Always use gh releases in base platformio file (#3988) 2022-11-03 11:41:12 +13:00
Jesse Hills
0e66c899ce Allow multiple bluetooth proxy connections (#3971) 2022-11-02 23:02:33 +13:00
dependabot[bot]
e7d236f939 Bump pytest-mock from 3.8.2 to 3.10.0 (#3877)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-02 19:17:21 +13:00
dependabot[bot]
fae4d03473 Bump pylint from 2.15.3 to 2.15.5 (#3978)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-02 19:16:58 +13:00
dependabot[bot]
97bd3e7320 Bump actions/stale from 5 to 6 (#3841)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-01 17:19:28 +13:00
dependabot[bot]
dfca2f88d3 Bump pytest from 7.1.3 to 7.2.0 (#3966)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-01 17:18:22 +13:00
dependabot[bot]
8cad93de37 Bump pyupgrade from 3.0.0 to 3.2.0 (#3973)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-01 15:32:30 +13:00
Jesse Hills
e5b8dd7f2d Fix bluetooth_proxy not connecting (#3967) 2022-10-29 12:42:48 +13:00
Paulus Schoutsen
a1c8b8092b Remove gitpod (#3964) 2022-10-28 10:25:02 -04:00
dependabot[bot]
109ca2406d Bump pytest-cov from 3.0.0 to 4.0.0 (#3922)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-10-28 15:40:42 +13:00
dependabot[bot]
3a689112fd Bump tornado from 6.1 to 6.2 (#3620)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-10-28 13:29:10 +13:00
Jesse Hills
40e0cd0f03 Make some minor changes to I²C so rp2040 works (#3959) 2022-10-28 12:33:49 +13:00
Jesse Hills
bf4d3df906 Fix RP2040 SPISettings (#3960) 2022-10-28 12:33:15 +13:00
Jesse Hills
0e30c49e3f Allow using LED pin on rpi pico-w (#3957) 2022-10-28 10:06:22 +13:00
Jesse Hills
e61a01f7bb Fix imports for rp2040 with no wifi (#3956) 2022-10-28 08:12:12 +13:00
Paul Nicholls
f8640cf2cd [SM300D2] Reduce log severity for successful reads (#3955) 2022-10-27 19:27:17 +13:00
Jesse Hills
4bcfeb6e33 Update rp2040 to latest framework release from GitHub (#3954) 2022-10-27 19:26:22 +13:00
Jesse Hills
a5d4ca0f6d Some RP2040 wifi changes for AP mode (#3953) 2022-10-27 19:14:41 +13:00
Jesse Hills
85faecb2fd Add core config option to limit compile process count (#3952) 2022-10-27 12:17:21 +13:00
Jesse Hills
991fc54994 Bump esphome/Improv to 1.2.3 (#3948) 2022-10-27 07:44:15 +13:00
Jesse Hills
2de891dc32 Fix filesystem size for RP2040 OTA (#3947) 2022-10-26 22:57:08 +13:00
Jesse Hills
9865cb7f55 Implement RP2040 preferences (#3946) 2022-10-26 22:56:56 +13:00
Jesse Hills
f97252b93a Implement InterruptLock for RP2040 (#3945) 2022-10-26 16:41:51 +13:00
Jesse Hills
f2c4f018de Prefix devcontainer image with ghcr.io (#3942) 2022-10-26 12:04:29 +13:00
Franck Nijhof
237c7dd169 Add wind_speed sensor device class (#3941) 2022-10-26 07:35:12 +13:00
NP v/d Spek
b781b8d77d Fixed touch release issue using the interrupt pin (#3925)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2022-10-26 07:33:18 +13:00
Franck Nijhof
60b7d1c8a1 Add water & precipitation_intensity sensor device classes (#3940) 2022-10-25 20:37:19 +13:00
cvwillegen
8161222b33 Show local mac when scanning wifi (#3635)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2022-10-25 19:50:46 +13:00
H. Árkosi Róbert
1000c4466f add proper device class to uptime (#3928)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2022-10-25 19:10:03 +13:00
Jesse Hills
60717b074e Implement different random for rp2040 (#3939) 2022-10-25 17:59:10 +13:00
Jesse Hills
c3fba97b4c Update base platformio board for rp2040 (#3937) 2022-10-25 17:46:55 +13:00
Jesse Hills
d93f35701f Send manufacturer name via API (#3938) 2022-10-25 17:24:38 +13:00
Jesse Hills
702b60ce66 Make mDNS on rp2040 work (#3936) 2022-10-25 16:23:20 +13:00
Jesse Hills
f8ce597918 Fix error with require_framework_version and rp2040 (#3923) 2022-10-25 13:47:48 +13:00
Jesse Hills
22e0a944c8 Update bluetooth proxy limit as soon as connection requested (#3935) 2022-10-25 12:54:10 +13:00
Jesse Hills
3a134ef009 Update the PR template (#3934) 2022-10-25 12:53:01 +13:00
Jesse Hills
96e8cb66b6 Fix missing dependencies for heatpumpir (#3933) 2022-10-25 12:12:55 +13:00
Jesse Hills
615288c151 Bump esphome-dashboard to 20221020.0 (#3920) 2022-10-20 08:59:41 -04:00
Jesse Hills
6153bcc6ad Initial Support for RP2040 platform (#3284)
Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
2022-10-20 16:50:39 +13:00
RoboMagus
e87edcc77a Add API interface to request a complete device config as JSON. (#3911)
Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>
2022-10-19 23:39:34 -04:00
dependabot[bot]
a21c3e8e2d Bump platformio from 6.0.2 to 6.1.4 (#3711)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2022-10-20 12:49:11 +13:00
hagak
d7576f67e8 Added component Daikin BRC to support ceiling cassette heatpumps (#3743) 2022-10-19 20:29:22 +13:00
Maximilian
138de643a2 Add adc128s102 sensor (#3822)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2022-10-19 17:06:22 +13:00
Carlos Gustavo Sarmiento
f30e54d177 Implementation for Atlas Scientific Peristaltic Pump (#3528)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2022-10-19 16:08:27 +13:00
Jadson Santos
41b5cb06d3 New platform ethernet_info from component text_sensor (#3811)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2022-10-19 13:44:48 +13:00
Marcel Hoppe
4ac72d7d08 Add support for wl-134 (#3569)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2022-10-19 13:44:26 +13:00
Jesse Hills
06ac4980ba Merge branch 'release' into dev 2022-10-19 11:15:19 +13:00
Jesse Hills
ccbfa20bb9 Merge pull request #3918 from esphome/bump-2022.10.0
2022.10.0
2022-10-19 11:13:16 +13:00
Jesse Hills
58cd754e07 Fix bad merge 2022-10-19 10:50:32 +13:00
Jesse Hills
d1263e583b Bump version to 2022.10.0 2022-10-19 10:30:22 +13:00
Jesse Hills
67c911c37f Merge branch 'beta' into bump-2022.10.0 2022-10-19 10:30:21 +13:00
Jesse Hills
288e3c3e3e Merge pull request #3909 from esphome/bump-2022.10.0b2
2022.10.0b2
2022-10-15 09:12:58 +13:00
Jesse Hills
a84378c6c2 Bump version to 2022.10.0b2 2022-10-15 08:37:11 +13:00
Jesse Hills
b6073408f4 Remove address type map from bluetooth proxy (#3905) 2022-10-15 08:37:11 +13:00
Frédéric Jouault
b2d91ac5de Send true and not RSSI in ble_presence (#3904) 2022-10-15 08:37:11 +13:00
Sergey Dudanov
8bf34e09f4 Modbus QWORD fix (#3856) 2022-10-15 08:37:11 +13:00
Guillermo Ruffino
be914f2c15 fix never calling preset change trigger (#3864)
Co-authored-by: Keith Burzinski <kbx81x@gmail.com>
2022-10-15 08:37:11 +13:00
Jesse Hills
8bb670521d Remove address type map from bluetooth proxy (#3905) 2022-10-15 08:35:35 +13:00
Frédéric Jouault
225b3c1494 Send true and not RSSI in ble_presence (#3904) 2022-10-14 12:47:05 +13:00
Kuba Szczodrzyński
4bf94e0757 Allow preserving WiFi credentials entered with captive_portal (#3813) 2022-10-14 08:58:42 +13:00
Brian Kaufman
3b21d1d81e Don't Use Base Network Manual IP for WiFi AP (#3902) 2022-10-14 08:55:59 +13:00
Frank Riley
5ec1588110 Update the ibeacon code (#3859) 2022-10-13 12:59:07 +13:00
Sergey Dudanov
71387be72e Modbus QWORD fix (#3856) 2022-10-13 12:50:45 +13:00
Guillermo Ruffino
98171c9f49 fix never calling preset change trigger (#3864)
Co-authored-by: Keith Burzinski <kbx81x@gmail.com>
2022-10-13 10:11:59 +13:00
Jesse Hills
a6c999dea0 Merge pull request #3900 from esphome/bump-2022.10.0b1
2022.10.0b1
2022-10-13 09:50:04 +13:00
Jesse Hills
bf15b1d302 Bump version to 2022.11.0-dev 2022-10-13 09:18:46 +13:00
Jesse Hills
f422fabab4 Bump version to 2022.10.0b1 2022-10-13 09:18:46 +13:00
Jesse Hills
01b130ec59 Merge branch 'dev' into bump-2022.10.0b1 2022-10-13 09:18:46 +13:00
definitio
48a1797e72 Do not require CS pin for ST7789V (#3888)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2022-10-13 08:26:28 +13:00
Jesse Hills
b34d24735a Send GATT error events to HA (#3884) 2022-10-12 22:22:07 +13:00
Chris Feenstra
fe38b36c26 Add support for ZHLT01 heatpump IR protocol (#3655)
Co-authored-by: Chris Feenstra <chris@cfeenstra.nl>
2022-10-12 15:29:57 +13:00
cstaahl
03fca8d91e Fix pulse_meter filter logic (#3321) 2022-10-12 15:26:35 +13:00
RoboMagus
9f9980e338 Add ble RSSI sensor for connected devices (#3860) 2022-10-12 15:23:56 +13:00
Quentin Smith
19900b004b Fix type annotation on extract_registry_entry_config (#3623)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2022-10-12 15:15:03 +13:00
Gustavo Ambrozio
a8ff0a8913 Exposing coordinates from touchscreen binary sensor (#3891) 2022-10-11 12:22:13 +13:00
RoboMagus
45861456f1 Fix default unit for ble_rssi sensor (#3874) 2022-10-11 12:03:54 +13:00
RoboMagus
44b335e7e3 Correctly set ble_write UUIDs based on their lengths. (#3885) 2022-10-11 12:02:53 +13:00
Jesse Hills
de23bbace2 Update webserver index file (#3896) 2022-10-11 12:01:41 +13:00
RoboMagus
edff9ae322 Proxy friendly host url resolution for use_address with path. (#3653) 2022-10-11 12:01:31 +13:00
NP v/d Spek
3c2766448d Refactor xpt2046 to be a touchscreen platform (#3793) 2022-10-11 10:10:22 +13:00
Jesse Hills
786c8b6cfe Add new sensor device classes (#3895) 2022-10-11 09:54:58 +13:00
Jesse Hills
fd57b21aff Dont add wifi block to yaml if discovered device uses ethernet (#3882) 2022-10-07 15:35:48 +13:00
Jesse Hills
6087183a0c Bump esphome-dashboard to 20221007.0 (#3881) 2022-10-07 15:20:13 +13:00
Jesse Hills
01b7c4200e Add network type to mdns service message (#3880) 2022-10-07 14:42:28 +13:00
dependabot[bot]
7171286c3c Bump pylint from 2.15.2 to 2.15.3 (#3870)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-10-06 14:57:07 +13:00
Jesse Hills
d220d41182 Bump python min to 3.9 (#3871) 2022-10-05 20:09:27 +13:00
Jesse Hills
c3a8972550 Add min_version to esphome config (#3866) 2022-10-05 16:30:56 +13:00
dependabot[bot]
263b603188 Bump pyupgrade from 2.37.3 to 3.0.0 (#3867)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2022-10-05 16:29:49 +13:00
Javier Peletier
584b722e7e Fix time/automation (cron) wdt crash when time jumps ahead too much (#3844) 2022-10-05 14:52:45 +13:00
Javier Peletier
05edfd0e82 Add cg.with_local_variable (#3577) 2022-10-05 11:50:03 +13:00
Jesse Hills
16249c02a5 Bump CI to python 3.9 (#3869) 2022-10-05 11:21:13 +13:00
Guillermo Ruffino
e8ff36d1f3 fix dump preset string type (#3863) 2022-10-04 10:50:33 +13:00
Jesse Hills
ed443c6153 Bluetooth Proxy active connections (#3817) 2022-10-04 10:45:06 +13:00
Keith Burzinski
f4a84765cd Add display GPIO setup instruction for Aliexpress display (#3851) 2022-09-30 19:10:53 +13:00
J. Nick Koston
106de3530d Add support for parsing the short local name in the tracker (#3854) 2022-09-30 09:15:30 +13:00
Michael Davidson
8095db6715 Thermostat remove deprecated config (#3643)
* Raise errors for all the now deprecated options

* Fix CONF_DEFAULT_PRESET detection

* Stop attempting to set the non-existent normal_config

* Add support for default presets

* Fix correct detection of Two Point temperature mode

* Fix lint issues

* Fix tests

* Generate correct yaml for equivalent configurations

* Remove debug code

* Only set default preset if the thermostat does not have state to restore

* Add restore_default_preset_on_boot option
If set to True then the default_preset will be applied on every boot. If False (Default) state will be restored from memory as per prior versions

* Apply lint suggestions

* Switch from restore_default_preset_on_boot to an enum for startup_behavior
This gives better self-documentation as well as the option for extending to other options down the track

* Lint fixes

* Rename startup_behavior to on_boot_restore_from
This removes any issues with different English locales

* Fix comparable_preset yaml output alignment

* Add dump of on_boot_restore_from setting

Co-authored-by: Keith Burzinski <kbx81x@gmail.com>
2022-09-25 21:59:04 -05:00
Guillermo Ruffino
ce2e161b08 Bump dashboard to 20220925.0 (#3846) 2022-09-25 18:16:30 -03:00
Jesse Hills
6ef93452f5 Revert "fix spi timing issues" (#3838) 2022-09-22 07:38:31 +12:00
Guillermo Ruffino
68ea59f3ae Bump dashboard to 20220920.1 (#3834) 2022-09-21 12:32:53 +12:00
Jesse Hills
9a69769a7e Dont fail fast on CI for docker (#3832) 2022-09-21 07:54:35 +12:00
Paulus Schoutsen
3572c62315 Bump dashboard to 20220920.0 (#3831) 2022-09-21 07:35:46 +12:00
Guillermo Ruffino
1444cddda9 Fix-esphome-validation-line-number (#3815) 2022-09-20 17:23:55 +12:00
Paulus Schoutsen
b2db524366 Bump dashboard to 20220919.1 (#3828) 2022-09-20 17:13:59 +12:00
Keith Burzinski
ab8674a5c7 Make sprinkler reset_resume() method public (#3824) 2022-09-20 13:02:55 +12:00
Geek_cat
d1c85fc3fa Allow CORS for web_server (#3819) 2022-09-20 13:01:00 +12:00
h3ndrik
55ad45e3ee [BME280] raise standby time (#3804) 2022-09-19 07:25:59 +12:00
pawel3410
f6e5a8cb2a Fix mcp23s17 addressing beyond 3 (#3797) 2022-09-15 18:19:41 -07:00
Samuel Sieb
7a91ca9809 split pronto codes if they are too long (#3812)
Co-authored-by: Samuel Sieb <samuel@sieb.net>
2022-09-16 07:27:50 +12:00
Azimath
917bbc669c Remove floating point calculation from ac_dimmer ISR (#3770) 2022-09-15 11:54:33 +12:00
Jesse Hills
0ac4c055de Initialize all child sensors to nullptr (#3808) 2022-09-15 11:53:22 +12:00
RoboMagus
78b55d86e9 Unify 'nullptr' initalization of class members; (#3805) 2022-09-15 11:53:02 +12:00
Guillermo Ruffino
aaf50fc2e6 Sim800l add calls, multiline sms and ussd (#3630)
Co-authored-by: Matus Ivanecky <matus.ivanecky@gmail.com>
Co-authored-by: Matus Ivanecky <maty535@users.noreply.github.com>
2022-09-15 07:43:03 +12:00
RoboMagus
6a8f4e92df null initialize total sensor for pulse counter (#3803)
* null initialize total sensor.

* pedantic styling fix

Co-authored-by: Guillermo Ruffino <glm.net@gmail.com>

Co-authored-by: Guillermo Ruffino <glm.net@gmail.com>
2022-09-14 10:01:28 -03:00
Jesse Hills
88943103a2 Bump version to 2022.10.0-dev 2022-09-14 17:02:22 +12:00
180 changed files with 5893 additions and 1234 deletions

View File

@@ -1,6 +1,6 @@
{
"name": "ESPHome Dev",
"image": "esphome/esphome-lint:dev",
"image": "ghcr.io/esphome/esphome-lint:dev",
"postCreateCommand": [
"script/devcontainer-post-create"
],

View File

@@ -1,6 +1,6 @@
# What does this implement/fix?
Quick description and explanation of changes
<!-- Quick description and explanation of changes -->
## Types of changes
@@ -18,6 +18,7 @@ Quick description and explanation of changes
- [ ] ESP32
- [ ] ESP32 IDF
- [ ] ESP8266
- [ ] RP2040
## Example entry for `config.yaml`:
<!--

View File

@@ -28,6 +28,7 @@ jobs:
name: Build docker containers
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
arch: [amd64, armv7, aarch64]
build_type: ["ha-addon", "docker", "lint"]

View File

@@ -48,6 +48,10 @@ jobs:
file: tests/test5.yaml
name: Test tests/test5.yaml
pio_cache_key: test5
- id: test
file: tests/test6.yaml
name: Test tests/test6.yaml
pio_cache_key: test6
- id: pytest
name: Run pytest
- id: clang-format
@@ -85,7 +89,7 @@ jobs:
uses: actions/setup-python@v4
id: python
with:
python-version: "3.8"
python-version: "3.9"
- name: Cache virtualenv
uses: actions/cache@v3

View File

@@ -18,7 +18,7 @@ jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v5
- uses: actions/stale@v6
with:
days-before-pr-stale: 90
days-before-pr-close: 7
@@ -38,7 +38,7 @@ jobs:
close-issues:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v5
- uses: actions/stale@v6
with:
days-before-pr-stale: -1
days-before-pr-close: -1

View File

@@ -1,8 +0,0 @@
---
ports:
- port: 6052
onOpen: open-preview
tasks:
# yamllint disable-line rule:line-length
- before: pyenv local $(pyenv version | grep '^3\.' | cut -d ' ' -f 1) && script/setup
command: python -m esphome dashboard config

View File

@@ -27,7 +27,7 @@ repos:
- --branch=release
- --branch=beta
- repo: https://github.com/asottile/pyupgrade
rev: v2.37.3
rev: v3.0.0
hooks:
- id: pyupgrade
args: [--py38-plus]
args: [--py39-plus]

View File

@@ -13,6 +13,7 @@ esphome/core/* @esphome/core
# Integrations
esphome/components/ac_dimmer/* @glmnet
esphome/components/adc/* @esphome/core
esphome/components/adc128s102/* @DeerMaximum
esphome/components/addressable_light/* @justfalter
esphome/components/airthings_ble/* @jeromelaban
esphome/components/airthings_wave_mini/* @ncareau
@@ -57,6 +58,7 @@ esphome/components/cse7761/* @berfenger
esphome/components/ct_clamp/* @jesserockz
esphome/components/current_based/* @djwmarcx
esphome/components/dac7678/* @NickB1
esphome/components/daikin_brc/* @hagak
esphome/components/daly_bms/* @s1lvi0
esphome/components/dashboard_import/* @esphome/core
esphome/components/debug/* @OttoWinter
@@ -76,8 +78,10 @@ esphome/components/esp32_camera_web_server/* @ayufan
esphome/components/esp32_can/* @Sympatron
esphome/components/esp32_improv/* @jesserockz
esphome/components/esp8266/* @esphome/core
esphome/components/ethernet_info/* @gtjadsonsantos
esphome/components/exposure_notifications/* @OttoWinter
esphome/components/ezo/* @ssieb
esphome/components/ezo_pmp/* @carlos-sarmiento
esphome/components/factory_reset/* @anatoly-savchenkov
esphome/components/fastled_base/* @OttoWinter
esphome/components/feedback/* @ianchi
@@ -180,6 +184,8 @@ esphome/components/rc522_spi/* @glmnet
esphome/components/restart/* @esphome/core
esphome/components/rf_bridge/* @jesserockz
esphome/components/rgbct/* @jesserockz
esphome/components/rp2040/* @jesserockz
esphome/components/rp2040_pwm/* @jesserockz
esphome/components/rtttl/* @glmnet
esphome/components/safe_mode/* @jsuanet @paulmonigatti
esphome/components/scd4x/* @martgras @sjtrny
@@ -254,8 +260,9 @@ esphome/components/wake_on_lan/* @willwill2will54
esphome/components/web_server_base/* @OttoWinter
esphome/components/whirlpool/* @glmnet
esphome/components/whynter/* @aeonsablaze
esphome/components/wl_134/* @hobbypunk90
esphome/components/xiaomi_lywsd03mmc/* @ahpohl
esphome/components/xiaomi_mhoc303/* @drug123
esphome/components/xiaomi_mhoc401/* @vevsvevs
esphome/components/xiaomi_rtcgq02lm/* @jesserockz
esphome/components/xpt2046/* @numo68
esphome/components/xpt2046/* @nielsnl68 @numo68

View File

@@ -46,7 +46,7 @@ RUN \
# Ubuntu python3-pip is missing wheel
pip3 install --no-cache-dir \
wheel==0.37.1 \
platformio==6.0.2 \
platformio==6.1.4 \
# Change some platformio settings
&& platformio settings set enable_telemetry No \
&& platformio settings set check_platformio_interval 1000000 \

View File

@@ -4,6 +4,7 @@ import logging
import os
import re
import sys
import time
from datetime import datetime
from esphome import const, writer, yaml_util
@@ -22,6 +23,9 @@ from esphome.const import (
CONF_ESPHOME,
CONF_PLATFORMIO_OPTIONS,
CONF_SUBSTITUTIONS,
PLATFORM_ESP32,
PLATFORM_ESP8266,
PLATFORM_RP2040,
SECRETS_FILES,
)
from esphome.core import CORE, EsphomeError, coroutine
@@ -101,11 +105,11 @@ def run_miniterm(config, port):
if CONF_LOGGER not in config:
_LOGGER.info("Logger is not enabled. Not starting UART logs.")
return
return 1
baud_rate = config["logger"][CONF_BAUD_RATE]
if baud_rate == 0:
_LOGGER.info("UART logging is disabled (baud_rate=0). Not starting UART logs.")
return
return 1
_LOGGER.info("Starting log output from %s with baud rate %s", port, baud_rate)
backtrace_state = False
@@ -119,25 +123,34 @@ def run_miniterm(config, port):
ser.dtr = False
ser.rts = False
with ser:
while True:
try:
raw = ser.readline()
except serial.SerialException:
_LOGGER.error("Serial port closed!")
return
line = (
raw.replace(b"\r", b"")
.replace(b"\n", b"")
.decode("utf8", "backslashreplace")
)
time = datetime.now().time().strftime("[%H:%M:%S]")
message = time + line
safe_print(message)
tries = 0
while tries < 5:
try:
with ser:
while True:
try:
raw = ser.readline()
except serial.SerialException:
_LOGGER.error("Serial port closed!")
return 0
line = (
raw.replace(b"\r", b"")
.replace(b"\n", b"")
.decode("utf8", "backslashreplace")
)
time_str = datetime.now().time().strftime("[%H:%M:%S]")
message = time_str + line
safe_print(message)
backtrace_state = platformio_api.process_stacktrace(
config, line, backtrace_state=backtrace_state
)
backtrace_state = platformio_api.process_stacktrace(
config, line, backtrace_state=backtrace_state
)
except serial.SerialException:
tries += 1
time.sleep(1)
if tries >= 5:
_LOGGER.error("Could not connect to serial port %s", port)
return 1
def wrap_to_code(name, comp):
@@ -258,9 +271,21 @@ def upload_using_esptool(config, port):
def upload_program(config, args, host):
# if upload is to a serial port use platformio, otherwise assume ota
if get_port_type(host) == "SERIAL":
return upload_using_esptool(config, host)
if CORE.target_platform in (PLATFORM_ESP32, PLATFORM_ESP8266):
return upload_using_esptool(config, host)
if CORE.target_platform in (PLATFORM_RP2040):
from esphome import platformio_api
upload_args = ["-t", "upload"]
if args.device is not None:
upload_args += ["--upload-port", args.device]
return platformio_api.run_platformio_cli_run(
config, CORE.verbose, *upload_args
)
return 1 # Unknown target platform
from esphome import espota2
@@ -280,8 +305,7 @@ def show_logs(config, args, port):
if "logger" not in config:
raise EsphomeError("Logger is not configured!")
if get_port_type(port) == "SERIAL":
run_miniterm(config, port)
return 0
return run_miniterm(config, port)
if get_port_type(port) == "NETWORK" and "api" in config:
from esphome.components.api.client import run_logs

View File

@@ -22,6 +22,7 @@ from esphome.cpp_generator import ( # noqa
static_const_array,
statement,
variable,
with_local_variable,
new_variable,
Pvariable,
new_Pvariable,

View File

@@ -0,0 +1,23 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import spi
from esphome.const import CONF_ID
DEPENDENCIES = ["spi"]
MULTI_CONF = True
CODEOWNERS = ["@DeerMaximum"]
adc128s102_ns = cg.esphome_ns.namespace("adc128s102")
ADC128S102 = adc128s102_ns.class_("ADC128S102", cg.Component, spi.SPIDevice)
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.declare_id(ADC128S102),
}
).extend(spi.spi_device_schema(cs_pin_required=True))
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await spi.register_spi_device(var, config)

View File

@@ -0,0 +1,35 @@
#include "adc128s102.h"
#include "esphome/core/log.h"
namespace esphome {
namespace adc128s102 {
static const char *const TAG = "adc128s102";
float ADC128S102::get_setup_priority() const { return setup_priority::HARDWARE; }
void ADC128S102::setup() {
ESP_LOGCONFIG(TAG, "Setting up adc128s102");
this->spi_setup();
}
void ADC128S102::dump_config() {
ESP_LOGCONFIG(TAG, "ADC128S102:");
LOG_PIN(" CS Pin:", this->cs_);
}
uint16_t ADC128S102::read_data(uint8_t channel) {
uint8_t control = channel << 3;
this->enable();
uint8_t adc_primary_byte = this->transfer_byte(control);
uint8_t adc_secondary_byte = this->transfer_byte(0x00);
this->disable();
uint16_t digital_value = adc_primary_byte << 8 | adc_secondary_byte;
return digital_value;
}
} // namespace adc128s102
} // namespace esphome

View File

@@ -0,0 +1,23 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/hal.h"
#include "esphome/components/spi/spi.h"
namespace esphome {
namespace adc128s102 {
class ADC128S102 : public Component,
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW, spi::CLOCK_PHASE_LEADING,
spi::DATA_RATE_10MHZ> {
public:
ADC128S102() = default;
void setup() override;
void dump_config() override;
float get_setup_priority() const override;
uint16_t read_data(uint8_t channel);
};
} // namespace adc128s102
} // namespace esphome

View File

@@ -0,0 +1,35 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import sensor, voltage_sampler
from esphome.const import CONF_ID, CONF_CHANNEL
from .. import adc128s102_ns, ADC128S102
AUTO_LOAD = ["voltage_sampler"]
DEPENDENCIES = ["adc128s102"]
ADC128S102Sensor = adc128s102_ns.class_(
"ADC128S102Sensor",
sensor.Sensor,
cg.PollingComponent,
voltage_sampler.VoltageSampler,
)
CONF_ADC128S102_ID = "adc128s102_id"
CONFIG_SCHEMA = sensor.SENSOR_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(ADC128S102Sensor),
cv.GenerateID(CONF_ADC128S102_ID): cv.use_id(ADC128S102),
cv.Required(CONF_CHANNEL): cv.int_range(min=0, max=7),
}
).extend(cv.polling_component_schema("60s"))
async def to_code(config):
var = cg.new_Pvariable(
config[CONF_ID],
config[CONF_CHANNEL],
)
await cg.register_parented(var, config[CONF_ADC128S102_ID])
await cg.register_component(var, config)
await sensor.register_sensor(var, config)

View File

@@ -0,0 +1,24 @@
#include "adc128s102_sensor.h"
#include "esphome/core/log.h"
namespace esphome {
namespace adc128s102 {
static const char *const TAG = "adc128s102.sensor";
ADC128S102Sensor::ADC128S102Sensor(uint8_t channel) : channel_(channel) {}
float ADC128S102Sensor::get_setup_priority() const { return setup_priority::DATA; }
void ADC128S102Sensor::dump_config() {
LOG_SENSOR("", "ADC128S102 Sensor", this);
ESP_LOGCONFIG(TAG, " Pin: %u", this->channel_);
LOG_UPDATE_INTERVAL(this);
}
float ADC128S102Sensor::sample() { return this->parent_->read_data(this->channel_); }
void ADC128S102Sensor::update() { this->publish_state(this->sample()); }
} // namespace adc128s102
} // namespace esphome

View File

@@ -0,0 +1,29 @@
#pragma once
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/voltage_sampler/voltage_sampler.h"
#include "esphome/core/component.h"
#include "esphome/core/hal.h"
#include "../adc128s102.h"
namespace esphome {
namespace adc128s102 {
class ADC128S102Sensor : public PollingComponent,
public Parented<ADC128S102>,
public sensor::Sensor,
public voltage_sampler::VoltageSampler {
public:
ADC128S102Sensor(uint8_t channel);
void update() override;
void dump_config() override;
float get_setup_priority() const override;
float sample() override;
protected:
uint8_t channel_;
};
} // namespace adc128s102
} // namespace esphome

View File

@@ -13,7 +13,7 @@ _LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ["display"]
MULTI_CONF = True
Animation_ = display.display_ns.class_("Animation")
Animation_ = display.display_ns.class_("Animation", espImage.Image_)
ANIMATION_SCHEMA = cv.Schema(
{

View File

@@ -204,6 +204,8 @@ message DeviceInfoResponse {
uint32 webserver_port = 10;
uint32 bluetooth_proxy_version = 11;
string manufacturer = 12;
}
message ListEntitiesRequest {
@@ -1298,3 +1300,31 @@ message BluetoothConnectionsFreeResponse {
uint32 free = 1;
uint32 limit = 2;
}
message BluetoothGATTErrorResponse {
option (id) = 82;
option (source) = SOURCE_SERVER;
option (ifdef) = "USE_BLUETOOTH_PROXY";
uint64 address = 1;
uint32 handle = 2;
int32 error = 3;
}
message BluetoothGATTWriteResponse {
option (id) = 83;
option (source) = SOURCE_SERVER;
option (ifdef) = "USE_BLUETOOTH_PROXY";
uint64 address = 1;
uint32 handle = 2;
}
message BluetoothGATTNotifyResponse {
option (id) = 84;
option (source) = SOURCE_SERVER;
option (ifdef) = "USE_BLUETOOTH_PROXY";
uint64 address = 1;
uint32 handle = 2;
}

View File

@@ -932,6 +932,11 @@ DeviceInfoResponse APIConnection::device_info(const DeviceInfoRequest &msg) {
resp.mac_address = get_mac_address_pretty();
resp.esphome_version = ESPHOME_VERSION;
resp.compilation_time = App.get_compilation_time();
#if defined(USE_ESP8266) || defined(USE_ESP32)
resp.manufacturer = "Espressif";
#elif defined(USE_RP2040)
resp.manufacturer = "Raspberry Pi";
#endif
resp.model = ESPHOME_BOARD;
#ifdef USE_DEEP_SLEEP
resp.has_deep_sleep = deep_sleep::global_has_deep_sleep;

View File

@@ -576,6 +576,10 @@ bool DeviceInfoResponse::decode_length(uint32_t field_id, ProtoLengthDelimited v
this->project_version = value.as_string();
return true;
}
case 12: {
this->manufacturer = value.as_string();
return true;
}
default:
return false;
}
@@ -592,6 +596,7 @@ void DeviceInfoResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(9, this->project_version);
buffer.encode_uint32(10, this->webserver_port);
buffer.encode_uint32(11, this->bluetooth_proxy_version);
buffer.encode_string(12, this->manufacturer);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void DeviceInfoResponse::dump_to(std::string &out) const {
@@ -642,6 +647,10 @@ void DeviceInfoResponse::dump_to(std::string &out) const {
sprintf(buffer, "%u", this->bluetooth_proxy_version);
out.append(buffer);
out.append("\n");
out.append(" manufacturer: ");
out.append("'").append(this->manufacturer).append("'");
out.append("\n");
out.append("}");
}
#endif
@@ -5746,6 +5755,118 @@ void BluetoothConnectionsFreeResponse::dump_to(std::string &out) const {
out.append("}");
}
#endif
bool BluetoothGATTErrorResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 1: {
this->address = value.as_uint64();
return true;
}
case 2: {
this->handle = value.as_uint32();
return true;
}
case 3: {
this->error = value.as_int32();
return true;
}
default:
return false;
}
}
void BluetoothGATTErrorResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_uint64(1, this->address);
buffer.encode_uint32(2, this->handle);
buffer.encode_int32(3, this->error);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void BluetoothGATTErrorResponse::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
out.append("BluetoothGATTErrorResponse {\n");
out.append(" address: ");
sprintf(buffer, "%llu", this->address);
out.append(buffer);
out.append("\n");
out.append(" handle: ");
sprintf(buffer, "%u", this->handle);
out.append(buffer);
out.append("\n");
out.append(" error: ");
sprintf(buffer, "%d", this->error);
out.append(buffer);
out.append("\n");
out.append("}");
}
#endif
bool BluetoothGATTWriteResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 1: {
this->address = value.as_uint64();
return true;
}
case 2: {
this->handle = value.as_uint32();
return true;
}
default:
return false;
}
}
void BluetoothGATTWriteResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_uint64(1, this->address);
buffer.encode_uint32(2, this->handle);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void BluetoothGATTWriteResponse::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
out.append("BluetoothGATTWriteResponse {\n");
out.append(" address: ");
sprintf(buffer, "%llu", this->address);
out.append(buffer);
out.append("\n");
out.append(" handle: ");
sprintf(buffer, "%u", this->handle);
out.append(buffer);
out.append("\n");
out.append("}");
}
#endif
bool BluetoothGATTNotifyResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 1: {
this->address = value.as_uint64();
return true;
}
case 2: {
this->handle = value.as_uint32();
return true;
}
default:
return false;
}
}
void BluetoothGATTNotifyResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_uint64(1, this->address);
buffer.encode_uint32(2, this->handle);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void BluetoothGATTNotifyResponse::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
out.append("BluetoothGATTNotifyResponse {\n");
out.append(" address: ");
sprintf(buffer, "%llu", this->address);
out.append(buffer);
out.append("\n");
out.append(" handle: ");
sprintf(buffer, "%u", this->handle);
out.append(buffer);
out.append("\n");
out.append("}");
}
#endif
} // namespace api
} // namespace esphome

View File

@@ -273,6 +273,7 @@ class DeviceInfoResponse : public ProtoMessage {
std::string project_version{};
uint32_t webserver_port{0};
uint32_t bluetooth_proxy_version{0};
std::string manufacturer{};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
@@ -1481,6 +1482,43 @@ class BluetoothConnectionsFreeResponse : public ProtoMessage {
protected:
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class BluetoothGATTErrorResponse : public ProtoMessage {
public:
uint64_t address{0};
uint32_t handle{0};
int32_t error{0};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class BluetoothGATTWriteResponse : public ProtoMessage {
public:
uint64_t address{0};
uint32_t handle{0};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class BluetoothGATTNotifyResponse : public ProtoMessage {
public:
uint64_t address{0};
uint32_t handle{0};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
} // namespace api
} // namespace esphome

View File

@@ -401,6 +401,30 @@ bool APIServerConnectionBase::send_bluetooth_connections_free_response(const Blu
return this->send_message_<BluetoothConnectionsFreeResponse>(msg, 81);
}
#endif
#ifdef USE_BLUETOOTH_PROXY
bool APIServerConnectionBase::send_bluetooth_gatt_error_response(const BluetoothGATTErrorResponse &msg) {
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "send_bluetooth_gatt_error_response: %s", msg.dump().c_str());
#endif
return this->send_message_<BluetoothGATTErrorResponse>(msg, 82);
}
#endif
#ifdef USE_BLUETOOTH_PROXY
bool APIServerConnectionBase::send_bluetooth_gatt_write_response(const BluetoothGATTWriteResponse &msg) {
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "send_bluetooth_gatt_write_response: %s", msg.dump().c_str());
#endif
return this->send_message_<BluetoothGATTWriteResponse>(msg, 83);
}
#endif
#ifdef USE_BLUETOOTH_PROXY
bool APIServerConnectionBase::send_bluetooth_gatt_notify_response(const BluetoothGATTNotifyResponse &msg) {
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "send_bluetooth_gatt_notify_response: %s", msg.dump().c_str());
#endif
return this->send_message_<BluetoothGATTNotifyResponse>(msg, 84);
}
#endif
bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) {
switch (msg_type) {
case 1: {

View File

@@ -200,6 +200,15 @@ class APIServerConnectionBase : public ProtoService {
#endif
#ifdef USE_BLUETOOTH_PROXY
bool send_bluetooth_connections_free_response(const BluetoothConnectionsFreeResponse &msg);
#endif
#ifdef USE_BLUETOOTH_PROXY
bool send_bluetooth_gatt_error_response(const BluetoothGATTErrorResponse &msg);
#endif
#ifdef USE_BLUETOOTH_PROXY
bool send_bluetooth_gatt_write_response(const BluetoothGATTWriteResponse &msg);
#endif
#ifdef USE_BLUETOOTH_PROXY
bool send_bluetooth_gatt_notify_response(const BluetoothGATTNotifyResponse &msg);
#endif
protected:
bool read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override;

View File

@@ -324,11 +324,21 @@ void APIServer::send_bluetooth_gatt_read_response(const BluetoothGATTReadRespons
client->send_bluetooth_gatt_read_response(call);
}
}
void APIServer::send_bluetooth_gatt_write_response(const BluetoothGATTWriteResponse &call) {
for (auto &client : this->clients_) {
client->send_bluetooth_gatt_write_response(call);
}
}
void APIServer::send_bluetooth_gatt_notify_data_response(const BluetoothGATTNotifyDataResponse &call) {
for (auto &client : this->clients_) {
client->send_bluetooth_gatt_notify_data_response(call);
}
}
void APIServer::send_bluetooth_gatt_notify_response(const BluetoothGATTNotifyResponse &call) {
for (auto &client : this->clients_) {
client->send_bluetooth_gatt_notify_response(call);
}
}
void APIServer::send_bluetooth_gatt_services(const BluetoothGATTGetServicesResponse &call) {
for (auto &client : this->clients_) {
client->send_bluetooth_gatt_get_services_response(call);
@@ -342,6 +352,17 @@ void APIServer::send_bluetooth_gatt_services_done(uint64_t address) {
client->send_bluetooth_gatt_get_services_done_response(call);
}
}
void APIServer::send_bluetooth_gatt_error(uint64_t address, uint16_t handle, esp_err_t error) {
BluetoothGATTErrorResponse call;
call.address = address;
call.handle = handle;
call.error = error;
for (auto &client : this->clients_) {
client->send_bluetooth_gatt_error_response(call);
}
}
#endif
APIServer::APIServer() { global_api_server = this; }
void APIServer::subscribe_home_assistant_state(std::string entity_id, optional<std::string> attribute,

View File

@@ -75,12 +75,15 @@ class APIServer : public Component, public Controller {
void send_homeassistant_service_call(const HomeassistantServiceResponse &call);
#ifdef USE_BLUETOOTH_PROXY
void send_bluetooth_le_advertisement(const BluetoothLEAdvertisementResponse &call);
void send_bluetooth_device_connection(uint64_t address, bool connected, uint16_t mtu, esp_err_t error = ESP_OK);
void send_bluetooth_device_connection(uint64_t address, bool connected, uint16_t mtu = 0, esp_err_t error = ESP_OK);
void send_bluetooth_connections_free(uint8_t free, uint8_t limit);
void send_bluetooth_gatt_read_response(const BluetoothGATTReadResponse &call);
void send_bluetooth_gatt_write_response(const BluetoothGATTWriteResponse &call);
void send_bluetooth_gatt_notify_data_response(const BluetoothGATTNotifyDataResponse &call);
void send_bluetooth_gatt_notify_response(const BluetoothGATTNotifyResponse &call);
void send_bluetooth_gatt_services(const BluetoothGATTGetServicesResponse &call);
void send_bluetooth_gatt_services_done(uint64_t address);
void send_bluetooth_gatt_error(uint64_t address, uint16_t handle, esp_err_t error);
#endif
void register_user_service(UserServiceDescriptor *descriptor) { this->user_services_.push_back(descriptor); }
#ifdef USE_HOMEASSISTANT_TIME

View File

@@ -100,12 +100,40 @@ async def ble_write_to_code(config, action_id, template_arg, args):
else:
cg.add(var.set_value_simple(value))
serv_uuid128 = esp32_ble_tracker.as_reversed_hex_array(config[CONF_SERVICE_UUID])
cg.add(var.set_service_uuid128(serv_uuid128))
char_uuid128 = esp32_ble_tracker.as_reversed_hex_array(
config[CONF_CHARACTERISTIC_UUID]
)
cg.add(var.set_char_uuid128(char_uuid128))
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

View File

@@ -46,10 +46,14 @@ class BLEWriterClientNode : public BLEClientNode {
// Attempts to write the contents of value to char_uuid_.
void write(const std::vector<uint8_t> &value);
void set_char_uuid128(uint8_t *uuid) { this->char_uuid_ = espbt::ESPBTUUID::from_raw(uuid); }
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;

View File

@@ -47,11 +47,12 @@ void BLEClient::set_enabled(bool enabled) {
this->enabled = enabled;
}
void BLEClient::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t esp_gattc_if,
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_();
BLEClientBase::gattc_event_handler(event, esp_gattc_if, param);
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);
@@ -62,6 +63,14 @@ void BLEClient::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t es
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) {

View File

@@ -27,7 +27,8 @@ 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 loop(){};
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
@@ -49,8 +50,10 @@ class BLEClient : public BLEClientBase {
void dump_config() override;
void loop() override;
void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
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);

View File

@@ -5,7 +5,11 @@ 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
@@ -16,6 +20,8 @@ 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")
@@ -27,33 +33,67 @@ BLESensorNotifyTrigger = ble_client_ns.class_(
"BLESensorNotifyTrigger", automation.Trigger.template(cg.float_)
)
CONFIG_SCHEMA = cv.All(
sensor.sensor_schema(
BLESensor,
accuracy_decimals=0,
)
.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
),
}
),
}
)
.extend(cv.polling_component_schema("60s"))
.extend(ble_client.BLE_CLIENT_SCHEMA)
BLEClientRssiSensor = ble_client_ns.class_(
"BLEClientRSSISensor", sensor.Sensor, cg.PollingComponent, ble_client.BLEClientNode
)
async def to_code(config):
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(
@@ -125,3 +165,10 @@ async def to_code(config):
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)

View File

@@ -0,0 +1,78 @@
#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

View File

@@ -0,0 +1,31 @@
#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

View File

@@ -58,7 +58,7 @@ class BLEPresenceDevice : public binary_sensor::BinarySensorInitiallyOff,
case MATCH_BY_SERVICE_UUID:
for (auto uuid : device.get_service_uuids()) {
if (this->uuid_ == uuid) {
this->publish_state(device.get_rssi());
this->publish_state(true);
this->found_ = true;
return true;
}
@@ -83,7 +83,7 @@ class BLEPresenceDevice : public binary_sensor::BinarySensorInitiallyOff,
return false;
}
this->publish_state(device.get_rssi());
this->publish_state(true);
this->found_ = true;
return true;
}

View File

@@ -9,7 +9,7 @@ from esphome.const import (
CONF_MAC_ADDRESS,
DEVICE_CLASS_SIGNAL_STRENGTH,
STATE_CLASS_MEASUREMENT,
UNIT_DECIBEL,
UNIT_DECIBEL_MILLIWATT,
)
DEPENDENCIES = ["esp32_ble_tracker"]
@@ -31,7 +31,7 @@ def _validate(config):
CONFIG_SCHEMA = cv.All(
sensor.sensor_schema(
BLERSSISensor,
unit_of_measurement=UNIT_DECIBEL,
unit_of_measurement=UNIT_DECIBEL_MILLIWATT,
accuracy_decimals=0,
device_class=DEVICE_CLASS_SIGNAL_STRENGTH,
state_class=STATE_CLASS_MEASUREMENT,

View File

@@ -4,22 +4,59 @@ import esphome.codegen as cg
from esphome.const import CONF_ACTIVE, CONF_ID
AUTO_LOAD = ["esp32_ble_client", "esp32_ble_tracker"]
DEPENDENCIES = ["esp32"]
DEPENDENCIES = ["api", "esp32"]
CODEOWNERS = ["@jesserockz"]
CONF_CONNECTIONS = "connections"
MAX_CONNECTIONS = 3
bluetooth_proxy_ns = cg.esphome_ns.namespace("bluetooth_proxy")
BluetoothProxy = bluetooth_proxy_ns.class_(
"BluetoothProxy", esp32_ble_client.BLEClientBase
"BluetoothProxy", esp32_ble_tracker.ESPBTDeviceListener, cg.Component
)
BluetoothConnection = bluetooth_proxy_ns.class_(
"BluetoothConnection", esp32_ble_client.BLEClientBase
)
CONFIG_SCHEMA = cv.Schema(
CONNECTION_SCHEMA = esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(BluetoothProxy),
cv.Optional(CONF_ACTIVE, default=False): cv.boolean,
cv.GenerateID(): cv.declare_id(BluetoothConnection),
}
).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA)
).extend(cv.COMPONENT_SCHEMA)
def validate_connections(config):
if CONF_CONNECTIONS in config:
if not config[CONF_ACTIVE]:
raise cv.Invalid(
"Connections can only be used if the proxy is set to active"
)
else:
if config[CONF_ACTIVE]:
conf = config.copy()
conf[CONF_CONNECTIONS] = [
CONNECTION_SCHEMA({}) for _ in range(MAX_CONNECTIONS)
]
return conf
return config
CONFIG_SCHEMA = cv.All(
cv.Schema(
{
cv.GenerateID(): cv.declare_id(BluetoothProxy),
cv.Optional(CONF_ACTIVE, default=False): cv.boolean,
cv.Optional(CONF_CONNECTIONS): cv.All(
cv.ensure_list(CONNECTION_SCHEMA),
cv.Length(min=1, max=MAX_CONNECTIONS),
),
}
)
.extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA)
.extend(cv.COMPONENT_SCHEMA),
validate_connections,
)
async def to_code(config):
@@ -27,7 +64,12 @@ async def to_code(config):
await cg.register_component(var, config)
cg.add(var.set_active(config[CONF_ACTIVE]))
await esp32_ble_tracker.register_ble_device(var, config)
await esp32_ble_tracker.register_client(var, config)
for connection_conf in config.get(CONF_CONNECTIONS, []):
connection_var = cg.new_Pvariable(connection_conf[CONF_ID])
await cg.register_component(connection_var, connection_conf)
cg.add(var.register_connection(connection_var))
await esp32_ble_tracker.register_client(connection_var, connection_conf)
cg.add_define("USE_BLUETOOTH_PROXY")

View File

@@ -0,0 +1,281 @@
#include "bluetooth_connection.h"
#include "esphome/components/api/api_server.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#ifdef USE_ESP32
#include "bluetooth_proxy.h"
namespace esphome {
namespace bluetooth_proxy {
static const char *const TAG = "bluetooth_proxy.connection";
bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
esp_ble_gattc_cb_param_t *param) {
if (!BLEClientBase::gattc_event_handler(event, gattc_if, param))
return false;
switch (event) {
case ESP_GATTC_DISCONNECT_EVT: {
api::global_api_server->send_bluetooth_device_connection(this->address_, false, 0, param->disconnect.reason);
this->set_address(0);
api::global_api_server->send_bluetooth_connections_free(this->proxy_->get_bluetooth_connections_free(),
this->proxy_->get_bluetooth_connections_limit());
break;
}
case ESP_GATTC_OPEN_EVT: {
if (param->open.conn_id != this->conn_id_)
break;
if (param->open.status != ESP_GATT_OK && param->open.status != ESP_GATT_ALREADY_OPEN) {
api::global_api_server->send_bluetooth_device_connection(this->address_, false, 0, param->open.status);
this->set_address(0);
api::global_api_server->send_bluetooth_connections_free(this->proxy_->get_bluetooth_connections_free(),
this->proxy_->get_bluetooth_connections_limit());
}
break;
}
case ESP_GATTC_SEARCH_CMPL_EVT: {
if (param->search_cmpl.conn_id != this->conn_id_)
break;
api::global_api_server->send_bluetooth_device_connection(this->address_, true, this->mtu_);
api::global_api_server->send_bluetooth_connections_free(this->proxy_->get_bluetooth_connections_free(),
this->proxy_->get_bluetooth_connections_limit());
break;
}
case ESP_GATTC_READ_DESCR_EVT:
case ESP_GATTC_READ_CHAR_EVT: {
if (param->read.conn_id != this->conn_id_)
break;
if (param->read.status != ESP_GATT_OK) {
ESP_LOGW(TAG, "[%d] [%s] Error reading char/descriptor at handle 0x%2X, status=%d", this->connection_index_,
this->address_str_.c_str(), param->read.handle, param->read.status);
api::global_api_server->send_bluetooth_gatt_error(this->address_, param->read.handle, param->read.status);
break;
}
api::BluetoothGATTReadResponse resp;
resp.address = this->address_;
resp.handle = param->read.handle;
resp.data.reserve(param->read.value_len);
for (uint16_t i = 0; i < param->read.value_len; i++) {
resp.data.push_back(param->read.value[i]);
}
api::global_api_server->send_bluetooth_gatt_read_response(resp);
break;
}
case ESP_GATTC_WRITE_CHAR_EVT:
case ESP_GATTC_WRITE_DESCR_EVT: {
if (param->write.conn_id != this->conn_id_)
break;
if (param->write.status != ESP_GATT_OK) {
ESP_LOGW(TAG, "[%d] [%s] Error writing char/descriptor at handle 0x%2X, status=%d", this->connection_index_,
this->address_str_.c_str(), param->write.handle, param->write.status);
api::global_api_server->send_bluetooth_gatt_error(this->address_, param->write.handle, param->write.status);
break;
}
api::BluetoothGATTWriteResponse resp;
resp.address = this->address_;
resp.handle = param->write.handle;
api::global_api_server->send_bluetooth_gatt_write_response(resp);
break;
}
case ESP_GATTC_UNREG_FOR_NOTIFY_EVT: {
if (this->get_characteristic(param->unreg_for_notify.handle) == nullptr) // No conn_id in this event
break;
if (param->unreg_for_notify.status != ESP_GATT_OK) {
ESP_LOGW(TAG, "[%d] [%s] Error unregistering notifications for handle 0x%2X, status=%d",
this->connection_index_, this->address_str_.c_str(), param->unreg_for_notify.handle,
param->unreg_for_notify.status);
api::global_api_server->send_bluetooth_gatt_error(this->address_, param->unreg_for_notify.handle,
param->unreg_for_notify.status);
break;
}
api::BluetoothGATTNotifyResponse resp;
resp.address = this->address_;
resp.handle = param->unreg_for_notify.handle;
api::global_api_server->send_bluetooth_gatt_notify_response(resp);
break;
}
case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
if (this->get_characteristic(param->reg_for_notify.handle) == nullptr) // No conn_id in this event
break;
if (param->reg_for_notify.status != ESP_GATT_OK) {
ESP_LOGW(TAG, "[%d] [%s] Error registering notifications for handle 0x%2X, status=%d", this->connection_index_,
this->address_str_.c_str(), param->reg_for_notify.handle, param->reg_for_notify.status);
api::global_api_server->send_bluetooth_gatt_error(this->address_, param->reg_for_notify.handle,
param->reg_for_notify.status);
break;
}
api::BluetoothGATTNotifyResponse resp;
resp.address = this->address_;
resp.handle = param->reg_for_notify.handle;
api::global_api_server->send_bluetooth_gatt_notify_response(resp);
break;
}
case ESP_GATTC_NOTIFY_EVT: {
if (param->notify.conn_id != this->conn_id_)
break;
ESP_LOGV(TAG, "[%d] [%s] ESP_GATTC_NOTIFY_EVT: handle=0x%2X", this->connection_index_, this->address_str_.c_str(),
param->notify.handle);
api::BluetoothGATTNotifyDataResponse resp;
resp.address = this->address_;
resp.handle = param->notify.handle;
resp.data.reserve(param->notify.value_len);
for (uint16_t i = 0; i < param->notify.value_len; i++) {
resp.data.push_back(param->notify.value[i]);
}
api::global_api_server->send_bluetooth_gatt_notify_data_response(resp);
break;
}
default:
break;
}
return true;
}
esp_err_t BluetoothConnection::read_characteristic(uint16_t handle) {
if (!this->connected()) {
ESP_LOGW(TAG, "[%d] [%s] Cannot read GATT characteristic, not connected.", this->connection_index_,
this->address_str_.c_str());
return ESP_GATT_NOT_CONNECTED;
}
auto *characteristic = this->get_characteristic(handle);
if (characteristic == nullptr) {
ESP_LOGW(TAG, "[%d] [%s] Cannot read GATT characteristic, not found.", this->connection_index_,
this->address_str_.c_str());
return ESP_GATT_INVALID_HANDLE;
}
ESP_LOGV(TAG, "[%d] [%s] Reading GATT characteristic %s", this->connection_index_, this->address_str_.c_str(),
characteristic->uuid.to_string().c_str());
esp_err_t err =
esp_ble_gattc_read_char(this->gattc_if_, this->conn_id_, characteristic->handle, ESP_GATT_AUTH_REQ_NONE);
if (err != ERR_OK) {
ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_read_char error, err=%d", this->connection_index_,
this->address_str_.c_str(), err);
return err;
}
return ESP_OK;
}
esp_err_t BluetoothConnection::write_characteristic(uint16_t handle, const std::string &data, bool response) {
if (!this->connected()) {
ESP_LOGW(TAG, "[%d] [%s] Cannot write GATT characteristic, not connected.", this->connection_index_,
this->address_str_.c_str());
return ESP_GATT_NOT_CONNECTED;
}
auto *characteristic = this->get_characteristic(handle);
if (characteristic == nullptr) {
ESP_LOGW(TAG, "[%d] [%s] Cannot write GATT characteristic, not found.", this->connection_index_,
this->address_str_.c_str());
return ESP_GATT_INVALID_HANDLE;
}
ESP_LOGV(TAG, "[%d] [%s] Writing GATT characteristic %s", this->connection_index_, this->address_str_.c_str(),
characteristic->uuid.to_string().c_str());
auto err = characteristic->write_value((uint8_t *) data.data(), data.size(),
response ? ESP_GATT_WRITE_TYPE_RSP : ESP_GATT_WRITE_TYPE_NO_RSP);
if (err != ERR_OK) {
ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_write_char error, err=%d", this->connection_index_,
this->address_str_.c_str(), err);
return err;
}
return ESP_OK;
}
esp_err_t BluetoothConnection::read_descriptor(uint16_t handle) {
if (!this->connected()) {
ESP_LOGW(TAG, "[%d] [%s] Cannot read GATT descriptor, not connected.", this->connection_index_,
this->address_str_.c_str());
return ESP_GATT_NOT_CONNECTED;
}
auto *descriptor = this->get_descriptor(handle);
if (descriptor == nullptr) {
ESP_LOGW(TAG, "[%d] [%s] Cannot read GATT descriptor, not found.", this->connection_index_,
this->address_str_.c_str());
return ESP_GATT_INVALID_HANDLE;
}
ESP_LOGV(TAG, "[%d] [%s] Reading GATT descriptor %s", this->connection_index_, this->address_str_.c_str(),
descriptor->uuid.to_string().c_str());
esp_err_t err =
esp_ble_gattc_read_char_descr(this->gattc_if_, this->conn_id_, descriptor->handle, ESP_GATT_AUTH_REQ_NONE);
if (err != ERR_OK) {
ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_read_char_descr error, err=%d", this->connection_index_,
this->address_str_.c_str(), err);
return err;
}
return ESP_OK;
}
esp_err_t BluetoothConnection::write_descriptor(uint16_t handle, const std::string &data) {
if (!this->connected()) {
ESP_LOGW(TAG, "[%d] [%s] Cannot write GATT descriptor, not connected.", this->connection_index_,
this->address_str_.c_str());
return ESP_GATT_NOT_CONNECTED;
}
auto *descriptor = this->get_descriptor(handle);
if (descriptor == nullptr) {
ESP_LOGW(TAG, "[%d] [%s] Cannot write GATT descriptor, not found.", this->connection_index_,
this->address_str_.c_str());
return ESP_GATT_INVALID_HANDLE;
}
ESP_LOGV(TAG, "[%d] [%s] Writing GATT descriptor %s", this->connection_index_, this->address_str_.c_str(),
descriptor->uuid.to_string().c_str());
auto err =
esp_ble_gattc_write_char_descr(this->gattc_if_, this->conn_id_, descriptor->handle, data.size(),
(uint8_t *) data.data(), ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
if (err != ERR_OK) {
ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_write_char_descr error, err=%d", this->connection_index_,
this->address_str_.c_str(), err);
return err;
}
return ESP_OK;
}
esp_err_t BluetoothConnection::notify_characteristic(uint16_t handle, bool enable) {
if (!this->connected()) {
ESP_LOGW(TAG, "[%d] [%s] Cannot notify GATT characteristic, not connected.", this->connection_index_,
this->address_str_.c_str());
return ESP_GATT_NOT_CONNECTED;
}
auto *characteristic = this->get_characteristic(handle);
if (characteristic == nullptr) {
ESP_LOGW(TAG, "[%d] [%s] Cannot notify GATT characteristic, not found.", this->connection_index_,
this->address_str_.c_str());
return ESP_GATT_INVALID_HANDLE;
}
if (enable) {
ESP_LOGV(TAG, "[%d] [%s] Registering for GATT characteristic notifications %s", this->connection_index_,
this->address_str_.c_str(), characteristic->uuid.to_string().c_str());
esp_err_t err = esp_ble_gattc_register_for_notify(this->gattc_if_, this->remote_bda_, characteristic->handle);
if (err != ESP_OK) {
ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_register_for_notify failed, err=%d", this->connection_index_,
this->address_str_.c_str(), err);
return err;
}
} else {
ESP_LOGV(TAG, "[%d] [%s] Unregistering for GATT characteristic notifications %s", this->connection_index_,
this->address_str_.c_str(), characteristic->uuid.to_string().c_str());
esp_err_t err = esp_ble_gattc_unregister_for_notify(this->gattc_if_, this->remote_bda_, characteristic->handle);
if (err != ESP_OK) {
ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_unregister_for_notify failed, err=%d", this->connection_index_,
this->address_str_.c_str(), err);
return err;
}
}
return ESP_OK;
}
} // namespace bluetooth_proxy
} // namespace esphome
#endif // USE_ESP32

View File

@@ -0,0 +1,34 @@
#pragma once
#ifdef USE_ESP32
#include "esphome/components/esp32_ble_client/ble_client_base.h"
namespace esphome {
namespace bluetooth_proxy {
class BluetoothProxy;
class BluetoothConnection : public esp32_ble_client::BLEClientBase {
public:
bool gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
esp_ble_gattc_cb_param_t *param) override;
esp_err_t read_characteristic(uint16_t handle);
esp_err_t write_characteristic(uint16_t handle, const std::string &data, bool response);
esp_err_t read_descriptor(uint16_t handle);
esp_err_t write_descriptor(uint16_t handle, const std::string &data);
esp_err_t notify_characteristic(uint16_t handle, bool enable);
protected:
friend class BluetoothProxy;
int16_t send_service_{-1};
BluetoothProxy *proxy_;
};
} // namespace bluetooth_proxy
} // namespace esphome
#endif // USE_ESP32

View File

@@ -4,43 +4,26 @@
#ifdef USE_ESP32
#ifdef USE_API
#include "esphome/components/api/api_server.h"
#endif
namespace esphome {
namespace bluetooth_proxy {
static const char *const TAG = "bluetooth_proxy";
BluetoothProxy::BluetoothProxy() {
global_bluetooth_proxy = this;
this->address_ = 0;
}
BluetoothProxy::BluetoothProxy() { global_bluetooth_proxy = this; }
bool BluetoothProxy::parse_device(const esp32_ble_tracker::ESPBTDevice &device) {
if (!api::global_api_server->is_connected())
return false;
ESP_LOGV(TAG, "Proxying packet from %s - %s. RSSI: %d dB", device.get_name().c_str(), device.address_str().c_str(),
device.get_rssi());
this->send_api_packet_(device);
this->address_type_map_[device.address_uint64()] = device.get_address_type();
if (this->address_ == 0)
return true;
if (this->state_ == espbt::ClientState::DISCOVERED) {
ESP_LOGV(TAG, "Connecting to address %s", this->address_str().c_str());
return true;
}
BLEClientBase::parse_device(device);
return true;
}
void BluetoothProxy::send_api_packet_(const esp32_ble_tracker::ESPBTDevice &device) {
#ifndef USE_API
return;
#else
api::BluetoothLEAdvertisementResponse resp;
resp.address = device.address_uint64();
if (!device.get_name().empty())
@@ -62,152 +45,103 @@ void BluetoothProxy::send_api_packet_(const esp32_ble_tracker::ESPBTDevice &devi
resp.manufacturer_data.push_back(std::move(manufacturer_data));
}
api::global_api_server->send_bluetooth_le_advertisement(resp);
#endif
}
void BluetoothProxy::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
esp_ble_gattc_cb_param_t *param) {
BLEClientBase::gattc_event_handler(event, gattc_if, param);
switch (event) {
case ESP_GATTC_DISCONNECT_EVT: {
#ifdef USE_API
api::global_api_server->send_bluetooth_device_connection(this->address_, false, this->mtu_,
param->disconnect.reason);
api::global_api_server->send_bluetooth_connections_free(this->get_bluetooth_connections_free(),
this->get_bluetooth_connections_limit());
#endif
this->address_ = 0;
}
case ESP_GATTC_OPEN_EVT: {
if (param->open.status != ESP_GATT_OK && param->open.status != ESP_GATT_ALREADY_OPEN) {
#ifdef USE_API
api::global_api_server->send_bluetooth_device_connection(this->address_, false, this->mtu_, param->open.status);
#endif
break;
}
break;
}
case ESP_GATTC_SEARCH_CMPL_EVT: {
#ifdef USE_API
api::global_api_server->send_bluetooth_device_connection(this->address_, true, this->mtu_);
api::global_api_server->send_bluetooth_connections_free(this->get_bluetooth_connections_free(),
this->get_bluetooth_connections_limit());
#endif
break;
}
case ESP_GATTC_READ_DESCR_EVT:
case ESP_GATTC_READ_CHAR_EVT: {
if (param->read.conn_id != this->conn_id_)
break;
if (param->read.status != ESP_GATT_OK) {
ESP_LOGW(TAG, "Error reading char/descriptor at handle %d, status=%d", param->read.handle, param->read.status);
break;
}
#ifdef USE_API
api::BluetoothGATTReadResponse resp;
resp.address = this->address_;
resp.handle = param->read.handle;
resp.data.reserve(param->read.value_len);
for (uint16_t i = 0; i < param->read.value_len; i++) {
resp.data.push_back(param->read.value[i]);
}
api::global_api_server->send_bluetooth_gatt_read_response(resp);
#endif
break;
}
case ESP_GATTC_NOTIFY_EVT: {
if (param->notify.conn_id != this->conn_id_)
break;
ESP_LOGV(TAG, "ESP_GATTC_NOTIFY_EVT: handle=0x%x", param->notify.handle);
#ifdef USE_API
api::BluetoothGATTNotifyDataResponse resp;
resp.address = this->address_;
resp.handle = param->notify.handle;
resp.data.reserve(param->notify.value_len);
for (uint16_t i = 0; i < param->notify.value_len; i++) {
resp.data.push_back(param->notify.value[i]);
}
api::global_api_server->send_bluetooth_gatt_notify_data_response(resp);
#endif
break;
}
default:
break;
}
void BluetoothProxy::dump_config() {
ESP_LOGCONFIG(TAG, "Bluetooth Proxy:");
ESP_LOGCONFIG(TAG, " Active: %s", YESNO(this->active_));
}
void BluetoothProxy::dump_config() { ESP_LOGCONFIG(TAG, "Bluetooth Proxy:"); }
void BluetoothProxy::loop() {
BLEClientBase::loop();
#ifdef USE_API
if (this->state_ != espbt::ClientState::IDLE && !api::global_api_server->is_connected()) {
ESP_LOGI(TAG, "[%s] Disconnecting.", this->address_str().c_str());
auto err = esp_ble_gattc_close(this->gattc_if_, this->conn_id_);
if (err != ERR_OK) {
ESP_LOGW(TAG, "esp_ble_gattc_close error, address=%s err=%d", this->address_str().c_str(), err);
}
}
if (this->send_service_ == this->services_.size()) {
this->send_service_ = -1;
api::global_api_server->send_bluetooth_gatt_services_done(this->address_);
} else if (this->send_service_ >= 0) {
auto &service = this->services_[this->send_service_];
api::BluetoothGATTGetServicesResponse resp;
resp.address = this->address_;
api::BluetoothGATTService service_resp;
service_resp.uuid = {service->uuid.get_128bit_high(), service->uuid.get_128bit_low()};
service_resp.handle = service->start_handle;
for (auto &characteristic : service->characteristics) {
api::BluetoothGATTCharacteristic characteristic_resp;
characteristic_resp.uuid = {characteristic->uuid.get_128bit_high(), characteristic->uuid.get_128bit_low()};
characteristic_resp.handle = characteristic->handle;
characteristic_resp.properties = characteristic->properties;
for (auto &descriptor : characteristic->descriptors) {
api::BluetoothGATTDescriptor descriptor_resp;
descriptor_resp.uuid = {descriptor->uuid.get_128bit_high(), descriptor->uuid.get_128bit_low()};
descriptor_resp.handle = descriptor->handle;
characteristic_resp.descriptors.push_back(std::move(descriptor_resp));
if (!api::global_api_server->is_connected()) {
for (auto *connection : this->connections_) {
if (connection->get_address() != 0) {
connection->disconnect();
}
service_resp.characteristics.push_back(std::move(characteristic_resp));
}
resp.services.push_back(std::move(service_resp));
api::global_api_server->send_bluetooth_gatt_services(resp);
this->send_service_++;
return;
}
for (auto *connection : this->connections_) {
if (connection->send_service_ == connection->services_.size()) {
connection->send_service_ = -1;
api::global_api_server->send_bluetooth_gatt_services_done(connection->get_address());
} else if (connection->send_service_ >= 0) {
auto &service = connection->services_[connection->send_service_];
api::BluetoothGATTGetServicesResponse resp;
resp.address = connection->get_address();
api::BluetoothGATTService service_resp;
service_resp.uuid = {service->uuid.get_128bit_high(), service->uuid.get_128bit_low()};
service_resp.handle = service->start_handle;
for (auto &characteristic : service->characteristics) {
api::BluetoothGATTCharacteristic characteristic_resp;
characteristic_resp.uuid = {characteristic->uuid.get_128bit_high(), characteristic->uuid.get_128bit_low()};
characteristic_resp.handle = characteristic->handle;
characteristic_resp.properties = characteristic->properties;
for (auto &descriptor : characteristic->descriptors) {
api::BluetoothGATTDescriptor descriptor_resp;
descriptor_resp.uuid = {descriptor->uuid.get_128bit_high(), descriptor->uuid.get_128bit_low()};
descriptor_resp.handle = descriptor->handle;
characteristic_resp.descriptors.push_back(std::move(descriptor_resp));
}
service_resp.characteristics.push_back(std::move(characteristic_resp));
}
resp.services.push_back(std::move(service_resp));
api::global_api_server->send_bluetooth_gatt_services(resp);
connection->send_service_++;
}
}
#endif
}
#ifdef USE_API
BluetoothConnection *BluetoothProxy::get_connection_(uint64_t address, bool reserve) {
for (auto *connection : this->connections_) {
if (connection->get_address() == address)
return connection;
}
if (!reserve)
return nullptr;
for (auto *connection : this->connections_) {
if (connection->get_address() == 0) {
connection->set_address(address);
return connection;
}
}
return nullptr;
}
void BluetoothProxy::bluetooth_device_request(const api::BluetoothDeviceRequest &msg) {
switch (msg.request_type) {
case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT: {
this->address_ = msg.address;
if (this->address_type_map_.find(this->address_) != this->address_type_map_.end()) {
// Utilise the address type cache
this->remote_addr_type_ = this->address_type_map_[this->address_];
} else {
this->remote_addr_type_ = BLE_ADDR_TYPE_PUBLIC;
auto *connection = this->get_connection_(msg.address, true);
if (connection == nullptr) {
ESP_LOGW(TAG, "No free connections available");
api::global_api_server->send_bluetooth_device_connection(msg.address, false);
return;
}
this->remote_bda_[0] = (this->address_ >> 40) & 0xFF;
this->remote_bda_[1] = (this->address_ >> 32) & 0xFF;
this->remote_bda_[2] = (this->address_ >> 24) & 0xFF;
this->remote_bda_[3] = (this->address_ >> 16) & 0xFF;
this->remote_bda_[4] = (this->address_ >> 8) & 0xFF;
this->remote_bda_[5] = (this->address_ >> 0) & 0xFF;
this->set_state(espbt::ClientState::DISCOVERED);
esp_ble_gap_stop_scanning();
ESP_LOGV(TAG, "[%d] [%s] Searching to connect", connection->get_connection_index(),
connection->address_str().c_str());
connection->set_state(espbt::ClientState::SEARCHING);
api::global_api_server->send_bluetooth_connections_free(this->get_bluetooth_connections_free(),
this->get_bluetooth_connections_limit());
break;
}
case api::enums::BLUETOOTH_DEVICE_REQUEST_TYPE_DISCONNECT: {
if (this->state() != espbt::ClientState::IDLE) {
ESP_LOGI(TAG, "[%s] Disconnecting.", this->address_str().c_str());
auto err = esp_ble_gattc_close(this->gattc_if_, this->conn_id_);
if (err != ERR_OK) {
ESP_LOGW(TAG, "esp_ble_gattc_close error, address=%s err=%d", this->address_str().c_str(), err);
}
auto *connection = this->get_connection_(msg.address, false);
if (connection == nullptr) {
api::global_api_server->send_bluetooth_device_connection(msg.address, false);
api::global_api_server->send_bluetooth_connections_free(this->get_bluetooth_connections_free(),
this->get_bluetooth_connections_limit());
return;
}
if (connection->state() != espbt::ClientState::IDLE) {
connection->disconnect();
} else {
connection->set_address(0);
api::global_api_server->send_bluetooth_device_connection(msg.address, false);
api::global_api_server->send_bluetooth_connections_free(this->get_bluetooth_connections_free(),
this->get_bluetooth_connections_limit());
}
break;
}
@@ -218,141 +152,91 @@ void BluetoothProxy::bluetooth_device_request(const api::BluetoothDeviceRequest
}
void BluetoothProxy::bluetooth_gatt_read(const api::BluetoothGATTReadRequest &msg) {
if (this->state_ != espbt::ClientState::ESTABLISHED) {
ESP_LOGW(TAG, "Cannot read GATT characteristic, not connected.");
return;
}
if (this->address_ != msg.address) {
ESP_LOGW(TAG, "Address mismatch for read GATT characteristic request");
auto *connection = this->get_connection_(msg.address, false);
if (connection == nullptr) {
ESP_LOGW(TAG, "Cannot read GATT characteristic, not connected");
api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, ESP_GATT_NOT_CONNECTED);
return;
}
auto *characteristic = this->get_characteristic(msg.handle);
if (characteristic == nullptr) {
ESP_LOGW(TAG, "Cannot read GATT characteristic, not found.");
return;
}
ESP_LOGV(TAG, "Reading GATT characteristic %s", characteristic->uuid.to_string().c_str());
esp_err_t err =
esp_ble_gattc_read_char(this->gattc_if_, this->conn_id_, characteristic->handle, ESP_GATT_AUTH_REQ_NONE);
if (err != ERR_OK) {
ESP_LOGW(TAG, "esp_ble_gattc_read_char error, err=%d", err);
auto err = connection->read_characteristic(msg.handle);
if (err != ESP_OK) {
api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, err);
}
}
void BluetoothProxy::bluetooth_gatt_write(const api::BluetoothGATTWriteRequest &msg) {
if (this->state_ != espbt::ClientState::ESTABLISHED) {
ESP_LOGW(TAG, "Cannot write GATT characteristic, not connected.");
return;
}
if (this->address_ != msg.address) {
ESP_LOGW(TAG, "Address mismatch for write GATT characteristic request");
auto *connection = this->get_connection_(msg.address, false);
if (connection == nullptr) {
ESP_LOGW(TAG, "Cannot write GATT characteristic, not connected");
api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, ESP_GATT_NOT_CONNECTED);
return;
}
auto *characteristic = this->get_characteristic(msg.handle);
if (characteristic == nullptr) {
ESP_LOGW(TAG, "Cannot write GATT characteristic, not found.");
return;
auto err = connection->write_characteristic(msg.handle, msg.data, msg.response);
if (err != ESP_OK) {
api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, err);
}
ESP_LOGV(TAG, "Writing GATT characteristic %s", characteristic->uuid.to_string().c_str());
characteristic->write_value((uint8_t *) msg.data.data(), msg.data.size(),
msg.response ? ESP_GATT_WRITE_TYPE_RSP : ESP_GATT_WRITE_TYPE_NO_RSP);
}
void BluetoothProxy::bluetooth_gatt_read_descriptor(const api::BluetoothGATTReadDescriptorRequest &msg) {
if (this->state_ != espbt::ClientState::ESTABLISHED) {
ESP_LOGW(TAG, "Cannot read GATT characteristic descriptor, not connected.");
return;
}
if (this->address_ != msg.address) {
ESP_LOGW(TAG, "Address mismatch for read GATT characteristic descriptor request");
auto *connection = this->get_connection_(msg.address, false);
if (connection == nullptr) {
ESP_LOGW(TAG, "Cannot read GATT descriptor, not connected");
api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, ESP_GATT_NOT_CONNECTED);
return;
}
auto *descriptor = this->get_descriptor(msg.handle);
if (descriptor == nullptr) {
ESP_LOGW(TAG, "Cannot read GATT characteristic descriptor, not found.");
return;
}
ESP_LOGV(TAG, "Reading GATT characteristic descriptor %s -> %s", descriptor->characteristic->uuid.to_string().c_str(),
descriptor->uuid.to_string().c_str());
esp_err_t err =
esp_ble_gattc_read_char_descr(this->gattc_if_, this->conn_id_, descriptor->handle, ESP_GATT_AUTH_REQ_NONE);
if (err != ERR_OK) {
ESP_LOGW(TAG, "esp_ble_gattc_read_char error, err=%d", err);
auto err = connection->read_descriptor(msg.handle);
if (err != ESP_OK) {
api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, err);
}
}
void BluetoothProxy::bluetooth_gatt_write_descriptor(const api::BluetoothGATTWriteDescriptorRequest &msg) {
if (this->state_ != espbt::ClientState::ESTABLISHED) {
ESP_LOGW(TAG, "Cannot write GATT characteristic descriptor, not connected.");
return;
}
if (this->address_ != msg.address) {
ESP_LOGW(TAG, "Address mismatch for write GATT characteristic descriptor request");
auto *connection = this->get_connection_(msg.address, false);
if (connection == nullptr) {
ESP_LOGW(TAG, "Cannot write GATT descriptor, not connected");
api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, ESP_GATT_NOT_CONNECTED);
return;
}
auto *descriptor = this->get_descriptor(msg.handle);
if (descriptor == nullptr) {
ESP_LOGW(TAG, "Cannot write GATT characteristic descriptor, not found.");
return;
}
ESP_LOGV(TAG, "Writing GATT characteristic descriptor %s -> %s", descriptor->characteristic->uuid.to_string().c_str(),
descriptor->uuid.to_string().c_str());
esp_err_t err =
esp_ble_gattc_write_char_descr(this->gattc_if_, this->conn_id_, descriptor->handle, msg.data.size(),
(uint8_t *) msg.data.data(), ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
if (err != ERR_OK) {
ESP_LOGW(TAG, "esp_ble_gattc_write_char_descr error, err=%d", err);
auto err = connection->write_descriptor(msg.handle, msg.data);
if (err != ESP_OK) {
api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, err);
}
}
void BluetoothProxy::bluetooth_gatt_send_services(const api::BluetoothGATTGetServicesRequest &msg) {
if (this->address_ != msg.address) {
ESP_LOGW(TAG, "Address mismatch for service list request");
auto *connection = this->get_connection_(msg.address, false);
if (connection == nullptr || !connection->connected()) {
ESP_LOGW(TAG, "Cannot get GATT services, not connected");
api::global_api_server->send_bluetooth_gatt_error(msg.address, 0, ESP_GATT_NOT_CONNECTED);
return;
}
this->send_service_ = 0;
if (connection->services_.empty()) {
ESP_LOGW(TAG, "[%d] [%s] No GATT services found", connection->connection_index_, connection->address_str().c_str());
api::global_api_server->send_bluetooth_gatt_services_done(msg.address);
return;
}
if (connection->send_service_ == -1) // Don't start sending services again if we're already sending them
connection->send_service_ = 0;
}
void BluetoothProxy::bluetooth_gatt_notify(const api::BluetoothGATTNotifyRequest &msg) {
if (this->address_ != msg.address) {
ESP_LOGW(TAG, "Address mismatch for notify");
auto *connection = this->get_connection_(msg.address, false);
if (connection == nullptr) {
ESP_LOGW(TAG, "Cannot notify GATT characteristic, not connected");
api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, ESP_GATT_NOT_CONNECTED);
return;
}
auto *characteristic = this->get_characteristic(msg.handle);
if (characteristic == nullptr) {
ESP_LOGW(TAG, "Cannot notify GATT characteristic, not found.");
return;
}
esp_err_t err;
if (msg.enable) {
err = esp_ble_gattc_register_for_notify(this->gattc_if_, this->remote_bda_, characteristic->handle);
if (err != ESP_OK) {
ESP_LOGW(TAG, "esp_ble_gattc_register_for_notify failed, err=%d", err);
}
} else {
err = esp_ble_gattc_unregister_for_notify(this->gattc_if_, this->remote_bda_, characteristic->handle);
if (err != ESP_OK) {
ESP_LOGW(TAG, "esp_ble_gattc_unregister_for_notify failed, err=%d", err);
}
auto err = connection->notify_characteristic(msg.handle, msg.enable);
if (err != ESP_OK) {
api::global_api_server->send_bluetooth_gatt_error(msg.address, msg.handle, err);
}
}
#endif
BluetoothProxy *global_bluetooth_proxy = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
} // namespace bluetooth_proxy

View File

@@ -4,34 +4,34 @@
#include <map>
#include "esphome/components/api/api_pb2.h"
#include "esphome/components/esp32_ble_client/ble_client_base.h"
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
#include "esphome/core/automation.h"
#include "esphome/core/component.h"
#include "esphome/core/defines.h"
#include <map>
#ifdef USE_API
#include "esphome/components/api/api_pb2.h"
#endif // USE_API
#include "bluetooth_connection.h"
namespace esphome {
namespace bluetooth_proxy {
static const esp_err_t ESP_GATT_NOT_CONNECTED = -1;
using namespace esp32_ble_client;
class BluetoothProxy : public BLEClientBase {
class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Component {
public:
BluetoothProxy();
bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override;
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;
void register_connection(BluetoothConnection *connection) {
this->connections_.push_back(connection);
connection->proxy_ = this;
}
#ifdef USE_API
void bluetooth_device_request(const api::BluetoothDeviceRequest &msg);
void bluetooth_gatt_read(const api::BluetoothGATTReadRequest &msg);
void bluetooth_gatt_write(const api::BluetoothGATTWriteRequest &msg);
@@ -39,10 +39,17 @@ class BluetoothProxy : public BLEClientBase {
void bluetooth_gatt_write_descriptor(const api::BluetoothGATTWriteDescriptorRequest &msg);
void bluetooth_gatt_send_services(const api::BluetoothGATTGetServicesRequest &msg);
void bluetooth_gatt_notify(const api::BluetoothGATTNotifyRequest &msg);
#endif
int get_bluetooth_connections_free() { return this->state_ == espbt::ClientState::IDLE ? 1 : 0; }
int get_bluetooth_connections_limit() { return 1; }
int get_bluetooth_connections_free() {
int free = 0;
for (auto *connection : this->connections_) {
if (connection->address_ == 0) {
free++;
}
}
return free;
}
int get_bluetooth_connections_limit() { return this->connections_.size(); }
void set_active(bool active) { this->active_ = active; }
bool has_active() { return this->active_; }
@@ -50,9 +57,12 @@ class BluetoothProxy : public BLEClientBase {
protected:
void send_api_packet_(const esp32_ble_tracker::ESPBTDevice &device);
std::map<uint64_t, esp_ble_addr_type_t> address_type_map_;
BluetoothConnection *get_connection_(uint64_t address, bool reserve);
int16_t send_service_{-1};
bool active_;
std::vector<BluetoothConnection *> connections_{};
};
extern BluetoothProxy *global_bluetooth_proxy; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)

View File

@@ -1,8 +1,9 @@
import esphome.codegen as cg
import esphome.config_validation as cv
import esphome.final_validate as fv
from esphome.components import web_server_base
from esphome.components.web_server_base import CONF_WEB_SERVER_BASE_ID
from esphome.const import CONF_ID
from esphome.const import CONF_ID, CONF_NETWORKS, CONF_PASSWORD, CONF_SSID, CONF_WIFI
from esphome.core import coroutine_with_priority, CORE
AUTO_LOAD = ["web_server_base"]
@@ -12,6 +13,7 @@ CODEOWNERS = ["@OttoWinter"]
captive_portal_ns = cg.esphome_ns.namespace("captive_portal")
CaptivePortal = captive_portal_ns.class_("CaptivePortal", cg.Component)
CONF_KEEP_USER_CREDENTIALS = "keep_user_credentials"
CONFIG_SCHEMA = cv.All(
cv.Schema(
{
@@ -19,12 +21,29 @@ CONFIG_SCHEMA = cv.All(
cv.GenerateID(CONF_WEB_SERVER_BASE_ID): cv.use_id(
web_server_base.WebServerBase
),
cv.Optional(CONF_KEEP_USER_CREDENTIALS, default=False): cv.boolean,
}
).extend(cv.COMPONENT_SCHEMA),
cv.only_with_arduino,
)
def validate_wifi(config):
wifi_conf = fv.full_config.get()[CONF_WIFI]
if config.get(CONF_KEEP_USER_CREDENTIALS, False) and (
CONF_SSID in wifi_conf
or CONF_PASSWORD in wifi_conf
or CONF_NETWORKS in wifi_conf
):
raise cv.Invalid(
f"WiFi credentials cannot be used together with {CONF_KEEP_USER_CREDENTIALS}"
)
return config
FINAL_VALIDATE_SCHEMA = validate_wifi
@coroutine_with_priority(64.0)
async def to_code(config):
paren = await cg.get_variable(config[CONF_WEB_SERVER_BASE_ID])
@@ -38,3 +57,6 @@ async def to_code(config):
cg.add_library("WiFi", None)
if CORE.is_esp8266:
cg.add_library("DNSServer", None)
if config.get(CONF_KEEP_USER_CREDENTIALS, False):
cg.add_define("USE_CAPTIVE_PORTAL_KEEP_USER_CREDENTIALS")

View File

@@ -0,0 +1 @@
CODEOWNERS = ["@hagak"]

View File

@@ -0,0 +1,24 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import climate_ir
from esphome.const import CONF_ID
AUTO_LOAD = ["climate_ir"]
daikin_brc_ns = cg.esphome_ns.namespace("daikin_brc")
DaikinBrcClimate = daikin_brc_ns.class_("DaikinBrcClimate", climate_ir.ClimateIR)
CONF_USE_FAHRENHEIT = "use_fahrenheit"
CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(DaikinBrcClimate),
cv.Optional(CONF_USE_FAHRENHEIT, default=False): cv.boolean,
}
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await climate_ir.register_climate_ir(var, config)
cg.add(var.set_fahrenheit(config[CONF_USE_FAHRENHEIT]))

View File

@@ -0,0 +1,273 @@
#include "daikin_brc.h"
#include "esphome/components/remote_base/remote_base.h"
namespace esphome {
namespace daikin_brc {
static const char *const TAG = "daikin_brc.climate";
void DaikinBrcClimate::control(const climate::ClimateCall &call) {
this->mode_button_ = 0x00;
if (call.get_mode().has_value()) {
// Need to determine if this is call due to Mode button pressed so that we can set the Mode button byte
this->mode_button_ = DAIKIN_BRC_IR_MODE_BUTTON;
}
ClimateIR::control(call);
}
void DaikinBrcClimate::transmit_state() {
uint8_t remote_state[DAIKIN_BRC_TRANSMIT_FRAME_SIZE] = {0x11, 0xDA, 0x17, 0x18, 0x04, 0x00, 0x1E, 0x11,
0xDA, 0x17, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x20, 0x00};
remote_state[12] = this->alt_mode_();
remote_state[13] = this->mode_button_;
remote_state[14] = this->operation_mode_();
remote_state[17] = this->temperature_();
remote_state[18] = this->fan_speed_swing_();
// Calculate checksum
for (int i = DAIKIN_BRC_PREAMBLE_SIZE; i < DAIKIN_BRC_TRANSMIT_FRAME_SIZE - 1; i++) {
remote_state[DAIKIN_BRC_TRANSMIT_FRAME_SIZE - 1] += remote_state[i];
}
auto transmit = this->transmitter_->transmit();
auto *data = transmit.get_data();
data->set_carrier_frequency(DAIKIN_BRC_IR_FREQUENCY);
data->mark(DAIKIN_BRC_HEADER_MARK);
data->space(DAIKIN_BRC_HEADER_SPACE);
for (int i = 0; i < DAIKIN_BRC_PREAMBLE_SIZE; i++) {
for (uint8_t mask = 1; mask > 0; mask <<= 1) { // iterate through bit mask
data->mark(DAIKIN_BRC_BIT_MARK);
bool bit = remote_state[i] & mask;
data->space(bit ? DAIKIN_BRC_ONE_SPACE : DAIKIN_BRC_ZERO_SPACE);
}
}
data->mark(DAIKIN_BRC_BIT_MARK);
data->space(DAIKIN_BRC_MESSAGE_SPACE);
data->mark(DAIKIN_BRC_HEADER_MARK);
data->space(DAIKIN_BRC_HEADER_SPACE);
for (int i = DAIKIN_BRC_PREAMBLE_SIZE; i < DAIKIN_BRC_TRANSMIT_FRAME_SIZE; i++) {
for (uint8_t mask = 1; mask > 0; mask <<= 1) { // iterate through bit mask
data->mark(DAIKIN_BRC_BIT_MARK);
bool bit = remote_state[i] & mask;
data->space(bit ? DAIKIN_BRC_ONE_SPACE : DAIKIN_BRC_ZERO_SPACE);
}
}
data->mark(DAIKIN_BRC_BIT_MARK);
data->space(0);
transmit.perform();
}
uint8_t DaikinBrcClimate::alt_mode_() {
uint8_t alt_mode = 0x00;
switch (this->mode) {
case climate::CLIMATE_MODE_DRY:
alt_mode = 0x23;
break;
case climate::CLIMATE_MODE_FAN_ONLY:
alt_mode = 0x63;
break;
case climate::CLIMATE_MODE_HEAT_COOL:
case climate::CLIMATE_MODE_COOL:
case climate::CLIMATE_MODE_HEAT:
default:
alt_mode = 0x73;
break;
}
return alt_mode;
}
uint8_t DaikinBrcClimate::operation_mode_() {
uint8_t operating_mode = DAIKIN_BRC_MODE_ON;
switch (this->mode) {
case climate::CLIMATE_MODE_COOL:
operating_mode |= DAIKIN_BRC_MODE_COOL;
break;
case climate::CLIMATE_MODE_DRY:
operating_mode |= DAIKIN_BRC_MODE_DRY;
break;
case climate::CLIMATE_MODE_HEAT:
operating_mode |= DAIKIN_BRC_MODE_HEAT;
break;
case climate::CLIMATE_MODE_HEAT_COOL:
operating_mode |= DAIKIN_BRC_MODE_AUTO;
break;
case climate::CLIMATE_MODE_FAN_ONLY:
operating_mode |= DAIKIN_BRC_MODE_FAN;
break;
case climate::CLIMATE_MODE_OFF:
default:
operating_mode = DAIKIN_BRC_MODE_OFF;
break;
}
return operating_mode;
}
uint8_t DaikinBrcClimate::fan_speed_swing_() {
uint16_t fan_speed;
switch (this->fan_mode.value()) {
case climate::CLIMATE_FAN_LOW:
fan_speed = DAIKIN_BRC_FAN_1;
break;
case climate::CLIMATE_FAN_MEDIUM:
fan_speed = DAIKIN_BRC_FAN_2;
break;
case climate::CLIMATE_FAN_HIGH:
fan_speed = DAIKIN_BRC_FAN_3;
break;
default:
fan_speed = DAIKIN_BRC_FAN_1;
}
// If swing is enabled switch first 4 bits to 1111
switch (this->swing_mode) {
case climate::CLIMATE_SWING_BOTH:
fan_speed |= DAIKIN_BRC_IR_SWING_ON;
break;
default:
fan_speed |= DAIKIN_BRC_IR_SWING_OFF;
break;
}
return fan_speed;
}
uint8_t DaikinBrcClimate::temperature_() {
// Force special temperatures depending on the mode
switch (this->mode) {
case climate::CLIMATE_MODE_FAN_ONLY:
case climate::CLIMATE_MODE_DRY:
if (this->fahrenheit_) {
return DAIKIN_BRC_IR_DRY_FAN_TEMP_F;
}
return DAIKIN_BRC_IR_DRY_FAN_TEMP_C;
case climate::CLIMATE_MODE_HEAT_COOL:
default:
uint8_t temperature;
// Temperature in remote is in F
if (this->fahrenheit_) {
temperature = (uint8_t) roundf(
clamp<float>(((this->target_temperature * 1.8) + 32), DAIKIN_BRC_TEMP_MIN_F, DAIKIN_BRC_TEMP_MAX_F));
} else {
temperature = ((uint8_t) roundf(this->target_temperature) - 9) << 1;
}
return temperature;
}
}
bool DaikinBrcClimate::parse_state_frame_(const uint8_t frame[]) {
uint8_t checksum = 0;
for (int i = 0; i < (DAIKIN_BRC_STATE_FRAME_SIZE - 1); i++) {
checksum += frame[i];
}
if (frame[DAIKIN_BRC_STATE_FRAME_SIZE - 1] != checksum) {
ESP_LOGCONFIG(TAG, "Bad CheckSum %x", checksum);
return false;
}
uint8_t mode = frame[7];
if (mode & DAIKIN_BRC_MODE_ON) {
switch (mode & 0xF0) {
case DAIKIN_BRC_MODE_COOL:
this->mode = climate::CLIMATE_MODE_COOL;
break;
case DAIKIN_BRC_MODE_DRY:
this->mode = climate::CLIMATE_MODE_DRY;
break;
case DAIKIN_BRC_MODE_HEAT:
this->mode = climate::CLIMATE_MODE_HEAT;
break;
case DAIKIN_BRC_MODE_AUTO:
this->mode = climate::CLIMATE_MODE_HEAT_COOL;
break;
case DAIKIN_BRC_MODE_FAN:
this->mode = climate::CLIMATE_MODE_FAN_ONLY;
break;
}
} else {
this->mode = climate::CLIMATE_MODE_OFF;
}
uint8_t temperature = frame[10];
float temperature_c;
if (this->fahrenheit_) {
temperature_c = clamp<float>(((temperature - 32) / 1.8), DAIKIN_BRC_TEMP_MIN_C, DAIKIN_BRC_TEMP_MAX_C);
} else {
temperature_c = (temperature >> 1) + 9;
}
this->target_temperature = temperature_c;
uint8_t fan_mode = frame[11];
uint8_t swing_mode = frame[11];
switch (swing_mode & 0xF) {
case DAIKIN_BRC_IR_SWING_ON:
this->swing_mode = climate::CLIMATE_SWING_BOTH;
break;
case DAIKIN_BRC_IR_SWING_OFF:
this->swing_mode = climate::CLIMATE_SWING_OFF;
break;
}
switch (fan_mode & 0xF0) {
case DAIKIN_BRC_FAN_1:
this->fan_mode = climate::CLIMATE_FAN_LOW;
break;
case DAIKIN_BRC_FAN_2:
this->fan_mode = climate::CLIMATE_FAN_MEDIUM;
break;
case DAIKIN_BRC_FAN_3:
this->fan_mode = climate::CLIMATE_FAN_HIGH;
break;
}
this->publish_state();
return true;
}
bool DaikinBrcClimate::on_receive(remote_base::RemoteReceiveData data) {
uint8_t state_frame[DAIKIN_BRC_STATE_FRAME_SIZE] = {};
if (!data.expect_item(DAIKIN_BRC_HEADER_MARK, DAIKIN_BRC_HEADER_SPACE)) {
return false;
}
for (uint8_t pos = 0; pos < DAIKIN_BRC_STATE_FRAME_SIZE; pos++) {
uint8_t byte = 0;
for (int8_t bit = 0; bit < 8; bit++) {
if (data.expect_item(DAIKIN_BRC_BIT_MARK, DAIKIN_BRC_ONE_SPACE)) {
byte |= 1 << bit;
} else if (!data.expect_item(DAIKIN_BRC_BIT_MARK, DAIKIN_BRC_ZERO_SPACE)) {
return false;
}
}
state_frame[pos] = byte;
if (pos == 0) {
// frame header
if (byte != 0x11)
return false;
} else if (pos == 1) {
// frame header
if (byte != 0xDA)
return false;
} else if (pos == 2) {
// frame header
if (byte != 0x17)
return false;
} else if (pos == 3) {
// frame header
if (byte != 0x18)
return false;
} else if (pos == 4) {
// frame type
if (byte != 0x00)
return false;
}
}
return this->parse_state_frame_(state_frame);
}
} // namespace daikin_brc
} // namespace esphome

View File

@@ -0,0 +1,82 @@
#pragma once
#include "esphome/components/climate_ir/climate_ir.h"
namespace esphome {
namespace daikin_brc {
// Values for Daikin BRC4CXXX IR Controllers
// Temperature
const uint8_t DAIKIN_BRC_TEMP_MIN_F = 60; // fahrenheit
const uint8_t DAIKIN_BRC_TEMP_MAX_F = 90; // fahrenheit
const float DAIKIN_BRC_TEMP_MIN_C = (DAIKIN_BRC_TEMP_MIN_F - 32) / 1.8; // fahrenheit
const float DAIKIN_BRC_TEMP_MAX_C = (DAIKIN_BRC_TEMP_MAX_F - 32) / 1.8; // fahrenheit
// Modes
const uint8_t DAIKIN_BRC_MODE_AUTO = 0x30;
const uint8_t DAIKIN_BRC_MODE_COOL = 0x20;
const uint8_t DAIKIN_BRC_MODE_HEAT = 0x10;
const uint8_t DAIKIN_BRC_MODE_DRY = 0x70;
const uint8_t DAIKIN_BRC_MODE_FAN = 0x00;
const uint8_t DAIKIN_BRC_MODE_OFF = 0x00;
const uint8_t DAIKIN_BRC_MODE_ON = 0x01;
// Fan Speed
const uint8_t DAIKIN_BRC_FAN_1 = 0x10;
const uint8_t DAIKIN_BRC_FAN_2 = 0x30;
const uint8_t DAIKIN_BRC_FAN_3 = 0x50;
const uint8_t DAIKIN_BRC_FAN_AUTO = 0xA0;
// IR Transmission
const uint32_t DAIKIN_BRC_IR_FREQUENCY = 38000;
const uint32_t DAIKIN_BRC_HEADER_MARK = 5070;
const uint32_t DAIKIN_BRC_HEADER_SPACE = 2140;
const uint32_t DAIKIN_BRC_BIT_MARK = 370;
const uint32_t DAIKIN_BRC_ONE_SPACE = 1780;
const uint32_t DAIKIN_BRC_ZERO_SPACE = 710;
const uint32_t DAIKIN_BRC_MESSAGE_SPACE = 29410;
const uint8_t DAIKIN_BRC_IR_DRY_FAN_TEMP_F = 72; // Dry/Fan mode is always 17 Celsius.
const uint8_t DAIKIN_BRC_IR_DRY_FAN_TEMP_C = (17 - 9) * 2; // Dry/Fan mode is always 17 Celsius.
const uint8_t DAIKIN_BRC_IR_SWING_ON = 0x5;
const uint8_t DAIKIN_BRC_IR_SWING_OFF = 0x6;
const uint8_t DAIKIN_BRC_IR_MODE_BUTTON = 0x4; // This is set after a mode action
// State Frame size
const uint8_t DAIKIN_BRC_STATE_FRAME_SIZE = 15;
// Preamble size
const uint8_t DAIKIN_BRC_PREAMBLE_SIZE = 7;
// Transmit Frame size - includes a preamble
const uint8_t DAIKIN_BRC_TRANSMIT_FRAME_SIZE = DAIKIN_BRC_PREAMBLE_SIZE + DAIKIN_BRC_STATE_FRAME_SIZE;
class DaikinBrcClimate : public climate_ir::ClimateIR {
public:
DaikinBrcClimate()
: climate_ir::ClimateIR(DAIKIN_BRC_TEMP_MIN_C, DAIKIN_BRC_TEMP_MAX_C, 0.5f, true, true,
{climate::CLIMATE_FAN_LOW, climate::CLIMATE_FAN_MEDIUM, climate::CLIMATE_FAN_HIGH},
{climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_BOTH}) {}
/// Set use of Fahrenheit units
void set_fahrenheit(bool value) {
this->fahrenheit_ = value;
this->temperature_step_ = value ? 0.5f : 1.0f;
}
protected:
uint8_t mode_button_ = 0x00;
// Capture if the MODE was changed
void control(const climate::ClimateCall &call) override;
// Transmit via IR the state of this climate controller.
void transmit_state() override;
uint8_t alt_mode_();
uint8_t operation_mode_();
uint8_t fan_speed_swing_();
uint8_t temperature_();
// Handle received IR Buffer
bool on_receive(remote_base::RemoteReceiveData data) override;
bool parse_state_frame_(const uint8_t frame[]);
bool fahrenheit_{false};
};
} // namespace daikin_brc
} // namespace esphome

View File

@@ -1,7 +1,7 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.const import CONF_ID, CONF_TYPE, CONF_UUID
from esphome.core import CORE
from esphome.const import CONF_ID, CONF_TYPE, CONF_UUID, CONF_TX_POWER
from esphome.core import CORE, TimePeriod
from esphome.components.esp32 import add_idf_sdkconfig_option
DEPENDENCIES = ["esp32"]
@@ -12,16 +12,47 @@ ESP32BLEBeacon = esp32_ble_beacon_ns.class_("ESP32BLEBeacon", cg.Component)
CONF_MAJOR = "major"
CONF_MINOR = "minor"
CONF_MIN_INTERVAL = "min_interval"
CONF_MAX_INTERVAL = "max_interval"
CONF_MEASURED_POWER = "measured_power"
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.declare_id(ESP32BLEBeacon),
cv.Required(CONF_TYPE): cv.one_of("IBEACON", upper=True),
cv.Required(CONF_UUID): cv.uuid,
cv.Optional(CONF_MAJOR, default=10167): cv.uint16_t,
cv.Optional(CONF_MINOR, default=61958): cv.uint16_t,
}
).extend(cv.COMPONENT_SCHEMA)
def validate_config(config):
if config[CONF_MIN_INTERVAL] > config.get(CONF_MAX_INTERVAL):
raise cv.Invalid("min_interval must be <= max_interval")
return config
CONFIG_SCHEMA = cv.All(
cv.Schema(
{
cv.GenerateID(): cv.declare_id(ESP32BLEBeacon),
cv.Required(CONF_TYPE): cv.one_of("IBEACON", upper=True),
cv.Required(CONF_UUID): cv.uuid,
cv.Optional(CONF_MAJOR, default=10167): cv.uint16_t,
cv.Optional(CONF_MINOR, default=61958): cv.uint16_t,
cv.Optional(CONF_MIN_INTERVAL, default="100ms"): cv.All(
cv.positive_time_period_milliseconds,
cv.Range(
min=TimePeriod(milliseconds=20), max=TimePeriod(milliseconds=10240)
),
),
cv.Optional(CONF_MAX_INTERVAL, default="100ms"): cv.All(
cv.positive_time_period_milliseconds,
cv.Range(
min=TimePeriod(milliseconds=20), max=TimePeriod(milliseconds=10240)
),
),
cv.Optional(CONF_MEASURED_POWER, default=-59): cv.int_range(
min=-128, max=0
),
cv.Optional(CONF_TX_POWER, default="3dBm"): cv.All(
cv.decibel, cv.one_of(-12, -9, -6, -3, 0, 3, 6, 9, int=True)
),
}
).extend(cv.COMPONENT_SCHEMA),
validate_config,
)
async def to_code(config):
@@ -31,6 +62,10 @@ async def to_code(config):
await cg.register_component(var, config)
cg.add(var.set_major(config[CONF_MAJOR]))
cg.add(var.set_minor(config[CONF_MINOR]))
cg.add(var.set_min_interval(config[CONF_MIN_INTERVAL]))
cg.add(var.set_max_interval(config[CONF_MAX_INTERVAL]))
cg.add(var.set_measured_power(config[CONF_MEASURED_POWER]))
cg.add(var.set_tx_power(config[CONF_TX_POWER]))
if CORE.using_esp_idf:
add_idf_sdkconfig_option("CONFIG_BT_ENABLED", True)

View File

@@ -36,11 +36,24 @@ static esp_ble_adv_params_t ble_adv_params = {
#define ENDIAN_CHANGE_U16(x) ((((x) &0xFF00) >> 8) + (((x) &0xFF) << 8))
static const esp_ble_ibeacon_head_t IBEACON_COMMON_HEAD = {
.flags = {0x02, 0x01, 0x06}, .length = 0x1A, .type = 0xFF, .company_id = 0x004C, .beacon_type = 0x1502};
.flags = {0x02, 0x01, 0x06}, .length = 0x1A, .type = 0xFF, .company_id = {0x4C, 0x00}, .beacon_type = {0x02, 0x15}};
void ESP32BLEBeacon::dump_config() {
ESP_LOGCONFIG(TAG, "ESP32 BLE Beacon:");
ESP_LOGCONFIG(TAG, " Major: %u, Minor: %u", this->major_, this->minor_);
char uuid[37];
char *bpos = uuid;
for (int8_t ii = 0; ii < 16; ++ii) {
bpos += sprintf(bpos, "%02X", this->uuid_[ii]);
if (ii == 3 || ii == 5 || ii == 7 || ii == 9) {
bpos += sprintf(bpos, "-");
}
}
uuid[36] = '\0';
ESP_LOGCONFIG(TAG,
" UUID: %s, Major: %u, Minor: %u, Min Interval: %ums, Max Interval: %ums, Measured Power: %d"
", TX Power: %ddBm",
uuid, this->major_, this->minor_, this->min_interval_, this->max_interval_, this->measured_power_,
this->tx_power_);
}
void ESP32BLEBeacon::setup() {
@@ -67,6 +80,9 @@ void ESP32BLEBeacon::ble_core_task(void *params) {
}
void ESP32BLEBeacon::ble_setup() {
ble_adv_params.adv_int_min = static_cast<uint16_t>(global_esp32_ble_beacon->min_interval_ / 0.625f);
ble_adv_params.adv_int_max = static_cast<uint16_t>(global_esp32_ble_beacon->max_interval_ / 0.625f);
// Initialize non-volatile storage for the bluetooth controller
esp_err_t err = nvs_flash_init();
if (err != ESP_OK) {
@@ -118,6 +134,12 @@ void ESP32BLEBeacon::ble_setup() {
ESP_LOGE(TAG, "esp_bluedroid_enable failed: %d", err);
return;
}
err = esp_ble_tx_power_set(ESP_BLE_PWR_TYPE_ADV,
static_cast<esp_power_level_t>((global_esp32_ble_beacon->tx_power_ + 12) / 3));
if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_ble_tx_power_set failed: %s", esp_err_to_name(err));
return;
}
err = esp_ble_gap_register_callback(ESP32BLEBeacon::gap_event_handler);
if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_ble_gap_register_callback failed: %d", err);
@@ -130,7 +152,7 @@ void ESP32BLEBeacon::ble_setup() {
sizeof(ibeacon_adv_data.ibeacon_vendor.proximity_uuid));
ibeacon_adv_data.ibeacon_vendor.minor = ENDIAN_CHANGE_U16(global_esp32_ble_beacon->minor_);
ibeacon_adv_data.ibeacon_vendor.major = ENDIAN_CHANGE_U16(global_esp32_ble_beacon->major_);
ibeacon_adv_data.ibeacon_vendor.measured_power = 0xC5;
ibeacon_adv_data.ibeacon_vendor.measured_power = static_cast<uint8_t>(global_esp32_ble_beacon->measured_power_);
esp_ble_gap_config_adv_data_raw((uint8_t *) &ibeacon_adv_data, sizeof(ibeacon_adv_data));
}
@@ -153,7 +175,7 @@ void ESP32BLEBeacon::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap
break;
}
case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT: {
err = param->adv_start_cmpl.status;
err = param->adv_stop_cmpl.status;
if (err != ESP_BT_STATUS_SUCCESS) {
ESP_LOGE(TAG, "BLE adv stop failed: %s", esp_err_to_name(err));
} else {

View File

@@ -5,6 +5,7 @@
#ifdef USE_ESP32
#include <esp_gap_ble_api.h>
#include <esp_bt.h>
namespace esphome {
namespace esp32_ble_beacon {
@@ -14,8 +15,8 @@ typedef struct {
uint8_t flags[3];
uint8_t length;
uint8_t type;
uint16_t company_id;
uint16_t beacon_type;
uint8_t company_id[2];
uint8_t beacon_type[2];
} __attribute__((packed)) esp_ble_ibeacon_head_t;
// NOLINTNEXTLINE(modernize-use-using)
@@ -42,6 +43,10 @@ class ESP32BLEBeacon : public Component {
void set_major(uint16_t major) { this->major_ = major; }
void set_minor(uint16_t minor) { this->minor_ = minor; }
void set_min_interval(uint16_t val) { this->min_interval_ = val; }
void set_max_interval(uint16_t val) { this->max_interval_ = val; }
void set_measured_power(int8_t val) { this->measured_power_ = val; }
void set_tx_power(int8_t val) { this->tx_power_ = val; }
protected:
static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param);
@@ -51,6 +56,10 @@ class ESP32BLEBeacon : public Component {
std::array<uint8_t, 16> uuid_;
uint16_t major_{};
uint16_t minor_{};
uint16_t min_interval_{};
uint16_t max_interval_{};
int8_t measured_power_{};
int8_t tx_power_{};
};
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)

View File

@@ -9,7 +9,7 @@
namespace esphome {
namespace esp32_ble_client {
static const char *const TAG = "esp32_ble_client.characteristic";
static const char *const TAG = "esp32_ble_client";
BLECharacteristic::~BLECharacteristic() {
for (auto &desc : this->descriptors)
@@ -29,7 +29,8 @@ void BLECharacteristic::parse_descriptors() {
break;
}
if (status != ESP_GATT_OK) {
ESP_LOGW(TAG, "esp_ble_gattc_get_all_descr error, status=%d", status);
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) {
@@ -41,7 +42,8 @@ void BLECharacteristic::parse_descriptors() {
desc->handle = result.handle;
desc->characteristic = this;
this->descriptors.push_back(desc);
ESP_LOGV(TAG, " descriptor %s, handle 0x%x", desc->uuid.to_string().c_str(), desc->handle);
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++;
}
}
@@ -64,17 +66,19 @@ BLEDescriptor *BLECharacteristic::get_descriptor_by_handle(uint16_t handle) {
return nullptr;
}
void BLECharacteristic::write_value(uint8_t *new_val, int16_t new_val_size, esp_gatt_write_type_t write_type) {
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, "Error sending write value to BLE gattc server, status=%d", 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;
}
void BLECharacteristic::write_value(uint8_t *new_val, int16_t new_val_size) {
write_value(new_val, new_val_size, ESP_GATT_WRITE_TYPE_NO_RSP);
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

View File

@@ -24,8 +24,8 @@ class BLECharacteristic {
BLEDescriptor *get_descriptor(espbt::ESPBTUUID uuid);
BLEDescriptor *get_descriptor(uint16_t uuid);
BLEDescriptor *get_descriptor_by_handle(uint16_t handle);
void write_value(uint8_t *new_val, int16_t new_val_size);
void write_value(uint8_t *new_val, int16_t new_val_size, esp_gatt_write_type_t write_type);
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;
};

View File

@@ -11,6 +11,9 @@ namespace esp32_ble_client {
static const char *const TAG = "esp32_ble_client";
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);
@@ -30,10 +33,10 @@ float BLEClientBase::get_setup_priority() const { return setup_priority::AFTER_B
bool BLEClientBase::parse_device(const espbt::ESPBTDevice &device) {
if (device.address_uint64() != this->address_)
return false;
if (this->state_ != espbt::ClientState::IDLE)
if (this->state_ != espbt::ClientState::IDLE && this->state_ != espbt::ClientState::SEARCHING)
return false;
ESP_LOGD(TAG, "Found device at MAC address [%s]", device.address_str().c_str());
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();
@@ -47,80 +50,88 @@ bool BLEClientBase::parse_device(const espbt::ESPBTDevice &device) {
return true;
}
std::string BLEClientBase::address_str() const {
return 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 BLEClientBase::connect() {
ESP_LOGI(TAG, "Attempting BLE connection to %s", this->address_str().c_str());
ESP_LOGI(TAG, "[%d] [%s] Attempting BLE connection", this->connection_index_, this->address_str_.c_str());
auto ret = esp_ble_gattc_open(this->gattc_if_, this->remote_bda_, this->remote_addr_type_, true);
if (ret) {
ESP_LOGW(TAG, "esp_ble_gattc_open error, address=%s status=%d", this->address_str().c_str(), 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::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t esp_gattc_if,
void BLEClientBase::disconnect() {
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->set_address(0);
this->set_state(espbt::ClientState::IDLE);
}
}
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;
return false;
if (event != ESP_GATTC_REG_EVT && esp_gattc_if != ESP_GATT_IF_NONE && esp_gattc_if != this->gattc_if_)
return;
return false;
ESP_LOGV(TAG, "[%d] [%s] gattc_event_handler: event=%d gattc_if=%d conn_id=%d", this->connection_index_,
this->address_str_.c_str(), event, esp_gattc_if, this->conn_id_);
switch (event) {
case ESP_GATTC_REG_EVT: {
if (param->reg.status == ESP_GATT_OK) {
ESP_LOGV(TAG, "gattc registered app id %d", this->app_id);
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, "gattc app registration failed id=%d code=%d", param->reg.app_id, param->reg.status);
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, "[%s] ESP_GATTC_OPEN_EVT", this->address_str().c_str());
ESP_LOGV(TAG, "[%d] [%s] ESP_GATTC_OPEN_EVT", this->connection_index_, this->address_str_.c_str());
this->conn_id_ = param->open.conn_id;
if (param->open.status != ESP_GATT_OK && param->open.status != ESP_GATT_ALREADY_OPEN) {
ESP_LOGW(TAG, "connect to %s failed, status=%d", this->address_str().c_str(), param->open.status);
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;
}
break;
}
case ESP_GATTC_CONNECT_EVT: {
ESP_LOGV(TAG, "[%s] ESP_GATTC_CONNECT_EVT", this->address_str().c_str());
if (this->conn_id_ != param->connect.conn_id) {
ESP_LOGD(TAG, "[%s] Unexpected conn_id in CONNECT_EVT: param conn=%d, open conn=%d",
this->address_str().c_str(), param->connect.conn_id, this->conn_id_);
}
auto ret = esp_ble_gattc_send_mtu_req(this->gattc_if_, param->connect.conn_id);
auto ret = esp_ble_gattc_send_mtu_req(this->gattc_if_, param->open.conn_id);
if (ret) {
ESP_LOGW(TAG, "esp_ble_gattc_send_mtu_req failed, status=%x", ret);
ESP_LOGW(TAG, "[%d] [%s] esp_ble_gattc_send_mtu_req failed, status=%x", this->connection_index_,
this->address_str_.c_str(), ret);
}
break;
}
case ESP_GATTC_CFG_MTU_EVT: {
if (param->cfg_mtu.status != ESP_GATT_OK) {
ESP_LOGW(TAG, "cfg_mtu to %s failed, mtu %d, status %d", this->address_str().c_str(), param->cfg_mtu.mtu,
param->cfg_mtu.status);
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, "cfg_mtu status %d, mtu %d", param->cfg_mtu.status, param->cfg_mtu.mtu);
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;
esp_ble_gattc_search_service(esp_gattc_if, param->cfg_mtu.conn_id, nullptr);
break;
}
case ESP_GATTC_DISCONNECT_EVT: {
if (memcmp(param->disconnect.remote_bda, this->remote_bda_, 6) != 0) {
return;
}
ESP_LOGV(TAG, "[%s] ESP_GATTC_DISCONNECT_EVT, reason %d", this->address_str().c_str(), param->disconnect.reason);
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);
for (auto &svc : this->services_)
delete svc; // NOLINT(cppcoreguidelines-owning-memory)
this->services_.clear();
@@ -137,10 +148,12 @@ void BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
break;
}
case ESP_GATTC_SEARCH_CMPL_EVT: {
ESP_LOGV(TAG, "[%s] ESP_GATTC_SEARCH_CMPL_EVT", this->address_str().c_str());
ESP_LOGV(TAG, "[%d] [%s] ESP_GATTC_SEARCH_CMPL_EVT", this->connection_index_, this->address_str_.c_str());
for (auto &svc : this->services_) {
ESP_LOGI(TAG, "Service UUID: %s", svc->uuid.to_string().c_str());
ESP_LOGI(TAG, " start_handle: 0x%x end_handle: 0x%x", svc->start_handle, svc->end_handle);
ESP_LOGI(TAG, "[%d] [%s] Service UUID: %s", this->connection_index_, this->address_str_.c_str(),
svc->uuid.to_string().c_str());
ESP_LOGI(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);
svc->parse_characteristics();
}
this->set_state(espbt::ClientState::CONNECTED);
@@ -149,14 +162,10 @@ void BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
}
case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
auto *descr = this->get_config_descriptor(param->reg_for_notify.handle);
if (descr == nullptr) {
ESP_LOGW(TAG, "No descriptor found for notify of handle 0x%x", param->reg_for_notify.handle);
break;
}
if (descr->uuid.get_uuid().len != ESP_UUID_LEN_16 ||
descr->uuid.get_uuid().uuid.uuid16 != ESP_GATT_UUID_CHAR_CLIENT_CONFIG) {
ESP_LOGW(TAG, "Handle 0x%x (uuid %s) is not a client config char uuid", param->reg_for_notify.handle,
descr->uuid.to_string().c_str());
ESP_LOGW(TAG, "[%d] [%s] Handle 0x%x (uuid %s) is not a client config char uuid", this->connection_index_,
this->address_str_.c_str(), param->reg_for_notify.handle, descr->uuid.to_string().c_str());
break;
}
uint16_t notify_en = 1;
@@ -164,7 +173,8 @@ void BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
esp_ble_gattc_write_char_descr(this->gattc_if_, this->conn_id_, descr->handle, sizeof(notify_en),
(uint8_t *) &notify_en, ESP_GATT_WRITE_TYPE_RSP, ESP_GATT_AUTH_REQ_NONE);
if (status) {
ESP_LOGW(TAG, "esp_ble_gattc_write_char_descr error, status=%d", 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;
}
@@ -172,24 +182,28 @@ void BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
default:
break;
}
return true;
}
void BLEClientBase::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) {
switch (event) {
// This event is sent by the server when it requests security
case ESP_GAP_BLE_SEC_REQ_EVT:
ESP_LOGV(TAG, "ESP_GAP_BLE_SEC_REQ_EVT %x", event);
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_bd_addr_t bd_addr;
memcpy(bd_addr, param->ble_security.auth_cmpl.bd_addr, sizeof(esp_bd_addr_t));
ESP_LOGI(TAG, "auth complete. remote BD_ADDR: %s", format_hex(bd_addr, 6).c_str());
ESP_LOGI(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, "auth fail reason = 0x%x", param->ble_security.auth_cmpl.fail_reason);
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_LOGV(TAG, "auth success. address type = %d auth mode = %d", param->ble_security.auth_cmpl.addr_type,
ESP_LOGV(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;
@@ -245,7 +259,8 @@ float BLEClientBase::parse_char_value(uint8_t *value, uint16_t length) {
(int32_t)(value[4]));
}
}
ESP_LOGW(TAG, "Cannot parse characteristic value of type 0x%x length %d", value[0], length);
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;
}

View File

@@ -28,13 +28,27 @@ class BLEClientBase : public espbt::ESPBTClient, public Component {
bool parse_device(const espbt::ESPBTDevice &device) override;
void on_scan_end() override {}
void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
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 set_address(uint64_t address) { this->address_ = address; }
std::string address_str() const;
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);
}
}
std::string address_str() const { return this->address_str_; }
BLEService *get_service(espbt::ESPBTUUID uuid);
BLEService *get_service(uint16_t uuid);
@@ -55,12 +69,16 @@ class BLEClientBase : public espbt::ESPBTClient, public Component {
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_; }
protected:
int gattc_if_;
esp_bd_addr_t remote_bda_;
esp_ble_addr_type_t remote_addr_type_;
uint16_t conn_id_;
uint64_t address_;
uint16_t conn_id_{0xFFFF};
uint64_t address_{0};
std::string address_str_{};
uint8_t connection_index_;
uint16_t mtu_{23};
std::vector<BLEService *> services_;

View File

@@ -8,7 +8,7 @@
namespace esphome {
namespace esp32_ble_client {
static const char *const TAG = "esp32_ble_client.service";
static const char *const TAG = "esp32_ble_client";
BLECharacteristic *BLEService::get_characteristic(espbt::ESPBTUUID uuid) {
for (auto &chr : this->characteristics) {
@@ -40,7 +40,8 @@ void BLEService::parse_characteristics() {
break;
}
if (status != ESP_GATT_OK) {
ESP_LOGW(TAG, "esp_ble_gattc_get_all_char error, status=%d", status);
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) {
@@ -53,8 +54,9 @@ void BLEService::parse_characteristics() {
characteristic->handle = result.char_handle;
characteristic->service = this;
this->characteristics.push_back(characteristic);
ESP_LOGI(TAG, " characteristic %s, handle 0x%x, properties 0x%x", characteristic->uuid.to_string().c_str(),
characteristic->handle, characteristic->properties);
ESP_LOGI(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);
characteristic->parse_descriptors();
offset++;
}

View File

@@ -648,11 +648,17 @@ void ESPBTDevice::parse_adv_(const esp_ble_gap_cb_param_t::ble_scan_result_evt_p
// (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.
this->name_ = std::string(reinterpret_cast<const char *>(record), record_length);
// 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: {

View File

@@ -145,6 +145,8 @@ class ESPBTDeviceListener {
enum class ClientState {
// Connection is idle, no device detected.
IDLE,
// Searching for device.
SEARCHING,
// Device advertisement found.
DISCOVERED,
// Connection in progress.
@@ -157,7 +159,7 @@ enum class ClientState {
class ESPBTClient : public ESPBTDeviceListener {
public:
virtual void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
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;

View File

@@ -56,7 +56,7 @@ async def to_code(config):
cg.add(ble_server.register_service_component(var))
cg.add_define("USE_IMPROV")
cg.add_library("esphome/Improv", "1.2.1")
cg.add_library("esphome/Improv", "1.2.3")
cg.add(var.set_identify_duration(config[CONF_IDENTIFY_DURATION]))
cg.add(var.set_authorized_duration(config[CONF_AUTHORIZED_DURATION]))

View File

@@ -1,6 +1,5 @@
import logging
from dataclasses import dataclass
from typing import List
from esphome.const import (
CONF_ID,
@@ -200,7 +199,7 @@ async def esp8266_pin_to_code(config):
@coroutine_with_priority(-999.0)
async def add_pin_initial_states_array():
# Add includes at the very end, so that they override everything
initial_states: List[PinInitialState] = CORE.data[KEY_ESP8266][
initial_states: list[PinInitialState] = CORE.data[KEY_ESP8266][
KEY_PIN_INITIAL_STATES
]
initial_modes_s = ", ".join(str(x.mode) for x in initial_states)

View File

@@ -0,0 +1 @@
CODEOWNERS = ["@gtjadsonsantos"]

View File

@@ -0,0 +1,16 @@
#include "ethernet_info_text_sensor.h"
#include "esphome/core/log.h"
#ifdef USE_ESP32_FRAMEWORK_ARDUINO
namespace esphome {
namespace ethernet_info {
static const char *const TAG = "ethernet_info";
void IPAddressEthernetInfo::dump_config() { LOG_TEXT_SENSOR("", "EthernetInfo IPAddress", this); }
} // namespace ethernet_info
} // namespace esphome
#endif // USE_ESP32_FRAMEWORK_ARDUINO

View File

@@ -0,0 +1,35 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/text_sensor/text_sensor.h"
#include "esphome/components/ethernet/ethernet_component.h"
#ifdef USE_ESP32_FRAMEWORK_ARDUINO
namespace esphome {
namespace ethernet_info {
class IPAddressEthernetInfo : public PollingComponent, public text_sensor::TextSensor {
public:
void update() override {
tcpip_adapter_ip_info_t tcpip;
tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_ETH, &tcpip);
auto ip = tcpip.ip.addr;
if (ip != this->last_ip_) {
this->last_ip_ = ip;
this->publish_state(network::IPAddress(ip).str());
}
}
float get_setup_priority() const override { return setup_priority::ETHERNET; }
std::string unique_id() override { return get_mac_address() + "-ethernetinfo"; }
void dump_config() override;
protected:
network::IPAddress last_ip_;
};
} // namespace ethernet_info
} // namespace esphome
#endif // USE_ESP32_FRAMEWORK_ARDUINO

View File

@@ -0,0 +1,34 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import text_sensor
from esphome.const import (
CONF_IP_ADDRESS,
ENTITY_CATEGORY_DIAGNOSTIC,
)
DEPENDENCIES = ["ethernet"]
ethernet_info_ns = cg.esphome_ns.namespace("ethernet_info")
IPAddressEsthernetInfo = ethernet_info_ns.class_(
"IPAddressEthernetInfo", text_sensor.TextSensor, cg.PollingComponent
)
CONFIG_SCHEMA = cv.Schema(
{
cv.Optional(CONF_IP_ADDRESS): text_sensor.text_sensor_schema(
IPAddressEsthernetInfo, entity_category=ENTITY_CATEGORY_DIAGNOSTIC
).extend(cv.polling_component_schema("1s"))
}
)
async def setup_conf(config, key):
if key in config:
conf = config[key]
var = await text_sensor.new_text_sensor(conf)
await cg.register_component(var, conf)
async def to_code(config):
await setup_conf(config, CONF_IP_ADDRESS)

View File

@@ -98,7 +98,7 @@ async def to_code(config):
def _process_git_config(config: dict, refresh) -> str:
repo_dir = git.clone_or_update(
repo_dir, _ = git.clone_or_update(
url=config[CONF_URL],
ref=config.get(CONF_REF),
refresh=refresh,

View File

@@ -0,0 +1,291 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import i2c
from esphome.const import CONF_ADDRESS, CONF_COMMAND, CONF_ID, CONF_DURATION
from esphome import automation
from esphome.automation import maybe_simple_id
CODEOWNERS = ["@carlos-sarmiento"]
DEPENDENCIES = ["i2c"]
MULTI_CONF = True
CONF_VOLUME = "volume"
CONF_VOLUME_PER_MINUTE = "volume_per_minute"
ezo_pmp_ns = cg.esphome_ns.namespace("ezo_pmp")
EzoPMP = ezo_pmp_ns.class_("EzoPMP", cg.PollingComponent, i2c.I2CDevice)
CONFIG_SCHEMA = (
cv.Schema(
{
cv.GenerateID(): cv.declare_id(EzoPMP),
}
)
.extend(cv.COMPONENT_SCHEMA)
.extend(cv.polling_component_schema("60s"))
.extend(i2c.i2c_device_schema(103))
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await i2c.register_i2c_device(var, config)
EZO_PMP_NO_ARGS_ACTION_SCHEMA = maybe_simple_id(
{
cv.Required(CONF_ID): cv.use_id(EzoPMP),
}
)
# Actions that do not require more arguments
EzoPMPFindAction = ezo_pmp_ns.class_("EzoPMPFindAction", automation.Action)
EzoPMPClearTotalVolumeDispensedAction = ezo_pmp_ns.class_(
"EzoPMPClearTotalVolumeDispensedAction", automation.Action
)
EzoPMPClearCalibrationAction = ezo_pmp_ns.class_(
"EzoPMPClearCalibrationAction", automation.Action
)
EzoPMPPauseDosingAction = ezo_pmp_ns.class_(
"EzoPMPPauseDosingAction", automation.Action
)
EzoPMPStopDosingAction = ezo_pmp_ns.class_("EzoPMPStopDosingAction", automation.Action)
EzoPMPDoseContinuouslyAction = ezo_pmp_ns.class_(
"EzoPMPDoseContinuouslyAction", automation.Action
)
# Actions that require more arguments
EzoPMPDoseVolumeAction = ezo_pmp_ns.class_("EzoPMPDoseVolumeAction", automation.Action)
EzoPMPDoseVolumeOverTimeAction = ezo_pmp_ns.class_(
"EzoPMPDoseVolumeOverTimeAction", automation.Action
)
EzoPMPDoseWithConstantFlowRateAction = ezo_pmp_ns.class_(
"EzoPMPDoseWithConstantFlowRateAction", automation.Action
)
EzoPMPSetCalibrationVolumeAction = ezo_pmp_ns.class_(
"EzoPMPSetCalibrationVolumeAction", automation.Action
)
EzoPMPChangeI2CAddressAction = ezo_pmp_ns.class_(
"EzoPMPChangeI2CAddressAction", automation.Action
)
EzoPMPArbitraryCommandAction = ezo_pmp_ns.class_(
"EzoPMPArbitraryCommandAction", automation.Action
)
@automation.register_action(
"ezo_pmp.find", EzoPMPFindAction, EZO_PMP_NO_ARGS_ACTION_SCHEMA
)
async def ezo_pmp_find_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID])
return cg.new_Pvariable(action_id, template_arg, paren)
@automation.register_action(
"ezo_pmp.dose_continuously",
EzoPMPDoseContinuouslyAction,
EZO_PMP_NO_ARGS_ACTION_SCHEMA,
)
async def ezo_pmp_dose_continuously_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID])
return cg.new_Pvariable(action_id, template_arg, paren)
@automation.register_action(
"ezo_pmp.clear_total_volume_dosed",
EzoPMPClearTotalVolumeDispensedAction,
EZO_PMP_NO_ARGS_ACTION_SCHEMA,
)
async def ezo_pmp_clear_total_volume_dosed_to_code(
config, action_id, template_arg, args
):
paren = await cg.get_variable(config[CONF_ID])
return cg.new_Pvariable(action_id, template_arg, paren)
@automation.register_action(
"ezo_pmp.clear_calibration",
EzoPMPClearCalibrationAction,
EZO_PMP_NO_ARGS_ACTION_SCHEMA,
)
async def ezo_pmp_clear_calibration_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID])
return cg.new_Pvariable(action_id, template_arg, paren)
@automation.register_action(
"ezo_pmp.pause_dosing", EzoPMPPauseDosingAction, EZO_PMP_NO_ARGS_ACTION_SCHEMA
)
async def ezo_pmp_pause_dosing_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID])
return cg.new_Pvariable(action_id, template_arg, paren)
@automation.register_action(
"ezo_pmp.stop_dosing", EzoPMPStopDosingAction, EZO_PMP_NO_ARGS_ACTION_SCHEMA
)
async def ezo_pmp_stop_dosing_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID])
return cg.new_Pvariable(action_id, template_arg, paren)
# Actions that require Multiple Args
EZO_PMP_DOSE_VOLUME_ACTION_SCHEMA = cv.All(
{
cv.Required(CONF_ID): cv.use_id(EzoPMP),
cv.Required(CONF_VOLUME): cv.templatable(
cv.float_range()
), # Any way to represent as proper volume (vs. raw int)
}
)
@automation.register_action(
"ezo_pmp.dose_volume", EzoPMPDoseVolumeAction, EZO_PMP_DOSE_VOLUME_ACTION_SCHEMA
)
async def ezo_pmp_dose_volume_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)
template_ = await cg.templatable(config[CONF_VOLUME], args, cg.double)
cg.add(var.set_volume(template_))
return var
EZO_PMP_DOSE_VOLUME_OVER_TIME_ACTION_SCHEMA = cv.All(
{
cv.Required(CONF_ID): cv.use_id(EzoPMP),
cv.Required(CONF_VOLUME): cv.templatable(
cv.float_range()
), # Any way to represent as proper volume (vs. raw int)
cv.Required(CONF_DURATION): cv.templatable(
cv.int_range(1)
), # Any way to represent it as minutes (vs. raw int)
}
)
@automation.register_action(
"ezo_pmp.dose_volume_over_time",
EzoPMPDoseVolumeOverTimeAction,
EZO_PMP_DOSE_VOLUME_OVER_TIME_ACTION_SCHEMA,
)
async def ezo_pmp_dose_volume_over_time_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)
template_ = await cg.templatable(config[CONF_VOLUME], args, cg.double)
cg.add(var.set_volume(template_))
template_ = await cg.templatable(config[CONF_DURATION], args, int)
cg.add(var.set_duration(template_))
return var
EZO_PMP_DOSE_WITH_CONSTANT_FLOW_RATE_ACTION_SCHEMA = cv.All(
{
cv.Required(CONF_ID): cv.use_id(EzoPMP),
cv.Required(CONF_VOLUME_PER_MINUTE): cv.templatable(
cv.float_range()
), # Any way to represent as proper volume (vs. raw int)
cv.Required(CONF_DURATION): cv.templatable(
cv.int_range(1)
), # Any way to represent it as minutes (vs. raw int)
}
)
@automation.register_action(
"ezo_pmp.dose_with_constant_flow_rate",
EzoPMPDoseWithConstantFlowRateAction,
EZO_PMP_DOSE_WITH_CONSTANT_FLOW_RATE_ACTION_SCHEMA,
)
async def ezo_pmp_dose_with_constant_flow_rate_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)
template_ = await cg.templatable(config[CONF_VOLUME_PER_MINUTE], args, cg.double)
cg.add(var.set_volume(template_))
template_ = await cg.templatable(config[CONF_DURATION], args, int)
cg.add(var.set_duration(template_))
return var
EZO_PMP_SET_CALIBRATION_VOLUME_ACTION_SCHEMA = cv.All(
{
cv.Required(CONF_ID): cv.use_id(EzoPMP),
cv.Required(CONF_VOLUME): cv.templatable(
cv.float_range()
), # Any way to represent as proper volume (vs. raw int)
}
)
@automation.register_action(
"ezo_pmp.set_calibration_volume",
EzoPMPSetCalibrationVolumeAction,
EZO_PMP_SET_CALIBRATION_VOLUME_ACTION_SCHEMA,
)
async def ezo_pmp_set_calibration_volume_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)
template_ = await cg.templatable(config[CONF_VOLUME], args, cg.double)
cg.add(var.set_volume(template_))
return var
EZO_PMP_CHANGE_I2C_ADDRESS_ACTION_SCHEMA = cv.All(
{
cv.Required(CONF_ID): cv.use_id(EzoPMP),
cv.Required(CONF_ADDRESS): cv.templatable(cv.int_range(min=1, max=127)),
}
)
@automation.register_action(
"ezo_pmp.change_i2c_address",
EzoPMPChangeI2CAddressAction,
EZO_PMP_CHANGE_I2C_ADDRESS_ACTION_SCHEMA,
)
async def ezo_pmp_change_i2c_address_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)
template_ = await cg.templatable(config[CONF_ADDRESS], args, cg.double)
cg.add(var.set_address(template_))
return var
EZO_PMP_ARBITRARY_COMMAND_ACTION_SCHEMA = cv.All(
{
cv.Required(CONF_ID): cv.use_id(EzoPMP),
cv.Required(CONF_COMMAND): cv.templatable(cv.string_strict),
}
)
@automation.register_action(
"ezo_pmp.arbitrary_command",
EzoPMPArbitraryCommandAction,
EZO_PMP_ARBITRARY_COMMAND_ACTION_SCHEMA,
)
async def ezo_pmp_arbitrary_command_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)
template_ = await cg.templatable(config[CONF_COMMAND], args, cg.std_string)
cg.add(var.set_command(template_))
return var

View File

@@ -0,0 +1,42 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import binary_sensor
from esphome.const import (
ENTITY_CATEGORY_NONE,
DEVICE_CLASS_RUNNING,
DEVICE_CLASS_EMPTY,
CONF_ID,
)
from . import EzoPMP
DEPENDENCIES = ["ezo_pmp"]
CONF_PUMP_STATE = "pump_state"
CONF_IS_PAUSED = "is_paused"
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.use_id(EzoPMP),
cv.Optional(CONF_PUMP_STATE): binary_sensor.binary_sensor_schema(
device_class=DEVICE_CLASS_RUNNING,
entity_category=ENTITY_CATEGORY_NONE,
),
cv.Optional(CONF_IS_PAUSED): binary_sensor.binary_sensor_schema(
device_class=DEVICE_CLASS_EMPTY,
entity_category=ENTITY_CATEGORY_NONE,
),
}
)
async def to_code(config):
parent = await cg.get_variable(config[CONF_ID])
if CONF_PUMP_STATE in config:
sens = await binary_sensor.new_binary_sensor(config[CONF_PUMP_STATE])
cg.add(parent.set_is_dosing(sens))
if CONF_IS_PAUSED in config:
sens = await binary_sensor.new_binary_sensor(config[CONF_IS_PAUSED])
cg.add(parent.set_is_paused(sens))

View File

@@ -0,0 +1,542 @@
#include "ezo_pmp.h"
#include "esphome/core/log.h"
#include "esphome/core/hal.h"
namespace esphome {
namespace ezo_pmp {
static const char *const TAG = "ezo-pmp";
static const uint16_t EZO_PMP_COMMAND_NONE = 0;
static const uint16_t EZO_PMP_COMMAND_TYPE_READ = 1;
static const uint16_t EZO_PMP_COMMAND_FIND = 2;
static const uint16_t EZO_PMP_COMMAND_DOSE_CONTINUOUSLY = 4;
static const uint16_t EZO_PMP_COMMAND_DOSE_VOLUME = 8;
static const uint16_t EZO_PMP_COMMAND_DOSE_VOLUME_OVER_TIME = 16;
static const uint16_t EZO_PMP_COMMAND_DOSE_WITH_CONSTANT_FLOW_RATE = 32;
static const uint16_t EZO_PMP_COMMAND_SET_CALIBRATION_VOLUME = 64;
static const uint16_t EZO_PMP_COMMAND_CLEAR_TOTAL_VOLUME_DOSED = 128;
static const uint16_t EZO_PMP_COMMAND_CLEAR_CALIBRATION = 256;
static const uint16_t EZO_PMP_COMMAND_PAUSE_DOSING = 512;
static const uint16_t EZO_PMP_COMMAND_STOP_DOSING = 1024;
static const uint16_t EZO_PMP_COMMAND_CHANGE_I2C_ADDRESS = 2048;
static const uint16_t EZO_PMP_COMMAND_EXEC_ARBITRARY_COMMAND_ADDRESS = 4096;
static const uint16_t EZO_PMP_COMMAND_READ_DOSING = 3;
static const uint16_t EZO_PMP_COMMAND_READ_SINGLE_REPORT = 5;
static const uint16_t EZO_PMP_COMMAND_READ_MAX_FLOW_RATE = 9;
static const uint16_t EZO_PMP_COMMAND_READ_PAUSE_STATUS = 17;
static const uint16_t EZO_PMP_COMMAND_READ_TOTAL_VOLUME_DOSED = 33;
static const uint16_t EZO_PMP_COMMAND_READ_ABSOLUTE_TOTAL_VOLUME_DOSED = 65;
static const uint16_t EZO_PMP_COMMAND_READ_CALIBRATION_STATUS = 129;
static const uint16_t EZO_PMP_COMMAND_READ_PUMP_VOLTAGE = 257;
static const std::string DOSING_MODE_NONE = "None";
static const std::string DOSING_MODE_VOLUME = "Volume";
static const std::string DOSING_MODE_VOLUME_OVER_TIME = "Volume/Time";
static const std::string DOSING_MODE_CONSTANT_FLOW_RATE = "Constant Flow Rate";
static const std::string DOSING_MODE_CONTINUOUS = "Continuous";
void EzoPMP::dump_config() {
LOG_I2C_DEVICE(this);
if (this->is_failed())
ESP_LOGE(TAG, "Communication with EZO-PMP circuit failed!");
LOG_UPDATE_INTERVAL(this);
}
void EzoPMP::update() {
if (this->is_waiting_) {
return;
}
if (this->is_first_read_) {
this->queue_command_(EZO_PMP_COMMAND_READ_CALIBRATION_STATUS, 0, 0, (bool) this->calibration_status_);
this->queue_command_(EZO_PMP_COMMAND_READ_MAX_FLOW_RATE, 0, 0, (bool) this->max_flow_rate_);
this->queue_command_(EZO_PMP_COMMAND_READ_SINGLE_REPORT, 0, 0, (bool) this->current_volume_dosed_);
this->queue_command_(EZO_PMP_COMMAND_READ_TOTAL_VOLUME_DOSED, 0, 0, (bool) this->total_volume_dosed_);
this->queue_command_(EZO_PMP_COMMAND_READ_ABSOLUTE_TOTAL_VOLUME_DOSED, 0, 0,
(bool) this->absolute_total_volume_dosed_);
this->queue_command_(EZO_PMP_COMMAND_READ_PAUSE_STATUS, 0, 0, true);
this->is_first_read_ = false;
}
if (!this->is_waiting_ && this->peek_next_command_() == EZO_PMP_COMMAND_NONE) {
this->queue_command_(EZO_PMP_COMMAND_READ_DOSING, 0, 0, true);
if (this->is_dosing_flag_) {
this->queue_command_(EZO_PMP_COMMAND_READ_SINGLE_REPORT, 0, 0, (bool) this->current_volume_dosed_);
this->queue_command_(EZO_PMP_COMMAND_READ_TOTAL_VOLUME_DOSED, 0, 0, (bool) this->total_volume_dosed_);
this->queue_command_(EZO_PMP_COMMAND_READ_ABSOLUTE_TOTAL_VOLUME_DOSED, 0, 0,
(bool) this->absolute_total_volume_dosed_);
}
this->queue_command_(EZO_PMP_COMMAND_READ_PUMP_VOLTAGE, 0, 0, (bool) this->pump_voltage_);
} else {
ESP_LOGV(TAG, "Not Scheduling new Command during update()");
}
}
void EzoPMP::loop() {
// If we are not waiting for anything and there is no command to be sent, return
if (!this->is_waiting_ && this->peek_next_command_() == EZO_PMP_COMMAND_NONE) {
return;
}
// If we are not waiting for anything and there IS a command to be sent, do it.
if (!this->is_waiting_ && this->peek_next_command_() != EZO_PMP_COMMAND_NONE) {
this->send_next_command_();
}
// If we are waiting for something but it isn't ready yet, then return
if (this->is_waiting_ && millis() - this->start_time_ < this->wait_time_) {
return;
}
// We are waiting for something and it should be ready.
this->read_command_result_();
}
void EzoPMP::clear_current_command_() {
this->current_command_ = EZO_PMP_COMMAND_NONE;
this->is_waiting_ = false;
}
void EzoPMP::read_command_result_() {
uint8_t response_buffer[21] = {'\0'};
response_buffer[0] = 0;
if (!this->read_bytes_raw(response_buffer, 20)) {
ESP_LOGE(TAG, "read error");
this->clear_current_command_();
return;
}
switch (response_buffer[0]) {
case 254:
return; // keep waiting
case 1:
break;
case 2:
ESP_LOGE(TAG, "device returned a syntax error");
this->clear_current_command_();
return;
case 255:
ESP_LOGE(TAG, "device returned no data");
this->clear_current_command_();
return;
default:
ESP_LOGE(TAG, "device returned an unknown response: %d", response_buffer[0]);
this->clear_current_command_();
return;
}
char first_parameter_buffer[10] = {'\0'};
char second_parameter_buffer[10] = {'\0'};
char third_parameter_buffer[10] = {'\0'};
first_parameter_buffer[0] = '\0';
second_parameter_buffer[0] = '\0';
third_parameter_buffer[0] = '\0';
int current_parameter = 1;
size_t position_in_parameter_buffer = 0;
// some sensors return multiple comma-separated values, terminate string after first one
for (size_t i = 1; i < sizeof(response_buffer) - 1; i++) {
char current_char = response_buffer[i];
if (current_char == '\0') {
ESP_LOGV(TAG, "Read Response from device: %s", (char *) response_buffer);
ESP_LOGV(TAG, "First Component: %s", (char *) first_parameter_buffer);
ESP_LOGV(TAG, "Second Component: %s", (char *) second_parameter_buffer);
ESP_LOGV(TAG, "Third Component: %s", (char *) third_parameter_buffer);
break;
}
if (current_char == ',') {
current_parameter++;
position_in_parameter_buffer = 0;
continue;
}
switch (current_parameter) {
case 1:
first_parameter_buffer[position_in_parameter_buffer] = current_char;
first_parameter_buffer[position_in_parameter_buffer + 1] = '\0';
break;
case 2:
second_parameter_buffer[position_in_parameter_buffer] = current_char;
second_parameter_buffer[position_in_parameter_buffer + 1] = '\0';
break;
case 3:
third_parameter_buffer[position_in_parameter_buffer] = current_char;
third_parameter_buffer[position_in_parameter_buffer + 1] = '\0';
break;
}
position_in_parameter_buffer++;
}
auto parsed_first_parameter = parse_number<float>(first_parameter_buffer);
auto parsed_second_parameter = parse_number<float>(second_parameter_buffer);
auto parsed_third_parameter = parse_number<float>(third_parameter_buffer);
switch (this->current_command_) {
// Read Commands
case EZO_PMP_COMMAND_READ_DOSING: // Page 54
if (parsed_third_parameter.has_value())
this->is_dosing_flag_ = parsed_third_parameter.value_or(0) == 1;
if (this->is_dosing_)
this->is_dosing_->publish_state(this->is_dosing_flag_);
if (parsed_second_parameter.has_value() && this->last_volume_requested_) {
this->last_volume_requested_->publish_state(parsed_second_parameter.value_or(0));
}
if (!this->is_dosing_flag_ && !this->is_paused_flag_) {
// If pump is not paused and not dispensing
if (this->dosing_mode_ && this->dosing_mode_->state != DOSING_MODE_NONE)
this->dosing_mode_->publish_state(DOSING_MODE_NONE);
}
break;
case EZO_PMP_COMMAND_READ_SINGLE_REPORT: // Single Report (page 53)
if (parsed_first_parameter.has_value() && (bool) this->current_volume_dosed_) {
this->current_volume_dosed_->publish_state(parsed_first_parameter.value_or(0));
}
break;
case EZO_PMP_COMMAND_READ_MAX_FLOW_RATE: // Constant Flow Rate (page 57)
if (parsed_second_parameter.has_value() && this->max_flow_rate_)
this->max_flow_rate_->publish_state(parsed_second_parameter.value_or(0));
break;
case EZO_PMP_COMMAND_READ_PAUSE_STATUS: // Pause (page 61)
if (parsed_second_parameter.has_value())
this->is_paused_flag_ = parsed_second_parameter.value_or(0) == 1;
if (this->is_paused_)
this->is_paused_->publish_state(this->is_paused_flag_);
break;
case EZO_PMP_COMMAND_READ_TOTAL_VOLUME_DOSED: // Total Volume Dispensed (page 64)
if (parsed_second_parameter.has_value() && this->total_volume_dosed_)
this->total_volume_dosed_->publish_state(parsed_second_parameter.value_or(0));
break;
case EZO_PMP_COMMAND_READ_ABSOLUTE_TOTAL_VOLUME_DOSED: // Total Volume Dispensed (page 64)
if (parsed_second_parameter.has_value() && this->absolute_total_volume_dosed_)
this->absolute_total_volume_dosed_->publish_state(parsed_second_parameter.value_or(0));
break;
case EZO_PMP_COMMAND_READ_CALIBRATION_STATUS: // Calibration (page 65)
if (parsed_second_parameter.has_value() && this->calibration_status_) {
if (parsed_second_parameter.value_or(0) == 1) {
this->calibration_status_->publish_state("Fixed Volume");
} else if (parsed_second_parameter.value_or(0) == 2) {
this->calibration_status_->publish_state("Volume/Time");
} else if (parsed_second_parameter.value_or(0) == 3) {
this->calibration_status_->publish_state("Fixed Volume & Volume/Time");
} else {
this->calibration_status_->publish_state("Uncalibrated");
}
}
break;
case EZO_PMP_COMMAND_READ_PUMP_VOLTAGE: // Pump Voltage (page 67)
if (parsed_second_parameter.has_value() && this->pump_voltage_)
this->pump_voltage_->publish_state(parsed_second_parameter.value_or(0));
break;
// Non-Read Commands
case EZO_PMP_COMMAND_DOSE_VOLUME: // Volume Dispensing (page 55)
if (this->dosing_mode_ && this->dosing_mode_->state != DOSING_MODE_VOLUME)
this->dosing_mode_->publish_state(DOSING_MODE_VOLUME);
break;
case EZO_PMP_COMMAND_DOSE_VOLUME_OVER_TIME: // Dose over time (page 56)
if (this->dosing_mode_ && this->dosing_mode_->state != DOSING_MODE_VOLUME_OVER_TIME)
this->dosing_mode_->publish_state(DOSING_MODE_VOLUME_OVER_TIME);
break;
case EZO_PMP_COMMAND_DOSE_WITH_CONSTANT_FLOW_RATE: // Constant Flow Rate (page 57)
if (this->dosing_mode_ && this->dosing_mode_->state != DOSING_MODE_CONSTANT_FLOW_RATE)
this->dosing_mode_->publish_state(DOSING_MODE_CONSTANT_FLOW_RATE);
break;
case EZO_PMP_COMMAND_DOSE_CONTINUOUSLY: // Continuous Dispensing (page 54)
if (this->dosing_mode_ && this->dosing_mode_->state != DOSING_MODE_CONTINUOUS)
this->dosing_mode_->publish_state(DOSING_MODE_CONTINUOUS);
break;
case EZO_PMP_COMMAND_STOP_DOSING: // Stop (page 62)
this->is_paused_flag_ = false;
if (this->is_paused_)
this->is_paused_->publish_state(this->is_paused_flag_);
if (this->dosing_mode_ && this->dosing_mode_->state != DOSING_MODE_NONE)
this->dosing_mode_->publish_state(DOSING_MODE_NONE);
break;
case EZO_PMP_COMMAND_EXEC_ARBITRARY_COMMAND_ADDRESS:
ESP_LOGI(TAG, "Arbitrary Command Response: %s", (char *) response_buffer);
break;
case EZO_PMP_COMMAND_CLEAR_CALIBRATION: // Clear Calibration (page 65)
case EZO_PMP_COMMAND_PAUSE_DOSING: // Pause (page 61)
case EZO_PMP_COMMAND_SET_CALIBRATION_VOLUME: // Set Calibration Volume (page 65)
case EZO_PMP_COMMAND_CLEAR_TOTAL_VOLUME_DOSED: // Clear Total Volume Dosed (page 64)
case EZO_PMP_COMMAND_FIND: // Find (page 52)
// Nothing to do here
break;
case EZO_PMP_COMMAND_TYPE_READ:
case EZO_PMP_COMMAND_NONE:
default:
ESP_LOGE(TAG, "Unsupported command received: %d", this->current_command_);
return;
}
this->clear_current_command_();
}
void EzoPMP::send_next_command_() {
int wait_time_for_command = 400; // milliseconds
uint8_t command_buffer[21];
int command_buffer_length = 0;
this->pop_next_command_(); // this->next_command will be updated.
switch (this->next_command_) {
// Read Commands
case EZO_PMP_COMMAND_READ_DOSING: // Page 54
command_buffer_length = sprintf((char *) command_buffer, "D,?");
break;
case EZO_PMP_COMMAND_READ_SINGLE_REPORT: // Single Report (page 53)
command_buffer_length = sprintf((char *) command_buffer, "R");
break;
case EZO_PMP_COMMAND_READ_MAX_FLOW_RATE:
command_buffer_length = sprintf((char *) command_buffer, "DC,?");
break;
case EZO_PMP_COMMAND_READ_PAUSE_STATUS:
command_buffer_length = sprintf((char *) command_buffer, "P,?");
break;
case EZO_PMP_COMMAND_READ_TOTAL_VOLUME_DOSED:
command_buffer_length = sprintf((char *) command_buffer, "TV,?");
break;
case EZO_PMP_COMMAND_READ_ABSOLUTE_TOTAL_VOLUME_DOSED:
command_buffer_length = sprintf((char *) command_buffer, "ATV,?");
break;
case EZO_PMP_COMMAND_READ_CALIBRATION_STATUS:
command_buffer_length = sprintf((char *) command_buffer, "Cal,?");
break;
case EZO_PMP_COMMAND_READ_PUMP_VOLTAGE:
command_buffer_length = sprintf((char *) command_buffer, "PV,?");
break;
// Non-Read Commands
case EZO_PMP_COMMAND_FIND: // Find (page 52)
command_buffer_length = sprintf((char *) command_buffer, "Find");
wait_time_for_command = 60000; // This command will block all updates for a minute
break;
case EZO_PMP_COMMAND_DOSE_CONTINUOUSLY: // Continuous Dispensing (page 54)
command_buffer_length = sprintf((char *) command_buffer, "D,*");
break;
case EZO_PMP_COMMAND_CLEAR_TOTAL_VOLUME_DOSED: // Clear Total Volume Dosed (page 64)
command_buffer_length = sprintf((char *) command_buffer, "Clear");
break;
case EZO_PMP_COMMAND_CLEAR_CALIBRATION: // Clear Calibration (page 65)
command_buffer_length = sprintf((char *) command_buffer, "Cal,clear");
break;
case EZO_PMP_COMMAND_PAUSE_DOSING: // Pause (page 61)
command_buffer_length = sprintf((char *) command_buffer, "P");
break;
case EZO_PMP_COMMAND_STOP_DOSING: // Stop (page 62)
command_buffer_length = sprintf((char *) command_buffer, "X");
break;
// Non-Read commands with parameters
case EZO_PMP_COMMAND_DOSE_VOLUME: // Volume Dispensing (page 55)
command_buffer_length = sprintf((char *) command_buffer, "D,%0.1f", this->next_command_volume_);
break;
case EZO_PMP_COMMAND_DOSE_VOLUME_OVER_TIME: // Dose over time (page 56)
command_buffer_length =
sprintf((char *) command_buffer, "D,%0.1f,%i", this->next_command_volume_, this->next_command_duration_);
break;
case EZO_PMP_COMMAND_DOSE_WITH_CONSTANT_FLOW_RATE: // Constant Flow Rate (page 57)
command_buffer_length =
sprintf((char *) command_buffer, "DC,%0.1f,%i", this->next_command_volume_, this->next_command_duration_);
break;
case EZO_PMP_COMMAND_SET_CALIBRATION_VOLUME: // Set Calibration Volume (page 65)
command_buffer_length = sprintf((char *) command_buffer, "Cal,%0.2f", this->next_command_volume_);
break;
case EZO_PMP_COMMAND_CHANGE_I2C_ADDRESS: // Change I2C Address (page 73)
command_buffer_length = sprintf((char *) command_buffer, "I2C,%i", this->next_command_duration_);
break;
case EZO_PMP_COMMAND_EXEC_ARBITRARY_COMMAND_ADDRESS: // Run an arbitrary command
command_buffer_length = sprintf((char *) command_buffer, this->arbitrary_command_, this->next_command_duration_);
ESP_LOGI(TAG, "Sending arbitrary command: %s", (char *) command_buffer);
break;
case EZO_PMP_COMMAND_TYPE_READ:
case EZO_PMP_COMMAND_NONE:
default:
ESP_LOGE(TAG, "Unsupported command received: %d", this->next_command_);
return;
}
// Send command
ESP_LOGV(TAG, "Sending command to device: %s", (char *) command_buffer);
this->write(command_buffer, command_buffer_length);
this->current_command_ = this->next_command_;
this->next_command_ = EZO_PMP_COMMAND_NONE;
this->is_waiting_ = true;
this->start_time_ = millis();
this->wait_time_ = wait_time_for_command;
}
void EzoPMP::pop_next_command_() {
if (this->next_command_queue_length_ <= 0) {
ESP_LOGE(TAG, "Tried to dequeue command from empty queue");
this->next_command_ = EZO_PMP_COMMAND_NONE;
this->next_command_volume_ = 0;
this->next_command_duration_ = 0;
return;
}
// Read from Head
this->next_command_ = this->next_command_queue_[this->next_command_queue_head_];
this->next_command_volume_ = this->next_command_volume_queue_[this->next_command_queue_head_];
this->next_command_duration_ = this->next_command_duration_queue_[this->next_command_queue_head_];
// Move positions
next_command_queue_head_++;
if (next_command_queue_head_ >= 10) {
next_command_queue_head_ = 0;
}
next_command_queue_length_--;
}
uint16_t EzoPMP::peek_next_command_() {
if (this->next_command_queue_length_ <= 0) {
return EZO_PMP_COMMAND_NONE;
}
return this->next_command_queue_[this->next_command_queue_head_];
}
void EzoPMP::queue_command_(uint16_t command, double volume, int duration, bool should_schedule) {
if (!should_schedule) {
return;
}
if (this->next_command_queue_length_ >= 10) {
ESP_LOGE(TAG, "Tried to queue command '%d' but queue is full", command);
return;
}
this->next_command_queue_[this->next_command_queue_last_] = command;
this->next_command_volume_queue_[this->next_command_queue_last_] = volume;
this->next_command_duration_queue_[this->next_command_queue_last_] = duration;
ESP_LOGV(TAG, "Queue command '%d' in position '%d'", command, next_command_queue_last_);
// Move positions
next_command_queue_last_++;
if (next_command_queue_last_ >= 10) {
next_command_queue_last_ = 0;
}
next_command_queue_length_++;
}
// Actions
void EzoPMP::find() { this->queue_command_(EZO_PMP_COMMAND_FIND, 0, 0, true); }
void EzoPMP::dose_continuously() {
this->queue_command_(EZO_PMP_COMMAND_DOSE_CONTINUOUSLY, 0, 0, true);
this->queue_command_(EZO_PMP_COMMAND_READ_DOSING, 0, 0, true);
this->queue_command_(EZO_PMP_COMMAND_READ_SINGLE_REPORT, 0, 0, (bool) this->current_volume_dosed_);
}
void EzoPMP::dose_volume(double volume) {
this->queue_command_(EZO_PMP_COMMAND_DOSE_VOLUME, volume, 0, true);
this->queue_command_(EZO_PMP_COMMAND_READ_DOSING, 0, 0, true);
this->queue_command_(EZO_PMP_COMMAND_READ_SINGLE_REPORT, 0, 0, (bool) this->current_volume_dosed_);
}
void EzoPMP::dose_volume_over_time(double volume, int duration) {
this->queue_command_(EZO_PMP_COMMAND_DOSE_VOLUME_OVER_TIME, volume, duration, true);
this->queue_command_(EZO_PMP_COMMAND_READ_DOSING, 0, 0, true);
this->queue_command_(EZO_PMP_COMMAND_READ_SINGLE_REPORT, 0, 0, (bool) this->current_volume_dosed_);
}
void EzoPMP::dose_with_constant_flow_rate(double volume, int duration) {
this->queue_command_(EZO_PMP_COMMAND_DOSE_WITH_CONSTANT_FLOW_RATE, volume, duration, true);
this->queue_command_(EZO_PMP_COMMAND_READ_DOSING, 0, 0, true);
this->queue_command_(EZO_PMP_COMMAND_READ_SINGLE_REPORT, 0, 0, (bool) this->current_volume_dosed_);
}
void EzoPMP::set_calibration_volume(double volume) {
this->queue_command_(EZO_PMP_COMMAND_SET_CALIBRATION_VOLUME, volume, 0, true);
this->queue_command_(EZO_PMP_COMMAND_READ_CALIBRATION_STATUS, 0, 0, true);
this->queue_command_(EZO_PMP_COMMAND_READ_MAX_FLOW_RATE, 0, 0, true);
}
void EzoPMP::clear_total_volume_dosed() {
this->queue_command_(EZO_PMP_COMMAND_CLEAR_TOTAL_VOLUME_DOSED, 0, 0, true);
this->queue_command_(EZO_PMP_COMMAND_READ_SINGLE_REPORT, 0, 0, true);
this->queue_command_(EZO_PMP_COMMAND_READ_TOTAL_VOLUME_DOSED, 0, 0, true);
this->queue_command_(EZO_PMP_COMMAND_READ_ABSOLUTE_TOTAL_VOLUME_DOSED, 0, 0, true);
}
void EzoPMP::clear_calibration() {
this->queue_command_(EZO_PMP_COMMAND_CLEAR_CALIBRATION, 0, 0, true);
this->queue_command_(EZO_PMP_COMMAND_READ_CALIBRATION_STATUS, 0, 0, true);
this->queue_command_(EZO_PMP_COMMAND_READ_MAX_FLOW_RATE, 0, 0, true);
}
void EzoPMP::pause_dosing() {
this->queue_command_(EZO_PMP_COMMAND_PAUSE_DOSING, 0, 0, true);
this->queue_command_(EZO_PMP_COMMAND_READ_PAUSE_STATUS, 0, 0, true);
}
void EzoPMP::stop_dosing() { this->queue_command_(EZO_PMP_COMMAND_STOP_DOSING, 0, 0, true); }
void EzoPMP::change_i2c_address(int address) {
this->queue_command_(EZO_PMP_COMMAND_CHANGE_I2C_ADDRESS, 0, address, true);
}
void EzoPMP::exec_arbitrary_command(const std::basic_string<char> &command) {
this->arbitrary_command_ = command.c_str();
this->queue_command_(EZO_PMP_COMMAND_EXEC_ARBITRARY_COMMAND_ADDRESS, 0, 0, true);
}
} // namespace ezo_pmp
} // namespace esphome

View File

@@ -0,0 +1,252 @@
#pragma once
#include "esphome/core/defines.h"
#include "esphome/core/component.h"
#include "esphome/core/automation.h"
#include "esphome/components/i2c/i2c.h"
#ifdef USE_BINARY_SENSOR
#include "esphome/components/binary_sensor/binary_sensor.h"
#endif
#ifdef USE_SENSOR
#include "esphome/components/sensor/sensor.h"
#endif
#ifdef USE_TEXT_SENSOR
#include "esphome/components/text_sensor/text_sensor.h"
#endif
namespace esphome {
namespace ezo_pmp {
class EzoPMP : public PollingComponent, public i2c::I2CDevice {
public:
void dump_config() override;
float get_setup_priority() const override { return setup_priority::DATA; };
void loop() override;
void update() override;
#ifdef USE_SENSOR
void set_current_volume_dosed(sensor::Sensor *current_volume_dosed) { current_volume_dosed_ = current_volume_dosed; }
void set_total_volume_dosed(sensor::Sensor *total_volume_dosed) { total_volume_dosed_ = total_volume_dosed; }
void set_absolute_total_volume_dosed(sensor::Sensor *absolute_total_volume_dosed) {
absolute_total_volume_dosed_ = absolute_total_volume_dosed;
}
void set_pump_voltage(sensor::Sensor *pump_voltage) { pump_voltage_ = pump_voltage; }
void set_last_volume_requested(sensor::Sensor *last_volume_requested) {
last_volume_requested_ = last_volume_requested;
}
void set_max_flow_rate(sensor::Sensor *max_flow_rate) { max_flow_rate_ = max_flow_rate; }
#endif
#ifdef USE_BINARY_SENSOR
void set_is_dosing(binary_sensor::BinarySensor *is_dosing) { is_dosing_ = is_dosing; }
void set_is_paused(binary_sensor::BinarySensor *is_paused) { is_paused_ = is_paused; }
#endif
#ifdef USE_TEXT_SENSOR
void set_dosing_mode(text_sensor::TextSensor *dosing_mode) { dosing_mode_ = dosing_mode; }
void set_calibration_status(text_sensor::TextSensor *calibration_status) { calibration_status_ = calibration_status; }
#endif
// Actions for EZO-PMP
void find();
void dose_continuously();
void dose_volume(double volume);
void dose_volume_over_time(double volume, int duration);
void dose_with_constant_flow_rate(double volume, int duration);
void set_calibration_volume(double volume);
void clear_total_volume_dosed();
void clear_calibration();
void pause_dosing();
void stop_dosing();
void change_i2c_address(int address);
void exec_arbitrary_command(const std::basic_string<char> &command);
protected:
uint32_t start_time_ = 0;
uint32_t wait_time_ = 0;
bool is_waiting_ = false;
bool is_first_read_ = true;
uint16_t next_command_ = 0;
double next_command_volume_ = 0; // might be negative
int next_command_duration_ = 0;
uint16_t next_command_queue_[10];
double next_command_volume_queue_[10];
int next_command_duration_queue_[10];
int next_command_queue_head_ = 0;
int next_command_queue_last_ = 0;
int next_command_queue_length_ = 0;
uint16_t current_command_ = 0;
bool is_paused_flag_ = false;
bool is_dosing_flag_ = false;
const char *arbitrary_command_{nullptr};
void send_next_command_();
void read_command_result_();
void clear_current_command_();
void queue_command_(uint16_t command, double volume, int duration, bool should_schedule);
void pop_next_command_();
uint16_t peek_next_command_();
#ifdef USE_SENSOR
sensor::Sensor *current_volume_dosed_{nullptr};
sensor::Sensor *total_volume_dosed_{nullptr};
sensor::Sensor *absolute_total_volume_dosed_{nullptr};
sensor::Sensor *pump_voltage_{nullptr};
sensor::Sensor *max_flow_rate_{nullptr};
sensor::Sensor *last_volume_requested_{nullptr};
#endif
#ifdef USE_BINARY_SENSOR
binary_sensor::BinarySensor *is_dosing_{nullptr};
binary_sensor::BinarySensor *is_paused_{nullptr};
#endif
#ifdef USE_TEXT_SENSOR
text_sensor::TextSensor *dosing_mode_{nullptr};
text_sensor::TextSensor *calibration_status_{nullptr};
#endif
};
// Action Templates
template<typename... Ts> class EzoPMPFindAction : public Action<Ts...> {
public:
EzoPMPFindAction(EzoPMP *ezopmp) : ezopmp_(ezopmp) {}
void play(Ts... x) override { this->ezopmp_->find(); }
protected:
EzoPMP *ezopmp_;
};
template<typename... Ts> class EzoPMPDoseContinuouslyAction : public Action<Ts...> {
public:
EzoPMPDoseContinuouslyAction(EzoPMP *ezopmp) : ezopmp_(ezopmp) {}
void play(Ts... x) override { this->ezopmp_->dose_continuously(); }
protected:
EzoPMP *ezopmp_;
};
template<typename... Ts> class EzoPMPDoseVolumeAction : public Action<Ts...> {
public:
EzoPMPDoseVolumeAction(EzoPMP *ezopmp) : ezopmp_(ezopmp) {}
void play(Ts... x) override { this->ezopmp_->dose_volume(this->volume_.value(x...)); }
TEMPLATABLE_VALUE(double, volume)
protected:
EzoPMP *ezopmp_;
};
template<typename... Ts> class EzoPMPDoseVolumeOverTimeAction : public Action<Ts...> {
public:
EzoPMPDoseVolumeOverTimeAction(EzoPMP *ezopmp) : ezopmp_(ezopmp) {}
void play(Ts... x) override {
this->ezopmp_->dose_volume_over_time(this->volume_.value(x...), this->duration_.value(x...));
}
TEMPLATABLE_VALUE(double, volume)
TEMPLATABLE_VALUE(int, duration)
protected:
EzoPMP *ezopmp_;
};
template<typename... Ts> class EzoPMPDoseWithConstantFlowRateAction : public Action<Ts...> {
public:
EzoPMPDoseWithConstantFlowRateAction(EzoPMP *ezopmp) : ezopmp_(ezopmp) {}
void play(Ts... x) override {
this->ezopmp_->dose_with_constant_flow_rate(this->volume_.value(x...), this->duration_.value(x...));
}
TEMPLATABLE_VALUE(double, volume)
TEMPLATABLE_VALUE(int, duration)
protected:
EzoPMP *ezopmp_;
};
template<typename... Ts> class EzoPMPSetCalibrationVolumeAction : public Action<Ts...> {
public:
EzoPMPSetCalibrationVolumeAction(EzoPMP *ezopmp) : ezopmp_(ezopmp) {}
void play(Ts... x) override { this->ezopmp_->set_calibration_volume(this->volume_.value(x...)); }
TEMPLATABLE_VALUE(double, volume)
protected:
EzoPMP *ezopmp_;
};
template<typename... Ts> class EzoPMPClearTotalVolumeDispensedAction : public Action<Ts...> {
public:
EzoPMPClearTotalVolumeDispensedAction(EzoPMP *ezopmp) : ezopmp_(ezopmp) {}
void play(Ts... x) override { this->ezopmp_->clear_total_volume_dosed(); }
protected:
EzoPMP *ezopmp_;
};
template<typename... Ts> class EzoPMPClearCalibrationAction : public Action<Ts...> {
public:
EzoPMPClearCalibrationAction(EzoPMP *ezopmp) : ezopmp_(ezopmp) {}
void play(Ts... x) override { this->ezopmp_->clear_calibration(); }
protected:
EzoPMP *ezopmp_;
};
template<typename... Ts> class EzoPMPPauseDosingAction : public Action<Ts...> {
public:
EzoPMPPauseDosingAction(EzoPMP *ezopmp) : ezopmp_(ezopmp) {}
void play(Ts... x) override { this->ezopmp_->pause_dosing(); }
protected:
EzoPMP *ezopmp_;
};
template<typename... Ts> class EzoPMPStopDosingAction : public Action<Ts...> {
public:
EzoPMPStopDosingAction(EzoPMP *ezopmp) : ezopmp_(ezopmp) {}
void play(Ts... x) override { this->ezopmp_->stop_dosing(); }
protected:
EzoPMP *ezopmp_;
};
template<typename... Ts> class EzoPMPChangeI2CAddressAction : public Action<Ts...> {
public:
EzoPMPChangeI2CAddressAction(EzoPMP *ezopmp) : ezopmp_(ezopmp) {}
void play(Ts... x) override { this->ezopmp_->change_i2c_address(this->address_.value(x...)); }
TEMPLATABLE_VALUE(int, address)
protected:
EzoPMP *ezopmp_;
};
template<typename... Ts> class EzoPMPArbitraryCommandAction : public Action<Ts...> {
public:
EzoPMPArbitraryCommandAction(EzoPMP *ezopmp) : ezopmp_(ezopmp) {}
void play(Ts... x) override { this->ezopmp_->exec_arbitrary_command(this->command_.value(x...)); }
TEMPLATABLE_VALUE(std::string, command)
protected:
EzoPMP *ezopmp_;
};
} // namespace ezo_pmp
} // namespace esphome

View File

@@ -0,0 +1,104 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import sensor
from esphome.const import (
ENTITY_CATEGORY_DIAGNOSTIC,
ENTITY_CATEGORY_NONE,
DEVICE_CLASS_EMPTY,
DEVICE_CLASS_VOLTAGE,
STATE_CLASS_MEASUREMENT,
STATE_CLASS_NONE,
CONF_ID,
UNIT_VOLT,
)
from . import EzoPMP
DEPENDENCIES = ["ezo_pmp"]
CONF_CURRENT_VOLUME_DOSED = "current_volume_dosed"
CONF_TOTAL_VOLUME_DOSED = "total_volume_dosed"
CONF_ABSOLUTE_TOTAL_VOLUME_DOSED = "absolute_total_volume_dosed"
CONF_PUMP_VOLTAGE = "pump_voltage"
CONF_LAST_VOLUME_REQUESTED = "last_volume_requested"
CONF_MAX_FLOW_RATE = "max_flow_rate"
UNIT_MILILITER = "ml"
UNIT_MILILITERS_PER_MINUTE = "ml/min"
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.use_id(EzoPMP),
cv.Optional(CONF_CURRENT_VOLUME_DOSED): sensor.sensor_schema(
unit_of_measurement=UNIT_MILILITER,
accuracy_decimals=2,
device_class=DEVICE_CLASS_EMPTY,
state_class=STATE_CLASS_MEASUREMENT,
entity_category=ENTITY_CATEGORY_NONE,
),
cv.Optional(CONF_LAST_VOLUME_REQUESTED): sensor.sensor_schema(
unit_of_measurement=UNIT_MILILITER,
accuracy_decimals=2,
device_class=DEVICE_CLASS_EMPTY,
state_class=STATE_CLASS_MEASUREMENT,
entity_category=ENTITY_CATEGORY_NONE,
),
cv.Optional(CONF_MAX_FLOW_RATE): sensor.sensor_schema(
unit_of_measurement=UNIT_MILILITERS_PER_MINUTE,
accuracy_decimals=2,
device_class=DEVICE_CLASS_EMPTY,
state_class=STATE_CLASS_NONE,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
cv.Optional(CONF_TOTAL_VOLUME_DOSED): sensor.sensor_schema(
unit_of_measurement=UNIT_MILILITER,
accuracy_decimals=2,
device_class=DEVICE_CLASS_EMPTY,
state_class=STATE_CLASS_MEASUREMENT,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
cv.Optional(CONF_ABSOLUTE_TOTAL_VOLUME_DOSED): sensor.sensor_schema(
unit_of_measurement=UNIT_MILILITER,
accuracy_decimals=2,
device_class=DEVICE_CLASS_EMPTY,
state_class=STATE_CLASS_MEASUREMENT,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
cv.Optional(CONF_PUMP_VOLTAGE): sensor.sensor_schema(
unit_of_measurement=UNIT_VOLT,
accuracy_decimals=2,
device_class=DEVICE_CLASS_VOLTAGE,
state_class=STATE_CLASS_MEASUREMENT,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
}
)
async def to_code(config):
parent = await cg.get_variable(config[CONF_ID])
if CONF_CURRENT_VOLUME_DOSED in config:
sens = await sensor.new_sensor(config[CONF_CURRENT_VOLUME_DOSED])
cg.add(parent.set_current_volume_dosed(sens))
if CONF_LAST_VOLUME_REQUESTED in config:
sens = await sensor.new_sensor(config[CONF_LAST_VOLUME_REQUESTED])
cg.add(parent.set_last_volume_requested(sens))
if CONF_TOTAL_VOLUME_DOSED in config:
sens = await sensor.new_sensor(config[CONF_TOTAL_VOLUME_DOSED])
cg.add(parent.set_total_volume_dosed(sens))
if CONF_ABSOLUTE_TOTAL_VOLUME_DOSED in config:
sens = await sensor.new_sensor(config[CONF_ABSOLUTE_TOTAL_VOLUME_DOSED])
cg.add(parent.set_absolute_total_volume_dosed(sens))
if CONF_PUMP_VOLTAGE in config:
sens = await sensor.new_sensor(config[CONF_PUMP_VOLTAGE])
cg.add(parent.set_pump_voltage(sens))
if CONF_MAX_FLOW_RATE in config:
sens = await sensor.new_sensor(config[CONF_MAX_FLOW_RATE])
cg.add(parent.set_max_flow_rate(sens))

View File

@@ -0,0 +1,39 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import text_sensor
from esphome.const import (
ENTITY_CATEGORY_NONE,
ENTITY_CATEGORY_DIAGNOSTIC,
CONF_ID,
)
from . import EzoPMP
DEPENDENCIES = ["ezo_pmp"]
CONF_DOSING_MODE = "dosing_mode"
CONF_CALIBRATION_STATUS = "calibration_status"
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.use_id(EzoPMP),
cv.Optional(CONF_DOSING_MODE): text_sensor.text_sensor_schema(
entity_category=ENTITY_CATEGORY_NONE,
),
cv.Optional(CONF_CALIBRATION_STATUS): text_sensor.text_sensor_schema(
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
}
)
async def to_code(config):
parent = await cg.get_variable(config[CONF_ID])
if CONF_DOSING_MODE in config:
sens = await text_sensor.new_text_sensor(config[CONF_DOSING_MODE])
cg.add(parent.set_dosing_mode(sens))
if CONF_CALIBRATION_STATUS in config:
sens = await text_sensor.new_text_sensor(config[CONF_CALIBRATION_STATUS])
cg.add(parent.set_calibration_status(sens))

View File

@@ -8,6 +8,7 @@ from esphome.const import (
CONF_PROTOCOL,
CONF_VISUAL,
)
from esphome.core import CORE
CODEOWNERS = ["@rob-deutsch"]
@@ -58,6 +59,7 @@ PROTOCOLS = {
"sharp": Protocol.PROTOCOL_SHARP,
"toshiba_daiseikai": Protocol.PROTOCOL_TOSHIBA_DAISEIKAI,
"toshiba": Protocol.PROTOCOL_TOSHIBA,
"zhlt01": Protocol.PROTOCOL_ZHLT01,
}
CONF_HORIZONTAL_DEFAULT = "horizontal_default"
@@ -114,3 +116,6 @@ def to_code(config):
cg.add(var.set_min_temperature(config[CONF_MIN_TEMPERATURE]))
cg.add_library("tonia/HeatpumpIR", "1.0.20")
if CORE.is_esp8266 or CORE.is_esp32:
cg.add_library("crankyoldgit/IRremoteESP8266", "2.7.12")

View File

@@ -53,6 +53,7 @@ const std::map<Protocol, std::function<HeatpumpIR *()>> PROTOCOL_CONSTRUCTOR_MAP
{PROTOCOL_SHARP, []() { return new SharpHeatpumpIR(); }}, // NOLINT
{PROTOCOL_TOSHIBA_DAISEIKAI, []() { return new ToshibaDaiseikaiHeatpumpIR(); }}, // NOLINT
{PROTOCOL_TOSHIBA, []() { return new ToshibaHeatpumpIR(); }}, // NOLINT
{PROTOCOL_ZHLT01, []() { return new ZHLT01HeatpumpIR(); }}, // NOLINT
};
void HeatpumpIRClimate::setup() {

View File

@@ -53,6 +53,7 @@ enum Protocol {
PROTOCOL_SHARP,
PROTOCOL_TOSHIBA_DAISEIKAI,
PROTOCOL_TOSHIBA,
PROTOCOL_ZHLT01,
};
// Simple enum to represent horizontal directios

View File

@@ -14,7 +14,7 @@ static const char *const TAG = "i2c.arduino";
void ArduinoI2CBus::setup() {
recover_();
#ifdef USE_ESP32
#if defined(USE_ESP32)
static uint8_t next_bus_num = 0;
if (next_bus_num == 0) {
wire_ = &Wire;
@@ -22,11 +22,25 @@ void ArduinoI2CBus::setup() {
wire_ = new TwoWire(next_bus_num); // NOLINT(cppcoreguidelines-owning-memory)
}
next_bus_num++;
#else
#elif defined(USE_ESP8266)
wire_ = &Wire; // NOLINT(cppcoreguidelines-prefer-member-initializer)
#elif defined(USE_RP2040)
static bool first = true;
if (first) {
wire_ = &Wire;
first = false;
} else {
wire_ = &Wire1; // NOLINT(cppcoreguidelines-owning-memory)
}
#endif
#ifdef USE_RP2040
wire_->setSDA(this->sda_pin_);
wire_->setSCL(this->scl_pin_);
wire_->begin();
#else
wire_->begin(static_cast<int>(sda_pin_), static_cast<int>(scl_pin_));
#endif
wire_->setClock(frequency_);
initialized_ = true;
if (this->scan_) {

View File

@@ -37,4 +37,4 @@ FINAL_VALIDATE_SCHEMA = validate_logger
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
cg.add_library("esphome/Improv", "1.2.1")
cg.add_library("esphome/Improv", "1.2.3")

View File

@@ -77,6 +77,8 @@ UART_SELECTION_ESP8266 = [UART0, UART0_SWAP, UART1]
ESP_IDF_UARTS = [USB_CDC, USB_SERIAL_JTAG]
UART_SELECTION_RP2040 = [UART0, UART1]
HARDWARE_UART_TO_UART_SELECTION = {
UART0: logger_ns.UART_SELECTION_UART0,
UART0_SWAP: logger_ns.UART_SELECTION_UART0_SWAP,
@@ -106,6 +108,8 @@ def uart_selection(value):
return cv.one_of(*UART_SELECTION_ESP32[variant], upper=True)(value)
if CORE.is_esp8266:
return cv.one_of(*UART_SELECTION_ESP8266, upper=True)(value)
if CORE.is_rp2040:
return cv.one_of(*UART_SELECTION_RP2040, upper=True)(value)
raise NotImplementedError
@@ -158,12 +162,13 @@ CONFIG_SCHEMA = cv.All(
@coroutine_with_priority(90.0)
async def to_code(config):
baud_rate = config[CONF_BAUD_RATE]
rhs = Logger.new(
baud_rate,
config[CONF_TX_BUFFER_SIZE],
HARDWARE_UART_TO_UART_SELECTION[config[CONF_HARDWARE_UART]],
)
log = cg.Pvariable(config[CONF_ID], rhs)
log = cg.new_Pvariable(config[CONF_ID], baud_rate, config[CONF_TX_BUFFER_SIZE])
if CONF_HARDWARE_UART in config:
cg.add(
log.set_uart_selection(
HARDWARE_UART_TO_UART_SELECTION[config[CONF_HARDWARE_UART]]
)
)
cg.add(log.pre_setup())
for tag, level in config[CONF_LOGS].items():

View File

@@ -148,8 +148,7 @@ void HOT Logger::log_message_(int level, const char *tag, int offset) {
this->log_callback_.call(level, tag, msg);
}
Logger::Logger(uint32_t baud_rate, size_t tx_buffer_size, UARTSelection uart)
: baud_rate_(baud_rate), tx_buffer_size_(tx_buffer_size), uart_(uart) {
Logger::Logger(uint32_t baud_rate, size_t tx_buffer_size) : baud_rate_(baud_rate), tx_buffer_size_(tx_buffer_size) {
// add 1 to buffer size for null terminator
this->tx_buffer_ = new char[this->tx_buffer_size_ + 1]; // NOLINT
}
@@ -270,6 +269,9 @@ const char *const UART_SELECTIONS[] = {
#endif // USE_ESP32
#ifdef USE_ESP8266
const char *const UART_SELECTIONS[] = {"UART0", "UART1", "UART0_SWAP"};
#endif
#ifdef USE_RP2040
const char *const UART_SELECTIONS[] = {"UART0", "UART1"};
#endif // USE_ESP8266
void Logger::dump_config() {
ESP_LOGCONFIG(TAG, "Logger:");

View File

@@ -7,8 +7,15 @@
#include <cstdarg>
#ifdef USE_ARDUINO
#if defined(USE_ESP8266) || defined(USE_ESP32)
#include <HardwareSerial.h>
#endif
#endif // USE_ESP8266 || USE_ESP32
#ifdef USE_RP2040
#include <HardwareSerial.h>
#include <SerialUSB.h>
#endif // USE_RP2040
#endif // USE_ARDUINO
#ifdef USE_ESP_IDF
#include <driver/uart.h>
#endif
@@ -44,7 +51,7 @@ enum UARTSelection {
class Logger : public Component {
public:
explicit Logger(uint32_t baud_rate, size_t tx_buffer_size, UARTSelection uart);
explicit Logger(uint32_t baud_rate, size_t tx_buffer_size);
/// Manually set the baud rate for serial, set to 0 to disable.
void set_baud_rate(uint32_t baud_rate);
@@ -56,6 +63,7 @@ class Logger : public Component {
uart_port_t get_uart_num() const { return uart_num_; }
#endif
void set_uart_selection(UARTSelection uart_selection) { uart_ = uart_selection; }
/// Get the UART used by the logger.
UARTSelection get_uart() const;

View File

@@ -23,6 +23,13 @@ void MCP23S17::setup() {
this->transfer_byte(0b00011000); // Enable HAEN pins for addressing
this->disable();
this->enable();
cmd = 0b01001000;
this->transfer_byte(cmd);
this->transfer_byte(mcp23x17_base::MCP23X17_IOCONA);
this->transfer_byte(0b00011000); // Enable HAEN pins for addressing
this->disable();
// Read current output register state
this->read_reg(mcp23x17_base::MCP23X17_OLATA, &this->olat_a_);
this->read_reg(mcp23x17_base::MCP23X17_OLATB, &this->olat_b_);

View File

@@ -6,7 +6,7 @@
namespace esphome {
namespace md5 {
#ifdef USE_ARDUINO
#if defined(USE_ARDUINO) && !defined(USE_RP2040)
void MD5Digest::init() {
memset(this->digest_, 0, 16);
MD5Init(&this->ctx_);
@@ -15,7 +15,7 @@ void MD5Digest::init() {
void MD5Digest::add(const uint8_t *data, size_t len) { MD5Update(&this->ctx_, data, len); }
void MD5Digest::calculate() { MD5Final(this->digest_, &this->ctx_); }
#endif // USE_ARDUINO
#endif // USE_ARDUINO && !USE_RP2040
#ifdef USE_ESP_IDF
void MD5Digest::init() {
@@ -28,6 +28,17 @@ void MD5Digest::add(const uint8_t *data, size_t len) { esp_rom_md5_update(&this-
void MD5Digest::calculate() { esp_rom_md5_final(this->digest_, &this->ctx_); }
#endif // USE_ESP_IDF
#ifdef USE_RP2040
void MD5Digest::init() {
memset(this->digest_, 0, 16);
br_md5_init(&this->ctx_);
}
void MD5Digest::add(const uint8_t *data, size_t len) { br_md5_update(&this->ctx_, data, len); }
void MD5Digest::calculate() { br_md5_out(&this->ctx_, this->digest_); }
#endif // USE_RP2040
void MD5Digest::get_bytes(uint8_t *output) { memcpy(output, this->digest_, 16); }
void MD5Digest::get_hex(char *output) {

View File

@@ -17,6 +17,11 @@
#define MD5_CTX_TYPE md5_context_t
#endif
#ifdef USE_RP2040
#include <MD5Builder.h>
#define MD5_CTX_TYPE br_md5_context
#endif
namespace esphome {
namespace md5 {

View File

@@ -36,6 +36,8 @@ async def to_code(config):
cg.add_library("ESPmDNS", None)
elif CORE.is_esp8266:
cg.add_library("ESP8266mDNS", None)
elif CORE.is_rp2040:
cg.add_library("LEAmDNS", None)
if config[CONF_DISABLED]:
return

View File

@@ -38,6 +38,9 @@ void MDNSComponent::compile_records_() {
#endif
#ifdef USE_ESP32
platform = "ESP32";
#endif
#ifdef USE_RP2040
platform = "RP2040";
#endif
if (platform != nullptr) {
service.txt_records.push_back({"platform", platform});

View File

@@ -28,7 +28,7 @@ class MDNSComponent : public Component {
void setup() override;
void dump_config() override;
#if defined(USE_ESP8266) && defined(USE_ARDUINO)
#if (defined(USE_ESP8266) || defined(USE_RP2040)) && defined(USE_ARDUINO)
void loop() override;
#endif
float get_setup_priority() const override { return setup_priority::AFTER_WIFI; }

View File

@@ -0,0 +1,44 @@
#ifdef USE_RP2040
#include "esphome/components/network/ip_address.h"
#include "esphome/components/network/util.h"
#include "esphome/core/log.h"
#include "mdns_component.h"
#include <ESP8266mDNS.h>
namespace esphome {
namespace mdns {
void MDNSComponent::setup() {
this->compile_records_();
network::IPAddress addr = network::get_ip_address();
MDNS.begin(this->hostname_.c_str(), (uint32_t) addr);
for (const auto &service : this->services_) {
// Strip the leading underscore from the proto and service_type. While it is
// part of the wire protocol to have an underscore, and for example ESP-IDF
// expects the underscore to be there, the ESP8266 implementation always adds
// the underscore itself.
auto *proto = service.proto.c_str();
while (*proto == '_') {
proto++;
}
auto *service_type = service.service_type.c_str();
while (*service_type == '_') {
service_type++;
}
MDNS.addService(service_type, proto, service.port);
for (const auto &record : service.txt_records) {
MDNS.addServiceTxt(service_type, proto, record.key.c_str(), record.value.c_str());
}
}
}
void MDNSComponent::loop() { MDNS.update(); }
} // namespace mdns
} // namespace esphome
#endif

View File

@@ -571,24 +571,16 @@ int64_t payload_to_number(const std::vector<uint8_t> &data, SensorValueType sens
static_cast<int32_t>(((value & 0x7FFF) << 16 | (value & 0xFFFF0000) >> 16) | sign_bit), bitmask);
} break;
case SensorValueType::U_QWORD:
// Ignore bitmask for U_QWORD
value = get_data<uint64_t>(data, offset);
break;
case SensorValueType::S_QWORD:
// Ignore bitmask for S_QWORD
value = get_data<int64_t>(data, offset);
// Ignore bitmask for QWORD
value = get_data<uint64_t>(data, offset);
break;
case SensorValueType::U_QWORD_R:
// Ignore bitmask for U_QWORD
value = get_data<uint64_t>(data, offset);
value = static_cast<uint64_t>(value & 0xFFFF) << 48 | (value & 0xFFFF000000000000) >> 48 |
static_cast<uint64_t>(value & 0xFFFF0000) << 32 | (value & 0x0000FFFF00000000) >> 32 |
static_cast<uint64_t>(value & 0xFFFF00000000) << 16 | (value & 0x00000000FFFF0000) >> 16;
break;
case SensorValueType::S_QWORD_R:
// Ignore bitmask for S_QWORD
value = get_data<int64_t>(data, offset);
break;
case SensorValueType::S_QWORD_R: {
// Ignore bitmask for QWORD
uint64_t tmp = get_data<uint64_t>(data, offset);
value = (tmp << 48) | (tmp >> 48) | ((tmp & 0xFFFF0000) << 16) | ((tmp >> 16) & 0xFFFF0000);
} break;
case SensorValueType::RAW:
default:
break;

View File

@@ -1,5 +1,5 @@
from dataclasses import dataclass
from typing import Any, List
from typing import Any
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.const import (
@@ -349,7 +349,7 @@ def _spi_extra_validate(config):
class MethodDescriptor:
method_schema: Any
to_code: Any
supported_chips: List[str]
supported_chips: list[str]
extra_validate: Any = None

View File

@@ -39,7 +39,7 @@ CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.declare_id(OTAComponent),
cv.Optional(CONF_SAFE_MODE, default=True): cv.boolean,
cv.SplitDefault(CONF_PORT, esp8266=8266, esp32=3232): cv.port,
cv.SplitDefault(CONF_PORT, esp8266=8266, esp32=3232, rp2040=2040): cv.port,
cv.Optional(CONF_PASSWORD): cv.string,
cv.Optional(
CONF_REBOOT_TIMEOUT, default="5min"
@@ -94,6 +94,9 @@ async def to_code(config):
if CORE.is_esp32 and CORE.using_arduino:
cg.add_library("Update", None)
if CORE.is_rp2040 and CORE.using_arduino:
cg.add_library("Updater", None)
use_state_callback = False
for conf in config.get(CONF_ON_STATE_CHANGE, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)

View File

@@ -0,0 +1,59 @@
#include "esphome/core/defines.h"
#ifdef USE_ARDUINO
#ifdef USE_RP2040
#include "esphome/components/rp2040/preferences.h"
#include "ota_backend.h"
#include "ota_backend_arduino_rp2040.h"
#include "ota_component.h"
#include <Updater.h>
namespace esphome {
namespace ota {
OTAResponseTypes ArduinoRP2040OTABackend::begin(size_t image_size) {
bool ret = Update.begin(image_size, U_FLASH);
if (ret) {
rp2040::preferences_prevent_write(true);
return OTA_RESPONSE_OK;
}
uint8_t error = Update.getError();
if (error == UPDATE_ERROR_BOOTSTRAP)
return OTA_RESPONSE_ERROR_INVALID_BOOTSTRAPPING;
if (error == UPDATE_ERROR_NEW_FLASH_CONFIG)
return OTA_RESPONSE_ERROR_WRONG_NEW_FLASH_CONFIG;
if (error == UPDATE_ERROR_FLASH_CONFIG)
return OTA_RESPONSE_ERROR_WRONG_CURRENT_FLASH_CONFIG;
if (error == UPDATE_ERROR_SPACE)
return OTA_RESPONSE_ERROR_RP2040_NOT_ENOUGH_SPACE;
return OTA_RESPONSE_ERROR_UNKNOWN;
}
void ArduinoRP2040OTABackend::set_update_md5(const char *md5) { Update.setMD5(md5); }
OTAResponseTypes ArduinoRP2040OTABackend::write(uint8_t *data, size_t len) {
size_t written = Update.write(data, len);
if (written != len) {
return OTA_RESPONSE_ERROR_WRITING_FLASH;
}
return OTA_RESPONSE_OK;
}
OTAResponseTypes ArduinoRP2040OTABackend::end() {
if (!Update.end())
return OTA_RESPONSE_ERROR_UPDATE_END;
return OTA_RESPONSE_OK;
}
void ArduinoRP2040OTABackend::abort() {
Update.end();
rp2040::preferences_prevent_write(false);
}
} // namespace ota
} // namespace esphome
#endif // USE_RP2040
#endif // USE_ARDUINO

View File

@@ -0,0 +1,27 @@
#pragma once
#include "esphome/core/defines.h"
#ifdef USE_ARDUINO
#ifdef USE_RP2040
#include "esphome/core/macros.h"
#include "ota_backend.h"
#include "ota_component.h"
namespace esphome {
namespace ota {
class ArduinoRP2040OTABackend : public OTABackend {
public:
OTAResponseTypes begin(size_t image_size) override;
void set_update_md5(const char *md5) override;
OTAResponseTypes write(uint8_t *data, size_t len) override;
OTAResponseTypes end() override;
void abort() override;
bool supports_compression() override { return false; }
};
} // namespace ota
} // namespace esphome
#endif // USE_RP2040
#endif // USE_ARDUINO

View File

@@ -2,6 +2,7 @@
#include "ota_backend.h"
#include "ota_backend_arduino_esp32.h"
#include "ota_backend_arduino_esp8266.h"
#include "ota_backend_arduino_rp2040.h"
#include "ota_backend_esp_idf.h"
#include "esphome/core/log.h"
@@ -35,6 +36,9 @@ std::unique_ptr<OTABackend> make_ota_backend() {
#ifdef USE_ESP_IDF
return make_unique<IDFOTABackend>();
#endif // USE_ESP_IDF
#ifdef USE_RP2040
return make_unique<ArduinoRP2040OTABackend>();
#endif // USE_RP2040
}
OTAComponent::OTAComponent() { global_ota_component = this; }

View File

@@ -33,6 +33,7 @@ enum OTAResponseTypes {
OTA_RESPONSE_ERROR_ESP32_NOT_ENOUGH_SPACE = 137,
OTA_RESPONSE_ERROR_NO_UPDATE_PARTITION = 138,
OTA_RESPONSE_ERROR_MD5_MISMATCH = 139,
OTA_RESPONSE_ERROR_RP2040_NOT_ENOUGH_SPACE = 140,
OTA_RESPONSE_ERROR_UNKNOWN = 255,
};

View File

@@ -5,14 +5,17 @@ from esphome.config_helpers import merge_config
from esphome import git, yaml_util
from esphome.const import (
CONF_ESPHOME,
CONF_FILE,
CONF_FILES,
CONF_MIN_VERSION,
CONF_PACKAGES,
CONF_REF,
CONF_REFRESH,
CONF_URL,
CONF_USERNAME,
CONF_PASSWORD,
__version__ as ESPHOME_VERSION,
)
import esphome.config_validation as cv
@@ -104,7 +107,7 @@ CONFIG_SCHEMA = cv.All(
def _process_base_package(config: dict) -> dict:
repo_dir = git.clone_or_update(
repo_dir, revert = git.clone_or_update(
url=config[CONF_URL],
ref=config.get(CONF_REF),
refresh=config[CONF_REFRESH],
@@ -112,21 +115,51 @@ def _process_base_package(config: dict) -> dict:
username=config.get(CONF_USERNAME),
password=config.get(CONF_PASSWORD),
)
files: str = config[CONF_FILES]
files: list[str] = config[CONF_FILES]
def get_packages(files) -> dict:
packages = {}
for file in files:
yaml_file: Path = repo_dir / file
if not yaml_file.is_file():
raise cv.Invalid(
f"{file} does not exist in repository", path=[CONF_FILES]
)
try:
new_yaml = yaml_util.load_yaml(yaml_file)
if (
CONF_ESPHOME in new_yaml
and CONF_MIN_VERSION in new_yaml[CONF_ESPHOME]
):
min_version = new_yaml[CONF_ESPHOME][CONF_MIN_VERSION]
if cv.Version.parse(min_version) > cv.Version.parse(
ESPHOME_VERSION
):
raise cv.Invalid(
f"Current ESPHome Version is too old to use this package: {ESPHOME_VERSION} < {min_version}"
)
packages[file] = new_yaml
except EsphomeError as e:
raise cv.Invalid(
f"{file} is not a valid YAML file. Please check the file contents."
) from e
return packages
packages = {}
for file in files:
yaml_file: Path = repo_dir / file
if not yaml_file.is_file():
raise cv.Invalid(f"{file} does not exist in repository", path=[CONF_FILES])
try:
packages = get_packages(files)
except cv.Invalid:
if revert is not None:
revert()
packages = get_packages(files)
finally:
if packages is None:
raise cv.Invalid("Failed to load packages")
try:
packages[file] = yaml_util.load_yaml(yaml_file)
except EsphomeError as e:
raise cv.Invalid(
f"{file} is not a valid YAML file. Please check the file contents."
) from e
return {"packages": packages}

View File

@@ -11,62 +11,43 @@ void PulseMeterSensor::setup() {
this->isr_pin_ = pin_->to_isr();
this->pin_->attach_interrupt(PulseMeterSensor::gpio_intr, this, gpio::INTERRUPT_ANY_EDGE);
this->pulse_width_us_ = 0;
this->last_detected_edge_us_ = 0;
this->last_valid_low_edge_us_ = 0;
this->last_valid_high_edge_us_ = 0;
this->last_valid_low_edge_us_ = 0;
this->sensor_is_high_ = this->isr_pin_.digital_read();
this->has_valid_high_edge_ = false;
this->has_valid_low_edge_ = false;
}
void PulseMeterSensor::loop() {
// Get a local copy of the volatile sensor values, to make sure they are not
// modified by the ISR. This could cause overflow in the following arithmetic
const uint32_t last_valid_high_edge_us = this->last_valid_high_edge_us_;
const bool has_valid_high_edge = this->has_valid_high_edge_;
const uint32_t now = micros();
// Check to see if we should filter this edge out
if (this->filter_mode_ == FILTER_EDGE) {
if ((this->last_detected_edge_us_ - this->last_valid_high_edge_us_) >= this->filter_us_) {
// Don't measure the first valid pulse (we need at least two pulses to measure the width)
if (this->last_valid_high_edge_us_ != 0) {
this->pulse_width_us_ = (this->last_detected_edge_us_ - this->last_valid_high_edge_us_);
}
this->total_pulses_++;
this->last_valid_high_edge_us_ = this->last_detected_edge_us_;
}
} else {
// Make sure the signal has been stable long enough
if ((now - this->last_detected_edge_us_) >= this->filter_us_) {
// Only consider HIGH pulses and "new" edges if sensor state is LOW
if (!this->sensor_is_high_ && this->isr_pin_.digital_read() &&
(this->last_detected_edge_us_ != this->last_valid_high_edge_us_)) {
// Don't measure the first valid pulse (we need at least two pulses to measure the width)
if (this->last_valid_high_edge_us_ != 0) {
this->pulse_width_us_ = (this->last_detected_edge_us_ - this->last_valid_high_edge_us_);
}
this->sensor_is_high_ = true;
this->total_pulses_++;
this->last_valid_high_edge_us_ = this->last_detected_edge_us_;
}
// Only consider LOW pulses and "new" edges if sensor state is HIGH
else if (this->sensor_is_high_ && !this->isr_pin_.digital_read() &&
(this->last_detected_edge_us_ != this->last_valid_low_edge_us_)) {
this->sensor_is_high_ = false;
this->last_valid_low_edge_us_ = this->last_detected_edge_us_;
}
}
}
// If we've exceeded our timeout interval without receiving any pulses, assume 0 pulses/min until
// we get at least two valid pulses.
const uint32_t time_since_valid_edge_us = now - this->last_valid_high_edge_us_;
if ((this->last_valid_high_edge_us_ != 0) && (time_since_valid_edge_us > this->timeout_us_) &&
(this->pulse_width_us_ != 0)) {
// If we've exceeded our timeout interval without receiving any pulses, assume
// 0 pulses/min until we get at least two valid pulses.
const uint32_t time_since_valid_edge_us = now - last_valid_high_edge_us;
if ((has_valid_high_edge) && (time_since_valid_edge_us > this->timeout_us_)) {
ESP_LOGD(TAG, "No pulse detected for %us, assuming 0 pulses/min", time_since_valid_edge_us / 1000000);
this->pulse_width_us_ = 0;
this->last_detected_edge_us_ = 0;
this->last_valid_high_edge_us_ = 0;
this->last_valid_low_edge_us_ = 0;
this->has_detected_edge_ = false;
this->has_valid_high_edge_ = false;
this->has_valid_low_edge_ = false;
}
// We quantize our pulse widths to 1 ms to avoid unnecessary jitter
const uint32_t pulse_width_ms = this->pulse_width_us_ / 1000;
if (this->pulse_width_dedupe_.next(pulse_width_ms)) {
if (pulse_width_ms == 0) {
// Treat 0 pulse width as 0 pulses/min (normally because we've not detected any pulses for a while)
// Treat 0 pulse width as 0 pulses/min (normally because we've not
// detected any pulses for a while)
this->publish_state(0);
} else {
// Calculate pulses/min from the pulse width in ms
@@ -96,9 +77,11 @@ void PulseMeterSensor::dump_config() {
}
void IRAM_ATTR PulseMeterSensor::gpio_intr(PulseMeterSensor *sensor) {
// This is an interrupt handler - we can't call any virtual method from this method
// This is an interrupt handler - we can't call any virtual method from this
// method
// Get the current time before we do anything else so the measurements are consistent
// Get the current time before we do anything else so the measurements are
// consistent
const uint32_t now = micros();
// We only look at rising edges in EDGE mode, and all edges in PULSE mode
@@ -106,7 +89,45 @@ void IRAM_ATTR PulseMeterSensor::gpio_intr(PulseMeterSensor *sensor) {
if (sensor->isr_pin_.digital_read()) {
sensor->last_detected_edge_us_ = now;
}
}
// Check to see if we should filter this edge out
if (sensor->filter_mode_ == FILTER_EDGE) {
if ((sensor->last_detected_edge_us_ - sensor->last_valid_high_edge_us_) >= sensor->filter_us_) {
// Don't measure the first valid pulse (we need at least two pulses to
// measure the width)
if (sensor->has_valid_high_edge_) {
sensor->pulse_width_us_ = (sensor->last_detected_edge_us_ - sensor->last_valid_high_edge_us_);
}
sensor->total_pulses_++;
sensor->last_valid_high_edge_us_ = sensor->last_detected_edge_us_;
sensor->has_valid_high_edge_ = true;
}
} else {
// Filter Mode is PULSE
bool pin_val = sensor->isr_pin_.digital_read();
// Ignore false edges that may be caused by bouncing and exit the ISR ASAP
if (pin_val == sensor->sensor_is_high_) {
return;
}
// Make sure the signal has been stable long enough
if (sensor->has_detected_edge_ && (now - sensor->last_detected_edge_us_ >= sensor->filter_us_)) {
if (pin_val) {
sensor->has_valid_high_edge_ = true;
sensor->last_valid_high_edge_us_ = sensor->last_detected_edge_us_;
sensor->sensor_is_high_ = true;
} else {
// Count pulses when a sufficiently long high pulse is concluded.
sensor->total_pulses_++;
if (sensor->has_valid_low_edge_) {
sensor->pulse_width_us_ = sensor->last_detected_edge_us_ - sensor->last_valid_low_edge_us_;
}
sensor->has_valid_low_edge_ = true;
sensor->last_valid_low_edge_us_ = sensor->last_detected_edge_us_;
sensor->sensor_is_high_ = false;
}
}
sensor->has_detected_edge_ = true;
sensor->last_detected_edge_us_ = now;
}
}

View File

@@ -1,8 +1,8 @@
#pragma once
#include "esphome/components/sensor/sensor.h"
#include "esphome/core/component.h"
#include "esphome/core/hal.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/core/helpers.h"
namespace esphome {
@@ -42,11 +42,14 @@ class PulseMeterSensor : public sensor::Sensor, public Component {
Deduplicator<uint32_t> total_dedupe_;
volatile uint32_t last_detected_edge_us_ = 0;
volatile uint32_t last_valid_low_edge_us_ = 0;
volatile uint32_t last_valid_high_edge_us_ = 0;
volatile uint32_t last_valid_low_edge_us_ = 0;
volatile uint32_t pulse_width_us_ = 0;
volatile uint32_t total_pulses_ = 0;
volatile bool sensor_is_high_ = false;
volatile bool has_detected_edge_ = false;
volatile bool has_valid_high_edge_ = false;
volatile bool has_valid_low_edge_ = false;
};
} // namespace pulse_meter

View File

@@ -6,6 +6,7 @@ from esphome.const import CONF_ID, CONF_ON_TAG, CONF_TRIGGER_ID
DEPENDENCIES = ["uart"]
AUTO_LOAD = ["binary_sensor"]
MULTI_CONF = True
rdm6300_ns = cg.esphome_ns.namespace("rdm6300")
RDM6300Component = rdm6300_ns.class_("RDM6300Component", cg.Component, uart.UARTDevice)

View File

@@ -0,0 +1,161 @@
import logging
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.const import (
CONF_BOARD,
CONF_FRAMEWORK,
CONF_SOURCE,
CONF_VERSION,
KEY_CORE,
KEY_FRAMEWORK_VERSION,
KEY_TARGET_FRAMEWORK,
KEY_TARGET_PLATFORM,
)
from esphome.core import CORE, coroutine_with_priority
from .const import KEY_BOARD, KEY_RP2040, rp2040_ns
# force import gpio to register pin schema
from .gpio import rp2040_pin_to_code # noqa
_LOGGER = logging.getLogger(__name__)
CODEOWNERS = ["@jesserockz"]
AUTO_LOAD = []
def set_core_data(config):
CORE.data[KEY_RP2040] = {}
CORE.data[KEY_CORE][KEY_TARGET_PLATFORM] = "rp2040"
CORE.data[KEY_CORE][KEY_TARGET_FRAMEWORK] = "arduino"
CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] = cv.Version.parse(
config[CONF_FRAMEWORK][CONF_VERSION]
)
CORE.data[KEY_RP2040][KEY_BOARD] = config[CONF_BOARD]
return config
def _format_framework_arduino_version(ver: cv.Version) -> str:
# The most recent releases have not been uploaded to platformio so grabbing them directly from
# the GitHub release is one path forward for now.
return f"https://github.com/earlephilhower/arduino-pico/releases/download/{ver}/rp2040-{ver}.zip"
# format the given arduino (https://github.com/earlephilhower/arduino-pico/releases) version to
# a PIO earlephilhower/framework-arduinopico value
# List of package versions: https://api.registry.platformio.org/v3/packages/earlephilhower/tool/framework-arduinopico
# return f"~1.{ver.major}{ver.minor:02d}{ver.patch:02d}.0"
# NOTE: Keep this in mind when updating the recommended version:
# * The new version needs to be thoroughly validated before changing the
# recommended version as otherwise a bunch of devices could be bricked
# * For all constants below, update platformio.ini (in this repo)
# and platformio.ini/platformio-lint.ini in the esphome-docker-base repository
# The default/recommended arduino framework version
# - https://github.com/earlephilhower/arduino-pico/releases
# - https://api.registry.platformio.org/v3/packages/earlephilhower/tool/framework-arduinopico
RECOMMENDED_ARDUINO_FRAMEWORK_VERSION = cv.Version(2, 6, 2)
# The platformio/raspberrypi version to use for arduino frameworks
# - https://github.com/platformio/platform-raspberrypi/releases
# - https://api.registry.platformio.org/v3/packages/platformio/platform/raspberrypi
ARDUINO_PLATFORM_VERSION = cv.Version(1, 7, 0)
def _arduino_check_versions(value):
value = value.copy()
lookups = {
"dev": (cv.Version(2, 6, 2), "https://github.com/earlephilhower/arduino-pico"),
"latest": (cv.Version(2, 6, 2), None),
"recommended": (RECOMMENDED_ARDUINO_FRAMEWORK_VERSION, None),
}
if value[CONF_VERSION] in lookups:
if CONF_SOURCE in value:
raise cv.Invalid(
"Framework version needs to be explicitly specified when custom source is used."
)
version, source = lookups[value[CONF_VERSION]]
else:
version = cv.Version.parse(cv.version_number(value[CONF_VERSION]))
source = value.get(CONF_SOURCE, None)
value[CONF_VERSION] = str(version)
value[CONF_SOURCE] = source or _format_framework_arduino_version(version)
value[CONF_PLATFORM_VERSION] = value.get(
CONF_PLATFORM_VERSION, _parse_platform_version(str(ARDUINO_PLATFORM_VERSION))
)
if version != RECOMMENDED_ARDUINO_FRAMEWORK_VERSION:
_LOGGER.warning(
"The selected Arduino framework version is not the recommended one."
)
return value
def _parse_platform_version(value):
try:
# if platform version is a valid version constraint, prefix the default package
cv.platformio_version_constraint(value)
return f"platformio/raspberrypi @ {value}"
except cv.Invalid:
return value
CONF_PLATFORM_VERSION = "platform_version"
ARDUINO_FRAMEWORK_SCHEMA = cv.All(
cv.Schema(
{
cv.Optional(CONF_VERSION, default="recommended"): cv.string_strict,
cv.Optional(CONF_SOURCE): cv.string_strict,
cv.Optional(CONF_PLATFORM_VERSION): _parse_platform_version,
}
),
_arduino_check_versions,
)
CONFIG_SCHEMA = cv.All(
cv.Schema(
{
cv.Required(CONF_BOARD): cv.string_strict,
cv.Optional(CONF_FRAMEWORK, default={}): ARDUINO_FRAMEWORK_SCHEMA,
}
),
set_core_data,
)
@coroutine_with_priority(1000)
async def to_code(config):
cg.add(rp2040_ns.setup_preferences())
cg.add_platformio_option("board", config[CONF_BOARD])
cg.add_build_flag("-DUSE_RP2040")
cg.add_define("ESPHOME_BOARD", config[CONF_BOARD])
cg.add_define("ESPHOME_VARIANT", "RP2040")
conf = config[CONF_FRAMEWORK]
cg.add_platformio_option("framework", "arduino")
cg.add_build_flag("-DUSE_ARDUINO")
cg.add_build_flag("-DUSE_RP2040_FRAMEWORK_ARDUINO")
# cg.add_build_flag("-DPICO_BOARD=pico_w")
cg.add_platformio_option("platform", conf[CONF_PLATFORM_VERSION])
cg.add_platformio_option(
"platform_packages",
[f"earlephilhower/framework-arduinopico @ {conf[CONF_SOURCE]}"],
)
cg.add_platformio_option("board_build.core", "earlephilhower")
cg.add_platformio_option("board_build.filesystem_size", "1m")
ver: cv.Version = CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION]
cg.add_define(
"USE_ARDUINO_VERSION_CODE",
cg.RawExpression(f"VERSION_CODE({ver.major}, {ver.minor}, {ver.patch})"),
)

View File

@@ -0,0 +1,19 @@
RP2040_BASE_PINS = {}
RP2040_BOARD_PINS = {
"pico": {
"SDA": 4,
"SCL": 5,
"LED": 25,
"SDA1": 26,
"SCL1": 27,
},
"rpipico": "pico",
"rpipicow": {
"SDA": 4,
"SCL": 5,
"LED": 32,
"SDA1": 26,
"SCL1": 27,
},
}

View File

@@ -0,0 +1,6 @@
import esphome.codegen as cg
KEY_BOARD = "board"
KEY_RP2040 = "rp2040"
rp2040_ns = cg.esphome_ns.namespace("rp2040")

View File

@@ -0,0 +1,33 @@
#ifdef USE_RP2040
#include "core.h"
#include "esphome/core/hal.h"
#include "esphome/core/helpers.h"
#include "hardware/watchdog.h"
namespace esphome {
void IRAM_ATTR HOT yield() { ::yield(); }
uint32_t IRAM_ATTR HOT millis() { return ::millis(); }
void IRAM_ATTR HOT delay(uint32_t ms) { ::delay(ms); }
uint32_t IRAM_ATTR HOT micros() { return ::micros(); }
void IRAM_ATTR HOT delayMicroseconds(uint32_t us) { delay_microseconds_safe(us); }
void arch_restart() {
watchdog_reboot(0, 0, 10);
while (1) {
continue;
}
}
void arch_init() { watchdog_enable(0x7fffff, false); }
void IRAM_ATTR HOT arch_feed_wdt() { watchdog_update(); }
uint8_t progmem_read_byte(const uint8_t *addr) {
return pgm_read_byte(addr); // NOLINT
}
uint32_t IRAM_ATTR HOT arch_get_cpu_cycle_count() { return ulMainGetRunTimeCounterValue(); }
uint32_t arch_get_cpu_freq_hz() { return RP2040::f_cpu(); }
} // namespace esphome
#endif // USE_RP2040

View File

@@ -0,0 +1,14 @@
#pragma once
#ifdef USE_RP2040
#include <Arduino.h>
#include <pico.h>
extern "C" unsigned long ulMainGetRunTimeCounterValue();
namespace esphome {
namespace rp2040 {} // namespace rp2040
} // namespace esphome
#endif // USE_RP2040

View File

@@ -0,0 +1,103 @@
#ifdef USE_RP2040
#include "gpio.h"
#include "esphome/core/log.h"
namespace esphome {
namespace rp2040 {
static const char *const TAG = "rp2040";
static int IRAM_ATTR flags_to_mode(gpio::Flags flags, uint8_t pin) {
if (flags == gpio::FLAG_INPUT) { // NOLINT(bugprone-branch-clone)
return INPUT;
} else if (flags == gpio::FLAG_OUTPUT) {
return OUTPUT;
} else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLUP)) {
return INPUT_PULLUP;
} else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLDOWN)) {
return INPUT_PULLDOWN;
// } else if (flags == (gpio::FLAG_OUTPUT | gpio::FLAG_OPEN_DRAIN)) {
// return OpenDrain;
} else {
return 0;
}
}
struct ISRPinArg {
uint8_t pin;
bool inverted;
};
ISRInternalGPIOPin RP2040GPIOPin::to_isr() const {
auto *arg = new ISRPinArg{}; // NOLINT(cppcoreguidelines-owning-memory)
arg->pin = pin_;
arg->inverted = inverted_;
return ISRInternalGPIOPin((void *) arg);
}
void RP2040GPIOPin::attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const {
PinStatus arduino_mode = LOW;
switch (type) {
case gpio::INTERRUPT_RISING_EDGE:
arduino_mode = inverted_ ? FALLING : RISING;
break;
case gpio::INTERRUPT_FALLING_EDGE:
arduino_mode = inverted_ ? RISING : FALLING;
break;
case gpio::INTERRUPT_ANY_EDGE:
arduino_mode = CHANGE;
break;
case gpio::INTERRUPT_LOW_LEVEL:
arduino_mode = inverted_ ? HIGH : LOW;
break;
case gpio::INTERRUPT_HIGH_LEVEL:
arduino_mode = inverted_ ? LOW : HIGH;
break;
}
attachInterrupt(pin_, func, arduino_mode, arg);
}
void RP2040GPIOPin::pin_mode(gpio::Flags flags) {
pinMode(pin_, flags_to_mode(flags, pin_)); // NOLINT
}
std::string RP2040GPIOPin::dump_summary() const {
char buffer[32];
snprintf(buffer, sizeof(buffer), "GPIO%u", pin_);
return buffer;
}
bool RP2040GPIOPin::digital_read() {
return bool(digitalRead(pin_)) != inverted_; // NOLINT
}
void RP2040GPIOPin::digital_write(bool value) {
digitalWrite(pin_, value != inverted_ ? 1 : 0); // NOLINT
}
void RP2040GPIOPin::detach_interrupt() const { detachInterrupt(pin_); }
} // namespace rp2040
using namespace rp2040;
bool IRAM_ATTR ISRInternalGPIOPin::digital_read() {
auto *arg = reinterpret_cast<ISRPinArg *>(arg_);
return bool(digitalRead(arg->pin)) != arg->inverted; // NOLINT
}
void IRAM_ATTR ISRInternalGPIOPin::digital_write(bool value) {
auto *arg = reinterpret_cast<ISRPinArg *>(arg_);
digitalWrite(arg->pin, value != arg->inverted ? 1 : 0); // NOLINT
}
void IRAM_ATTR ISRInternalGPIOPin::clear_interrupt() {
// TODO: implement
// auto *arg = reinterpret_cast<ISRPinArg *>(arg_);
// GPIO_REG_WRITE(GPIO_STATUS_W1TC_ADDRESS, 1UL << arg->pin);
}
void IRAM_ATTR ISRInternalGPIOPin::pin_mode(gpio::Flags flags) {
auto *arg = reinterpret_cast<ISRPinArg *>(arg_);
pinMode(arg->pin, flags_to_mode(flags, arg->pin)); // NOLINT
}
} // namespace esphome
#endif // USE_RP2040

Some files were not shown because too many files have changed in this diff Show More